use std::fmt;

use strum::{Display, EnumString};

#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, EnumString)]
#[repr(u8)]
#[non_exhaustive]
#[strum(ascii_case_insensitive)]
pub enum Register {
    // general purpose
    Rg0 = 0,
    Rg1 = 1,
    Rg2 = 2,
    Rg3 = 3,
    Rg4 = 4,
    Rg5 = 5,
    Rg6 = 6,
    Rg7 = 7,
    Rg8 = 8,
    Rg9 = 9,
    Rga = 10,
    Rgb = 11,
    Rgc = 12,
    Rgd = 13,
    Rge = 14,
    Rgf = 15,

    Acc = 16, // temp/scratch register

    // stack registers
    Spr = 17,
    Bpr = 18,

    Idr = 20, // points to interrupt descriptor table
    Mmr = 21, // points to page tables

    // system - read only
    Zero = 22, // always reads as zero.
    Sts = 23,  // status register for flags
    Cir = 24,  // current instruction register
    Pcx = 25,  // program counter

    #[strum(disabled, to_string = "Ret")]
    Ret = 19, // used under the hood by call/ret instructions but not used directly in asm.

    #[default]
    #[strum(disabled, to_string = "Null")] // don't include this in FromStr impl
    Null = 26,
}

impl std::fmt::Display for Register {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}", self)
    }
}

impl Register {
    /// Total number of registers, not counting Null as it's not a real register.
    /// reading or writing to Null is undefined behaviour!!
    pub const COUNT: u8 = Self::Null as u8;

    /// # Safety
    /// the caller must ensure that the index is always within the range 0..COUNT, failing to do so will result in undefined behaviour.
    #[must_use]
    #[inline]
    pub unsafe fn from_u8_unchecked(idx: u8) -> Self {
        debug_assert!(
            idx < Self::COUNT,
            "Tried to access a null register using the unchecked method. undefined behaviour alert!"
        );
        unsafe { std::mem::transmute(idx) }
    }

    #[inline]
    pub fn from_u8(idx: u8) -> Result<Self, ()> {
        if idx >= Self::COUNT {
            return Err(());
        }

        Ok(unsafe { Self::from_u8_unchecked(idx) })
    }
}
