use serde::{de::DeserializeOwned, ser::Serialize};
use std::{collections::BTreeMap, fmt::Debug};

use crate::{
    stream::{private::Sealed, Event, Writer},
    Date, Deserializer, Error, Integer, Serializer, Uid,
};

struct VecWriter {
    events: Vec<Event>,
}

impl VecWriter {
    pub fn new() -> VecWriter {
        VecWriter { events: Vec::new() }
    }

    pub fn into_inner(self) -> Vec<Event> {
        self.events
    }
}

impl Writer for VecWriter {
    fn write_start_array(&mut self, len: Option<u64>) -> Result<(), Error> {
        self.events.push(Event::StartArray(len));
        Ok(())
    }

    fn write_start_dictionary(&mut self, len: Option<u64>) -> Result<(), Error> {
        self.events.push(Event::StartDictionary(len));
        Ok(())
    }

    fn write_end_collection(&mut self) -> Result<(), Error> {
        self.events.push(Event::EndCollection);
        Ok(())
    }

    fn write_boolean(&mut self, value: bool) -> Result<(), Error> {
        self.events.push(Event::Boolean(value));
        Ok(())
    }

    fn write_data(&mut self, value: &[u8]) -> Result<(), Error> {
        self.events.push(Event::Data(value.to_owned()));
        Ok(())
    }

    fn write_date(&mut self, value: Date) -> Result<(), Error> {
        self.events.push(Event::Date(value));
        Ok(())
    }

    fn write_integer(&mut self, value: Integer) -> Result<(), Error> {
        self.events.push(Event::Integer(value));
        Ok(())
    }

    fn write_real(&mut self, value: f64) -> Result<(), Error> {
        self.events.push(Event::Real(value));
        Ok(())
    }

    fn write_string(&mut self, value: &str) -> Result<(), Error> {
        self.events.push(Event::String(value.to_owned()));
        Ok(())
    }

    fn write_uid(&mut self, value: Uid) -> Result<(), Error> {
        self.events.push(Event::Uid(value));
        Ok(())
    }
}

impl Sealed for VecWriter {}

fn new_serializer() -> Serializer<VecWriter> {
    Serializer::new(VecWriter::new())
}

fn new_deserializer(events: Vec<Event>) -> Deserializer<Vec<Result<Event, Error>>> {
    let result_events = events.into_iter().map(Ok).collect();
    Deserializer::new(result_events)
}

fn assert_roundtrip<T>(obj: T, comparison: Option<&[Event]>)
where
    T: Debug + DeserializeOwned + PartialEq + Serialize,
{
    let mut se = new_serializer();

    obj.serialize(&mut se).unwrap();

    let events = se.into_inner().into_inner();

    if let Some(comparison) = comparison {
        assert_eq!(&events[..], comparison);
    }

    let mut de = new_deserializer(events);

    let new_obj = T::deserialize(&mut de).unwrap();

    assert_eq!(new_obj, obj);
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
enum Animal {
    Cow,
    Dog(DogOuter),
    Frog(Result<String, bool>, Option<Vec<f64>>),
    Cat {
        age: Integer,
        name: String,
        firmware: Option<Vec<u8>>,
    },
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct DogOuter {
    inner: Vec<DogInner>,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct DogInner {
    a: (),
    b: usize,
    c: Vec<String>,
    d: Option<Uid>,
}

#[test]
fn cow() {
    let cow = Animal::Cow;

    let comparison = &[
        Event::StartDictionary(Some(1)),
        Event::String("Cow".to_owned()),
        Event::String("".to_owned()),
        Event::EndCollection,
    ];

    assert_roundtrip(cow, Some(comparison));
}

#[test]
fn dog() {
    let dog = Animal::Dog(DogOuter {
        inner: vec![DogInner {
            a: (),
            b: 12,
            c: vec!["a".to_string(), "b".to_string()],
            d: Some(Uid::new(42)),
        }],
    });

    let comparison = &[
        Event::StartDictionary(Some(1)),
        Event::String("Dog".to_owned()),
        Event::StartDictionary(None),
        Event::String("inner".to_owned()),
        Event::StartArray(Some(1)),
        Event::StartDictionary(None),
        Event::String("a".to_owned()),
        Event::String("".to_owned()),
        Event::String("b".to_owned()),
        Event::Integer(12.into()),
        Event::String("c".to_owned()),
        Event::StartArray(Some(2)),
        Event::String("a".to_owned()),
        Event::String("b".to_owned()),
        Event::EndCollection,
        Event::String("d".to_owned()),
        Event::Uid(Uid::new(42)),
        Event::EndCollection,
        Event::EndCollection,
        Event::EndCollection,
        Event::EndCollection,
    ];

    assert_roundtrip(dog, Some(comparison));
}

#[test]
fn frog() {
    let frog = Animal::Frog(
        Ok("hello".to_owned()),
        Some(vec![1.0, 2.0, 3.14159, 0.000000001, 1.27e31]),
    );

    let comparison = &[
        Event::StartDictionary(Some(1)),
        Event::String("Frog".to_owned()),
        Event::StartArray(Some(2)),
        Event::StartDictionary(Some(1)),
        Event::String("Ok".to_owned()),
        Event::String("hello".to_owned()),
        Event::EndCollection,
        Event::StartDictionary(Some(1)),
        Event::String("Some".to_owned()),
        Event::StartArray(Some(5)),
        Event::Real(1.0),
        Event::Real(2.0),
        Event::Real(3.14159),
        Event::Real(0.000000001),
        Event::Real(1.27e31),
        Event::EndCollection,
        Event::EndCollection,
        Event::EndCollection,
        Event::EndCollection,
    ];

    assert_roundtrip(frog, Some(comparison));
}

#[test]
fn cat_with_firmware() {
    let cat = Animal::Cat {
        age: 12.into(),
        name: "Paws".to_owned(),
        firmware: Some(vec![0, 1, 2, 3, 4, 5, 6, 7, 8]),
    };

    let comparison = &[
        Event::StartDictionary(Some(1)),
        Event::String("Cat".to_owned()),
        Event::StartDictionary(None),
        Event::String("age".to_owned()),
        Event::Integer(12.into()),
        Event::String("name".to_owned()),
        Event::String("Paws".to_owned()),
        Event::String("firmware".to_owned()),
        Event::StartArray(Some(9)),
        Event::Integer(0.into()),
        Event::Integer(1.into()),
        Event::Integer(2.into()),
        Event::Integer(3.into()),
        Event::Integer(4.into()),
        Event::Integer(5.into()),
        Event::Integer(6.into()),
        Event::Integer(7.into()),
        Event::Integer(8.into()),
        Event::EndCollection,
        Event::EndCollection,
        Event::EndCollection,
    ];

    assert_roundtrip(cat, Some(comparison));
}

#[test]
fn cat_without_firmware() {
    let cat = Animal::Cat {
        age: Integer::from(-12),
        name: "Paws".to_owned(),
        firmware: None,
    };

    let comparison = &[
        Event::StartDictionary(Some(1)),
        Event::String("Cat".to_owned()),
        Event::StartDictionary(None),
        Event::String("age".to_owned()),
        Event::Integer(Integer::from(-12)),
        Event::String("name".to_owned()),
        Event::String("Paws".to_owned()),
        Event::EndCollection,
        Event::EndCollection,
    ];

    assert_roundtrip(cat, Some(comparison));
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct NewtypeStruct(NewtypeInner);

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct NewtypeInner(u8, u8, u8);

#[test]
fn newtype_struct() {
    let newtype = NewtypeStruct(NewtypeInner(34, 32, 13));

    let comparison = &[
        Event::StartArray(Some(3)),
        Event::Integer(34.into()),
        Event::Integer(32.into()),
        Event::Integer(13.into()),
        Event::EndCollection,
    ];

    assert_roundtrip(newtype, Some(comparison));
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct TypeWithOptions {
    a: Option<String>,
    b: Option<Option<u32>>,
    c: Option<Box<TypeWithOptions>>,
}

#[test]
fn type_with_options() {
    let inner = TypeWithOptions {
        a: None,
        b: Some(Some(12)),
        c: None,
    };

    let obj = TypeWithOptions {
        a: Some("hello".to_owned()),
        b: Some(None),
        c: Some(Box::new(inner)),
    };

    let comparison = &[
        Event::StartDictionary(None),
        Event::String("a".to_owned()),
        Event::String("hello".to_owned()),
        Event::String("b".to_owned()),
        Event::StartDictionary(Some(1)),
        Event::String("None".to_owned()),
        Event::String("".to_owned()),
        Event::EndCollection,
        Event::String("c".to_owned()),
        Event::StartDictionary(None),
        Event::String("b".to_owned()),
        Event::StartDictionary(Some(1)),
        Event::String("Some".to_owned()),
        Event::Integer(12.into()),
        Event::EndCollection,
        Event::EndCollection,
        Event::EndCollection,
    ];

    assert_roundtrip(obj, Some(comparison));
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct TypeWithDate {
    a: Option<i32>,
    b: Option<Date>,
}

#[test]
fn type_with_date() {
    let date = Date::from_rfc3339("1920-01-01T00:10:00Z").unwrap();

    let obj = TypeWithDate {
        a: Some(28),
        b: Some(date.clone()),
    };

    let comparison = &[
        Event::StartDictionary(None),
        Event::String("a".to_owned()),
        Event::Integer(28.into()),
        Event::String("b".to_owned()),
        Event::Date(date),
        Event::EndCollection,
    ];

    assert_roundtrip(obj, Some(comparison));
}

#[test]
fn option_some() {
    let obj = Some(12);

    let comparison = &[Event::Integer(12.into())];

    assert_roundtrip(obj, Some(comparison));
}

#[test]
fn option_none() {
    let obj: Option<u32> = None;

    let comparison = &[];

    assert_roundtrip(obj, Some(comparison));
}

#[test]
fn option_some_some() {
    let obj = Some(Some(12));

    let comparison = &[
        Event::StartDictionary(Some(1)),
        Event::String("Some".to_owned()),
        Event::Integer(12.into()),
        Event::EndCollection,
    ];

    assert_roundtrip(obj, Some(comparison));
}

#[test]
fn option_some_none() {
    let obj: Option<Option<u32>> = Some(None);

    let comparison = &[
        Event::StartDictionary(Some(1)),
        Event::String("None".to_owned()),
        Event::String("".to_owned()),
        Event::EndCollection,
    ];

    assert_roundtrip(obj, Some(comparison));
}

#[test]
fn option_dictionary_values() {
    let mut obj = BTreeMap::new();
    obj.insert("a".to_owned(), None);
    obj.insert("b".to_owned(), Some(None));
    obj.insert("c".to_owned(), Some(Some(144)));

    let comparison = &[
        Event::StartDictionary(Some(3)),
        Event::String("a".to_owned()),
        Event::StartDictionary(Some(1)),
        Event::String("None".to_owned()),
        Event::String("".to_owned()),
        Event::EndCollection,
        Event::String("b".to_owned()),
        Event::StartDictionary(Some(1)),
        Event::String("Some".to_owned()),
        Event::StartDictionary(Some(1)),
        Event::String("None".to_owned()),
        Event::String("".to_owned()),
        Event::EndCollection,
        Event::EndCollection,
        Event::String("c".to_owned()),
        Event::StartDictionary(Some(1)),
        Event::String("Some".to_owned()),
        Event::StartDictionary(Some(1)),
        Event::String("Some".to_owned()),
        Event::Integer(144.into()),
        Event::EndCollection,
        Event::EndCollection,
        Event::EndCollection,
    ];

    assert_roundtrip(obj, Some(comparison));
}

#[test]
fn option_dictionary_keys() {
    let mut obj = BTreeMap::new();
    obj.insert(None, 1);
    obj.insert(Some(None), 2);
    obj.insert(Some(Some(144)), 3);

    let comparison = &[
        Event::StartDictionary(Some(3)),
        Event::StartDictionary(Some(1)),
        Event::String("None".to_owned()),
        Event::String("".to_owned()),
        Event::EndCollection,
        Event::Integer(1.into()),
        Event::StartDictionary(Some(1)),
        Event::String("Some".to_owned()),
        Event::StartDictionary(Some(1)),
        Event::String("None".to_owned()),
        Event::String("".to_owned()),
        Event::EndCollection,
        Event::EndCollection,
        Event::Integer(2.into()),
        Event::StartDictionary(Some(1)),
        Event::String("Some".to_owned()),
        Event::StartDictionary(Some(1)),
        Event::String("Some".to_owned()),
        Event::Integer(144.into()),
        Event::EndCollection,
        Event::EndCollection,
        Event::Integer(3.into()),
        Event::EndCollection,
    ];

    assert_roundtrip(obj, Some(comparison));
}

#[test]
fn option_array() {
    let obj = vec![None, Some(None), Some(Some(144))];

    let comparison = &[
        Event::StartArray(Some(3)),
        Event::StartDictionary(Some(1)),
        Event::String("None".to_owned()),
        Event::String("".to_owned()),
        Event::EndCollection,
        Event::StartDictionary(Some(1)),
        Event::String("Some".to_owned()),
        Event::StartDictionary(Some(1)),
        Event::String("None".to_owned()),
        Event::String("".to_owned()),
        Event::EndCollection,
        Event::EndCollection,
        Event::StartDictionary(Some(1)),
        Event::String("Some".to_owned()),
        Event::StartDictionary(Some(1)),
        Event::String("Some".to_owned()),
        Event::Integer(144.into()),
        Event::EndCollection,
        Event::EndCollection,
        Event::EndCollection,
    ];

    assert_roundtrip(obj, Some(comparison));
}

#[test]
fn enum_variant_types() {
    #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
    enum Foo {
        Unit,
        Newtype(u32),
        Tuple(u32, String),
        Struct { v: u32, s: String },
    }

    let expected = &[
        Event::StartDictionary(Some(1)),
        Event::String("Unit".into()),
        Event::String("".into()),
        Event::EndCollection,
    ];
    assert_roundtrip(Foo::Unit, Some(expected));

    let expected = &[
        Event::StartDictionary(Some(1)),
        Event::String("Newtype".into()),
        Event::Integer(42.into()),
        Event::EndCollection,
    ];
    assert_roundtrip(Foo::Newtype(42), Some(expected));

    let expected = &[
        Event::StartDictionary(Some(1)),
        Event::String("Tuple".into()),
        Event::StartArray(Some(2)),
        Event::Integer(42.into()),
        Event::String("bar".into()),
        Event::EndCollection,
        Event::EndCollection,
    ];
    assert_roundtrip(Foo::Tuple(42, "bar".into()), Some(expected));

    let expected = &[
        Event::StartDictionary(Some(1)),
        Event::String("Struct".into()),
        Event::StartDictionary(None),
        Event::String("v".into()),
        Event::Integer(42.into()),
        Event::String("s".into()),
        Event::String("bar".into()),
        Event::EndCollection,
        Event::EndCollection,
    ];
    assert_roundtrip(
        Foo::Struct {
            v: 42,
            s: "bar".into(),
        },
        Some(expected),
    );
}
