use std::collections::BTreeMap;

use serde_json::value::Value as Json;

use helpers::{HelperDef, HelperResult};
use registry::Registry;
use context::{to_json, JsonTruthy};
use render::{Helper, RenderContext, Renderable};
use error::RenderError;

#[derive(Clone, Copy)]
pub struct EachHelper;

impl HelperDef for EachHelper {
    fn call(&self, h: &Helper, r: &Registry, rc: &mut RenderContext) -> HelperResult {
        let value = try!(
            h.param(0)
                .ok_or_else(|| RenderError::new("Param not found for helper \"each\""))
        );

        let template = h.template();

        match template {
            Some(t) => {
                rc.promote_local_vars();
                let local_path_root = value
                    .path_root()
                    .map(|p| format!("{}/{}", rc.get_path(), p));

                debug!("each value {:?}", value.value());
                let rendered = match (value.value().is_truthy(), value.value()) {
                    (true, &Json::Array(ref list)) => {
                        let len = list.len();
                        for i in 0..len {
                            let mut local_rc = rc.derive();
                            if let Some(ref p) = local_path_root {
                                local_rc.push_local_path_root(p.clone());
                            }

                            local_rc.set_local_var("@first".to_string(), to_json(&(i == 0usize)));
                            local_rc.set_local_var("@last".to_string(), to_json(&(i == len - 1)));
                            local_rc.set_local_var("@index".to_string(), to_json(&i));

                            if let Some(inner_path) = value.path() {
                                let new_path =
                                    format!("{}/{}/[{}]", local_rc.get_path(), inner_path, i);
                                debug!("each path {:?}", new_path);
                                local_rc.set_path(new_path.clone());
                            }

                            if let Some(block_param) = h.block_param() {
                                let mut map = BTreeMap::new();
                                map.insert(block_param.to_string(), to_json(&list[i]));
                                local_rc.push_block_context(&map)?;
                            }

                            try!(t.render(r, &mut local_rc));

                            if h.block_param().is_some() {
                                local_rc.pop_block_context();
                            }

                            if local_path_root.is_some() {
                                local_rc.pop_local_path_root();
                            }
                        }
                        Ok(())
                    }
                    (true, &Json::Object(ref obj)) => {
                        let mut first: bool = true;
                        for k in obj.keys() {
                            let mut local_rc = rc.derive();
                            if let Some(ref p) = local_path_root {
                                local_rc.push_local_path_root(p.clone());
                            }
                            local_rc.set_local_var("@first".to_string(), to_json(&first));
                            if first {
                                first = false;
                            }

                            local_rc.set_local_var("@key".to_string(), to_json(k));

                            if let Some(inner_path) = value.path() {
                                let new_path =
                                    format!("{}/{}/[{}]", local_rc.get_path(), inner_path, k);
                                local_rc.set_path(new_path);
                            }

                            if let Some((bp_key, bp_val)) = h.block_param_pair() {
                                let mut map = BTreeMap::new();
                                map.insert(bp_key.to_string(), to_json(k));
                                map.insert(bp_val.to_string(), to_json(obj.get(k).unwrap()));
                                local_rc.push_block_context(&map)?;
                            }

                            try!(t.render(r, &mut local_rc));

                            if h.block_param().is_some() {
                                local_rc.pop_block_context();
                            }

                            if local_path_root.is_some() {
                                local_rc.pop_local_path_root();
                            }
                        }

                        Ok(())
                    }
                    (false, _) => {
                        if let Some(else_template) = h.inverse() {
                            try!(else_template.render(r, rc));
                        }
                        Ok(())
                    }
                    _ => Err(RenderError::new(format!(
                        "Param type is not iterable: {:?}",
                        template
                    ))),
                };

                rc.demote_local_vars();
                rendered
            }
            None => Ok(()),
        }
    }
}

pub static EACH_HELPER: EachHelper = EachHelper;

#[cfg(test)]
mod test {
    use registry::Registry;
    use context::to_json;

    use std::collections::BTreeMap;
    use std::str::FromStr;
    use serde_json::value::Value as Json;

    #[test]
    fn test_each() {
        let mut handlebars = Registry::new();
        assert!(
            handlebars
                .register_template_string(
                    "t0",
                    "{{#each this}}{{@first}}|{{@last}}|{{@index}}:{{this}}|{{/each}}"
                )
                .is_ok()
        );
        assert!(
            handlebars
                .register_template_string(
                    "t1",
                    "{{#each this}}{{@first}}|{{@key}}:{{this}}|{{/each}}"
                )
                .is_ok()
        );

        let r0 = handlebars.render("t0", &vec![1u16, 2u16, 3u16]);
        assert_eq!(
            r0.ok().unwrap(),
            "true|false|0:1|false|false|1:2|false|true|2:3|".to_string()
        );

        let mut m: BTreeMap<String, u16> = BTreeMap::new();
        m.insert("ftp".to_string(), 21);
        m.insert("http".to_string(), 80);
        let r1 = handlebars.render("t1", &m);
        assert_eq!(r1.ok().unwrap(), "true|ftp:21|false|http:80|".to_string());
    }

    #[test]
    fn test_each_with_parent() {
        let json_str = r#"{"a":{"a":99,"c":[{"d":100},{"d":200}]}}"#;

        let data = Json::from_str(json_str).unwrap();
        // println!("data: {}", data);
        let mut handlebars = Registry::new();

        // previously, to access the parent in an each block,
        // a user would need to specify ../../b, as the path
        // that is computed includes the array index: ./a.c.[0]
        assert!(
            handlebars
                .register_template_string("t0", "{{#each a.c}} d={{d}} b={{../a.a}} {{/each}}")
                .is_ok()
        );

        let r1 = handlebars.render("t0", &data);
        assert_eq!(r1.ok().unwrap(), " d=100 b=99  d=200 b=99 ".to_string());
    }

    #[test]
    fn test_nested_each_with_parent() {
        let json_str = r#"{"a": [{"b": [{"d": 100}], "c": 200}]}"#;

        let data = Json::from_str(json_str).unwrap();
        let mut handlebars = Registry::new();
        assert!(
            handlebars
                .register_template_string(
                    "t0",
                    "{{#each a}}{{#each b}}{{d}}:{{../c}}{{/each}}{{/each}}"
                )
                .is_ok()
        );

        let r1 = handlebars.render("t0", &data);
        assert_eq!(r1.ok().unwrap(), "100:200".to_string());
    }

    #[test]
    fn test_nested_each() {
        let json_str = r#"{"a": [{"b": true}], "b": [[1, 2, 3],[4, 5]]}"#;

        let data = Json::from_str(json_str).unwrap();

        let mut handlebars = Registry::new();
        assert!(
            handlebars
                .register_template_string(
                    "t0",
                    "{{#each b}}{{#if ../a}}{{#each this}}{{this}}{{/each}}{{/if}}{{/each}}"
                )
                .is_ok()
        );

        let r1 = handlebars.render("t0", &data);
        assert_eq!(r1.ok().unwrap(), "12345".to_string());
    }

    #[test]
    fn test_nested_array() {
        let mut handlebars = Registry::new();
        assert!(
            handlebars
                .register_template_string("t0", "{{#each this.[0]}}{{this}}{{/each}}")
                .is_ok()
        );

        let r0 = handlebars.render("t0", &(vec![vec![1, 2, 3]]));

        assert_eq!(r0.ok().unwrap(), "123".to_string());
    }

    #[test]
    fn test_empty_key() {
        let mut handlebars = Registry::new();
        assert!(
            handlebars
                .register_template_string("t0", "{{#each this}}{{@key}}-{{value}}\n{{/each}}")
                .is_ok()
        );

        let r0 = handlebars
            .render(
                "t0",
                &({
                    let mut rv = BTreeMap::new();
                    rv.insert("foo".to_owned(), {
                        let mut rv = BTreeMap::new();
                        rv.insert("value".to_owned(), "bar".to_owned());
                        rv
                    });
                    rv.insert("".to_owned(), {
                        let mut rv = BTreeMap::new();
                        rv.insert("value".to_owned(), "baz".to_owned());
                        rv
                    });
                    rv
                }),
            )
            .unwrap();

        let mut r0_sp: Vec<_> = r0.split('\n').collect();
        r0_sp.sort();

        assert_eq!(r0_sp, vec!["", "-baz", "foo-bar"]);
    }

    #[test]
    fn test_each_else() {
        let mut handlebars = Registry::new();
        assert!(
            handlebars
                .register_template_string("t0", "{{#each a}}1{{else}}empty{{/each}}")
                .is_ok()
        );
        let m1 = btreemap! {
            "a".to_string() => Vec::<String>::new(),
        };
        let r0 = handlebars.render("t0", &m1).unwrap();
        assert_eq!(r0, "empty");

        let m2 = btreemap!{
            "b".to_string() => Vec::<String>::new()
        };
        let r1 = handlebars.render("t0", &m2).unwrap();
        assert_eq!(r1, "empty");
    }

    #[test]
    fn test_block_param() {
        let mut handlebars = Registry::new();
        assert!(
            handlebars
                .register_template_string("t0", "{{#each a as |i|}}{{i}}{{/each}}")
                .is_ok()
        );
        let m1 = btreemap! {
            "a".to_string() => vec![1,2,3,4,5]
        };
        let r0 = handlebars.render("t0", &m1).unwrap();
        assert_eq!(r0, "12345");
    }

    #[test]
    fn test_each_object_block_param() {
        let mut handlebars = Registry::new();
        let template = "{{#each this as |k v|}}\
                        {{#with k as |inner_k|}}{{inner_k}}{{/with}}:{{v}}|\
                        {{/each}}";
        assert!(handlebars.register_template_string("t0", template).is_ok());

        let m = btreemap!{
            "ftp".to_string() => 21,
            "http".to_string() => 80
        };
        let r0 = handlebars.render("t0", &m);
        assert_eq!(r0.ok().unwrap(), "ftp:21|http:80|".to_string());
    }

    #[test]
    fn test_nested_each_with_path_ups() {
        let mut handlebars = Registry::new();
        assert!(
            handlebars
                .register_template_string(
                    "t0",
                    "{{#each a.b}}{{#each c}}{{../../d}}{{/each}}{{/each}}"
                )
                .is_ok()
        );

        let data = btreemap! {
            "a".to_string() => to_json(&btreemap! {
                "b".to_string() => vec![btreemap!{"c".to_string() => vec![1]}]
            }),
            "d".to_string() => to_json(&1)
        };

        let r0 = handlebars.render("t0", &data);
        assert_eq!(r0.ok().unwrap(), "1".to_string());
    }

    #[test]
    fn test_nested_each_with_path_up_this() {
        let mut handlebars = Registry::new();
        let template = "{{#each variant}}{{#each ../typearg}}\
                        {{#if @first}}template<{{/if}}{{this}}{{#if @last}}>{{else}},{{/if}}\
                        {{/each}}{{/each}}";
        assert!(handlebars.register_template_string("t0", template).is_ok());
        let data = btreemap! {
            "typearg".to_string() => vec!["T".to_string()],
            "variant".to_string() => vec!["1".to_string(), "2".to_string()]
        };
        let r0 = handlebars.render("t0", &data);
        assert_eq!(r0.ok().unwrap(), "template<T>template<T>".to_string());
    }

    #[test]
    fn test_key_iteration_with_unicode() {
        let mut handlebars = Registry::new();
        assert!(
            handlebars
                .register_template_string("t0", "{{#each this}}{{@key}}: {{this}}\n{{/each}}")
                .is_ok()
        );
        let data = json!({
            "normal": 1,
            "你好": 2,
            "#special key": 3,
            "😂": 4,
            "me.dot.key": 5
        });
        let r0 = handlebars.render("t0", &data).ok().unwrap();
        assert!(r0.contains("normal: 1"));
        assert!(r0.contains("你好: 2"));
        assert!(r0.contains("#special key: 3"));
        assert!(r0.contains("😂: 4"));
        assert!(r0.contains("me.dot.key: 5"));
    }
}
