use ini::Ini;
use std::{cell::RefCell, rc::Rc, str::FromStr};

const CONFIG_NAME: &str = "patchpal.conf";

#[derive(Clone, Debug)]
pub struct Config {
    ini: Rc<RefCell<Ini>>,
    local: Rc<RefCell<Ini>>,
    section: Option<&'static str>,
}

impl Config {
    /// For historical reason, the Gtk frontend uses `None` as the `section`. Other
    /// frontends should use their own section.
    pub fn new(section: Option<&'static str>) -> Self {
        let base = xdg::BaseDirectories::new();
        let ini = if let Some(path) = base.find_config_file(CONFIG_NAME) {
            Ini::load_from_file(&path).unwrap()
        } else {
            Ini::new()
        };
        Self {
            ini: Rc::new(RefCell::new(ini)),
            local: Rc::new(RefCell::new(Ini::new())),
            section,
        }
    }

    /// Get the given key from our section.
    pub fn get<T: FromStr>(&self, key: &str) -> Option<T> {
        let ini = self.ini.borrow();
        ini.get_from(self.section, key).map(|v| v.parse::<T>().ok()).flatten()
    }

    /// Set the given key in our section.
    pub fn set(&self, key: impl Into<String>, value: String) {
        let mut ini = self.ini.borrow_mut();
        ini.set_to(self.section, key.into(), value);
    }

    fn get_global<T: FromStr>(&self, key: &str) -> Option<T> {
        let ini = self.ini.borrow();
        ini.get_from(None::<String>, key).map(|v| v.parse::<T>().ok()).flatten()
    }

    fn set_global(&self, key: impl Into<String>, value: String) {
        let mut ini = self.ini.borrow_mut();
        ini.set_to(None::<String>, key.into(), value);
    }

    /// Get the telemetry id from the global section.
    pub fn get_telemetry_id(&self) -> Option<uuid::Uuid> {
        self.get_global("telemetry-id")
    }

    /// Set the telemetry id to the global section.
    pub fn set_telemetry_id(&self, id: uuid::Uuid) {
        self.set_global("telemetry-id", id.hyphenated().to_string());
    }

    /// Get the given key from local storage previously set by `set_local`.
    pub fn get_local<T: FromStr>(&self, key: &str) -> Option<T> {
        let local = self.local.borrow();
        local.get_from(None::<String>, key).map(|v| v.parse::<T>().ok()).flatten()
    }

    /// Set the given key to local storage. This will not be saved to the config file.
    pub fn set_local(&self, key: impl Into<String>, value: String) {
        let mut local = self.local.borrow_mut();
        local.set_to(None::<String>, key.into(), value);
    }

    pub fn save(&self) -> std::io::Result<()> {
        let ini = self.ini.borrow();
        let base = xdg::BaseDirectories::new();
        let path = base.place_config_file(CONFIG_NAME)?;
        ini.write_to_file(path)
    }
}
