use std::path::PathBuf;
use std::sync::OnceLock;

use sequoia_directories::Home;

use crate::Context;
use crate::Result;

enum ConfigFile {
    /// Use the default configuration file based on the Sequoia home
    /// directory.
    Default,

    /// Don't load a configuration file.
    None,

    /// Path to a configuration file to load.
    Path(PathBuf),
}

/// A builder to configure a Context.
///
/// # Examples
///
/// Create a [`Context`] using the defaults.
///
/// ```rust
/// use sequoia_cryptoki::ContextBuilder;
///
/// # fn main() -> anyhow::Result<()> {
/// #
/// let context = ContextBuilder::new().build()?;
/// # Ok(()) }
/// ```
pub struct ContextBuilder {
    /// An alternate sequoia home directory.
    home: Option<Home>,

    config_file: ConfigFile,

    /// Paths to the PKCS#11 modules.
    modules: Vec<PathBuf>,
}

impl ContextBuilder {
    /// Returns a new, default context builder.
    pub fn new() -> Self {
        ContextBuilder {
            home: None,
            config_file: ConfigFile::Default,
            modules: Vec::new(),
        }
    }

    /// Use an alternate sequoia home directory.
    ///
    /// If `None`, uses the default.
    pub fn home<H>(&mut self, home: H) -> &mut Self
    where
        H: Into<Option<Home>>
    {
        self.home = home.into();
        self
    }

    /// Load an alternate configuration file.
    ///
    /// By default, the configuration file in the Sequoia home
    /// directory is read.  When set, the specified file is read
    /// instead.
    pub fn config_file<P>(&mut self, path: P) -> &mut Self
    where
        P: Into<PathBuf>
    {
        self.config_file = ConfigFile::Path(path.into());
        self
    }

    /// Don't load a configuration file.
    ///
    /// By default, the configuration file in the Sequoia home
    /// directory is read.  When reading the configuration file is
    /// disabled, the default configuration is used.
    pub fn disable_config_file(&mut self) -> &mut Self {
        self.config_file = ConfigFile::None;
        self
    }

    /// Adds the specified module to the set of PKCS#11 modules to
    /// load.
    pub fn add_module<P>(&mut self, path: P) -> &mut Self
        where P: Into<PathBuf>
    {
        self.modules.push(path.into());
        self
    }

    /// Adds the specified modules to the set of PKCS#11 modules to
    /// load.
    pub fn extend_modules<P>(&mut self, paths: impl Iterator<Item=P>)
        -> &mut Self
    where P: Into<PathBuf>
    {
        self.modules.extend(paths.map(|p| p.into()));
        self
    }

    /// Returns a new `Context` based on the builder.
    pub fn build(&self) -> Result<Context> {
        let mut config = match &self.config_file {
            ConfigFile::Default => {
                // Use the default configuration file.
                let config_file = if let Some(ref home) = self.home {
                    crate::ConfigFile::parse_home(home)?
                } else {
                    crate::ConfigFile::parse_default_home()?
                };
                config_file.to_config()?
            }
            ConfigFile::None => {
                // Use the default configuration.
                crate::Config::default()
            }
            ConfigFile::Path(path) => {
                // Use the specified configuration file.
                crate::ConfigFile::parse_file(path)?
                    .to_config()?
            }
        };

        config.extend_modules(self.modules.iter());

        Ok(Context {
            config,
            backends: OnceLock::new(),
        })
    }
}
