use std::path::Path;
use std::path::PathBuf;
use std::sync::Mutex;

use sequoia_cryptoki::PublicKeyAlgorithm;

use super::Backend;
use super::Result;

pub struct SoftHsm2 {
    /// The kryoptic configuration directory.  This is where we store
    /// the configuration file and any token databases.
    softhsm2: PathBuf,

    /// The environment variables to set.
    env: Vec<(String, String)>,

    next_slot: Mutex<u64>,
}

impl SoftHsm2 {
    pub fn new(working_dir: &Path) -> Result<Box<dyn Backend>> {
        let softhsm2 = working_dir.join("softhsm2");
        let tokens = softhsm2.join("tokens");
        std::fs::create_dir_all(&tokens)
            .expect("can create softhsm2 and tokens directories");

        let config = format!("\
directories.tokendir = {}
objectstore.backend = file

# Either ERROR, WARNING, INFO, or DEBUG
log.level = DEBUG

# Whether the CKF_REMOVABLE_DEVICE flag should be set
slots.removable = false
",
                             tokens.display())
            .into_bytes();

        let config_file = softhsm2.join("config.file");

        std::fs::write(&config_file, &config)
            .expect("can create configuration file");

        Ok(Box::new(SoftHsm2 {
            softhsm2,
            env: vec![
                ("SOFTHSM2_CONF".to_string(),
                 config_file.display().to_string())
            ],
            next_slot: Mutex::new(0),
        }))
    }
}

impl Backend for SoftHsm2 {
    fn env(&self) -> &[(String, String)] {
        &self.env[..]
    }

    fn args(&self) -> &[String] {
        &[]
    }

    fn algo_supported(&self, algo: &PublicKeyAlgorithm) -> bool {
        use PublicKeyAlgorithm::*;
        match algo {
            RSA => true,
            Cv25519 => false,
            X25519 => false,
            X448 => false,
            EdDSA => true,
            Ed25519 => true,
            Ed448 => true,
            MLDSA65_Ed25519 => false,
            MLDSA87_Ed448 => false,
            SLHDSA128s => false,
            SLHDSA128f => false,
            SLHDSA256s => false,
            MLKEM768_X25519 => false,
            MLKEM1024_X448 => false,
            algo => unreachable!("Missing algorithm: {}", algo),
        }
    }

    fn allocate_slot(&self) -> u64 {
        let mut next_slot = self.next_slot.lock().unwrap();
        eprintln!("Allocating slot {}", next_slot);
        let slot = *next_slot;
        *next_slot += 1;
        slot
    }
}
