use std::cmp::PartialEq;
use std::fmt;
use std::ops::{
    Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not, Shl, Shr,
    Sub, SubAssign,
};

use bytemuck::TransparentWrapper;

pub trait U16Equivalent: Copy + Clone {
    fn to_u16(self) -> u16;
    fn from_u16(value: u16) -> Self;
    fn to_u8(self) -> Reg8 {
        Reg8(self.to_u16() as u8)
    }
    fn sext(self) -> RegI16 {
        RegI16(self.to_u16() as i8 as i16)
    }
}

#[derive(Copy, Clone, TransparentWrapper)]
#[repr(transparent)]
pub struct Reg8(pub u8);

impl PartialOrd for Reg8 {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Reg8 {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.0.cmp(&other.0)
    }
}

impl From<u8> for Reg8 {
    fn from(value: u8) -> Self {
        Self(value)
    }
}

impl U16Equivalent for Reg8 {
    fn to_u16(self) -> u16 {
        self.0 as u16
    }
    fn from_u16(value: u16) -> Self {
        Self(value as u8)
    }
}

#[derive(Copy, Clone, TransparentWrapper)]
#[repr(transparent)]
pub struct RegI16(pub i16);

impl U16Equivalent for RegI16 {
    fn to_u16(self) -> u16 {
        self.0 as u16
    }
    fn from_u16(value: u16) -> Self {
        Self(value as i16)
    }
}

#[derive(Copy, Clone, TransparentWrapper)]
#[repr(transparent)]
pub struct Reg16(pub u16);

impl U16Equivalent for Reg16 {
    fn to_u16(self) -> u16 {
        self.0
    }
    fn from_u16(value: u16) -> Self {
        Self(value)
    }
}

impl U16Equivalent for u16 {
    fn to_u16(self) -> u16 {
        self
    }
    fn from_u16(value: u16) -> Self {
        value
    }
}

impl U16Equivalent for u8 {
    fn to_u16(self) -> u16 {
        self as u16
    }
    fn from_u16(value: u16) -> Self {
        value as u8
    }
}

impl U16Equivalent for i32 {
    fn to_u16(self) -> u16 {
        self as u16
    }
    fn from_u16(value: u16) -> Self {
        value as i32
    }
}

macro_rules! derive_ops {
    ($type:ident) => {
        impl<T> Add<T> for $type
        where
            T: U16Equivalent,
        {
            type Output = $type;
            fn add(self, other: T) -> Self::Output {
                $type::from_u16(self.to_u16().wrapping_add(other.to_u16()))
            }
        }
        impl<T> AddAssign<T> for $type
        where
            T: U16Equivalent,
        {
            fn add_assign(&mut self, other: T) {
                *self = $type::from_u16(self.to_u16().wrapping_add(other.to_u16()))
            }
        }
        impl<T> Sub<T> for $type
        where
            T: U16Equivalent,
        {
            type Output = $type;
            fn sub(self, other: T) -> Self::Output {
                $type::from_u16(self.to_u16().wrapping_sub(other.to_u16()))
            }
        }
        impl<T> SubAssign<T> for $type
        where
            T: U16Equivalent,
        {
            fn sub_assign(&mut self, other: T) {
                *self = $type::from_u16(self.to_u16().wrapping_sub(other.to_u16()))
            }
        }
        impl<T> Shl<T> for $type
        where
            T: U16Equivalent,
        {
            type Output = Reg16;
            fn shl(self, other: T) -> Self::Output {
                Reg16(self.to_u16() << other.to_u16())
            }
        }
        impl<T> Shr<T> for $type
        where
            T: U16Equivalent,
        {
            type Output = $type;
            fn shr(self, other: T) -> Self::Output {
                $type::from_u16(self.to_u16() >> other.to_u16())
            }
        }
        impl<T> BitAnd<T> for $type
        where
            T: U16Equivalent,
        {
            type Output = $type;
            fn bitand(self, other: T) -> Self::Output {
                $type::from_u16(self.to_u16() & other.to_u16())
            }
        }
        impl<T> BitAndAssign<T> for $type
        where
            T: U16Equivalent,
        {
            fn bitand_assign(&mut self, other: T) {
                *self = $type::from_u16(self.to_u16() & other.to_u16())
            }
        }
        impl<T> BitOr<T> for $type
        where
            T: U16Equivalent,
        {
            type Output = $type;
            fn bitor(self, other: T) -> Self::Output {
                $type::from_u16(self.to_u16() | other.to_u16())
            }
        }
        impl<T> BitOrAssign<T> for $type
        where
            T: U16Equivalent,
        {
            fn bitor_assign(&mut self, other: T) {
                *self = $type::from_u16(self.to_u16() | other.to_u16())
            }
        }
        impl<T> BitXor<T> for $type
        where
            T: U16Equivalent,
        {
            type Output = $type;
            fn bitxor(self, other: T) -> Self::Output {
                $type::from_u16(self.to_u16() ^ other.to_u16())
            }
        }
        impl<T> BitXorAssign<T> for $type
        where
            T: U16Equivalent,
        {
            fn bitxor_assign(&mut self, other: T) {
                *self = $type::from_u16(self.to_u16() ^ other.to_u16())
            }
        }
        impl<T> PartialEq<T> for $type
        where
            T: U16Equivalent,
        {
            fn eq(&self, other: &T) -> bool {
                self.to_u16() == other.to_u16()
            }
        }
        impl Eq for $type {}
        impl Not for $type {
            type Output = $type;
            fn not(self) -> Self::Output {
                $type::from_u16(!self.to_u16())
            }
        }
        impl fmt::Display for $type {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                self.0.fmt(f)
            }
        }
        impl fmt::UpperHex for $type {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                self.0.fmt(f)
            }
        }
    };
}

derive_ops!(Reg8);
derive_ops!(Reg16);
derive_ops!(RegI16);
