use crate::config::LIB_CFG;
#[cfg(feature = "lang-detection")]
use crate::config::TMPL_FILTER_GET_LANG_ALL;
use crate::error::LibCfgError;
#[cfg(feature = "lang-detection")]
use lingua;
#[cfg(feature = "lang-detection")]
use lingua::IsoCode639_1;
use parking_lot::RwLock;
use std::collections::BTreeMap;
use std::env;
#[cfg(feature = "lang-detection")]
use std::str::FromStr;
#[cfg(target_family = "windows")]
use windows_sys::Win32::Globalization::GetUserDefaultLocaleName;
#[cfg(target_family = "windows")]
use windows_sys::Win32::System::SystemServices::LOCALE_NAME_MAX_LENGTH;
pub const ENV_VAR_TPNOTE_EXTENSION_DEFAULT: &str = "TPNOTE_EXTENSION_DEFAULT";
pub const ENV_VAR_TPNOTE_LANG: &str = "TPNOTE_LANG";
pub const ENV_VAR_TPNOTE_LANG_DETECTION: &str = "TPNOTE_LANG_DETECTION";
pub const ENV_VAR_TPNOTE_USER: &str = "TPNOTE_USER";
const ENV_VAR_LOGNAME: &str = "LOGNAME";
const ENV_VAR_USERNAME: &str = "USERNAME";
const ENV_VAR_USER: &str = "USER";
#[cfg(not(target_family = "windows"))]
const ENV_VAR_LANG: &str = "LANG";
#[derive(Debug)]
#[allow(dead_code)]
pub(crate) enum FilterGetLang {
    Disabled,
    AllLanguages,
    #[cfg(feature = "lang-detection")]
    SomeLanguages(Vec<IsoCode639_1>),
    #[cfg(not(feature = "lang-detection"))]
    SomeLanguages(Vec<String>),
    Error(LibCfgError),
}
#[derive(Debug)]
pub(crate) struct Settings {
    pub author: String,
    pub lang: String,
    pub extension_default: String,
    pub filter_get_lang: FilterGetLang,
    pub filter_map_lang_btmap: Option<BTreeMap<String, String>>,
}
const DEFAULT_SETTINGS: Settings = Settings {
    author: String::new(),
    lang: String::new(),
    extension_default: String::new(),
    filter_get_lang: FilterGetLang::Disabled,
    filter_map_lang_btmap: None,
};
impl Default for Settings {
    #[cfg(not(test))]
    fn default() -> Self {
        DEFAULT_SETTINGS
    }
    #[cfg(test)]
    fn default() -> Self {
        let mut settings = DEFAULT_SETTINGS;
        settings.author = String::from("testuser");
        settings.lang = String::from("ab_AB");
        settings.extension_default = String::from("md");
        settings
    }
}
#[cfg(not(test))]
pub(crate) static SETTINGS: RwLock<Settings> = RwLock::new(DEFAULT_SETTINGS);
#[cfg(test)]
lazy_static::lazy_static! {
pub(crate) static ref SETTINGS: RwLock<Settings> = RwLock::new(DEFAULT_SETTINGS);
}
pub(crate) fn force_lang_setting(lang: Option<String>) {
    let mut settings = SETTINGS.write();
    if let Some(l) = lang {
        settings.lang = l;
    }
    settings.filter_get_lang = FilterGetLang::Disabled;
    log::trace!(
        "`SETTINGS` updated after `force_lang_setting()`:\n{:#?}",
        settings
    );
}
pub fn update_settings() -> Result<(), LibCfgError> {
    let mut settings = SETTINGS.write();
    update_author_setting(&mut settings);
    update_extension_default_setting(&mut settings);
    update_lang_setting(&mut settings);
    update_filter_get_lang_setting(&mut settings);
    update_filter_map_lang_btmap_setting(&mut settings);
    update_env_lang_detection(&mut settings);
    log::trace!(
        "`SETTINGS` updated (reading config + env. vars.):\n{:#?}",
        settings
    );
    if let FilterGetLang::Error(e) = &settings.filter_get_lang {
        Err(e.clone())
    } else {
        Ok(())
    }
}
fn update_author_setting(settings: &mut Settings) {
    let author = env::var(ENV_VAR_TPNOTE_USER).unwrap_or_else(|_| {
        env::var(ENV_VAR_LOGNAME).unwrap_or_else(|_| {
            env::var(ENV_VAR_USERNAME)
                .unwrap_or_else(|_| env::var(ENV_VAR_USER).unwrap_or_default())
        })
    });
    settings.author = author;
}
fn update_extension_default_setting(settings: &mut Settings) {
    let ext = match env::var(ENV_VAR_TPNOTE_EXTENSION_DEFAULT) {
        Ok(ed_env) if !ed_env.is_empty() => ed_env,
        Err(_) | Ok(_) => {
            let lib_cfg = LIB_CFG.read_recursive();
            lib_cfg.filename.extension_default.to_string()
        }
    };
    settings.extension_default = ext;
}
fn update_lang_setting(settings: &mut Settings) {
    let mut lang = String::new();
    let tpnotelang = env::var(ENV_VAR_TPNOTE_LANG).ok();
    #[cfg(not(target_family = "windows"))]
    if let Some(tpnotelang) = tpnotelang {
        lang = tpnotelang;
    } else {
        if let Ok(lang_env) = env::var(ENV_VAR_LANG) {
            if !lang_env.is_empty() {
                let mut language = "";
                let mut territory = "";
                if let Some((l, lang_env)) = lang_env.split_once('_') {
                    language = l;
                    if let Some((t, _codeset)) = lang_env.split_once('.') {
                        territory = t;
                    }
                }
                lang = language.to_string();
                lang.push('-');
                lang.push_str(territory);
            }
        }
    }
    #[cfg(target_family = "windows")]
    if let Some(tpnotelang) = tpnotelang {
        lang = tpnotelang;
    } else {
        let mut buf = [0u16; LOCALE_NAME_MAX_LENGTH as usize];
        let len = unsafe { GetUserDefaultLocaleName(buf.as_mut_ptr(), buf.len() as i32) };
        if len > 0 {
            lang = String::from_utf16_lossy(&buf[..((len - 1) as usize)]);
        }
    };
    settings.lang = lang;
}
#[cfg(feature = "lang-detection")]
fn update_filter_get_lang_setting(settings: &mut Settings) {
    let lib_cfg = LIB_CFG.read_recursive();
    let mut all_languages_selected = false;
    match lib_cfg
        .tmpl
        .filter_get_lang
        .iter()
        .filter(|&l| {
            if l == TMPL_FILTER_GET_LANG_ALL {
                all_languages_selected = true;
                false
            } else {
                true
            }
        })
        .map(|l| {
            IsoCode639_1::from_str(l).map_err(|_| {
                let mut all_langs = lingua::Language::all()
                    .iter()
                    .map(|l| {
                        let mut s = l.iso_code_639_1().to_string();
                        s.push_str(", ");
                        s
                    })
                    .collect::<Vec<String>>();
                all_langs.sort();
                let mut all_langs = all_langs.into_iter().collect::<String>();
                all_langs.truncate(all_langs.len() - ", ".len());
                LibCfgError::ParseLanguageCode {
                    language_code: l.into(),
                    all_langs,
                }
            })
        })
        .collect::<Result<Vec<IsoCode639_1>, LibCfgError>>()
    {
        Ok(mut iso_codes) => {
            if all_languages_selected {
                settings.filter_get_lang = FilterGetLang::AllLanguages;
            } else {
                if !settings.lang.is_empty() {
                    if let Some((lang_subtag, _)) = settings.lang.split_once('-') {
                        if let Ok(iso_code) = IsoCode639_1::from_str(lang_subtag) {
                            if !iso_codes.contains(&iso_code) {
                                iso_codes.push(iso_code);
                            }
                        }
                    }
                }
                settings.filter_get_lang = match iso_codes.len() {
                    0 => FilterGetLang::Disabled,
                    1 => FilterGetLang::Error(LibCfgError::NotEnoughLanguageCodes {
                        language_code: iso_codes[0].to_string(),
                    }),
                    _ => FilterGetLang::SomeLanguages(iso_codes),
                }
            }
        }
        Err(e) =>
        {
            settings.filter_get_lang = FilterGetLang::Error(e);
        }
    }
}
#[cfg(not(feature = "lang-detection"))]
fn update_filter_get_lang_setting(settings: &mut Settings) {
    settings.filter_get_lang = FilterGetLang::Disabled;
}
fn update_filter_map_lang_btmap_setting(settings: &mut Settings) {
    let mut btm = BTreeMap::new();
    let lib_cfg = LIB_CFG.read_recursive();
    for l in &lib_cfg.tmpl.filter_map_lang {
        if l.len() >= 2 {
            btm.insert(l[0].to_string(), l[1].to_string());
        };
    }
    if !settings.lang.is_empty() {
        if let Some((lang_subtag, _)) = settings.lang.split_once('-') {
            if !lang_subtag.is_empty() && !btm.contains_key(lang_subtag) {
                btm.insert(lang_subtag.to_string(), settings.lang.to_string());
            }
        };
    }
    settings.filter_map_lang_btmap = Some(btm);
}
#[cfg(feature = "lang-detection")]
fn update_env_lang_detection(settings: &mut Settings) {
    if let Ok(env_var) = env::var(ENV_VAR_TPNOTE_LANG_DETECTION) {
        if env_var.is_empty() {
            settings.filter_get_lang = FilterGetLang::Disabled;
            settings.filter_map_lang_btmap = None;
            log::debug!(
                "Empty env. var. `{}` disables the `lang-detection` feature.",
                ENV_VAR_TPNOTE_LANG_DETECTION
            );
            return;
        }
        let mut hm: BTreeMap<String, String> = BTreeMap::new();
        let mut all_languages_selected = false;
        match env_var
            .split(',')
            .map(|t| {
                let t = t.trim();
                if let Some((lang_subtag, _)) = t.split_once('-') {
                    if !lang_subtag.is_empty() && !hm.contains_key(lang_subtag) {
                        hm.insert(lang_subtag.to_string(), t.to_string());
                    };
                    lang_subtag
                } else {
                    t
                }
            })
            .filter(|&l| {
                if l == TMPL_FILTER_GET_LANG_ALL {
                    all_languages_selected = true;
                    false
                } else {
                    true
                }
            })
            .map(|l| {
                IsoCode639_1::from_str(l.trim()).map_err(|_| {
                    let mut all_langs = lingua::Language::all()
                        .iter()
                        .map(|l| {
                            let mut s = l.iso_code_639_1().to_string();
                            s.push_str(", ");
                            s
                        })
                        .collect::<Vec<String>>();
                    all_langs.sort();
                    let mut all_langs = all_langs.into_iter().collect::<String>();
                    all_langs.truncate(all_langs.len() - ", ".len());
                    LibCfgError::ParseLanguageCode {
                        language_code: l.into(),
                        all_langs,
                    }
                })
            })
            .collect::<Result<Vec<IsoCode639_1>, LibCfgError>>()
        {
            Ok(mut iso_codes) => {
                if !settings.lang.is_empty() {
                    if let Some(lang_subtag) = settings.lang.split('-').next() {
                        if let Ok(iso_code) = IsoCode639_1::from_str(lang_subtag) {
                            if !iso_codes.contains(&iso_code) {
                                iso_codes.push(iso_code);
                            }
                            if lang_subtag != settings.lang && !hm.contains_key(lang_subtag) {
                                hm.insert(lang_subtag.to_string(), settings.lang.to_string());
                            }
                        }
                    }
                }
                if all_languages_selected {
                    settings.filter_get_lang = FilterGetLang::AllLanguages;
                } else {
                    settings.filter_get_lang = match iso_codes.len() {
                        0 => FilterGetLang::Disabled,
                        1 => FilterGetLang::Error(LibCfgError::NotEnoughLanguageCodes {
                            language_code: iso_codes[0].to_string(),
                        }),
                        _ => FilterGetLang::SomeLanguages(iso_codes),
                    }
                }
                settings.filter_map_lang_btmap = Some(hm);
            }
            Err(e) =>
            {
                settings.filter_get_lang = FilterGetLang::Error(e);
            }
        }
    }
}
#[cfg(not(feature = "lang-detection"))]
fn update_env_lang_detection(settings: &mut Settings) {
    if let Ok(env_var) = env::var(ENV_VAR_TPNOTE_LANG_DETECTION) {
        if !env_var.is_empty() {
            settings.filter_get_lang = FilterGetLang::Disabled;
            settings.filter_map_lang_btmap = None;
            log::debug!(
                "Ignoring the env. var. `{}`. The `lang-detection` feature \
                 is not included in this build.",
                ENV_VAR_TPNOTE_LANG_DETECTION
            );
        }
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    use crate::config::FILENAME_EXTENSION_DEFAULT;
    #[test]
    fn test_update_author_setting() {
        let mut settings = Settings::default();
        env::set_var(ENV_VAR_LOGNAME, "testauthor");
        update_author_setting(&mut settings);
        assert_eq!(settings.author, "testauthor");
    }
    #[test]
    fn test_update_extension_default_setting() {
        let mut settings = Settings::default();
        env::set_var(ENV_VAR_TPNOTE_EXTENSION_DEFAULT, "markdown");
        update_extension_default_setting(&mut settings);
        assert_eq!(settings.extension_default, "markdown");
        let mut settings = Settings::default();
        std::env::remove_var(ENV_VAR_TPNOTE_EXTENSION_DEFAULT);
        update_extension_default_setting(&mut settings);
        assert_eq!(settings.extension_default, FILENAME_EXTENSION_DEFAULT);
    }
    #[test]
    #[cfg(not(target_family = "windows"))]
    fn test_update_lang_setting() {
        let mut settings = Settings::default();
        env::remove_var(ENV_VAR_TPNOTE_LANG);
        env::set_var(ENV_VAR_LANG, "en_GB.UTF-8");
        update_lang_setting(&mut settings);
        assert_eq!(settings.lang, "en-GB");
        let mut settings = Settings::default();
        env::remove_var(ENV_VAR_TPNOTE_LANG);
        env::set_var(ENV_VAR_LANG, "");
        update_lang_setting(&mut settings);
        assert_eq!(settings.lang, "");
        let mut settings = Settings::default();
        env::set_var(ENV_VAR_TPNOTE_LANG, "it-IT");
        env::set_var(ENV_VAR_LANG, "en_GB.UTF-8");
        update_lang_setting(&mut settings);
        assert_eq!(settings.lang, "it-IT");
    }
    #[test]
    fn test_update_filter_get_lang_setting() {
        let mut settings = Settings::default();
        settings.lang = "en-GB".to_string();
        update_filter_get_lang_setting(&mut settings);
        if let FilterGetLang::SomeLanguages(ofgl) = settings.filter_get_lang {
            let output_filter_get_lang = ofgl
                .iter()
                .map(|l| {
                    let mut l = l.to_string();
                    l.push_str(" ");
                    l
                })
                .collect::<String>();
            assert_eq!(output_filter_get_lang, "en fr de ");
        } else {
            panic!("Wrong variant: {:?}", settings.filter_get_lang);
        }
        let mut settings = Settings::default();
        settings.lang = "it-IT".to_string();
        update_filter_get_lang_setting(&mut settings);
        if let FilterGetLang::SomeLanguages(ofgl) = settings.filter_get_lang {
            let output_filter_get_lang = ofgl
                .iter()
                .map(|l| {
                    let mut l = l.to_string();
                    l.push_str(" ");
                    l
                })
                .collect::<String>();
            assert_eq!(output_filter_get_lang, "en fr de it ");
        } else {
            panic!("Wrong variant: {:?}", settings.filter_get_lang);
        }
    }
    #[test]
    fn test_update_filter_map_lang_hmap_setting() {
        let mut settings = Settings::default();
        settings.lang = "it-IT".to_string();
        update_filter_map_lang_btmap_setting(&mut settings);
        let output_filter_map_lang = settings.filter_map_lang_btmap.unwrap();
        assert_eq!(output_filter_map_lang.get("de").unwrap(), "de-DE");
        assert_eq!(output_filter_map_lang.get("et").unwrap(), "et-ET");
        assert_eq!(output_filter_map_lang.get("it").unwrap(), "it-IT");
        let mut settings = Settings::default();
        settings.lang = "it".to_string();
        update_filter_map_lang_btmap_setting(&mut settings);
        let output_filter_map_lang = settings.filter_map_lang_btmap.unwrap();
        assert_eq!(output_filter_map_lang.get("de").unwrap(), "de-DE");
        assert_eq!(output_filter_map_lang.get("et").unwrap(), "et-ET");
        assert_eq!(output_filter_map_lang.get("it"), None);
    }
    #[test]
    fn test_update_env_lang_detection() {
        let mut settings = Settings::default();
        settings.lang = "en-GB".to_string();
        env::set_var(ENV_VAR_TPNOTE_LANG_DETECTION, "fr-FR, de-DE, hu");
        update_env_lang_detection(&mut settings);
        if let FilterGetLang::SomeLanguages(ofgl) = settings.filter_get_lang {
            let output_filter_get_lang = ofgl
                .iter()
                .map(|l| {
                    let mut l = l.to_string();
                    l.push_str(" ");
                    l
                })
                .collect::<String>();
            assert_eq!(output_filter_get_lang, "fr de hu en ");
        } else {
            panic!("Wrong variant: {:?}", settings.filter_get_lang);
        }
        let output_filter_map_lang = settings.filter_map_lang_btmap.unwrap();
        assert_eq!(output_filter_map_lang.get("de").unwrap(), "de-DE");
        assert_eq!(output_filter_map_lang.get("fr").unwrap(), "fr-FR");
        assert_eq!(output_filter_map_lang.get("en").unwrap(), "en-GB");
        let mut settings = Settings::default();
        settings.lang = "en-GB".to_string();
        env::set_var(ENV_VAR_TPNOTE_LANG_DETECTION, "de-DE, de-AT, en-US");
        update_env_lang_detection(&mut settings);
        if let FilterGetLang::SomeLanguages(ofgl) = settings.filter_get_lang {
            let output_filter_get_lang = ofgl
                .iter()
                .map(|l| {
                    let mut l = l.to_string();
                    l.push_str(" ");
                    l
                })
                .collect::<String>();
            assert_eq!(output_filter_get_lang, "de de en ");
        } else {
            panic!("Wrong variant: {:?}", settings.filter_get_lang);
        }
        let output_filter_map_lang = settings.filter_map_lang_btmap.unwrap();
        assert_eq!(output_filter_map_lang.get("de").unwrap(), "de-DE");
        assert_eq!(output_filter_map_lang.get("en").unwrap(), "en-US");
        let mut settings = Settings::default();
        settings.lang = "en-GB".to_string();
        env::set_var(ENV_VAR_TPNOTE_LANG_DETECTION, "de-DE, +all, en-US");
        update_env_lang_detection(&mut settings);
        assert!(matches!(
            settings.filter_get_lang,
            FilterGetLang::AllLanguages
        ));
        let output_filter_map_lang = settings.filter_map_lang_btmap.unwrap();
        assert_eq!(output_filter_map_lang.get("de").unwrap(), "de-DE");
        assert_eq!(output_filter_map_lang.get("en").unwrap(), "en-US");
        let mut settings = Settings::default();
        settings.lang = "en-GB".to_string();
        env::set_var(ENV_VAR_TPNOTE_LANG_DETECTION, "de-DE, de-AT, en");
        update_env_lang_detection(&mut settings);
        if let FilterGetLang::SomeLanguages(ofgl) = settings.filter_get_lang {
            let output_filter_get_lang = ofgl
                .iter()
                .map(|l| {
                    let mut l = l.to_string();
                    l.push_str(" ");
                    l
                })
                .collect::<String>();
            assert_eq!(output_filter_get_lang, "de de en ");
        } else {
            panic!("Wrong variant: {:?}", settings.filter_get_lang);
        }
        let output_filter_map_lang = settings.filter_map_lang_btmap.unwrap();
        assert_eq!(output_filter_map_lang.get("de").unwrap(), "de-DE");
        assert_eq!(output_filter_map_lang.get("en").unwrap(), "en-GB");
        let mut settings = Settings::default();
        settings.lang = "en-GB".to_string();
        env::set_var(ENV_VAR_TPNOTE_LANG_DETECTION, "de-DE, +all, de-AT, en");
        update_env_lang_detection(&mut settings);
        assert!(matches!(
            settings.filter_get_lang,
            FilterGetLang::AllLanguages
        ));
        let output_filter_map_lang = settings.filter_map_lang_btmap.unwrap();
        assert_eq!(output_filter_map_lang.get("de").unwrap(), "de-DE");
        assert_eq!(output_filter_map_lang.get("en").unwrap(), "en-GB");
        let mut settings = Settings::default();
        settings.lang = "".to_string();
        env::set_var(ENV_VAR_TPNOTE_LANG_DETECTION, "");
        update_env_lang_detection(&mut settings);
        assert!(matches!(settings.filter_get_lang, FilterGetLang::Disabled));
        assert!(settings.filter_map_lang_btmap.is_none());
        let mut settings = Settings::default();
        settings.lang = "xy-XY".to_string();
        env::set_var(ENV_VAR_TPNOTE_LANG_DETECTION, "en-GB, fr");
        update_env_lang_detection(&mut settings);
        if let FilterGetLang::SomeLanguages(ofgl) = settings.filter_get_lang {
            let output_filter_get_lang = ofgl
                .iter()
                .map(|l| {
                    let mut l = l.to_string();
                    l.push_str(" ");
                    l
                })
                .collect::<String>();
            assert_eq!(output_filter_get_lang, "en fr ");
        } else {
            panic!("Wrong variant: {:?}", settings.filter_get_lang);
        }
        let output_filter_map_lang = settings.filter_map_lang_btmap.unwrap();
        assert_eq!(output_filter_map_lang.get("en").unwrap(), "en-GB");
        let mut settings = Settings::default();
        settings.lang = "en-GB".to_string();
        env::set_var(ENV_VAR_TPNOTE_LANG_DETECTION, "de-DE, xy-XY");
        update_env_lang_detection(&mut settings);
        assert!(matches!(settings.filter_get_lang, FilterGetLang::Error(..)));
        assert!(settings.filter_map_lang_btmap.is_none());
        let mut settings = Settings::default();
        settings.lang = "en-GB".to_string();
        env::set_var(ENV_VAR_TPNOTE_LANG_DETECTION, "");
        update_env_lang_detection(&mut settings);
        assert!(matches!(settings.filter_get_lang, FilterGetLang::Disabled));
        assert!(settings.filter_map_lang_btmap.is_none());
    }
}