#![allow(unused)]

//! Config file handler
//! Cares about formatting and type conversion
use anyhow::{Context, Result};
use std::{
    collections::HashMap,
    fmt::Display,
    fs::File,
    io::{BufRead, BufReader, Write},
    path::Path,
    str::FromStr,
};
/// Value abstraction to convert to and from values
#[derive(Clone, Debug)]
pub enum Value {
    Value(String),
    Array(Vec<String>),
}
struct ConfigSection {
    header: String,
    body: HashMap<String, Value>,
}
impl Value {
    /// Creates an empty value
    pub fn empty() -> Value {
        Value::Value("".to_string())
    }
    /// Creates an empty array value
    pub fn empty_array() -> Value {
        Value::Array(Vec::new())
    }
    /// Create a value `Value::Array(Vec<String>)`, internal conversion to string
    ///
    /// # Arguments
    ///
    /// `array` array of type, type has to implement `Display` trait
    #[deprecated]
    pub fn from_array<T: Display>(array: &[T]) -> Self {
        Value::Array(array.iter().map(|v| format!("{}", v)).collect())
    }
    /// Creates a value `Value::Value(String)`, internal conversion to string
    ///
    /// # Arguments
    ///
    /// `value` type has to implement `Display` trait
    #[deprecated]
    pub fn from_value<T: Display>(value: &T) -> Self {
        Value::Value(format!("{}", value))
    }
    pub fn to_array<T: FromStr>(&self) -> Result<Vec<T>> {
        match self {
            Value::Array(value_array) => {
                let mut target_array = Vec::with_capacity(value_array.len());
                for value_string in value_array {
                    match value_string.parse::<T>() {
                        Ok(val) => target_array.push(val),
                        Err(_) => {
                            return Err(anyhow::Error::msg(format!(
                                "ConfigHandler: Error while parsing Value Array: {}",
                                value_string
                            )));
                        }
                    }
                }
                Ok(target_array)
            }
            _ => Err(anyhow::Error::msg(
                "ConfigHandler: Error when requesting the wrong value type",
            )),
        }
    }
    pub fn to_value<T: FromStr>(&self) -> Result<T> {
        match self {
            Value::Value(value_string) => match value_string.parse::<T>() {
                Ok(val) => Ok(val),
                Err(_) => Err(anyhow::Error::msg(format!(
                    "ConfigHandler: Error while parsing Value Array: {}",
                    value_string
                ))),
            },
            _ => Err(anyhow::Error::msg(
                "ConfigHandler: Error when requesting the wrong value type",
            )),
        }
    }
}
impl<'a, T: Display> From<&'a T> for Value {
    fn from(v: &'a T) -> Self {
        Value::Value(format!("{}", v))
    }
}
impl<'a, T: Display> From<&'a [T]> for Value {
    fn from(v: &'a [T]) -> Self {
        Value::Array(v.iter().map(|v| format!("{}", v)).collect())
    }
}
/// Handler struct
pub struct ConfigHandler {}
impl ConfigHandler {
    /// Reads the given config file
    ///
    /// # Arguments
    ///
    /// `file_name` file that is going to be read
    pub fn read_config(
        file_name: impl AsRef<Path>,
    ) -> Result<HashMap<String, HashMap<String, Value>>> {
        let file = File::open(&file_name).with_context({
            let file_name = file_name.as_ref().to_str().unwrap().to_string();
            || file_name
        })?;
        let mut infos = HashMap::new();
        let mut current_section: Option<ConfigSection> = None;
        for line_res in BufReader::new(file).lines() {
            if let Ok(line) = line_res {
                let mut trimmed = line.trim().to_string();
                if trimmed.starts_with('#') || trimmed.is_empty() {
                    continue;
                } else if trimmed.starts_with('[') && trimmed.ends_with(']') {
                    trimmed.remove(0);
                    trimmed.pop();
                    if let Some(ref section) = current_section {
                        infos.insert(section.header.clone(), section.body.clone());
                    }
                    current_section = Some(ConfigSection {
                        header: trimmed,
                        body: HashMap::new(),
                    });
                } else {
                    let mut split = trimmed.split('=');
                    let key = match split.nth(0) {
                        Some(key) => key.trim().to_string(),
                        None => {
                            println!("cannot get key from line: {}", trimmed);
                            continue;
                        }
                    };
                    let value = match split.last() {
                        Some(value) => value.trim().to_string(),
                        None => {
                            println!("cannot get value from line: {}", trimmed);
                            continue;
                        }
                    };
                    if value.starts_with('[') && value.ends_with(']') {
                        let mut trimmed_value = value;
                        trimmed_value.remove(0);
                        trimmed_value.pop();
                        let value_split = trimmed_value.split(',');
                        let mut value_array = Vec::new();
                        for v in value_split {
                            let trimmed = v.trim();
                            if !trimmed.is_empty() {
                                value_array.push(trimmed.to_string());
                            }
                        }
                        if let Some(ref mut section) = current_section {
                            section.body.insert(key, Value::Array(value_array));
                        }
                    } else if let Some(ref mut section) = current_section {
                        section.body.insert(key, Value::Value(value));
                    }
                }
            }
        }
        // also push the last section
        if let Some(section) = current_section {
            infos.insert(section.header, section.body);
        }
        Ok(infos)
    }
    /// writes a formatted config file
    ///
    /// # Arguments
    ///
    /// `file_name` the file to which the config gets written
    /// `sections` the sections and keys that are going to be written
    pub fn write_config(
        file_name: impl AsRef<Path>,
        sections: &[(&str, Vec<(&str, Value)>)],
    ) -> Result<()> {
        let mut file = File::create(file_name)?;
        for (header, body) in sections {
            let fmt_header = format!("[{}]\n", header);
            file.write_all(fmt_header.as_bytes())?;
            for (key, value) in body.iter() {
                let fmt_key_value = format!(
                    "{} = {}\n",
                    key,
                    match value {
                        Value::Value(val) => val.clone(),
                        Value::Array(array) => {
                            let mut array_value = "[".to_string();
                            for (i, val) in array.iter().enumerate() {
                                // if element is not the last one
                                if i != array.len() - 1 {
                                    array_value = format!("{}{}, ", array_value, val);
                                } else {
                                    array_value = format!("{}{}", array_value, val);
                                }
                            }
                            format!("{}]", array_value)
                        }
                    }
                );
                file.write_all(fmt_key_value.as_bytes())?;
            }
            file.write_all("\n".as_bytes())?;
        }
        Ok(())
    }
}
#[macro_export]
macro_rules! create_settings_section {
    ($struct_name:ident, $section_key:expr, {$($var:ident: $var_type:ty,)* $([$array:ident: $array_type:ty],)*} $(,$($derive:ident,)*)?) => {
        #[derive(Default, Debug, Clone, PartialEq $(, $($derive,)* )? )]
        pub struct $struct_name {
            $(
                pub $var: $var_type,
            )*
            $(
                pub $array: Vec<$array_type>,
            )*
        }
        impl $struct_name {
            const SECTION_KEY: &'static str = $section_key;
            pub fn load(
                parsed_config: &std::collections::HashMap<String, std::collections::HashMap<String, Value>>
            ) -> anyhow::Result<Self> {
                let mut me = Self::default();
                if let Some(section) = parsed_config.get(Self::SECTION_KEY) {
                    $(
                        if let Some(value) = section.get(stringify!($var)) {
                            me.$var = value.to_value()?;
                        }
                    )*
                    $(
                        if let Some(array) = section.get(stringify!($array)) {
                            me.$array = array.to_array()?;
                        }
                    )*
                }
                Ok(me)
            }
            pub fn store(&self) -> (&str, Vec<(&str, Value)>) {
                (
                    Self::SECTION_KEY,
                    vec![
                        $(
                            (stringify!($var), Value::from(&self.$var)),
                        )*
                        $(
                            (stringify!($array), Value::from(self.$array.as_slice())),
                        )*
                    ]
                )
            }
        }
    };
}
#[macro_export]
macro_rules! create_settings_container {
    ($struct_name:ident, {$($var:ident: $var_type:ty$(,)?)*} $(,$($derive:ident,)*)? ) => {
        #[derive(Default, Debug, Clone, PartialEq $(, $($derive,)* )? )]
        pub struct $struct_name {
            pub file_name: assetpath::AssetPath,
            $(
                pub $var: $var_type,
            )*
        }
        impl $struct_name {
            pub fn load(file: impl Into<assetpath::AssetPath>) -> anyhow::Result<Self> {
                let file = file.into();
                let parsed_stats_settings = ConfigHandler::read_config(&file.full_path())?;
                Ok($struct_name {
                    file_name: file,
                    $(
                        $var: <$var_type>::load(&parsed_stats_settings)?,
                    )*
                })
            }
            pub fn load_with_default(&mut self, file: impl Into<assetpath::AssetPath>) -> anyhow::Result<()> {
                let file = file.into();
                let parsed_stats_settings = ConfigHandler::read_config(&file.full_path())?;
                self.file_name = file;
                $(
                    self.$var = <$var_type>::load(&parsed_stats_settings)?;
                )*
                Ok(())
            }
            pub fn store(&self) -> anyhow::Result<()> {
                ConfigHandler::write_config(
                    &self.file_name.full_path(),
                    &[
                        $(
                            self.$var.store(),
                        )*
                    ]
                )
            }
        }
    };
}
