use ::alloc::{
    collections::{BTreeMap, BTreeSet, BinaryHeap, LinkedList, VecDeque},
    vec::Vec,
};

use crate::{
    format::Format,
    helpers::{take_byte, take_byte_iter, take_num, take_num_iter},
    Error, Unpackable, UnpackableBorrowed,
};

/// Unpacks the array length from the buffer.
pub fn unpack_array_len(mut buf: &[u8]) -> Result<(usize, usize), Error> {
    let format = take_byte(&mut buf)?;
    let (n, len) = match format {
        0x90..=0x9f => (1, (format & 0x0f) as usize),
        Format::ARRAY16 => (
            3,
            take_num(&mut buf, u16::from_be_bytes).map(|v| v as usize)?,
        ),
        Format::ARRAY32 => (
            5,
            take_num(&mut buf, u32::from_be_bytes).map(|v| v as usize)?,
        ),
        _ => return Err(Error::UnexpectedFormatTag),
    };
    Ok((n, len))
}

/// Unpacks an array from the buffer, returning a collectable type and the amount of read bytes.
pub fn unpack_array<V, C>(mut buf: &[u8]) -> Result<(usize, C), <V as Unpackable>::Error>
where
    V: Unpackable,
    C: FromIterator<V>,
{
    let (mut n, len) = unpack_array_len(buf)?;
    buf = &buf[n..];
    let array: C = (0..len)
        .map(|_| {
            let (count, v) = V::unpack_with_ofs(buf)?;
            buf = &buf[count..];
            n += count;
            Ok(v)
        })
        .collect::<Result<_, <V as Unpackable>::Error>>()?;
    Ok((n, array))
}

/// Unpacks an array from the iterator, returning a collectable type and the amount of read bytes.
pub fn unpack_array_iter<I, V, C>(iter: I) -> Result<(usize, C), <V as Unpackable>::Error>
where
    I: IntoIterator<Item = u8>,
    V: Unpackable,
    C: FromIterator<V>,
{
    let mut bytes = iter.into_iter();
    let format = take_byte_iter(bytes.by_ref())?;
    let (mut n, len) = match format {
        0x90..=0x9f => (1, (format & 0x0f) as usize),
        Format::ARRAY16 => (
            3,
            take_num_iter(bytes.by_ref(), u16::from_be_bytes).map(|v| v as usize)?,
        ),
        Format::ARRAY32 => (
            5,
            take_num_iter(bytes.by_ref(), u32::from_be_bytes).map(|v| v as usize)?,
        ),
        _ => return Err(Error::UnexpectedFormatTag.into()),
    };
    let array: C = (0..len)
        .map(|_| {
            let (count, v) = V::unpack_iter(bytes.by_ref())?;
            n += count;
            Ok(v)
        })
        .collect::<Result<_, <V as Unpackable>::Error>>()?;
    Ok((n, array))
}

/// Unpacks an array from the buffer using the borrowed deserialization path.
pub fn unpack_array_borrowed<'a, V, C>(
    buf: &'a [u8],
) -> Result<(usize, C), <V as UnpackableBorrowed<'a>>::Error>
where
    V: UnpackableBorrowed<'a>,
    C: FromIterator<V>,
{
    let (mut n, len) = unpack_array_len(buf)?;
    let mut remaining: &'a [u8] = &buf[n..];
    let array: C = (0..len)
        .map(|_| {
            let (count, v) = V::unpack_with_ofs(remaining)?;
            remaining = &remaining[count..];
            n += count;
            Ok(v)
        })
        .collect::<Result<_, <V as UnpackableBorrowed<'a>>::Error>>()?;
    Ok((n, array))
}

/// Unpacks a map length from the buffer.
pub fn unpack_map_len(mut buf: &[u8]) -> Result<(usize, usize), Error> {
    let format = take_byte(&mut buf)?;
    let (n, len) = match format {
        0x80..=0x8f => (1, (format & 0x0f) as usize),
        Format::MAP16 => (
            3,
            take_num(&mut buf, u16::from_be_bytes).map(|v| v as usize)?,
        ),
        Format::MAP32 => (
            5,
            take_num(&mut buf, u32::from_be_bytes).map(|v| v as usize)?,
        ),
        _ => return Err(Error::UnexpectedFormatTag),
    };
    Ok((n, len))
}

/// Unpacks a map from the buffer, returning a collectable type and the amount of read bytes.
pub fn unpack_map<K, V, C>(mut buf: &[u8]) -> Result<(usize, C), <V as Unpackable>::Error>
where
    K: Unpackable,
    V: Unpackable,
    <V as Unpackable>::Error: From<<K as Unpackable>::Error>,
    C: FromIterator<(K, V)>,
{
    let (mut n, len) = unpack_map_len(buf)?;
    buf = &buf[n..];
    let map: C = (0..len)
        .map(|_| {
            let (count, k) = K::unpack_with_ofs(buf)?;
            buf = &buf[count..];
            n += count;
            let (count, v) = V::unpack_with_ofs(buf)?;
            buf = &buf[count..];
            n += count;
            Ok((k, v))
        })
        .collect::<Result<_, <V as Unpackable>::Error>>()?;
    Ok((n, map))
}

/// Unpacks a map from the iterator, returning a collectable type and the amount of read bytes.
pub fn unpack_map_iter<I, K, V, C>(iter: I) -> Result<(usize, C), <V as Unpackable>::Error>
where
    I: IntoIterator<Item = u8>,
    K: Unpackable,
    V: Unpackable,
    <V as Unpackable>::Error: From<<K as Unpackable>::Error>,
    C: FromIterator<(K, V)>,
{
    let mut bytes = iter.into_iter();
    let format = take_byte_iter(bytes.by_ref())?;
    let (mut n, len) = match format {
        0x80..=0x8f => (1, (format & 0x0f) as usize),
        Format::MAP16 => (
            3,
            take_num_iter(bytes.by_ref(), u16::from_be_bytes).map(|v| v as usize)?,
        ),
        Format::MAP32 => (
            5,
            take_num_iter(bytes.by_ref(), u32::from_be_bytes).map(|v| v as usize)?,
        ),
        _ => return Err(Error::UnexpectedFormatTag.into()),
    };
    let map: C = (0..len)
        .map(|_| {
            let (count, k) = K::unpack_iter(bytes.by_ref())?;
            n += count;
            let (count, v) = V::unpack_iter(bytes.by_ref())?;
            n += count;
            Ok((k, v))
        })
        .collect::<Result<_, <V as Unpackable>::Error>>()?;
    Ok((n, map))
}

impl<X> Unpackable for BTreeSet<X>
where
    X: Unpackable + Ord,
{
    type Error = <X as Unpackable>::Error;

    fn unpack_with_ofs(buf: &[u8]) -> Result<(usize, Self), Self::Error> {
        unpack_array(buf)
    }

    fn unpack_iter<I>(bytes: I) -> Result<(usize, Self), Self::Error>
    where
        I: IntoIterator<Item = u8>,
    {
        unpack_array_iter(bytes)
    }
}

impl<X> Unpackable for BinaryHeap<X>
where
    X: Unpackable + Ord,
{
    type Error = <X as Unpackable>::Error;

    fn unpack_with_ofs(buf: &[u8]) -> Result<(usize, Self), Self::Error> {
        unpack_array(buf)
    }

    fn unpack_iter<I>(bytes: I) -> Result<(usize, Self), Self::Error>
    where
        I: IntoIterator<Item = u8>,
    {
        unpack_array_iter(bytes)
    }
}

impl<X> Unpackable for LinkedList<X>
where
    X: Unpackable,
{
    type Error = <X as Unpackable>::Error;

    fn unpack_with_ofs(buf: &[u8]) -> Result<(usize, Self), Self::Error> {
        unpack_array(buf)
    }

    fn unpack_iter<I>(bytes: I) -> Result<(usize, Self), Self::Error>
    where
        I: IntoIterator<Item = u8>,
    {
        unpack_array_iter(bytes)
    }
}

impl<X> Unpackable for Vec<X>
where
    X: Unpackable,
{
    type Error = <X as Unpackable>::Error;

    fn unpack_with_ofs(buf: &[u8]) -> Result<(usize, Self), Self::Error> {
        unpack_array(buf)
    }

    fn unpack_iter<I>(bytes: I) -> Result<(usize, Self), Self::Error>
    where
        I: IntoIterator<Item = u8>,
    {
        unpack_array_iter(bytes)
    }
}

impl<X> Unpackable for VecDeque<X>
where
    X: Unpackable,
{
    type Error = <X as Unpackable>::Error;

    fn unpack_with_ofs(buf: &[u8]) -> Result<(usize, Self), Self::Error> {
        unpack_array(buf)
    }

    fn unpack_iter<I>(bytes: I) -> Result<(usize, Self), Self::Error>
    where
        I: IntoIterator<Item = u8>,
    {
        unpack_array_iter(bytes)
    }
}

impl<K, V> Unpackable for BTreeMap<K, V>
where
    K: Unpackable + Ord,
    V: Unpackable,
    <V as Unpackable>::Error: From<<K as Unpackable>::Error>,
{
    type Error = <V as Unpackable>::Error;

    fn unpack_with_ofs(buf: &[u8]) -> Result<(usize, Self), Self::Error> {
        unpack_map(buf)
    }

    fn unpack_iter<I>(bytes: I) -> Result<(usize, Self), Self::Error>
    where
        I: IntoIterator<Item = u8>,
    {
        unpack_map_iter(bytes)
    }
}

#[cfg(feature = "std")]
mod std {
    use super::*;
    use ::std::{
        collections::{HashMap, HashSet},
        hash::Hash,
    };

    impl<X> Unpackable for HashSet<X>
    where
        X: Unpackable + Hash + Eq,
    {
        type Error = <X as Unpackable>::Error;

        fn unpack_with_ofs(buf: &[u8]) -> Result<(usize, Self), Self::Error> {
            unpack_array(buf)
        }

        fn unpack_iter<I>(bytes: I) -> Result<(usize, Self), Self::Error>
        where
            I: IntoIterator<Item = u8>,
        {
            unpack_array_iter(bytes)
        }
    }

    impl<K, V> Unpackable for HashMap<K, V>
    where
        K: Unpackable + Hash + Eq,
        V: Unpackable,
        <V as Unpackable>::Error: From<<K as Unpackable>::Error>,
    {
        type Error = <V as Unpackable>::Error;

        fn unpack_with_ofs(buf: &[u8]) -> Result<(usize, Self), Self::Error> {
            unpack_map(buf)
        }

        fn unpack_iter<I>(bytes: I) -> Result<(usize, Self), Self::Error>
        where
            I: IntoIterator<Item = u8>,
        {
            unpack_map_iter(bytes)
        }
    }
}
