use bracket_lib::prelude::*;
use toml::Value;
use serde::{ Serialize, Deserialize };

lazy_static! {
    pub static ref CONFIG: Config = try_load_configuration();
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Config {
    pub logging: LogConfig,
    pub visuals: VisualConfig,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct LogConfig {
    pub show_mapgen: bool,
    pub log_combat: bool,
    pub log_spawning: bool,
    pub log_ticks: bool,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct VisualConfig {
    pub with_scanlines: bool,
    pub with_screen_burn: bool,
    pub with_darken_by_distance: bool,
    pub use_bitset_walls: bool,
    pub use_coloured_tile_bg: bool,
    pub add_colour_variance: bool,
}

impl Default for Config {
    fn default() -> Self {
        Config {
            logging: LogConfig {
                show_mapgen: true,
                log_combat: false,
                log_spawning: false,
                log_ticks: false,
            },
            visuals: VisualConfig {
                with_scanlines: false,
                with_screen_burn: false,
                with_darken_by_distance: true,
                use_bitset_walls: true,
                use_coloured_tile_bg: true,
                add_colour_variance: true,
            },
        }
    }
}

impl Config {
    pub fn load_from_file(filename: &str) -> Config {
        if let Ok(contents) = std::fs::read_to_string(filename) {
            let parsed_config: Result<Value, _> = toml::from_str(&contents);
            if let Ok(parsed_config) = parsed_config {
                let mut config = Config::default();
                let mut requires_write = false;
                requires_write |= config.logging.apply_values(&parsed_config);
                requires_write |= config.visuals.apply_values(&parsed_config);
                if requires_write {
                    console::log("Parsed config, but some values were changed. Saving new ver.");
                    if let Err(write_err) = config.save_to_file(filename) {
                        console::log(format!("Error writing config: {:?}", write_err));
                    }
                }
                return config;
            }
        }
        let config = Config::default();
        if let Err(write_err) = config.save_to_file(filename) {
            console::log(format!("Error writing config: {:?}", write_err));
        }
        return config;
    }
    pub fn save_to_file(&self, filename: &str) -> Result<(), Box<dyn std::error::Error>> {
        let toml_string = toml::to_string(self)?;
        std::fs::write(filename, toml_string)?;
        Ok(())
    }
}

macro_rules! apply_bool_value {
    ($config:expr, $parsed_config:expr, $changed:expr, $field:ident) => {
        if let Some(value) = $parsed_config.get(stringify!($field)).and_then(|v| v.as_bool()) {
            if $config.$field != value {
                $config.$field = value;
                $changed = true;
            }
        }
    };
}

trait Section {
    fn apply_values(&mut self, parsed_config: &Value) -> bool;
}

impl Section for LogConfig {
    fn apply_values(&mut self, parsed_config: &Value) -> bool {
        if let Some(section) = parsed_config.get("logging") {
            let mut missing = false;
            apply_bool_value!(self, section, missing, show_mapgen);
            apply_bool_value!(self, section, missing, log_combat);
            apply_bool_value!(self, section, missing, log_spawning);
            apply_bool_value!(self, section, missing, log_ticks);
            missing
        } else {
            true
        }
    }
}

impl Section for VisualConfig {
    fn apply_values(&mut self, parsed_config: &Value) -> bool {
        if let Some(section) = parsed_config.get("visuals") {
            let mut missing = false;
            apply_bool_value!(self, section, missing, with_scanlines);
            apply_bool_value!(self, section, missing, with_screen_burn);
            apply_bool_value!(self, section, missing, with_darken_by_distance);
            apply_bool_value!(self, section, missing, use_bitset_walls);
            apply_bool_value!(self, section, missing, use_coloured_tile_bg);
            apply_bool_value!(self, section, missing, add_colour_variance);
            missing
        } else {
            true
        }
    }
}

#[cfg(not(target_arch = "wasm32"))]
pub fn try_load_configuration() -> Config {
    let config: Config = Config::load_from_file("config.toml");
    return config;
}

#[cfg(target_arch = "wasm32")]
pub fn try_load_configuration() -> Config {
    let config = Config::default();
    return config;
}
