use failure::{Error, Fail, ResultExt};
use handlebars;
use json;
use liquid;
use yaml;

use std::io::Read;

pub use super::spec::*;
use std::io::Cursor;
use std::str::FromStr;

pub mod liquid_filters {
    use liquid::compiler::Filter;
    use liquid::derive::*;
    use liquid::error::Result;
    use liquid::interpreter::Context;
    use liquid::value::Value;

    #[derive(Clone, ParseFilter, FilterReflection)]
    #[filter(name = "base64", description = "convert a string to base64", parsed(Base64Filter))]
    pub struct Base64;

    #[derive(Debug, Default, Display_filter)]
    #[name = "base64"]
    struct Base64Filter;

    impl Filter for Base64Filter {
        fn evaluate(&self, input: &Value, _context: &Context) -> Result<Value> {
            Ok(Value::scalar(base64::encode(input.to_str().as_bytes())))
        }
    }
}

#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Engine {
    Handlebars,
    Liquid,
}

impl FromStr for Engine {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
        Ok(match s {
            "liquid" => Engine::Liquid,
            "handlebars" => Engine::Handlebars,
            _ => bail!("Engine named '{}' is unknown", s),
        })
    }
}

pub enum EngineChoice {
    Handlebars(handlebars::Handlebars, json::Value),
    Liquid(liquid::Parser, liquid::value::Object),
}

pub fn validate(data: &StreamOrPath, specs: &[Spec]) -> Result<(), Error> {
    if specs.is_empty() {
        bail!("No spec provided, neither from standard input, nor from file")
    }
    let count_of_specs_needing_stdin = specs.iter().filter(|s| s.src.is_stream()).count();
    if count_of_specs_needing_stdin > 1 {
        bail!("Cannot read more than one template spec from standard input")
    }
    if data.is_stream() && count_of_specs_needing_stdin == 1 {
        bail!("Data is read from standard input, as well as one template. Please choose one")
    }
    if let Some(spec_which_overwrites_input) = specs
        .iter()
        .filter_map(|s| {
            use self::StreamOrPath::*;
            match (&s.src, &s.dst) {
                (&Path(ref src), &Path(ref dst)) => src
                    .canonicalize()
                    .and_then(|src| dst.canonicalize().map(|dst| (src, dst)))
                    .ok()
                    .and_then(|(src, dst)| if src == dst { Some(s) } else { None }),
                _ => None,
            }
        })
        .next()
    {
        bail!(
            "Refusing to overwrite input file at '{}' with output",
            spec_which_overwrites_input.src
        )
    }
    Ok(())
}

pub fn de_json_or_yaml<R: Read>(mut reader: R) -> Result<json::Value, Error> {
    let mut buf = Vec::<u8>::new();
    reader
        .read_to_end(&mut buf)
        .context("Could not read input stream data deserialization")?;

    Ok(match json::from_reader::<_, json::Value>(Cursor::new(&buf)) {
        Ok(v) => v,
        Err(json_err) => yaml::from_reader(Cursor::new(&buf)).map_err(|yaml_err| {
            yaml_err
                .context("YAML deserialization failed")
                .context(json_err)
                .context("JSON deserialization failed")
                .context("Could not deserialize data, tried JSON and YAML")
        })?,
    })
}
