use std::assert_matches;

use super::DepthFirst;
use crate::{
    json::{self, ElemId},
    test,
};

#[test]
fn should_return_scalar_root_element() {
    const JSON: &str = "101";

    test::setup();
    let doc = json::parse(JSON.into()).unwrap();
    let mut walker = DepthFirst::from_doc(&doc);

    let elem_root = walker.next().unwrap();
    assert_eq!(doc.root(), elem_root);
}

#[test]
fn should_return_compound_root_element() {
    const JSON: &str = "{}";

    test::setup();
    let doc = json::parse(JSON.into()).unwrap();
    let mut walker = DepthFirst::from_doc(&doc);

    let elem_root = walker.next().unwrap();
    assert_eq!(doc.root(), elem_root);
}

#[test]
fn should_traverse_nested_objects() {
    const JSON: &str = r#"{ "one": { "two": { "three": [101] }}}"#;

    test::setup();
    let doc = json::parse(JSON.into()).unwrap();
    let mut walker = DepthFirst::from_doc(&doc);

    let elem_root = walker.next().unwrap();
    assert_eq!(doc.root(), elem_root);

    {
        let elem_one = walker.next().unwrap();
        assert!(elem_one.value().is_object());

        {
            let elem_two = walker.next().unwrap();
            assert!(elem_two.value().is_object());

            {
                let elem_three = walker.next().unwrap();
                assert!(elem_three.value().is_array());

                {
                    let elem_four = walker.next().unwrap();
                    assert_matches!(elem_four.value().as_number().unwrap(), "101");
                }
            }
        }
    }
}

#[test]
fn iter_index_should_match_element_id_order() {
    const JSON: &str = include_str!("../../../test_data/v211/every_field_set/cdr.json");

    test::setup();
    let doc = json::parse(JSON.into()).unwrap();
    let walker = DepthFirst::from_doc(&doc);

    for (index, elem) in walker.enumerate() {
        assert_eq!(elem.id().0, index);
    }
}

#[test]
fn should_elem_map_from_element_iter() {
    const JSON: &str = r#"{
            "one": 1,
            "two": ["a", "b", "c"],
            "three": {
                "d": 4,
                "e": 5,
                "f": 6
            }
        }"#;

    test::setup();
    let doc = json::parse(JSON.into()).unwrap();
    let elem = doc.root();
    let elem_list = ElementList::from_doc(&doc);

    {
        let root_id = ElemId::from(0);
        assert_eq!(
            elem_list.get(root_id).id(),
            elem.id(),
            "The root `Element` is always Id 0"
        );

        assert_eq!(elem.path(), "$");

        {
            let elem = elem_list.get(ElemId::from(1));
            assert_eq!(elem.path(), "$.one");
            assert_matches!(elem.value().as_number().unwrap(), "1");
        }

        {
            let elem = elem_list.get(ElemId::from(2));
            assert_eq!(elem.path(), "$.two");

            let array = elem.value().as_array().unwrap();
            let [arr_elem_0, arr_elem_1, arr_elem_2] =
                array.iter().collect::<Vec<_>>().try_into().unwrap();

            let elem = elem_list.get(ElemId::from(3));
            assert_eq!(elem.id(), arr_elem_0.id());
            assert_eq!(elem.path(), "$.two[0]");
            assert_matches!(elem.value().to_raw_str().unwrap().as_unescaped_str(), "a");

            let elem = elem_list.get(ElemId::from(4));
            assert_eq!(elem.id(), arr_elem_1.id());
            assert_eq!(elem.path(), "$.two[1]");
            assert_matches!(elem.value().to_raw_str().unwrap().as_unescaped_str(), "b");

            let elem = elem_list.get(ElemId::from(5));
            assert_eq!(elem.id(), arr_elem_2.id());
            assert_eq!(elem.path(), "$.two[2]");
            assert_matches!(elem.value().to_raw_str().unwrap().as_unescaped_str(), "c");
        }

        {
            let elem = elem_list.get(ElemId::from(6));
            assert_eq!(elem.path(), "$.three");
            let fields = elem.value().as_object_fields().unwrap();
            let [field_0, field_1, field_2] = fields.iter().collect::<Vec<_>>().try_into().unwrap();

            let elem = elem_list.get(ElemId::from(7));
            assert_eq!(elem.id(), field_0.id());
            assert_eq!(elem.path(), "$.three.d");
            assert_matches!(elem.value().as_number().unwrap(), "4");

            let elem = elem_list.get(ElemId::from(8));
            assert_eq!(elem.id(), field_1.id());
            assert_eq!(elem.path(), "$.three.e");
            assert_matches!(elem.value().as_number().unwrap(), "5");

            let elem = elem_list.get(ElemId::from(9));
            assert_eq!(elem.id(), field_2.id());
            assert_eq!(elem.path(), "$.three.f");
            assert_matches!(elem.value().as_number().unwrap(), "6");
        }
    }
}

/// A map of `Element`s referenced by their unique Id.
struct ElementList<'a, 'buf>(Vec<&'a json::Element<'buf>>);

impl<'a, 'buf> ElementList<'a, 'buf> {
    /// Create a new `ElementMap` by traversing the `Element` tree from the given node.
    fn from_doc(doc: &'a json::Document<'buf>) -> Self {
        // The walker will emit `Element`s ordered by their Id.
        let walker = DepthFirst::from_doc(doc);
        Self(walker.collect())
    }

    /// Return the `Element` with the given id.
    fn get(&self, id: ElemId) -> &json::Element<'buf> {
        self.0.get(id.0).map(|e| &**e).unwrap()
    }
}
