use std::path::Path;

use sequoia_cryptoki::PublicKeyAlgorithm;

mod kryoptic;
mod softhsm2;

use super::Result;

/// A PKCS#11 backend.
pub trait Backend: Send + Sync {
    // /// Returns a configured backend (this is not part of the
    // /// trait, because then the trait can't be turned into a trait
    // /// object).
    // fn new(working_dir: &Path) -> Result<Box<dyn Backend>>;

    /// Returns the environment variables to set when executing
    /// sq-cryptoki.
    fn env(&self) -> &[(String, String)];

    /// Returns the additional top-level arguments to pass when
    /// executing sq-cryptoki.
    fn args(&self) -> &[String];

    /// Returns whether the backend supports the specified algorithm.
    fn algo_supported(&self, algo: &PublicKeyAlgorithm) -> bool;

    /// Returns the next free slot.
    ///
    /// If you allocate a slot, you must initialize it before
    /// allocating another slot.  Some backends, like softhsm2, only
    /// make the next slot available after the previous one has been
    /// initialized.
    ///
    /// Panics if there are no more free slots.
    fn allocate_slot(&self) -> u64;
}

/// The PKCS#11 backends that the test suite supports.
const BACKENDS: [(&str, fn(&Path) -> Result<Box<dyn Backend>>); 3] = [
    ("kryoptic-pqc", kryoptic::KryopticPqc::new),
    ("kryoptic-no-pqc", kryoptic::KryopticNoPqc::new),
    ("softhsm2", softhsm2::SoftHsm2::new),
];

/// Returns a configured backend.
///
/// Typically one or more backends are instantiated per test.
///
/// The user has to set `SEQUOIA_CRYPTOKI_BACKEND` to the backend and
/// `SEQUOIA_CRYPTOKI_MODULE` to the path to the library.
pub fn init(working_dir: &Path) -> Box<dyn Backend> {
    match std::env::var("SEQUOIA_CRYPTOKI_BACKEND") {
        Ok(backend) => {
            for (name, new) in BACKENDS.iter() {
                if *name == backend {
                    match new(working_dir) {
                        Ok(b) => return b,
                        Err(err) => {
                            panic!("Error initializing backend {}: {}",
                                   backend, err);
                        }
                    }
                }
            }
            eprintln!("The {} backend is not supported.", backend);
        }
        Err(err) => {
            eprintln!("SEQUOIA_CRYPTOKI_BACKEND is not set (or invalid): {}.",
                      err);
        }
    };

    eprintln!("Supported backends: {}",
              BACKENDS.iter().map(|(backend, _)| {
                  backend.to_string()
              })
              .collect::<Vec<String>>()
              .join(", "));
    panic!("Failed to configure PKCS#11 backend");
}
