//! Loads and saves configuration as json files.
//!
//! Follows the [xdg](https://crates.io/crates/xdg) requirements and uses a
//! prefix of `gog-sync`.
//!
//! Uses [serde_json](https://crates.io/crates/serde_json) for serialization/deserialisation.

use serde::{Deserialize, Serialize};
use serde_json;
use std::fmt;
use std::fs::File;
use std;
use std::io::{Read, Write};
use xdg;
use xdg::BaseDirectories;

/// Wraps `std::io::Error` and `serde_json::Error`.
#[derive(Debug)]
pub enum ConfigError {
    IOError(std::io::Error),
    SerdeJsonError(serde_json::Error),
}

impl fmt::Display for ConfigError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            ConfigError::IOError(ref err) => fmt::Display::fmt(err, f),
            ConfigError::SerdeJsonError(ref err) => fmt::Display::fmt(err, f),
        }
    }
}

impl From<std::io::Error> for ConfigError {
    fn from(e: std::io::Error) -> Self {
        ConfigError::IOError(e)
    }
}

impl From<serde_json::Error> for ConfigError {
    fn from(e: serde_json::Error) -> Self {
        ConfigError::SerdeJsonError(e)
    }
}

pub struct ConfigFiles {
    xdg_dirs: BaseDirectories,
}

impl ConfigFiles {
    /// Create a new instance of `ConfigFiles`.
    /// This sets the xdg prefix to `gog-sync`.
    ///
    /// # Example
    /// ```
    /// use configfiles::ConfigFiles;
    ///
    /// let configfiles = ConfigFiles::new();
    /// ```
    pub fn new() -> ConfigFiles {
        let xdg_dirs = xdg::BaseDirectories::with_prefix("gog-sync").unwrap();
        ConfigFiles { xdg_dirs: xdg_dirs }
    }

    /// Load configuration from a json file.
    ///
    /// # Example
    /// ```
    /// use configfiles::ConfigFiles;
    ///
    /// #[derive(Serialize, Deserialize)]
    /// pub struct Config {
    ///     pub manual_url: String,
    /// }
    ///
    /// let configfiles = ConfigFiles::new();
    /// let config: Config = configfiles.load("config.json");
    /// ```
    pub fn load<T>(&self, name: &str) -> Result<T, ConfigError>
        where T: Deserialize
    {
        let config_path = self.xdg_dirs.place_config_file(name)?;
        let mut config_file = File::open(config_path)?;

        let mut config_contents = String::new();
        config_file.read_to_string(&mut config_contents)?;

        match serde_json::from_str(config_contents.as_str()) {
            Ok(value) => Ok(value),
            Err(error) => Err(ConfigError::SerdeJsonError(error)),
        }
    }

    /// Save configuration to a json file.
    ///
    /// # Example
    /// ```
    /// use configfiles::ConfigFiles;
    ///
    /// #[derive(Serialize, Deserialize)]
    /// pub struct Config {
    ///     pub manual_url: String,
    /// }
    ///
    /// let configfiles = ConfigFiles::new();
    /// let config: Config = configfiles.load("config.json");
    /// ```
    pub fn save<T>(&self, name: &str, content: &T) -> Result<(), ConfigError>
        where T: Serialize
    {
        let config_path = self.xdg_dirs.place_config_file(name)?;
        let content_toml = serde_json::to_string_pretty(content)?;

        let mut config_file = File::create(&config_path)?;
        match config_file.write_all(content_toml.as_bytes()) {
            Ok(_) => Ok(()),
            Err(error) => Err(ConfigError::IOError(error)),
        }
    }
}
