use binrw::{binrw, BinRead, BinWrite};
use diff::Diff;
use std::{
    fmt::{Debug, Display},
    mem::size_of,
    ops::{Deref, DerefMut},
};

/// Wrapper to prefix arbitrary types with size of type as u32
#[binrw]
#[derive(Clone, Copy, Default, PartialEq)]
pub struct FieldOf<T>
where
    T: for<'a> BinRead<Args<'a> = ()>,
    T: for<'a> BinWrite<Args<'a> = ()>,
{
    // Temporary variable that must match size of value
    #[br(temp, assert(size == size_of::<T>().try_into().unwrap()))]
    #[bw(try_calc(u32::try_from(size_of::<T>())))]
    size: u32,

    pub value: T,
}

// Custom Diff implementation to only diff internal value
impl<T> Diff for FieldOf<T>
where
    T: for<'a> BinRead<Args<'a> = ()>,
    T: for<'a> BinWrite<Args<'a> = ()>,
    T: Diff + Default,
{
    type Repr = <T as Diff>::Repr;

    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: T::default(),
        }
    }
}

impl<T> Deref for FieldOf<T>
where
    T: for<'a> BinRead<Args<'a> = ()>,
    T: for<'a> BinWrite<Args<'a> = ()>,
{
    type Target = T;

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

impl<T> DerefMut for FieldOf<T>
where
    T: for<'a> BinRead<Args<'a> = ()>,
    T: for<'a> BinWrite<Args<'a> = ()>,
{
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.value
    }
}

impl<T> Debug for FieldOf<T>
where
    T: for<'a> BinRead<Args<'a> = ()>,
    T: for<'a> BinWrite<Args<'a> = ()>,
    T: Debug,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}", self.value)
    }
}

impl<T> Display for FieldOf<T>
where
    T: for<'a> BinRead<Args<'a> = ()>,
    T: for<'a> BinWrite<Args<'a> = ()>,
    T: Display,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.value)
    }
}

impl<T> From<T> for FieldOf<T>
where
    T: for<'a> BinRead<Args<'a> = ()>,
    T: for<'a> BinWrite<Args<'a> = ()>,
{
    fn from(value: T) -> Self {
        Self { value }
    }
}
