// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT

use std::convert;

use crate::error::{Error, LibraryError};

/// The configuration for a Nitrokey.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Config {
    /// If set, the stick will generate a code from the HOTP slot with the given number if Num Lock
    /// is pressed.  The slot number must be 0, 1 or 2.
    pub num_lock: Option<u8>,
    /// If set, the stick will generate a code from the HOTP slot with the given number if Caps
    /// Lock is pressed.  The slot number must be 0, 1 or 2.
    pub caps_lock: Option<u8>,
    /// If set, the stick will generate a code from the HOTP slot with the given number if Scroll
    /// Lock is pressed.  The slot number must be 0, 1 or 2.
    pub scroll_lock: Option<u8>,
    /// If set, OTP generation using [`get_hotp_code`][] or [`get_totp_code`][] requires user
    /// authentication.  Otherwise, OTPs can be generated without authentication.
    ///
    /// [`get_hotp_code`]: trait.ProvideOtp.html#method.get_hotp_code
    /// [`get_totp_code`]: trait.ProvideOtp.html#method.get_totp_code
    pub user_password: bool,
}

fn config_otp_slot_to_option(value: u8) -> Option<u8> {
    if value < 3 {
        Some(value)
    } else {
        None
    }
}

fn option_to_config_otp_slot(value: Option<u8>) -> Result<u8, Error> {
    if let Some(value) = value {
        if value < 3 {
            Ok(value)
        } else {
            Err(LibraryError::InvalidSlot.into())
        }
    } else {
        Ok(255)
    }
}

impl Config {
    /// Constructs a new instance of this struct.
    pub fn new(
        num_lock: Option<u8>,
        caps_lock: Option<u8>,
        scroll_lock: Option<u8>,
        user_password: bool,
    ) -> Config {
        Config {
            num_lock,
            caps_lock,
            scroll_lock,
            user_password,
        }
    }

    fn from_raw(numlock: u8, capslock: u8, scrollock: u8, user_password: bool) -> Config {
        Config {
            num_lock: config_otp_slot_to_option(numlock),
            caps_lock: config_otp_slot_to_option(capslock),
            scroll_lock: config_otp_slot_to_option(scrollock),
            user_password,
        }
    }
}

impl convert::TryFrom<Config> for nitrokey_sys::NK_config {
    type Error = Error;

    fn try_from(config: Config) -> Result<nitrokey_sys::NK_config, Error> {
        Ok(nitrokey_sys::NK_config {
            numlock: option_to_config_otp_slot(config.num_lock)?,
            capslock: option_to_config_otp_slot(config.caps_lock)?,
            scrolllock: option_to_config_otp_slot(config.scroll_lock)?,
            enable_user_password: config.user_password,
            disable_user_password: false,
        })
    }
}

impl From<nitrokey_sys::NK_config> for Config {
    fn from(config: nitrokey_sys::NK_config) -> Config {
        Config::from_raw(
            config.numlock,
            config.capslock,
            config.scrolllock,
            config.enable_user_password,
        )
    }
}

impl From<&nitrokey_sys::NK_status> for Config {
    fn from(status: &nitrokey_sys::NK_status) -> Config {
        Config::from_raw(
            status.config_numlock,
            status.config_capslock,
            status.config_scrolllock,
            status.otp_user_password,
        )
    }
}
