use std::vec;
use std::collections::BTreeMap;

use serde::ser::{Serialize, Serializer, SeqVisitor, MapVisitor};

#[derive(Clone, PartialEq, Debug)]
pub enum Token<'a> {
    Bool(bool),
    Isize(isize),
    I8(i8),
    I16(i16),
    I32(i32),
    I64(i64),
    Usize(usize),
    U8(u8),
    U16(u16),
    U32(u32),
    U64(u64),
    F32(f32),
    F64(f64),
    Char(char),
    Str(&'a str),

    Option(bool),

    Unit,
    NamedUnit(&'a str),
    EnumUnit(&'a str, &'a str),

    SeqStart(Option<usize>),
    NamedSeqStart(&'a str, Option<usize>),
    EnumSeqStart(&'a str, &'a str, Option<usize>),
    SeqSep,
    SeqEnd,

    MapStart(Option<usize>),
    NamedMapStart(&'a str, Option<usize>),
    EnumMapStart(&'a str, &'a str, Option<usize>),
    MapSep,
    MapEnd,
}

struct AssertSerializer<'a> {
    iter: vec::IntoIter<Token<'a>>,
}

impl<'a> AssertSerializer<'a> {
    fn new(values: Vec<Token<'a>>) -> AssertSerializer {
        AssertSerializer {
            iter: values.into_iter(),
        }
    }

    fn visit_sequence<V>(&mut self, mut visitor: V) -> Result<(), ()>
        where V: SeqVisitor
    {
        while let Some(()) = try!(visitor.visit(self)) { }

        assert_eq!(self.iter.next(), Some(Token::SeqEnd));

        Ok(())
    }

    fn visit_mapping<V>(&mut self, mut visitor: V) -> Result<(), ()>
        where V: MapVisitor
    {
        while let Some(()) = try!(visitor.visit(self)) { }

        assert_eq!(self.iter.next(), Some(Token::MapEnd));

        Ok(())
    }
}

impl<'a> Serializer for AssertSerializer<'a> {
    type Error = ();

    fn visit_unit(&mut self) -> Result<(), ()> {
        assert_eq!(self.iter.next(), Some(Token::Unit));
        Ok(())
    }

    fn visit_named_unit(&mut self, name: &str) -> Result<(), ()> {
        assert_eq!(self.iter.next().unwrap(), Token::NamedUnit(name));
        Ok(())
    }

    fn visit_enum_unit(&mut self, name: &str, variant: &str) -> Result<(), ()> {
        assert_eq!(self.iter.next().unwrap(), Token::EnumUnit(name, variant));
        Ok(())
    }

    fn visit_bool(&mut self, v: bool) -> Result<(), ()> {
        assert_eq!(self.iter.next(), Some(Token::Bool(v)));
        Ok(())
    }

    fn visit_isize(&mut self, v: isize) -> Result<(), ()> {
        assert_eq!(self.iter.next(), Some(Token::Isize(v)));
        Ok(())
    }

    fn visit_i8(&mut self, v: i8) -> Result<(), ()> {
        assert_eq!(self.iter.next(), Some(Token::I8(v)));
        Ok(())
    }

    fn visit_i16(&mut self, v: i16) -> Result<(), ()> {
        assert_eq!(self.iter.next(), Some(Token::I16(v)));
        Ok(())
    }

    fn visit_i32(&mut self, v: i32) -> Result<(), ()> {
        assert_eq!(self.iter.next(), Some(Token::I32(v)));
        Ok(())
    }

    fn visit_i64(&mut self, v: i64) -> Result<(), ()> {
        assert_eq!(self.iter.next(), Some(Token::I64(v)));
        Ok(())
    }

    fn visit_usize(&mut self, v: usize) -> Result<(), ()> {
        assert_eq!(self.iter.next(), Some(Token::Usize(v)));
        Ok(())
    }

    fn visit_u8(&mut self, v: u8) -> Result<(), ()> {
        assert_eq!(self.iter.next(), Some(Token::U8(v)));
        Ok(())
    }

    fn visit_u16(&mut self, v: u16) -> Result<(), ()> {
        assert_eq!(self.iter.next(), Some(Token::U16(v)));
        Ok(())
    }

    fn visit_u32(&mut self, v: u32) -> Result<(), ()> {
        assert_eq!(self.iter.next(), Some(Token::U32(v)));
        Ok(())
    }

    fn visit_u64(&mut self, v: u64) -> Result<(), ()> {
        assert_eq!(self.iter.next(), Some(Token::U64(v)));
        Ok(())
    }

    fn visit_f32(&mut self, v: f32) -> Result<(), ()> {
        assert_eq!(self.iter.next(), Some(Token::F32(v)));
        Ok(())
    }

    fn visit_f64(&mut self, v: f64) -> Result<(), ()> {
        assert_eq!(self.iter.next(), Some(Token::F64(v)));
        Ok(())
    }

    fn visit_char(&mut self, v: char) -> Result<(), ()> {
        assert_eq!(self.iter.next(), Some(Token::Char(v)));
        Ok(())
    }

    fn visit_str(&mut self, v: &str) -> Result<(), ()> {
        assert_eq!(self.iter.next().unwrap(), Token::Str(v));
        Ok(())
    }

    fn visit_none(&mut self) -> Result<(), ()> {
        assert_eq!(self.iter.next(), Some(Token::Option(false)));
        Ok(())
    }

    fn visit_some<V>(&mut self, value: V) -> Result<(), ()>
        where V: Serialize,
    {
        assert_eq!(self.iter.next(), Some(Token::Option(true)));
        value.serialize(self)
    }


    fn visit_seq<V>(&mut self, visitor: V) -> Result<(), ()>
        where V: SeqVisitor
    {
        let len = visitor.len();

        assert_eq!(self.iter.next(), Some(Token::SeqStart(len)));

        self.visit_sequence(visitor)
    }

    fn visit_named_seq<V>(&mut self, name: &str, visitor: V) -> Result<(), ()>
        where V: SeqVisitor
    {
        let len = visitor.len();

        assert_eq!(self.iter.next().unwrap(), Token::NamedSeqStart(name, len));

        self.visit_sequence(visitor)
    }

    fn visit_enum_seq<V>(&mut self,
                         name: &str,
                         variant: &str,
                         visitor: V) -> Result<(), ()>
        where V: SeqVisitor
    {
        let len = visitor.len();

        assert_eq!(self.iter.next().unwrap(), Token::EnumSeqStart(name, variant, len));

        self.visit_sequence(visitor)
    }

    fn visit_seq_elt<T>(&mut self, value: T) -> Result<(), ()>
        where T: Serialize
    {
        assert_eq!(self.iter.next(), Some(Token::SeqSep));
        value.serialize(self)
    }

    fn visit_map<V>(&mut self, visitor: V) -> Result<(), ()>
        where V: MapVisitor
    {
        let len = visitor.len();

        assert_eq!(self.iter.next(), Some(Token::MapStart(len)));

        self.visit_mapping(visitor)
    }

    fn visit_named_map<V>(&mut self, name: &str, visitor: V) -> Result<(), ()>
        where V: MapVisitor
    {
        let len = visitor.len();

        assert_eq!(self.iter.next().unwrap(), Token::NamedMapStart(name, len));

        self.visit_mapping(visitor)
    }

    fn visit_enum_map<V>(&mut self, name: &str, variant: &str, visitor: V) -> Result<(), ()>
        where V: MapVisitor
    {
        let len = visitor.len();

        assert_eq!(self.iter.next().unwrap(), Token::EnumMapStart(name, variant, len));

        self.visit_mapping(visitor)
    }

    fn visit_map_elt<K, V>(&mut self, key: K, value: V) -> Result<(), ()>
        where K: Serialize,
              V: Serialize,
    {
        assert_eq!(self.iter.next(), Some(Token::MapSep));

        try!(key.serialize(self));
        value.serialize(self)
    }
}

//////////////////////////////////////////////////////////////////////////

#[derive(Serialize)]
struct NamedUnit;

#[derive(Serialize)]
struct NamedSeq(i32, i32, i32);

#[derive(Serialize)]
struct NamedMap {
    a: i32,
    b: i32,
    c: i32,
}

#[derive(Serialize)]
enum Enum {
    Unit,
    Seq(i32, i32),
    Map { a: i32, b: i32 },
}

//////////////////////////////////////////////////////////////////////////

macro_rules! btreemap {
    () => {
        BTreeMap::new()
    };
    ($($key:expr => $value:expr),+) => {
        {
            let mut map = BTreeMap::new();
            $(map.insert($key, $value);)+
            map
        }
    }
}

macro_rules! declare_test {
    ($name:ident { $($value:expr => $tokens:expr,)+ }) => {
        #[test]
        fn $name() {
            $(
                let mut ser = AssertSerializer::new($tokens);
                assert_eq!($value.serialize(&mut ser), Ok(()));
            )+
        }
    }
}

macro_rules! declare_tests {
    ($($name:ident { $($value:expr => $tokens:expr,)+ })+) => {
        $(
            declare_test!($name { $($value => $tokens,)+ });
        )+
    }
}

declare_tests! {
    test_unit {
        () => vec![Token::Unit],
    }
    test_bool {
        true => vec![Token::Bool(true)],
        false => vec![Token::Bool(false)],
    }
    test_isizes {
        0isize => vec![Token::Isize(0)],
        0i8 => vec![Token::I8(0)],
        0i16 => vec![Token::I16(0)],
        0i32 => vec![Token::I32(0)],
        0i64 => vec![Token::I64(0)],
    }
    test_usizes {
        0usize => vec![Token::Usize(0)],
        0u8 => vec![Token::U8(0)],
        0u16 => vec![Token::U16(0)],
        0u32 => vec![Token::U32(0)],
        0u64 => vec![Token::U64(0)],
    }
    test_floats {
        0f32 => vec![Token::F32(0.)],
        0f64 => vec![Token::F64(0.)],
    }
    test_char {
        'a' => vec![Token::Char('a')],
    }
    test_str {
        "abc" => vec![Token::Str("abc")],
        "abc".to_string() => vec![Token::Str("abc")],
    }
    test_option {
        None::<i32> => vec![Token::Option(false)],
        Some(1) => vec![
            Token::Option(true),
            Token::I32(1),
        ],
    }
    test_slice {
        &[0][..0] => vec![
            Token::SeqStart(Some(0)),
            Token::SeqEnd,
        ],
        &[1, 2, 3][..] => vec![
            Token::SeqStart(Some(3)),
                Token::SeqSep,
                Token::I32(1),

                Token::SeqSep,
                Token::I32(2),

                Token::SeqSep,
                Token::I32(3),
            Token::SeqEnd,
        ],
    }
    test_array {
        [0; 0] => vec![
            Token::SeqStart(Some(0)),
            Token::SeqEnd,
        ],
        [1, 2, 3] => vec![
            Token::SeqStart(Some(3)),
                Token::SeqSep,
                Token::I32(1),

                Token::SeqSep,
                Token::I32(2),

                Token::SeqSep,
                Token::I32(3),
            Token::SeqEnd,
        ],
    }
    test_vec {
        Vec::<isize>::new() => vec![
            Token::SeqStart(Some(0)),
            Token::SeqEnd,
        ],
        vec![vec![], vec![1], vec![2, 3]] => vec![
            Token::SeqStart(Some(3)),
                Token::SeqSep,
                Token::SeqStart(Some(0)),
                Token::SeqEnd,

                Token::SeqSep,
                Token::SeqStart(Some(1)),
                    Token::SeqSep,
                    Token::I32(1),
                Token::SeqEnd,

                Token::SeqSep,
                Token::SeqStart(Some(2)),
                    Token::SeqSep,
                    Token::I32(2),

                    Token::SeqSep,
                    Token::I32(3),
                Token::SeqEnd,
            Token::SeqEnd,
        ],
    }
    test_tuple {
        (1,) => vec![
            Token::SeqStart(Some(1)),
                Token::SeqSep,
                Token::I32(1),
            Token::SeqEnd,
        ],
        (1, 2, 3) => vec![
            Token::SeqStart(Some(3)),
                Token::SeqSep,
                Token::I32(1),

                Token::SeqSep,
                Token::I32(2),

                Token::SeqSep,
                Token::I32(3),
            Token::SeqEnd,
        ],
    }
    test_btreemap {
        btreemap![1 => 2] => vec![
            Token::MapStart(Some(1)),
                Token::MapSep,
                Token::I32(1),
                Token::I32(2),
            Token::MapEnd,
        ],
        btreemap![1 => 2, 3 => 4] => vec![
            Token::MapStart(Some(2)),
                Token::MapSep,
                Token::I32(1),
                Token::I32(2),

                Token::MapSep,
                Token::I32(3),
                Token::I32(4),
            Token::MapEnd,
        ],
        btreemap![1 => btreemap![], 2 => btreemap![3 => 4, 5 => 6]] => vec![
            Token::MapStart(Some(2)),
                Token::MapSep,
                Token::I32(1),
                Token::MapStart(Some(0)),
                Token::MapEnd,

                Token::MapSep,
                Token::I32(2),
                Token::MapStart(Some(2)),
                    Token::MapSep,
                    Token::I32(3),
                    Token::I32(4),

                    Token::MapSep,
                    Token::I32(5),
                    Token::I32(6),
                Token::MapEnd,
            Token::MapEnd,
        ],
    }
    test_named_unit {
        NamedUnit => vec![Token::NamedUnit("NamedUnit")],
    }
    test_named_seq {
        NamedSeq(1, 2, 3) => vec![
            Token::NamedSeqStart("NamedSeq", Some(3)),
                Token::SeqSep,
                Token::I32(1),

                Token::SeqSep,
                Token::I32(2),

                Token::SeqSep,
                Token::I32(3),
            Token::SeqEnd,
        ],
    }
    test_named_map {
        NamedMap { a: 1, b: 2, c: 3 } => vec![
            Token::NamedMapStart("NamedMap", Some(3)),
                Token::MapSep,
                Token::Str("a"),
                Token::I32(1),

                Token::MapSep,
                Token::Str("b"),
                Token::I32(2),

                Token::MapSep,
                Token::Str("c"),
                Token::I32(3),
            Token::MapEnd,
        ],
    }
    test_enum {
        Enum::Unit => vec![Token::EnumUnit("Enum", "Unit")],
        Enum::Seq(1, 2) => vec![
            Token::EnumSeqStart("Enum", "Seq", Some(2)),
                Token::SeqSep,
                Token::I32(1),

                Token::SeqSep,
                Token::I32(2),
            Token::SeqEnd,
        ],
        Enum::Map { a: 1, b: 2 } => vec![
            Token::EnumMapStart("Enum", "Map", Some(2)),
                Token::MapSep,
                Token::Str("a"),
                Token::I32(1),

                Token::MapSep,
                Token::Str("b"),
                Token::I32(2),
            Token::MapEnd,
        ],
    }
}