use crate::error::DecodeError;
use crate::{DecodeResult, Pair};
use byteorder::{BigEndian, ReadBytesExt};
use std::io;
use std::time;

use super::marker;
use super::Value;

#[derive(Debug, Clone)]
struct Trait {
    class_name: Option<String>,
    is_dynamic: bool,
    fields: Vec<String>,
}

#[derive(Debug)]
enum SizeOrIndex {
    Size(usize),
    Index(usize),
}

/// AMF3 decoder.
#[derive(Debug)]
pub struct Decoder<R> {
    inner: R,
    traits: Vec<Trait>,
    strings: Vec<String>,
    complexes: Vec<Value>,
}
impl<R> Decoder<R> {
    /// Unwraps this `Decoder`, returning the underlying reader.
    pub fn into_inner(self) -> R {
        self.inner
    }
    /// Returns an immutable reference to the underlying reader.
    pub fn inner(&mut self) -> &R {
        &self.inner
    }
    /// Returns a mutable reference to the underlying reader.
    pub fn inner_mut(&mut self) -> &mut R {
        &mut self.inner
    }
}
impl<R> Decoder<R>
where
    R: io::Read,
{
    /// Makes a new instance.
    pub fn new(inner: R) -> Self {
        Decoder {
            inner,
            traits: Vec::new(),
            strings: Vec::new(),
            complexes: Vec::new(),
        }
    }

    /// Decodes a AMF3 value.
    pub fn decode(&mut self) -> DecodeResult<Value> {
        self.decode_value()
    }

    /// Clear the reference tables of this decoder.
    ///
    /// > Similar to AFM 0, AMF 3 object reference tables, object trait reference tables
    /// > and string reference tables must be reset each time a new context header or message is processed.
    /// >
    /// > [AMF 3 Specification: 4.1 NetConnection and AMF 3](https://www.adobe.com/content/dam/acom/en/devnet/pdf/amf-file-format-spec.pdf)
    pub fn clear_reference_table(&mut self) {
        self.traits.clear();
        self.strings.clear();
        self.complexes.clear();
    }

    fn decode_value(&mut self) -> DecodeResult<Value> {
        let marker = self.inner.read_u8()?;
        match marker {
            marker::UNDEFINED => Ok(Value::Undefined),
            marker::NULL => Ok(Value::Null),
            marker::FALSE => Ok(Value::Boolean(false)),
            marker::TRUE => Ok(Value::Boolean(true)),
            marker::INTEGER => self.decode_integer(),
            marker::DOUBLE => self.decode_double(),
            marker::STRING => self.decode_string(),
            marker::XML_DOC => self.decode_xml_doc(),
            marker::DATE => self.decode_date(),
            marker::ARRAY => self.decode_array(),
            marker::OBJECT => self.decode_object(),
            marker::XML => self.decode_xml(),
            marker::BYTE_ARRAY => self.decode_byte_array(),
            marker::VECTOR_INT => self.decode_vector_int(),
            marker::VECTOR_UINT => self.decode_vector_uint(),
            marker::VECTOR_DOUBLE => self.decode_vector_double(),
            marker::VECTOR_OBJECT => self.decode_vector_object(),
            marker::DICTIONARY => self.decode_dictionary(),
            _ => Err(DecodeError::Unknown { marker }),
        }
    }

    fn decode_integer(&mut self) -> DecodeResult<Value> {
        let n = self.decode_u29()? as i32;
        let n = if n >= (1 << 28) { n - (1 << 29) } else { n };
        Ok(Value::Integer(n))
    }
    fn decode_double(&mut self) -> DecodeResult<Value> {
        let n = self.inner.read_f64::<BigEndian>()?;
        Ok(Value::Double(n))
    }
    fn decode_string(&mut self) -> DecodeResult<Value> {
        let s = self.decode_utf8()?;
        Ok(Value::String(s))
    }
    fn decode_xml_doc(&mut self) -> DecodeResult<Value> {
        self.decode_complex_type(|this, len| this.read_utf8(len).map(Value::XmlDocument))
    }
    fn decode_date(&mut self) -> DecodeResult<Value> {
        self.decode_complex_type(|this, _| {
            let millis = this.inner.read_f64::<BigEndian>()?;
            if !(millis.is_finite() && millis.is_sign_positive()) {
                Err(DecodeError::InvalidDate { millis })
            } else {
                Ok(Value::Date {
                    unix_time: time::Duration::from_millis(millis as u64),
                })
            }
        })
    }
    fn decode_array(&mut self) -> DecodeResult<Value> {
        self.decode_complex_type(|this, count| {
            let assoc = this.decode_pairs()?;
            let dense = (0..count)
                .map(|_| this.decode_value())
                .collect::<DecodeResult<_>>()?;
            Ok(Value::Array {
                assoc_entries: assoc,
                dense_entries: dense,
            })
        })
    }
    fn decode_object(&mut self) -> DecodeResult<Value> {
        self.decode_complex_type(|this, u28| {
            let amf_trait = this.decode_trait(u28)?;
            let mut entries = amf_trait
                .fields
                .iter()
                .map(|k| {
                    Ok(Pair {
                        key: k.clone(),
                        value: this.decode_value()?,
                    })
                })
                .collect::<DecodeResult<Vec<_>>>()?;
            if amf_trait.is_dynamic {
                entries.extend(this.decode_pairs()?);
            }
            Ok(Value::Object {
                class_name: amf_trait.class_name,
                sealed_count: amf_trait.fields.len(),
                entries,
            })
        })
    }
    fn decode_xml(&mut self) -> DecodeResult<Value> {
        self.decode_complex_type(|this, len| this.read_utf8(len).map(Value::Xml))
    }
    fn decode_byte_array(&mut self) -> DecodeResult<Value> {
        self.decode_complex_type(|this, len| this.read_bytes(len).map(Value::ByteArray))
    }
    fn decode_vector_int(&mut self) -> DecodeResult<Value> {
        self.decode_complex_type(|this, count| {
            let is_fixed = this.inner.read_u8()? != 0;
            let entries = (0..count)
                .map(|_| this.inner.read_i32::<BigEndian>())
                .collect::<Result<_, _>>()?;
            Ok(Value::IntVector { is_fixed, entries })
        })
    }
    fn decode_vector_uint(&mut self) -> DecodeResult<Value> {
        self.decode_complex_type(|this, count| {
            let is_fixed = this.inner.read_u8()? != 0;
            let entries = (0..count)
                .map(|_| this.inner.read_u32::<BigEndian>())
                .collect::<Result<_, _>>()?;
            Ok(Value::UintVector { is_fixed, entries })
        })
    }
    fn decode_vector_double(&mut self) -> DecodeResult<Value> {
        self.decode_complex_type(|this, count| {
            let is_fixed = this.inner.read_u8()? != 0;
            let entries = (0..count)
                .map(|_| this.inner.read_f64::<BigEndian>())
                .collect::<Result<_, _>>()?;
            Ok(Value::DoubleVector { is_fixed, entries })
        })
    }
    fn decode_vector_object(&mut self) -> DecodeResult<Value> {
        self.decode_complex_type(|this, count| {
            let is_fixed = this.inner.read_u8()? != 0;
            let class_name = this.decode_utf8()?;
            let entries = (0..count)
                .map(|_| this.decode_value())
                .collect::<DecodeResult<_>>()?;
            Ok(Value::ObjectVector {
                class_name: if class_name == "*" {
                    None
                } else {
                    Some(class_name)
                },
                is_fixed,
                entries,
            })
        })
    }
    fn decode_dictionary(&mut self) -> DecodeResult<Value> {
        self.decode_complex_type(|this, count| {
            let is_weak = this.inner.read_u8()? == 1;
            let entries = (0..count)
                .map(|_| {
                    Ok(Pair {
                        key: this.decode_value()?,
                        value: this.decode_value()?,
                    })
                })
                .collect::<DecodeResult<_>>()?;
            Ok(Value::Dictionary { is_weak, entries })
        })
    }

    /// Decode an AMF3 string.
    ///
    /// Use this if you need to decode an AMF3 string outside of value context.
    /// An example for this is reading keys in Local Shared Object file.
    pub fn decode_utf8(&mut self) -> DecodeResult<String> {
        match self.decode_size_or_index()? {
            SizeOrIndex::Size(len) => {
                let bytes = self.read_bytes(len)?;
                let s = String::from_utf8(bytes)?;
                if !s.is_empty() {
                    self.strings.push(s.clone());
                }
                Ok(s)
            }
            SizeOrIndex::Index(index) => {
                let s = self
                    .strings
                    .get(index)
                    .ok_or(DecodeError::OutOfRangeReference { index })?;
                Ok(s.clone())
            }
        }
    }
    fn decode_u29(&mut self) -> DecodeResult<u32> {
        let mut n = 0;
        for _ in 0..3 {
            let b = self.inner.read_u8()? as u32;
            n = (n << 7) | (b & 0b0111_1111);
            if (b & 0b1000_0000) == 0 {
                return Ok(n);
            }
        }
        let b = self.inner.read_u8()? as u32;
        n = (n << 8) | b;
        Ok(n)
    }
    fn decode_size_or_index(&mut self) -> DecodeResult<SizeOrIndex> {
        let u29 = self.decode_u29()? as usize;
        let is_reference = (u29 & 0b01) == 0;
        let value = u29 >> 1;
        if is_reference {
            Ok(SizeOrIndex::Index(value))
        } else {
            Ok(SizeOrIndex::Size(value))
        }
    }
    fn decode_complex_type<F>(&mut self, f: F) -> DecodeResult<Value>
    where
        F: FnOnce(&mut Self, usize) -> DecodeResult<Value>,
    {
        match self.decode_size_or_index()? {
            SizeOrIndex::Index(index) => self
                .complexes
                .get(index)
                .ok_or(DecodeError::OutOfRangeReference { index })
                .and_then(|v| {
                    if *v == Value::Null {
                        Err(DecodeError::CircularReference { index })
                    } else {
                        Ok(v.clone())
                    }
                }),
            SizeOrIndex::Size(u28) => {
                let index = self.complexes.len();
                self.complexes.push(Value::Null);
                let value = f(self, u28)?;
                self.complexes[index] = value.clone();
                Ok(value)
            }
        }
    }
    fn decode_pairs(&mut self) -> DecodeResult<Vec<Pair<String, Value>>> {
        let mut pairs = Vec::new();
        loop {
            let key = self.decode_utf8()?;
            if key.is_empty() {
                return Ok(pairs);
            }
            let value = self.decode_value()?;
            pairs.push(Pair { key, value });
        }
    }
    fn decode_trait(&mut self, u28: usize) -> DecodeResult<Trait> {
        if (u28 & 0b1) == 0 {
            let i = (u28 >> 1) as usize;
            let t = self
                .traits
                .get(i)
                .ok_or(DecodeError::OutOfRangeReference { index: i })?;
            Ok(t.clone())
        } else if (u28 & 0b10) != 0 {
            let class_name = self.decode_utf8()?;
            Err(DecodeError::ExternalizableType { name: class_name })
        } else {
            let is_dynamic = (u28 & 0b100) != 0;
            let field_num = u28 >> 3;
            let class_name = self.decode_utf8()?;
            let fields = (0..field_num)
                .map(|_| self.decode_utf8())
                .collect::<DecodeResult<_>>()?;

            let t = Trait {
                class_name: if class_name.is_empty() {
                    None
                } else {
                    Some(class_name)
                },
                is_dynamic,
                fields,
            };
            self.traits.push(t.clone());
            Ok(t)
        }
    }
    fn read_bytes(&mut self, len: usize) -> DecodeResult<Vec<u8>> {
        let mut buf = vec![0; len];
        self.inner.read_exact(&mut buf)?;
        Ok(buf)
    }
    fn read_utf8(&mut self, len: usize) -> DecodeResult<String> {
        self.read_bytes(len).and_then(|b| Ok(String::from_utf8(b)?))
    }
}

#[cfg(test)]
mod tests {
    use super::super::Value;
    use crate::error::DecodeError;
    use crate::Pair;
    use std::f64;
    use std::io;
    use std::time;

    macro_rules! decode {
        ($file:expr) => {{
            let input = include_bytes!(concat!("../testdata/", $file));
            Value::read_from(&mut &input[..])
        }};
    }
    macro_rules! decode_eq {
        ($file:expr, $expected: expr) => {{
            let value = decode!($file).unwrap();
            assert_eq!(value, $expected)
        }};
    }
    macro_rules! decode_unexpected_eof {
        ($file:expr) => {{
            let result = decode!($file);
            match result {
                Err(DecodeError::Io(e)) => assert_eq!(e.kind(), io::ErrorKind::UnexpectedEof),
                _ => assert!(false),
            }
        }};
    }

    #[test]
    fn decodes_undefined() {
        decode_eq!("amf3-undefined.bin", Value::Undefined);
    }
    #[test]
    fn decodes_null() {
        decode_eq!("amf3-null.bin", Value::Null);
    }
    #[test]
    fn decodes_boolean() {
        decode_eq!("amf3-true.bin", Value::Boolean(true));
        decode_eq!("amf3-false.bin", Value::Boolean(false));
    }
    #[test]
    fn decodes_integer() {
        decode_eq!("amf3-0.bin", Value::Integer(0));
        decode_eq!("amf3-min.bin", Value::Integer(-0x1000_0000));
        decode_eq!("amf3-max.bin", Value::Integer(0x0FFF_FFFF));
        decode_eq!("amf3-integer-2byte.bin", Value::Integer(0b1000_0000));
        decode_eq!(
            "amf3-integer-3byte.bin",
            Value::Integer(0b100_0000_0000_0000)
        );
    }
    #[test]
    fn decodes_double() {
        decode_eq!("amf3-float.bin", Value::Double(3.5));
        decode_eq!("amf3-bignum.bin", Value::Double(2f64.powf(1000f64)));
        decode_eq!("amf3-large-min.bin", Value::Double(-0x1000_0001 as f64));
        decode_eq!("amf3-large-max.bin", Value::Double(268_435_456_f64));
        decode_eq!(
            "amf3-double-positive-infinity.bin",
            Value::Double(f64::INFINITY)
        );
    }
    #[test]
    fn decodes_string() {
        decode_eq!("amf3-string.bin", s("String . String"));
        decode_eq!("amf3-symbol.bin", s("foo"));
        decode_eq!(
            "amf3-string-ref.bin",
            dense_array(
                &[
                    s("foo"),
                    s("str"),
                    s("foo"),
                    s("str"),
                    s("foo"),
                    obj(&[("str", s("foo"))][..])
                ][..]
            )
        );
        decode_eq!(
            "amf3-encoded-string-ref.bin",
            dense_array(&[s("this is a テスト"), s("this is a テスト")][..])
        );
        decode_eq!(
            "amf3-complex-encoded-string-array.bin",
            dense_array(&[i(5), s("Shift テスト"), s("UTF テスト"), i(5)][..])
        );
        decode_eq!(
            "amf3-empty-string-ref.bin",
            dense_array(&[s(""), s("")][..])
        );
    }
    #[test]
    fn decodes_array() {
        decode_eq!(
            "amf3-primitive-array.bin",
            dense_array(&[i(1), i(2), i(3), i(4), i(5)][..])
        );
        decode_eq!(
            "amf3-empty-array-ref.bin",
            dense_array(
                &[
                    dense_array(&[][..]),
                    dense_array(&[][..]),
                    dense_array(&[][..]),
                    dense_array(&[][..])
                ][..]
            )
        );
        decode_eq!(
            "amf3-array-ref.bin",
            dense_array(
                &[
                    dense_array(&[i(1), i(2), i(3)][..]),
                    dense_array(&[s("a"), s("b"), s("c")][..]),
                    dense_array(&[i(1), i(2), i(3)][..]),
                    dense_array(&[s("a"), s("b"), s("c")][..])
                ][..]
            )
        );
        decode_eq!(
            "amf3-associative-array.bin",
            Value::Array {
                assoc_entries: [("2", s("bar3")), ("foo", s("bar")), ("asdf", s("fdsa"))]
                    .iter()
                    .map(|e| pair(e.0, e.1.clone()))
                    .collect(),
                dense_entries: vec![s("bar"), s("bar1"), s("bar2")],
            }
        );

        let o1 = obj(&[("foo_one", s("bar_one"))][..]);
        let o2 = obj(&[("foo_two", s(""))][..]);
        let o3 = obj(&[("foo_three", i(42))][..]);
        let empty = obj(&[][..]);
        decode_eq!(
            "amf3-mixed-array.bin",
            dense_array(
                &[
                    o1.clone(),
                    o2.clone(),
                    o3.clone(),
                    empty.clone(),
                    dense_array(&[o1, o2, o3.clone()][..]),
                    dense_array(&[][..]),
                    i(42),
                    s(""),
                    dense_array(&[][..]),
                    s(""),
                    empty,
                    s("bar_one"),
                    o3
                ][..]
            )
        );
    }
    #[test]
    fn decodes_object() {
        let o = obj(&[("foo", s("bar"))][..]);
        decode_eq!(
            "amf3-object-ref.bin",
            dense_array(
                &[
                    dense_array(&[o.clone(), o.clone()][..]),
                    s("bar"),
                    dense_array(&[o.clone(), o][..])
                ][..]
            )
        );

        decode_eq!(
            "amf3-dynamic-object.bin",
            obj(&[
                ("property_one", s("foo")),
                ("another_public_property", s("a_public_value")),
                ("nil_property", Value::Null)
            ][..])
        );

        decode_eq!(
            "amf3-typed-object.bin",
            typed_obj(
                "org.amf.ASClass",
                &[("foo", s("bar")), ("baz", Value::Null)][..]
            )
        );

        let o = [
            typed_obj(
                "org.amf.ASClass",
                &[("foo", s("foo")), ("baz", Value::Null)],
            ),
            typed_obj(
                "org.amf.ASClass",
                &[("foo", s("bar")), ("baz", Value::Null)],
            ),
        ];
        decode_eq!("amf3-trait-ref.bin", dense_array(&o[..]));

        decode_eq!(
            "amf3-hash.bin",
            obj(&[("foo", s("bar")), ("answer", i(42))][..])
        );

        assert_eq!(
            decode!("amf3-externalizable.bin"),
            Err(DecodeError::ExternalizableType {
                name: "ExternalizableTest".to_string()
            })
        );
        assert_eq!(
            decode!("amf3-array-collection.bin"),
            Err(DecodeError::ExternalizableType {
                name: "flex.messaging.io.ArrayCollection".to_string(),
            })
        );
    }
    #[test]
    fn decodes_xml_doc() {
        decode_eq!(
            "amf3-xml-doc.bin",
            Value::XmlDocument("<parent><child prop=\"test\" /></parent>".to_string())
        );
    }
    #[test]
    fn decodes_xml() {
        let xml = Value::Xml("<parent><child prop=\"test\"/></parent>".to_string());
        decode_eq!("amf3-xml.bin", xml);
        decode_eq!("amf3-xml-ref.bin", dense_array(&[xml.clone(), xml][..]));
    }
    #[test]
    fn decodes_byte_array() {
        decode_eq!(
            "amf3-byte-array.bin",
            Value::ByteArray(vec![
                0, 3, 227, 129, 147, 227, 130, 140, 116, 101, 115, 116, 64
            ])
        );

        let b = Value::ByteArray(b"ASDF".to_vec());
        decode_eq!("amf3-byte-array-ref.bin", dense_array(&[b.clone(), b][..]));
    }
    #[test]
    fn decodes_date() {
        let d = Value::Date {
            unix_time: time::Duration::from_secs(0),
        };
        decode_eq!("amf3-date.bin", d);
        decode_eq!("amf3-date-ref.bin", dense_array(&[d.clone(), d][..]));
    }
    #[test]
    fn decodes_dictionary() {
        let entries = vec![
            (s("bar"), s("asdf1")),
            (
                typed_obj(
                    "org.amf.ASClass",
                    &[("foo", s("baz")), ("baz", Value::Null)][..],
                ),
                s("asdf2"),
            ),
        ];
        decode_eq!("amf3-dictionary.bin", dic(&entries));
        decode_eq!("amf3-empty-dictionary.bin", dic(&[][..]));
    }
    #[test]
    fn decodes_vector() {
        decode_eq!(
            "amf3-vector-int.bin",
            Value::IntVector {
                is_fixed: false,
                entries: vec![4, -20, 12],
            }
        );

        decode_eq!(
            "amf3-vector-uint.bin",
            Value::UintVector {
                is_fixed: false,
                entries: vec![4, 20, 12],
            }
        );

        decode_eq!(
            "amf3-vector-double.bin",
            Value::DoubleVector {
                is_fixed: false,
                entries: vec![4.3, -20.6],
            }
        );

        let objects = vec![
            typed_obj(
                "org.amf.ASClass",
                &[("foo", s("foo")), ("baz", Value::Null)][..],
            ),
            typed_obj(
                "org.amf.ASClass",
                &[("foo", s("bar")), ("baz", Value::Null)][..],
            ),
            typed_obj(
                "org.amf.ASClass",
                &[("foo", s("baz")), ("baz", Value::Null)][..],
            ),
        ];
        decode_eq!(
            "amf3-vector-object.bin",
            Value::ObjectVector {
                class_name: Some("org.amf.ASClass".to_string()),
                is_fixed: false,
                entries: objects,
            }
        );
    }
    #[test]
    fn other_errors() {
        assert_eq!(
            decode!("amf3-graph-member.bin"),
            Err(DecodeError::CircularReference { index: 0 })
        );
        assert_eq!(
            decode!("amf3-bad-object-ref.bin"),
            Err(DecodeError::OutOfRangeReference { index: 10 })
        );
        assert_eq!(
            decode!("amf3-bad-trait-ref.bin"),
            Err(DecodeError::OutOfRangeReference { index: 4 })
        );
        assert_eq!(
            decode!("amf3-bad-string-ref.bin"),
            Err(DecodeError::OutOfRangeReference { index: 8 })
        );
        assert_eq!(
            decode!("amf3-unknown-marker.bin"),
            Err(DecodeError::Unknown { marker: 123 })
        );
        assert_eq!(
            decode!("amf3-date-invalid-millis.bin"),
            Err(DecodeError::InvalidDate {
                millis: f64::INFINITY
            })
        );
        assert_eq!(
            decode!("amf3-date-minus-millis.bin"),
            Err(DecodeError::InvalidDate { millis: -1.0 })
        );
        decode_unexpected_eof!("amf3-empty.bin");
        decode_unexpected_eof!("amf3-double-partial.bin");
        decode_unexpected_eof!("amf3-date-partial.bin");
        decode_unexpected_eof!("amf3-dictionary-partial.bin");
        decode_unexpected_eof!("amf3-vector-partial.bin");
        decode_unexpected_eof!("amf3-vector-int-partial.bin");
        decode_unexpected_eof!("amf3-vector-uint-partial.bin");
        decode_unexpected_eof!("amf3-xml-partial.bin");
        decode_unexpected_eof!("amf3-string-partial.bin");
        decode_unexpected_eof!("amf3-u29-partial.bin");
    }

    fn i(i: i32) -> Value {
        Value::Integer(i)
    }
    fn s(s: &str) -> Value {
        Value::String(s.to_string())
    }
    fn dense_array(entries: &[Value]) -> Value {
        Value::Array {
            assoc_entries: Vec::new(),
            dense_entries: entries.to_vec(),
        }
    }
    fn pair(key: &str, value: Value) -> Pair<String, Value> {
        Pair {
            key: key.to_string(),
            value,
        }
    }
    fn dic(entries: &[(Value, Value)]) -> Value {
        Value::Dictionary {
            is_weak: false,
            entries: entries
                .iter()
                .map(|e| Pair {
                    key: e.0.clone(),
                    value: e.1.clone(),
                })
                .collect(),
        }
    }
    fn obj(entries: &[(&str, Value)]) -> Value {
        Value::Object {
            class_name: None,
            sealed_count: 0,
            entries: entries
                .iter()
                .map(|e| Pair {
                    key: e.0.to_string(),
                    value: e.1.clone(),
                })
                .collect(),
        }
    }
    fn typed_obj(class: &str, entries: &[(&str, Value)]) -> Value {
        Value::Object {
            class_name: Some(class.to_string()),
            sealed_count: entries.len(),
            entries: entries
                .iter()
                .map(|e| Pair {
                    key: e.0.to_string(),
                    value: e.1.clone(),
                })
                .collect(),
        }
    }
}
