use failure::Error;
use json;

use std::path::PathBuf;
use std::str::FromStr;

use glob;
use std::borrow::Cow;
use std::fmt;
use treediff::tools::MutableFilter;

#[derive(Default)]
pub struct State {
    pub select_next_at: Option<String>,
    pub insert_next_at: Option<String>,
    pub output_mode: Option<OutputMode>,
    pub merge_mode: MergeMode,
    pub value: Option<json::Value>,
    pub buffer: Vec<json::Value>,
}

#[derive(Debug, Clone)]
pub enum MergeMode {
    Overwrite,
    NoOverwrite,
}

impl Default for MergeMode {
    fn default() -> Self {
        MergeMode::NoOverwrite
    }
}

#[derive(Debug)]
pub enum Command {
    MergeValue(String, String),
    InsertNextMergeAt(String),
    SelectNextMergeAt(String),
    SelectToBuffer(String),
    MergeStdin,
    MergePath(PathBuf),
    MergeEnvironment(glob::Pattern),
    SetMergeMode(MergeMode),
    Serialize,
    SerializeBuffer,
    SetOutputMode(OutputMode),
}

#[derive(Debug)]
pub enum OutputMode {
    Json,
    Yaml,
}

impl Default for OutputMode {
    fn default() -> Self {
        OutputMode::Json
    }
}

impl FromStr for OutputMode {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(match s.to_lowercase().as_str() {
            "json" => OutputMode::Json,
            "yaml" => OutputMode::Yaml,
            _ => bail!("Not a valid output mode: '{}'", s),
        })
    }
}

pub struct NeverDrop {
    pub mode: MergeMode,
    pub clashed_keys: Vec<String>,
}

impl NeverDrop {
    pub fn with_mode(mode: &MergeMode) -> Self {
        NeverDrop {
            mode: mode.clone(),
            clashed_keys: Vec::new(),
        }
    }
}

impl fmt::Display for NeverDrop {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        writeln!(f, "Refusing to merge due to the following clashing keys:")?;
        for key in &self.clashed_keys {
            writeln!(f, "{}\n", key)?;
        }
        Ok(())
    }
}

impl MutableFilter for NeverDrop {
    fn resolve_conflict<'a, K: fmt::Display, V: Clone>(
        &mut self,
        keys: &[K],
        old: &'a V,
        new: &'a V,
        _self_of_whole_value: &mut V,
    ) -> Option<Cow<'a, V>> {
        match self.mode {
            MergeMode::NoOverwrite => {
                self.clashed_keys
                    .push(keys.iter().map(|k| format!("{}", k)).collect::<Vec<_>>().join("."));
                Some(Cow::Borrowed(old))
            }
            MergeMode::Overwrite => Some(Cow::Borrowed(new)),
        }
    }

    fn resolve_removal<'a, K: fmt::Display, V: Clone>(
        &mut self,
        _keys: &[K],
        removed: &'a V,
        _self_of_whole_value: &mut V,
    ) -> Option<Cow<'a, V>> {
        Some(Cow::Borrowed(removed))
    }
}
