//! # Keyring library
//!
//! Allows for setting and getting passwords on Linux, OSX, Windows, and Android
#![deny(clippy::all)]

use log::*;
use std::path::Path;

mod error;
mod escape;
mod insecure;

pub use error::{KeyringError, Result};
use escape::*;
use insecure::InsecureKeyringManager;

/////////////////////////////////////////////////////////////////////////

use cfg_if::*;
cfg_if! {
    if #[cfg(target_os = "linux")] {
        mod linux;
        use linux::LinuxKeyringManager as PlatformKeyringManager;
    } else if #[cfg(target_os = "windows")] {
        mod windows;
        use windows::WindowsKeyringManager as PlatformKeyringManager;
    } else if #[cfg(target_os = "macos")] {
        mod macos;
        use macos::MacosKeyringManager as PlatformKeyringManager;
    } else if #[cfg(target_os = "ios")] {
        mod ios;
        use ios::IosKeyringManager as PlatformKeyringManager;
    } else if #[cfg(target_os = "android")] {
        pub(crate) mod android;
        use android::AndroidKeyringManager as PlatformKeyringManager;
        pub use android::AndroidKeyringContext;
    } else {
        struct PlatformKeyringManager {}

        impl PlatformKeyringManager {
            pub fn with_keyring<F, T>(&self, _service: &str, _key: &str, _func: F) -> Result<T>
            where
                F: FnOnce(&mut dyn Keyring) -> Result<T>,
            {
                Err(KeyringError::NoBackendFound)
            }
        }
    }
}
/////////////////////////////////////////////////////////////////////////

pub trait Keyring {
    fn set_value(&mut self, value: &str) -> Result<()>;
    fn get_value(&self) -> Result<String>;
    fn delete_value(&mut self) -> Result<()>;
}

enum KeyringManagerKind {
    #[allow(dead_code)]
    Secure(PlatformKeyringManager),
    Insecure(InsecureKeyringManager),
}

pub struct KeyringManager {
    kind: KeyringManagerKind,
}

impl KeyringManager {
    cfg_if! {
        if #[cfg(all(target_os = "macos", feature = "macos-specify-keychain"))] {
            pub fn new_secure_with_path(application: &str, path: &Path) -> Result<Self> {
                Ok(KeyringManager {
                    kind: KeyringManagerKind::Secure(PlatformKeyringManager::with_path(application, path)?),
                })
            }
        }
    }
    cfg_if! {
        if #[cfg(target_os = "android")] {
            pub fn new_secure(application: &str, context: AndroidKeyringContext) -> Result<Self> {
                Ok(KeyringManager {
                    kind: KeyringManagerKind::Secure(PlatformKeyringManager::new(application, context)?),
                })
            }
        } else if #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos", target_os = "ios"))] {
            pub fn new_secure(application: &str) -> Result<Self> {
                Ok(KeyringManager {
                    kind: KeyringManagerKind::Secure(PlatformKeyringManager::new(application)?),
                })
            }
        } else {
            pub fn new_secure(_application: &str) -> Result<Self> {
                Err(KeyringError::NoBackendFound)
            }
        }
    }

    pub fn new_insecure(application: &str, insecure_keyring_file: &Path) -> Result<Self> {
        Ok(KeyringManager {
            kind: KeyringManagerKind::Insecure(InsecureKeyringManager::new(
                application,
                insecure_keyring_file,
            )?),
        })
    }

    pub fn with_keyring<F, T>(&self, service: &str, key: &str, f: F) -> Result<T>
    where
        F: FnOnce(&mut dyn Keyring) -> Result<T>,
    {
        match &self.kind {
            KeyringManagerKind::Secure(pkm) => pkm.with_keyring(service, key, f),
            KeyringManagerKind::Insecure(ikm) => ikm.with_keyring(service, key, f),
        }
    }
}

pub(crate) fn map_to_generic<E: std::fmt::Display>(e: E) -> KeyringError {
    KeyringError::Generic(format!("{}", e))
}

pub(crate) fn map_log_err<T: std::error::Error>(e: T) -> T {
    error!("{}", e);
    e
}

cfg_if! {
    if #[cfg(test)] {
        mod tests;
    } else if #[cfg(feature="keyring_manager_android_tests")] {
        pub mod tests;
    } else if #[cfg(feature="keyring_manager_ios_tests")] {
        pub mod tests;
    }
}
