use crate::gettext;
use crate::settings_list::get_settings_list;
use json::JsonValue;
use std::collections::HashMap;
use std::fmt::{Debug, Display};
use std::fs::{remove_file, File};
use std::io::{Read, Write};
use std::path::Path;

/// Json value type
#[derive(Clone, Copy, PartialEq)]
pub enum JsonValueType {
    Str,
    Number,
    Boolean,
    Object,
    Array,
    Multiple,
}

impl JsonValueType {
    pub fn to_str(&self) -> &'static str {
        match self {
            JsonValueType::Str => "String",
            JsonValueType::Number => "Number",
            JsonValueType::Boolean => "Boolean",
            JsonValueType::Object => "Object",
            JsonValueType::Array => "Array",
            JsonValueType::Multiple => gettext("Multiple type"),
        }
    }
}

impl Debug for JsonValueType {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{}", self.to_str())
    }
}

impl Display for JsonValueType {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{}", self.to_str())
    }
}

/// An callback to check if a json value is valid
pub type SettingDesCallback = fn(obj: &JsonValue) -> bool;

/// An object to describe a setting
#[derive(Clone)]
pub struct SettingDes {
    /// The name of the setting
    _name: String,
    /// The description of the setting
    _description: String,
    /// The type of the setting
    _type: JsonValueType,
    /// The callback function of the setting
    _fun: Option<SettingDesCallback>,
}

impl SettingDes {
    /// Create a new setting description
    pub fn new(
        name: &str,
        description: &str,
        typ: JsonValueType,
        callback: Option<SettingDesCallback>,
    ) -> Option<SettingDes> {
        if (typ == JsonValueType::Array
            || typ == JsonValueType::Object
            || typ == JsonValueType::Multiple)
            && callback.is_none()
        {
            return None;
        }
        Some(SettingDes {
            _name: String::from(name),
            _description: String::from(description),
            _type: typ.clone(),
            _fun: callback,
        })
    }

    pub fn name(&self) -> &str {
        self._name.as_str()
    }

    pub fn description(&self) -> &str {
        self._description.as_str()
    }

    pub fn type_name(&self) -> &'static str {
        self._type.to_str()
    }
    /// Check if a value is valid
    pub fn is_vaild_value(&self, value: &JsonValue) -> bool {
        if self._type == JsonValueType::Array {
            if value.is_array() {
                return self._fun.unwrap()(&value);
            }
        } else if self._type == JsonValueType::Boolean {
            if value.is_boolean() {
                return true;
            }
        } else if self._type == JsonValueType::Multiple {
            return self._fun.unwrap()(&value);
        } else if self._type == JsonValueType::Number {
            if value.is_number() {
                match self._fun {
                    Some(fun) => {
                        return fun(&value);
                    }
                    None => {
                        return true;
                    }
                }
            }
        } else if self._type == JsonValueType::Object {
            if value.is_object() {
                return self._fun.unwrap()(&value);
            }
        } else if self._type == JsonValueType::Str {
            if value.is_string() {
                match self._fun {
                    Some(fun) => {
                        return fun(&value);
                    }
                    None => {
                        return true;
                    }
                }
            }
        }
        return false;
    }
}

impl Debug for SettingDes {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(
            f,
            "SettingDes {{ name: {}, description: {}, type: {} }}",
            self._name, self._description, self._type
        )
    }
}

/// Store a list of settings
#[derive(Clone, Debug)]
pub struct SettingDesStore {
    list: Vec<SettingDes>,
}

impl SettingDesStore {
    pub fn new(list: Vec<SettingDes>) -> SettingDesStore {
        SettingDesStore { list }
    }

    pub fn check_valid(&self, key: &str, value: &JsonValue) -> Option<bool> {
        for i in self.list.iter() {
            if i.name() == key {
                return Some(i.is_vaild_value(value));
            }
        }
        None
    }

    pub fn len(&self) -> usize {
        self.list.len()
    }

    pub fn print_help(&self) {
        let mut s = String::from("");
        for i in self.list.iter() {
            let mut t = format!("{}: {}", i.name(), i.type_name());
            if t.len() >= 20 {
                t += "\t";
            } else {
                t += " ".repeat(20 - t.len()).as_str();
            }
            t += i.description();
            if s.len() > 0 {
                s += "\n";
            }
            s += t.as_str();
        }
        log::error!("{}", s);
    }
}

impl Default for SettingDesStore {
    fn default() -> Self {
        Self {
            list: get_settings_list(),
        }
    }
}

/// An settings option
#[derive(Clone)]
pub struct SettingOpt {
    _name: String,
    _value: JsonValue,
}

impl SettingOpt {
    pub fn new(name: String, value: JsonValue) -> SettingOpt {
        SettingOpt {
            _name: name.clone(),
            _value: value.clone(),
        }
    }

    pub fn name(&self) -> String {
        self._name.clone()
    }

    pub fn value(&self) -> JsonValue {
        self._value.clone()
    }
}

impl Debug for SettingOpt {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        Display::fmt(&self._value, f)
    }
}

#[derive(Clone, Debug)]
pub struct SettingJar {
    pub settings: HashMap<String, SettingOpt>,
}

impl SettingJar {
    pub fn new() -> SettingJar {
        SettingJar {
            settings: HashMap::new(),
        }
    }

    pub fn add(&mut self, key: &str, opt: JsonValue) {
        self.settings
            .insert(String::from(key), SettingOpt::new(String::from(key), opt));
    }

    pub fn clear(&mut self) {
        self.settings.clear();
    }

    pub fn get(&self, key: &str) -> Option<JsonValue> {
        if self.settings.contains_key(key) {
            return Some(self.settings.get(key).unwrap().value());
        }
        None
    }

    pub fn have(&self, key: &str) -> bool {
        self.settings.contains_key(key)
    }

    pub fn to_json(&self) -> Option<JsonValue> {
        let mut v = JsonValue::new_object();
        for (_, val) in self.settings.iter() {
            match v.insert(val.name().as_str(), val.value()) {
                Ok(_) => {}
                Err(_) => {
                    log::error!("{}", gettext("Can not insert setting to JSON object."));
                    return None;
                }
            }
        }
        Some(v)
    }
}

#[derive(Clone, Debug)]
pub struct SettingStore {
    pub basic: SettingDesStore,
    pub data: SettingJar,
}

impl SettingStore {
    pub fn new(list: Vec<SettingDes>) -> Self {
        Self {
            basic: SettingDesStore::new(list),
            data: SettingJar::new(),
        }
    }

    pub fn get(&self, key: &str) -> Option<JsonValue> {
        self.data.get(key)
    }

    pub fn get_bool(&self, key: &str) -> Option<bool> {
        match self.data.get(key) {
            Some(obj) => obj.as_bool(),
            None => None,
        }
    }

    pub fn get_str(&self, key: &str) -> Option<String> {
        let obj = self.data.get(key);
        if obj.is_none() {
            return None;
        }
        let obj = obj.unwrap();
        if !obj.is_string() {
            None
        } else {
            Some(String::from(obj.as_str().unwrap()))
        }
    }

    pub fn get_u64(&self, key: &str) -> Option<u64> {
        match self.data.get(key) {
            Some(obj) => obj.as_u64(),
            None => None,
        }
    }

    pub fn have(&self, key: &str) -> bool {
        self.data.have(key)
    }

    pub fn have_bool(&self, key: &str) -> bool {
        match self.data.get(key) {
            Some(obj) => obj.is_boolean(),
            None => false,
        }
    }

    pub fn have_str(&self, key: &str) -> bool {
        let obj = self.data.get(key);
        if obj.is_none() {
            return false;
        }
        let obj = obj.unwrap();
        obj.is_string()
    }

    pub fn read(&mut self, file_name: &str, fix_invalid: bool) -> bool {
        self.data.clear();
        let path = Path::new(file_name);
        if !path.exists() {
            return false;
        }
        let re = File::open(path);
        if re.is_err() {
            log::error!("{}", re.unwrap_err());
            return false;
        }
        let mut f = re.unwrap();
        let mut s = String::from("");
        let r = f.read_to_string(&mut s);
        match r {
            Ok(le) => {
                if le == 0 {
                    if !fix_invalid {
                        log::error!("{}", gettext("Settings file is empty."));
                        return false;
                    }
                    return true;
                }
            }
            Err(_) => {
                log::error!("{}", gettext("Can not read from settings file."));
                return false;
            }
        }
        let re = json::parse(s.as_str());
        match re {
            Ok(_) => {}
            Err(_) => {
                if !fix_invalid {
                    log::error!("{}", gettext("Can not parse settings file."));
                    return false;
                }
                return true;
            }
        }
        let obj = re.unwrap();
        if !obj.is_object() {
            if !fix_invalid {
                log::error!("{}", gettext("Unknown settings file."));
                return false;
            }
            return true;
        }
        for (key, o) in obj.entries() {
            let re = self.basic.check_valid(key, o);
            match re {
                Some(re) => {
                    if !re {
                        if !fix_invalid {
                            let s = gettext("\"<key>\" is invalid, you can use \"pixiv_downloader config fix\" to remove all invalid value.").replace("<key>", key);
                            log::error!("{}", s.as_str());
                            return false;
                        }
                    } else {
                        self.data.add(key, o.clone());
                    }
                }
                None => {
                    self.data.add(key, o.clone());
                }
            }
        }
        true
    }

    pub fn save(&self, file_name: &str) -> bool {
        let obj = self.data.to_json();
        if obj.is_none() {
            log::error!("{}", gettext("Can not convert settings to JSON object."));
            return false;
        }
        let obj = obj.unwrap();
        let s = json::stringify(obj);
        let path = Path::new(file_name);
        if path.exists() {
            match remove_file(path) {
                Ok(_) => {}
                Err(e) => {
                    log::error!("{} {}", gettext("Failed to remove file:"), e);
                    return false;
                }
            }
        }
        let r = File::create(path);
        if r.is_err() {
            log::error!("{} {}", gettext("Failed to create file:"), r.unwrap_err());
            return false;
        }
        let mut f = r.unwrap();
        let r = f.write(s.as_bytes());
        if r.is_err() {
            log::error!("{} {}", gettext("Failed to write file:"), r.unwrap_err());
            return false;
        }
        let r = f.flush();
        if r.is_err() {
            log::error!("{} {}", gettext("Failed to flush file:"), r.unwrap_err());
            return false;
        }
        true
    }
}

impl Default for SettingStore {
    fn default() -> Self {
        Self::new(get_settings_list())
    }
}
