use crate::config::LIB_CFG;
use crate::error::ConfigError;
use lingua;
#[cfg(feature = "lang-detection")]
use lingua::IsoCode639_1;
use std::collections::HashMap;
use std::{env, mem, str::FromStr, sync::RwLock};
#[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_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";
#[cfg(feature = "lang-detection")]
#[derive(Debug)]
pub(crate) struct Settings {
    pub author: String,
    pub lang: String,
    pub filter_get_lang: Result<Vec<IsoCode639_1>, ConfigError>,
    pub filter_map_lang_hmap: Option<HashMap<String, String>>,
}
#[cfg(not(feature = "lang-detection"))]
#[derive(Debug)]
pub(crate) struct Settings {
    pub author: String,
    pub lang: String,
    pub filter_get_lang: Result<Vec<String>, ConfigError>,
    pub filter_map_lang_hmap: Option<HashMap<String, String>>,
}
impl Default for Settings {
    fn default() -> Self {
        Settings {
            author: String::new(),
            lang: String::new(),
            filter_get_lang: Ok(vec![]),
            filter_map_lang_hmap: None,
        }
    }
}
pub(crate) static SETTINGS: RwLock<Settings> = RwLock::new(Settings {
    author: String::new(),
    lang: String::new(),
    filter_get_lang: Ok(vec![]),
    filter_map_lang_hmap: None,
});
pub(crate) fn force_lang_setting(lang: &str) {
    let lang = lang.trim();
    let mut settings = SETTINGS.write().unwrap();
    if lang != "-" {
        let _ = mem::replace(&mut settings.lang, lang.to_string());
    }
    let _ = mem::replace(&mut settings.filter_get_lang, Ok(vec![]));
}
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())
        })
    });
    let _ = mem::replace(&mut settings.author, author);
}
pub fn update_settings() -> Result<(), ConfigError> {
    let mut settings = SETTINGS.write().unwrap();
    update_author_setting(&mut settings);
    update_lang_setting(&mut settings);
    update_filter_get_lang_setting(&mut settings);
    update_filter_map_lang_hmap_setting(&mut settings);
    update_env_lang_detection(&mut settings);
    log::trace!("`SETTINGS` updated:\n{:#?}", settings);
    if let Err(e) = &settings.filter_get_lang {
        Err(e.clone())
    } else {
        Ok(())
    }
}
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)]);
        }
    };
    let _ = mem::replace(&mut settings.lang, lang);
}
#[cfg(feature = "lang-detection")]
fn update_filter_get_lang_setting(settings: &mut Settings) {
    let lib_cfg = LIB_CFG.read().unwrap();
    match lib_cfg
        .tmpl
        .filter_get_lang
        .iter()
        .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());
                ConfigError::ParseLanguageCode {
                    language_code: l.into(),
                    all_langs,
                }
            })
        })
        .collect::<Result<Vec<IsoCode639_1>, ConfigError>>()
    {
        Ok(mut iso_codes) => {
            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);
                        }
                    }
                }
            }
            let _ = mem::replace(&mut settings.filter_get_lang, Ok(iso_codes));
        }
        Err(e) =>
        {
            let _ = mem::replace(&mut settings.filter_get_lang, Err(e));
        }
    }
}
#[cfg(not(feature = "lang-detection"))]
fn update_filter_get_lang_setting(settings: &mut Settings) {
    let _ = mem::replace(&mut settings.filter_get_lang, Ok(vec![]));
}
fn update_filter_map_lang_hmap_setting(settings: &mut Settings) {
    let mut hm = HashMap::new();
    let lib_cfg = LIB_CFG.read().unwrap();
    for l in &lib_cfg.tmpl.filter_map_lang {
        if l.len() >= 2 {
            hm.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() && !hm.contains_key(lang_subtag) {
                hm.insert(lang_subtag.to_string(), settings.lang.to_string());
            }
        };
    }
    let _ = mem::replace(&mut settings.filter_map_lang_hmap, Some(hm));
}
fn update_env_lang_detection(settings: &mut Settings) {
    if let Ok(env_var) = env::var(ENV_VAR_TPNOTE_LANG_DETECTION) {
        if env_var == "" {
            let _ = mem::replace(&mut settings.filter_get_lang, Ok(vec![]));
            let _ = mem::replace(&mut settings.filter_map_lang_hmap, None);
            log::info!(
                "Empty env. var. `{}` disables `lang-detection` feature.",
                ENV_VAR_TPNOTE_LANG_DETECTION
            );
            return;
        }
        let mut hm: HashMap<String, String> = HashMap::new();
        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
                }
            })
            .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());
                    ConfigError::ParseLanguageCode {
                        language_code: l.into(),
                        all_langs,
                    }
                })
            })
            .collect::<Result<Vec<IsoCode639_1>, ConfigError>>()
        {
            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());
                            }
                        }
                    }
                }
                let _ = mem::replace(&mut settings.filter_get_lang, Ok(iso_codes));
                let _ = mem::replace(&mut settings.filter_map_lang_hmap, Some(hm));
            }
            Err(e) =>
            {
                let _ = mem::replace(&mut settings.filter_get_lang, Err(e));
            }
        }
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_update_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]
    #[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();
        let _ = mem::replace(&mut settings.lang, "en-GB".to_string());
        update_filter_get_lang_setting(&mut settings);
        let output_filter_get_lang = settings
            .filter_get_lang
            .unwrap()
            .iter()
            .map(|l| {
                let mut l = l.to_string();
                l.push_str(" ");
                l
            })
            .collect::<String>();
        assert_eq!(output_filter_get_lang, "en fr de ");
        let mut settings = Settings::default();
        let _ = mem::replace(&mut settings.lang, "it-IT".to_string());
        update_filter_get_lang_setting(&mut settings);
        let output_filter_get_lang = settings
            .filter_get_lang
            .unwrap()
            .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 ");
    }
    #[test]
    fn test_update_filter_map_lang_hmap_setting() {
        let mut settings = Settings::default();
        let _ = mem::replace(&mut settings.lang, "it-IT".to_string());
        update_filter_map_lang_hmap_setting(&mut settings);
        let output_filter_map_lang = settings.filter_map_lang_hmap.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();
        let _ = mem::replace(&mut settings.lang, "it".to_string());
        update_filter_map_lang_hmap_setting(&mut settings);
        let output_filter_map_lang = settings.filter_map_lang_hmap.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();
        let _ = mem::replace(&mut 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);
        let output_filter_get_lang = settings
            .filter_get_lang
            .unwrap()
            .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 ");
        let output_filter_map_lang = settings.filter_map_lang_hmap.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();
        let _ = mem::replace(&mut 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);
        let output_filter_get_lang = settings
            .filter_get_lang
            .unwrap()
            .iter()
            .map(|l| {
                let mut l = l.to_string();
                l.push_str(" ");
                l
            })
            .collect::<String>();
        assert_eq!(output_filter_get_lang, "de de en ");
        let output_filter_map_lang = settings.filter_map_lang_hmap.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();
        let _ = mem::replace(&mut settings.lang, "".to_string());
        env::set_var(ENV_VAR_TPNOTE_LANG_DETECTION, "");
        update_env_lang_detection(&mut settings);
        assert!(settings.filter_get_lang.unwrap().is_empty());
        assert!(settings.filter_map_lang_hmap.is_none());
        let mut settings = Settings::default();
        let _ = mem::replace(&mut 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);
        let output_filter_get_lang = settings
            .filter_get_lang
            .unwrap()
            .iter()
            .map(|l| {
                let mut l = l.to_string();
                l.push_str(" ");
                l
            })
            .collect::<String>();
        assert_eq!(output_filter_get_lang, "de de en ");
        let output_filter_map_lang = settings.filter_map_lang_hmap.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();
        let _ = mem::replace(&mut settings.lang, "".to_string());
        env::set_var(ENV_VAR_TPNOTE_LANG_DETECTION, "");
        update_env_lang_detection(&mut settings);
        assert!(settings.filter_get_lang.unwrap().is_empty());
        assert!(settings.filter_map_lang_hmap.is_none());
        let mut settings = Settings::default();
        let _ = mem::replace(&mut settings.lang, "xy-XY".to_string());
        env::set_var(ENV_VAR_TPNOTE_LANG_DETECTION, "en-GB");
        update_env_lang_detection(&mut settings);
        let output_filter_get_lang = settings
            .filter_get_lang
            .unwrap()
            .iter()
            .map(|l| {
                let mut l = l.to_string();
                l.push_str(" ");
                l
            })
            .collect::<String>();
        assert_eq!(output_filter_get_lang, "en ");
        let output_filter_map_lang = settings.filter_map_lang_hmap.unwrap();
        assert_eq!(output_filter_map_lang.get("en").unwrap(), "en-GB");
        let mut settings = Settings::default();
        let _ = mem::replace(&mut 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!(settings.filter_get_lang.is_err());
        assert!(settings.filter_map_lang_hmap.is_none());
        let mut settings = Settings::default();
        let _ = mem::replace(&mut settings.lang, "en-GB".to_string());
        env::set_var(ENV_VAR_TPNOTE_LANG_DETECTION, "");
        update_env_lang_detection(&mut settings);
        assert_eq!(settings.filter_get_lang.unwrap(), vec![]);
        assert!(settings.filter_map_lang_hmap.is_none());
    }
}