use crate::*;

#[derive(Clone, Hash, PartialEq, Eq, Debug)]
/// ToDo
pub enum DataType {
    /// e.g. `( string, string, int )`
    Tuple(LVec<DataType>),

    /// e.g. `struct{ name: string, email: string, created: date }`
    Struct(LVec<(LString, DataType)>),

    /// e.g. `enum{ leaf: int, node: [int] }`
    Enum(Vec<(LString, DataType)>),

    /// e.g. `string`
    String,

    /// e.g. `binary`
    Binary,

    /// e.g. `int`
    Int,

    /// Array of values.
    Array(usize, LBox<DataType>),

    /// List of values.
    List(LBox<DataType>),

    /// e.g. `[string->int]`
    Map(LBox<DataType>, LBox<DataType>),

    /// List of 64-bit integers.
    IList,
}

impl DataType {
    /// Encode value (which must match DataType) as bytes. DataType will later be used to decode the bytes.
    pub fn value_to_bytes(&self, val: &mut Value) -> LVec<u8> {
        let mut w = LVec::new();
        self.value_to_writer(val, &mut w);
        w
    }

    /// Encode value (which must match DataType) as bytes. DataType will later be used to decode the bytes.
    pub fn value_to_writer<W>(&self, val: &mut Value, w: &mut W)
    where
        W: std::io::Write,
    {
        match self {
            DataType::Struct(fields) => {
                let list = val.list();
                for (i, f) in fields.into_iter().enumerate() {
                    f.1.value_to_writer(&mut list[i], w);
                }
            }
            DataType::String => {
                let s = val.string();
                self.write_len(s.len(), w);
                let _ = w.write(s.as_bytes());
            }
            DataType::Int => {
                let i = val.int();
                let _ = w.write(&i.to_le_bytes());
            }
            DataType::IList => {
                let list = val.ilist();
                self.write_len(list.len(), w);
                for i in list {
                    self.write_int(*i, w);
                }
            }
            _ => todo!(),
        }
    }

    /// Decode bytes of this DataType. ix is advanced according to the bytes read from buf. Returns decoded Value.
    pub fn bytes_to_value(&self, buf: &[u8], ix: &mut usize) -> Value {
        match self {
            DataType::Struct(fields) => {
                let mut list = LVec::with_capacity(fields.len());

                for f in fields {
                    let v = f.1.bytes_to_value(buf, ix);
                    list.push(v);
                }
                Value::List(list)
            }
            DataType::String => {
                let len = self.read_len(buf, ix);
                let s = &buf[*ix..*ix + len];
                *ix += len;
                let s = str::from_utf8(s).unwrap();
                let s = LString::from(s);
                Value::String(s)
            }
            DataType::Int => {
                let i = self.read_int(buf, ix);
                Value::Int(i)
            }
            DataType::IList => {
                let len = self.read_len(buf, ix);
                let mut list = LVec::with_capacity(len);
                for _i in 0..len {
                    let i = self.read_int(buf, ix);
                    list.push(i);
                }
                Value::IList(list)
            }
            _ => todo!(),
        }
    }

    fn write_len<W>(&self, val: usize, w: &mut W)
    where
        W: std::io::Write,
    {
        // Should use a variable length encoding to be efficient for small sizes.
        let val = val as u64;
        let _ = w.write(&val.to_le_bytes());
    }

    fn read_len(&self, buf: &[u8], ix: &mut usize) -> usize {
        // Should use a variable length encoding to be efficient for small sizes.
        let x = u64::from_le_bytes(buf[*ix..*ix + 8].try_into().unwrap());
        *ix += 8;
        x as usize
    }

    fn write_int<W>(&self, val: i64, w: &mut W)
    where
        W: std::io::Write,
    {
        // Could use a variable length encoding to be efficient for small ints.
        let _ = w.write(&val.to_le_bytes());
    }

    fn read_int(&self, buf: &[u8], ix: &mut usize) -> i64 {
        // Could use a variable length encoding to be efficient for small ints.
        let x = i64::from_le_bytes(buf[*ix..*ix + 8].try_into().unwrap());
        *ix += 8;
        x
    }

/*
    pub fn to_bytes(&self) -> Vec<u8> {
        postcard::to_stdvec(self).unwrap()
    }
    pub fn from_bytes(b: &[u8]) -> Self {
        postcard::from_bytes(b).unwrap()
    }
*/

}

#[test]
fn test_datatype() {
    use pstd::veca;

    let dt = DataType::Struct(veca![
        (LString::from("Name"), DataType::String),
        (LString::from("Email"), DataType::String),
        (LString::from("Postal"), DataType::String)
    ]);

    let mut v1 = Value::List(veca![
        Value::String(LString::from("George Barwood")),
        Value::String(LString::from("george.barwood@gmail.com")),
        Value::String(LString::from("33 Sandpipe Close, GL2 4LZ")),
    ]);

    let w = dt.value_to_bytes(&mut v1);

    println!("w = {:?}", w);

    let mut ix = 0;
    let v2 = dt.bytes_to_value(&w, &mut ix);

    println!("v1 = {:?}", v1);
    println!("v2 = {:?}", v2);
    assert_eq!(v1, v2);
}
