mod common;

use common::init_logger;
use serde::Deserialize;
use serde_xml_rs::{from_str, Deserializer};

#[derive(Debug, Deserialize, PartialEq)]
struct Item {
    name: String,
    source: String,
}

#[test]
fn simple_struct_from_attributes() {
    init_logger();

    let s = r##"
        <item name="hello" source="world.rs" />
    "##;

    let item: Item = from_str(s).unwrap();

    assert_eq!(
        item,
        Item {
            name: "hello".to_string(),
            source: "world.rs".to_string(),
        }
    );
}

#[test]
fn multiple_roots_attributes() {
    init_logger();

    let s = r##"
        <item name="hello" source="world.rs" />
        <item name="hello" source="world.rs" />
    "##;

    let item: Vec<Item> = from_str(s).unwrap();

    assert_eq!(
        item,
        vec![
            Item {
                name: "hello".to_string(),
                source: "world.rs".to_string(),
            },
            Item {
                name: "hello".to_string(),
                source: "world.rs".to_string(),
            },
        ]
    );
}

#[test]
fn simple_struct_from_attribute_and_child() {
    init_logger();

    let s = r##"
        <item name="hello">
            <source>world.rs</source>
        </item>
    "##;

    let item: Item = from_str(s).unwrap();

    assert_eq!(
        item,
        Item {
            name: "hello".to_string(),
            source: "world.rs".to_string(),
        }
    );
}

#[derive(Debug, Deserialize, PartialEq)]
struct Project {
    name: String,

    #[serde(rename = "item", default)]
    items: Vec<Item>,
}

#[test]
fn nested_collection() {
    init_logger();

    let s = r##"
        <project name="my_project">
            <item name="hello1" source="world1.rs" />
            <item name="hello2" source="world2.rs" />
        </project>
    "##;

    let project: Project = from_str(s).unwrap();

    assert_eq!(
        project,
        Project {
            name: "my_project".to_string(),
            items: vec![
                Item {
                    name: "hello1".to_string(),
                    source: "world1.rs".to_string(),
                },
                Item {
                    name: "hello2".to_string(),
                    source: "world2.rs".to_string(),
                },
            ],
        }
    );
}

#[derive(Debug, Deserialize, PartialEq)]
enum MyEnum {
    A(String),
    B { name: String, flag: bool },
    C,
}

#[derive(Debug, Deserialize, PartialEq)]
struct MyEnums {
    #[serde(rename = "$value")]
    items: Vec<MyEnum>,
}

#[test]
fn collection_of_enums() {
    init_logger();

    let s = r##"
        <enums>
            <A>test</A>
            <B name="hello" flag="true" />
            <C />
        </enums>
    "##;

    let project: MyEnums = from_str(s).unwrap();

    assert_eq!(
        project,
        MyEnums {
            items: vec![
                MyEnum::A("test".to_string()),
                MyEnum::B {
                    name: "hello".to_string(),
                    flag: true,
                },
                MyEnum::C,
            ],
        }
    );
}

#[test]
fn out_of_order_collection() {
    #[derive(Debug, Deserialize, PartialEq)]
    struct Collection {
        a: Vec<A>,
        b: Vec<B>,
        c: C,
    }

    #[derive(Debug, Deserialize, PartialEq)]
    struct A {
        name: String,
    }

    #[derive(Debug, Deserialize, PartialEq)]
    struct B {
        name: String,
    }

    #[derive(Debug, Deserialize, PartialEq)]
    struct C {
        name: String,
    }

    init_logger();

    let in_xml = r#"
        <collection>
            <a name="a1" />
            <a name="a2" />
            <b name="b1" />
            <a name="a3" />
            <c name="c" />
            <b name="b2" />
            <a name="a4" />
        </collection>
    "#;

    let should_be = Collection {
        a: vec![
            A { name: "a1".into() },
            A { name: "a2".into() },
            A { name: "a3".into() },
            A { name: "a4".into() },
        ],
        b: vec![B { name: "b1".into() }, B { name: "b2".into() }],
        c: C { name: "c".into() },
    };

    let mut de = Deserializer::new_from_reader(in_xml.as_bytes()).non_contiguous_seq_elements(true);
    let actual = Collection::deserialize(&mut de).unwrap();

    assert_eq!(should_be, actual);
}

#[test]
fn nested_out_of_order_collection() {
    #[derive(Debug, Deserialize, PartialEq)]
    struct OuterCollection {
        a: A,
        inner: Vec<InnerCollection>,
    }

    #[derive(Debug, Deserialize, PartialEq)]
    struct InnerCollection {
        b: Vec<B>,
        c: Vec<C>,
    }

    #[derive(Debug, Deserialize, PartialEq)]
    struct A {
        name: String,
    }

    #[derive(Debug, Deserialize, PartialEq)]
    struct B {
        name: String,
    }

    #[derive(Debug, Deserialize, PartialEq)]
    struct C {
        name: String,
    }

    init_logger();

    let in_xml = r#"
        <collection>
            <inner>
                <b name="b1" />
                <c name="c1" />
                <b name="b2" />
                <c name="c2" />
            </inner>
            <a name="a" />
            <inner>
                <c name="c3" />
                <b name="b3" />
                <c name="c4" />
                <b name="b4" />
            </inner>
        </collection>
    "#;

    let should_be = OuterCollection {
        a: A { name: "a".into() },
        inner: vec![
            InnerCollection {
                b: vec![B { name: "b1".into() }, B { name: "b2".into() }],
                c: vec![C { name: "c1".into() }, C { name: "c2".into() }],
            },
            InnerCollection {
                b: vec![B { name: "b3".into() }, B { name: "b4".into() }],
                c: vec![C { name: "c3".into() }, C { name: "c4".into() }],
            },
        ],
    };

    let mut de = Deserializer::new_from_reader(in_xml.as_bytes()).non_contiguous_seq_elements(true);
    let actual = OuterCollection::deserialize(&mut de).unwrap();

    assert_eq!(should_be, actual);
}

#[test]
fn out_of_order_tuple() {
    #[derive(Debug, Deserialize, PartialEq)]
    struct Collection {
        val: (A, B, C),
        other: A,
    }

    #[derive(Debug, Deserialize, PartialEq)]
    struct A {
        name_a: String,
    }

    #[derive(Debug, Deserialize, PartialEq)]
    struct B {
        name_b: String,
    }

    #[derive(Debug, Deserialize, PartialEq)]
    struct C {
        name_c: String,
    }

    init_logger();

    let in_xml = r#"
        <collection>
            <val name_a="a1" />
            <val name_b="b" />
            <other name_a="a2" />
            <val name_c="c" />
        </collection>
    "#;

    let should_be = Collection {
        val: (
            A {
                name_a: "a1".into(),
            },
            B { name_b: "b".into() },
            C { name_c: "c".into() },
        ),
        other: A {
            name_a: "a2".into(),
        },
    };

    let mut de = Deserializer::new_from_reader(in_xml.as_bytes()).non_contiguous_seq_elements(true);
    let actual = Collection::deserialize(&mut de).unwrap();

    assert_eq!(should_be, actual);
}

/// Ensure that identically-named elements at different depths are not deserialized as if they were
/// at the same depth.
#[test]
fn nested_collection_repeated_elements() {
    #[derive(Debug, Deserialize, PartialEq)]
    struct OuterCollection {
        a: Vec<A>,
        inner: Inner,
    }

    #[derive(Debug, Deserialize, PartialEq)]
    struct Inner {
        a: A,
    }

    #[derive(Debug, Deserialize, PartialEq)]
    struct A {
        name: String,
    }

    init_logger();

    let in_xml = r#"
        <collection>
            <a name="a1" />
            <inner>
                <a name="a2" />
            </inner>
            <a name="a3" />
        </collection>
    "#;

    let should_be = OuterCollection {
        a: vec![A { name: "a1".into() }, A { name: "a3".into() }],
        inner: Inner {
            a: A { name: "a2".into() },
        },
    };

    let mut de = Deserializer::new_from_reader(in_xml.as_bytes()).non_contiguous_seq_elements(true);
    let actual = OuterCollection::deserialize(&mut de).unwrap();

    assert_eq!(should_be, actual);
}
