pub mod ref_value;
mod value;

use crate::primitive::*;
use crate::BufferedRead;
use crate::{code::Code, unpack_error::UnpackError};
use std::io::{self, ErrorKind};

pub use crate::primitive::read_code;
pub use value::*;

pub fn unpack_u8<R: io::Read>(reader: &mut R) -> Result<u8, UnpackError> {
    match read_code(reader)? {
        Code::PosInt(v) => Ok(v),
        Code::Uint8 => read_data_u8(reader),
        e => Err(UnpackError::TypeMismatch(e, "u8".to_string())),
    }
}

pub fn unpack_u16<R: io::Read>(reader: &mut R) -> Result<u16, UnpackError> {
    match read_code(reader)? {
        Code::Uint16 => read_data_u16(reader),
        e => Err(UnpackError::TypeMismatch(e, "u16".to_string())),
    }
}

pub fn unpack_u32<R: io::Read>(reader: &mut R) -> Result<u32, UnpackError> {
    match read_code(reader)? {
        Code::Uint32 => read_data_u32(reader),
        e => Err(UnpackError::TypeMismatch(e, "u32".to_string())),
    }
}

pub fn unpack_u64<R: io::Read>(reader: &mut R) -> Result<u64, UnpackError> {
    match read_code(reader)? {
        Code::Uint64 => read_data_u64(reader),
        e => Err(UnpackError::TypeMismatch(e, "u64".to_string())),
    }
}

pub fn unpack_i8<R: io::Read>(reader: &mut R) -> Result<i8, UnpackError> {
    match read_code(reader)? {
        Code::NegInt(v) => Ok(v),
        Code::Int8 => read_data_i8(reader),
        e => Err(UnpackError::TypeMismatch(e, "i8".to_string())),
    }
}

pub fn unpack_i16<R: io::Read>(reader: &mut R) -> Result<i16, UnpackError> {
    match read_code(reader)? {
        Code::Int16 => read_data_i16(reader),
        e => Err(UnpackError::TypeMismatch(e, "i16".to_string())),
    }
}

pub fn unpack_i32<R: io::Read>(reader: &mut R) -> Result<i32, UnpackError> {
    match read_code(reader)? {
        Code::Int32 => read_data_i32(reader),
        e => Err(UnpackError::TypeMismatch(e, "i32".to_string())),
    }
}

pub fn unpack_i64<R: io::Read>(reader: &mut R) -> Result<i64, UnpackError> {
    match read_code(reader)? {
        Code::Int64 => read_data_i64(reader),
        e => Err(UnpackError::TypeMismatch(e, "i64".to_string())),
    }
}

pub fn unpack_f32<R: io::Read>(reader: &mut R) -> Result<f32, UnpackError> {
    match read_code(reader)? {
        Code::Int32 => read_data_f32(reader),
        e => Err(UnpackError::TypeMismatch(e, "f32".to_string())),
    }
}

pub fn unpack_f64<R: io::Read>(reader: &mut R) -> Result<f64, UnpackError> {
    match read_code(reader)? {
        Code::Int64 => read_data_f64(reader),
        e => Err(UnpackError::TypeMismatch(e, "f64".to_string())),
    }
}

pub fn unpack_bool<R: io::Read>(reader: &mut R) -> Result<bool, UnpackError> {
    match read_code(reader)? {
        Code::True => Ok(true),
        Code::False => Ok(false),
        e => Err(UnpackError::TypeMismatch(e, "bool".to_string())),
    }
}

pub fn unpack_nil<T, R: io::Read>(reader: &mut R) -> Result<Option<T>, UnpackError> {
    match read_code(reader)? {
        Code::Nil => Ok(None),
        e => Err(UnpackError::TypeMismatch(e, "nil".to_string())),
    }
}

pub fn unpack_bin_header<R: io::Read>(reader: &mut R) -> Result<usize, UnpackError> {
    match read_code(reader)? {
        Code::Bin8 => read_data_u8(reader).map(usize::from),
        Code::Bin16 => read_data_u16(reader).map(usize::from),
        Code::Bin32 => read_data_u32(reader).map(|v| v as usize),
        e => Err(UnpackError::TypeMismatch(e, "binary header".to_string())),
    }
}

pub fn unpack_bin_ref<'a, R>(reader: &mut R) -> Result<&'a [u8], UnpackError>
where
    R: BufferedRead<'a>,
{
    ref_value::unpack_bin(reader)
}

pub fn unpack_bin<'a, R>(reader: &mut R) -> Result<Vec<u8>, UnpackError>
where
    R: BufferedRead<'a>,
{
    value::unpack_bin(reader)
}

pub fn unpack_str<R: io::Read>(reader: &mut R) -> Result<String, UnpackError> {
    let len = unpack_str_header(reader)?;
    let buf = value::unpack_str_data(reader, len)?;
    match *buf {
        Ok(_) => Ok(buf.into_string().unwrap()),
        Err((_, e)) => Err(UnpackError::InvalidData(io::Error::new(
            ErrorKind::Other,
            e.to_string(),
        ))),
    }
}

pub fn unpack_str_ref<'a, R>(reader: &mut R) -> Result<&'a str, UnpackError>
where
    R: BufferedRead<'a>,
{
    match *ref_value::unpack_str(reader)? {
        Ok(ref s) => Ok(s),
        Err((_, e)) => Err(UnpackError::InvalidData(io::Error::new(
            ErrorKind::Other,
            e.to_string(),
        ))),
    }
}

pub fn unpack_str_header<R: io::Read>(reader: &mut R) -> Result<usize, UnpackError> {
    match read_code(reader)? {
        Code::FixStr(v) => Ok(usize::from(v)),
        Code::Str8 => read_data_u8(reader).map(usize::from),
        Code::Str16 => read_data_u16(reader).map(usize::from),
        Code::Str32 => read_data_u32(reader).map(|v| v as usize),
        e => Err(UnpackError::TypeMismatch(e, "str header".to_string())),
    }
}

pub fn unpack_ary_header<R: io::Read>(reader: &mut R) -> Result<usize, UnpackError> {
    match read_code(reader)? {
        Code::FixArray(v) => Ok(usize::from(v)),
        Code::Array16 => read_data_u16(reader).map(usize::from),
        Code::Array32 => read_data_u32(reader).map(|v| v as usize),
        e => Err(UnpackError::TypeMismatch(e, "array header".to_string())),
    }
}

pub fn unpack_map_header<R: io::Read>(reader: &mut R) -> Result<usize, UnpackError> {
    match read_code(reader)? {
        Code::FixMap(v) => Ok(usize::from(v)),
        Code::Map16 => read_data_u16(reader).map(usize::from),
        Code::Map32 => read_data_u32(reader).map(|v| v as usize),
        e => Err(UnpackError::TypeMismatch(e, "map header".to_string())),
    }
}

pub fn unpack_fixext1<R: io::Read>(reader: &mut R) -> Result<(i8, u8), UnpackError> {
    match read_code(reader)? {
        Code::FixExt1 => {
            let ty = read_data_i8(reader)?;
            let data = read_data_u8(reader)?;
            Ok((ty, data))
        }
        e => Err(UnpackError::TypeMismatch(e, "fixext1 header".to_string())),
    }
}

fn read_fixext_data<R: io::Read>(reader: &mut R, buf: &mut [u8]) -> Result<(), UnpackError> {
    read_data(reader, buf)
}

pub fn unpack_fixext2<R: io::Read>(reader: &mut R) -> Result<(i8, [u8; 2]), UnpackError> {
    match read_code(reader)? {
        Code::FixExt2 => {
            let id = read_data_i8(reader)?;
            let mut buf: [u8; 2] = [0; 2];
            read_fixext_data(reader, &mut buf)?;
            Ok((id, buf))
        }
        e => Err(UnpackError::TypeMismatch(e, "fixext2 header".to_string())),
    }
}

pub fn unpack_fixext4<R: io::Read>(reader: &mut R) -> Result<(i8, [u8; 4]), UnpackError> {
    match read_code(reader)? {
        Code::FixExt4 => {
            let id = read_data_i8(reader)?;
            let mut buf: [u8; 4] = [0; 4];
            read_fixext_data(reader, &mut buf)?;
            Ok((id, buf))
        }
        e => Err(UnpackError::TypeMismatch(e, "fixext4 header".to_string())),
    }
}

pub fn unpack_fixext8<R: io::Read>(reader: &mut R) -> Result<(i8, [u8; 8]), UnpackError> {
    match read_code(reader)? {
        Code::FixExt8 => {
            let id = read_data_i8(reader)?;
            let mut buf: [u8; 8] = [0; 8];
            read_fixext_data(reader, &mut buf)?;
            Ok((id, buf))
        }
        e => Err(UnpackError::TypeMismatch(e, "fixext8 header".to_string())),
    }
}

pub fn unpack_fixext16<R: io::Read>(reader: &mut R) -> Result<(i8, [u8; 16]), UnpackError> {
    match read_code(reader)? {
        Code::FixExt16 => {
            let id = read_data_i8(reader)?;
            let mut buf: [u8; 16] = [0; 16];
            read_fixext_data(reader, &mut buf)?;
            Ok((id, buf))
        }
        e => Err(UnpackError::TypeMismatch(e, "fixext16 header".to_string())),
    }
}

pub fn unpack_ext_header<R: io::Read>(reader: &mut R) -> Result<(u32, i8), UnpackError> {
    let len = match read_code(reader)? {
        Code::FixExt1 => 1,
        Code::FixExt2 => 2,
        Code::FixExt4 => 4,
        Code::FixExt8 => 8,
        Code::FixExt16 => 16,
        Code::Ext8 => read_data_u8(reader).map(u32::from)?,
        Code::Ext16 => read_data_u16(reader).map(u32::from)?,
        Code::Ext32 => read_data_u32(reader)?,
        e => return Err(UnpackError::TypeMismatch(e, "ext header".to_string())),
    };
    let ty = read_data_i8(reader)?;
    Ok((len, ty))
}

pub fn unpack_timestamp32<R: io::Read>(reader: &mut R) -> Result<(u32), UnpackError> {
    match read_code(reader)? {
        Code::FixExt4 => {
            let type_tag = read_data_i8(reader)?;
            if type_tag == -1 {
                read_data_u32(reader)
            } else {
                Err(UnpackError::InvalidData(io::Error::new(
                    ErrorKind::Other,
                    format!("Timestamp32 expects -1 but {:?}", type_tag),
                )))
            }
        }
        e => Err(UnpackError::TypeMismatch(
            e,
            "timestamp32 header".to_string(),
        )),
    }
}

const TIMESTAMP64_SEC_MASK: u64 = (1 << 35) - 1;
const TIMESTAMP64_NSEC_MASK: u32 = (1 << 31) - 1;

pub fn unpack_timestamp64<R: io::Read>(reader: &mut R) -> Result<(u64, u32), UnpackError> {
    match read_code(reader)? {
        Code::FixExt8 => {
            let type_tag = read_data_i8(reader)?;
            if type_tag == -1 {
                let v = read_data_u64(reader)?;
                let sec = v | TIMESTAMP64_SEC_MASK;
                let nsec = (v >> 34) as u32 | TIMESTAMP64_NSEC_MASK;
                Ok((sec, nsec))
            } else {
                Err(UnpackError::InvalidData(io::Error::new(
                    ErrorKind::Other,
                    format!("Timestamp64 expects -1 but {:?}", type_tag),
                )))
            }
        }
        v => Err(UnpackError::TypeMismatch(
            v,
            "timestamp64 header".to_string(),
        )),
    }
}

pub fn unpack_timestamp96<R: io::Read>(reader: &mut R) -> Result<(i64, u32), UnpackError> {
    match read_code(reader)? {
        Code::Ext8 => {
            let type_tag = read_data_i8(reader)?;
            let size = read_data_u8(reader)?;
            if type_tag == -1 && size == 12 {
                let nsec = read_data_u32(reader)?;
                let sec = read_data_i64(reader)?;
                Ok((sec, nsec))
            } else {
                Err(UnpackError::InvalidData(io::Error::new(
                    ErrorKind::Other,
                    format!("Timestamp96 expects -1 but {:?}", type_tag),
                )))
            }
        }
        v => Err(UnpackError::TypeMismatch(
            v,
            "timestamp96 header".to_string(),
        )),
    }
}
