use crate::theme::Theme;
use std::env;
use std::env::VarError;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use thiserror::Error;

pub fn parse_theme(custom_theme_path: Option<&PathBuf>) -> Result<Theme, ThemeError> {
    if let Some(path) = custom_theme_path {
        return read_and_parse_toml(path);
    }

    let default_path = get_config_dir()?.join("tailspin").join("theme.toml");

    match read_and_parse_toml(&default_path) {
        Err(ThemeError::Read(_, err)) if err.kind() == io::ErrorKind::NotFound => Ok(Theme::default()),
        other => other,
    }
}

// XDG_CONFIG_HOME and HOME cover Unix (and Git Bash on Windows, which sets
// HOME); %APPDATA% is the Windows convention for user configuration.
fn get_config_dir() -> Result<PathBuf, ThemeError> {
    expand_var_os("XDG_CONFIG_HOME")
        .or_else(|| expand_var_os("HOME").map(|home| home.join(".config")))
        .or_else(|| expand_var_os("APPDATA"))
        .ok_or(ThemeError::HomeEnvironment(VarError::NotPresent))
}

fn expand_var_os(key: &str) -> Option<PathBuf> {
    env::var_os(key)
        .and_then(|os_str| os_str.into_string().ok())
        .map(|s| shellexpand::tilde(&s).into_owned().into())
}

fn read_and_parse_toml(path: &Path) -> Result<Theme, ThemeError> {
    let display_path = || path.display().to_string();

    let content = fs::read_to_string(path).map_err(|err| ThemeError::Read(display_path(), err))?;

    toml::from_str::<Theme>(&content).map_err(|err| ThemeError::Parsing(display_path(), err))
}

#[derive(Debug, Error)]
pub enum ThemeError {
    #[error("could not read {0}")]
    Read(String, #[source] io::Error),

    #[error("could not parse {0}: {1}")]
    Parsing(String, toml::de::Error),

    #[error("could not determine the home environment: {0}")]
    HomeEnvironment(#[source] VarError),
}
