use binrw::{binrw, BinRead, BinWrite};
use diff::{Diff, VecDiff};
use num::Num;
use std::{
    fmt::{Debug, Display},
    marker::PhantomData,
    ops::{Deref, DerefMut},
};

/// Wrapper to prefix array of primitives (u8/u32/etc)
#[binrw]
#[derive(Clone, Default, PartialEq)]
pub struct ArrayOfPrimitive<T, S = u32>
where
    // Static bounds due to https://github.com/jam1garner/binrw/issues/199
    T: for<'a> BinRead<Args<'a> = ()> + for<'a> BinWrite<Args<'a> = ()>,
    T: Num + 'static,
    S: for<'a> BinRead<Args<'a> = ()> + for<'a> BinWrite<Args<'a> = ()>,
    S: TryFrom<usize> + Copy + Clone + Debug + 'static,
    <S as TryFrom<usize>>::Error: Display + Debug + Send + Sync,
    usize: TryFrom<S>,
{
    // Temporary variable that holds number of elements
    #[br(temp)]
    #[bw(try_calc(S::try_from(value.len())))]
    count: S,

    #[br(count = count)]
    pub value: Vec<T>,

    _marker: PhantomData<S>,
}

impl<T, S> Debug for ArrayOfPrimitive<T, S>
where
    T: for<'a> BinRead<Args<'a> = ()> + for<'a> BinWrite<Args<'a> = ()>,
    T: Debug + Num + 'static,
    S: for<'a> BinRead<Args<'a> = ()> + for<'a> BinWrite<Args<'a> = ()>,
    S: TryFrom<usize> + Copy + Clone + Debug + 'static,
    <S as TryFrom<usize>>::Error: Display + Debug + Send + Sync,
    usize: TryFrom<S>,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}", self.value)
    }
}

// Custom Diff implementation to only diff internal value
impl<T, S> Diff for ArrayOfPrimitive<T, S>
where
    T: for<'a> BinRead<Args<'a> = ()> + for<'a> BinWrite<Args<'a> = ()>,
    T: Diff + PartialEq + Num + 'static,
    S: for<'a> BinRead<Args<'a> = ()> + for<'a> BinWrite<Args<'a> = ()>,
    S: TryFrom<usize> + Copy + Clone + Debug + Display + 'static,
    <S as TryFrom<usize>>::Error: Display + Debug + Send + Sync,
    usize: TryFrom<S>,
{
    type Repr = VecDiff<T>;

    fn diff(&self, other: &Self) -> Self::Repr {
        self.value.diff(&other.value)
    }

    fn apply(&mut self, diff: &Self::Repr) {
        self.value.apply(diff)
    }

    fn identity() -> Self {
        Self {
            value: vec![],
            _marker: PhantomData,
        }
    }
}

impl<T, S> From<Vec<T>> for ArrayOfPrimitive<T, S>
where
    T: for<'a> BinRead<Args<'a> = ()> + for<'a> BinWrite<Args<'a> = ()>,
    T: Num + 'static,
    S: for<'a> BinRead<Args<'a> = ()> + for<'a> BinWrite<Args<'a> = ()>,
    S: TryFrom<usize> + Copy + Clone + Debug + 'static,
    <S as TryFrom<usize>>::Error: Display + Debug + Send + Sync,
    usize: TryFrom<S>,
{
    fn from(value: Vec<T>) -> Self {
        Self {
            value,
            _marker: PhantomData,
        }
    }
}

/// Wrapper to prefix array of single non-primitive type
#[binrw]
#[derive(Clone, PartialEq)]
pub struct ArrayOf<T, S = u32>
where
    // Static bounds due to https://github.com/jam1garner/binrw/issues/199
    T: for<'a> BinRead<Args<'a> = ()> + for<'a> BinWrite<Args<'a> = ()>,
    T: 'static,
    S: for<'a> BinRead<Args<'a> = ()> + for<'a> BinWrite<Args<'a> = ()>,
    S: TryFrom<usize> + Copy + Clone + Debug + 'static,
    <S as TryFrom<usize>>::Error: Display + Debug + Send + Sync,
    usize: TryFrom<S>,
{
    // Temporary variable that holds number of elements
    #[br(temp)]
    #[bw(try_calc(S::try_from(value.len())))]
    count: S,

    #[br(count = count)]
    pub value: Vec<T>,

    _marker: PhantomData<S>,
}

// Custom Diff implementation to only diff internal value
impl<T, S> Diff for ArrayOf<T, S>
where
    T: for<'a> BinRead<Args<'a> = ()> + for<'a> BinWrite<Args<'a> = ()>,
    T: Diff + PartialEq + 'static,
    S: for<'a> BinRead<Args<'a> = ()> + for<'a> BinWrite<Args<'a> = ()>,
    S: TryFrom<usize> + Copy + Clone + Debug + Display + 'static,
    <S as TryFrom<usize>>::Error: Display + Debug + Send + Sync,
    usize: TryFrom<S>,
{
    type Repr = VecDiff<T>;

    fn diff(&self, other: &Self) -> Self::Repr {
        self.value.diff(&other.value)
    }

    fn apply(&mut self, diff: &Self::Repr) {
        self.value.apply(diff)
    }

    fn identity() -> Self {
        Self {
            value: vec![],
            _marker: PhantomData,
        }
    }
}

impl<T, S> Debug for ArrayOf<T, S>
where
    T: for<'a> BinRead<Args<'a> = ()> + for<'a> BinWrite<Args<'a> = ()>,
    T: Debug + 'static,
    S: for<'a> BinRead<Args<'a> = ()> + for<'a> BinWrite<Args<'a> = ()>,
    S: TryFrom<usize> + Copy + Clone + Debug + Display + 'static,
    <S as TryFrom<usize>>::Error: Display + Debug + Send + Sync,
    usize: TryFrom<S>,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:#?}", self.value)
    }
}

impl<T, S> Deref for ArrayOf<T, S>
where
    T: for<'a> BinRead<Args<'a> = ()> + for<'a> BinWrite<Args<'a> = ()>,
    T: 'static,
    S: for<'a> BinRead<Args<'a> = ()> + for<'a> BinWrite<Args<'a> = ()>,
    S: TryFrom<usize> + Copy + Clone + Debug + 'static,
    <S as TryFrom<usize>>::Error: Display + Debug + Send + Sync,
    usize: TryFrom<S>,
{
    type Target = Vec<T>;

    fn deref(&self) -> &Self::Target {
        &self.value
    }
}

impl<T, S> DerefMut for ArrayOf<T, S>
where
    T: for<'a> BinRead<Args<'a> = ()> + for<'a> BinWrite<Args<'a> = ()>,
    T: 'static,
    S: for<'a> BinRead<Args<'a> = ()> + for<'a> BinWrite<Args<'a> = ()>,
    S: TryFrom<usize> + Copy + Clone + Debug + 'static,
    <S as TryFrom<usize>>::Error: Display + Debug + Send + Sync,
    usize: TryFrom<S>,
{
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.value
    }
}

impl<T, S> From<Vec<T>> for ArrayOf<T, S>
where
    T: for<'a> BinRead<Args<'a> = ()> + for<'a> BinWrite<Args<'a> = ()>,
    T: 'static,
    S: for<'a> BinRead<Args<'a> = ()> + for<'a> BinWrite<Args<'a> = ()>,
    S: TryFrom<usize> + Copy + Clone + Debug + 'static,
    <S as TryFrom<usize>>::Error: Display + Debug + Send + Sync,
    usize: TryFrom<S>,
{
    fn from(value: Vec<T>) -> Self {
        Self {
            value,
            _marker: PhantomData,
        }
    }
}
