use crate::{instructions::encode::Encode, prelude::*};

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Interrupt {
    Software(u8),
    Breakpoint,
    HardFault,
}

pub type Address = u32;

impl Interrupt {
    // someone tell clippy to stfu.
    #[allow(clippy::must_use_candidate)]
    pub const fn as_u8(self) -> u8 {
        match self {
            Self::Breakpoint => 0,
            Self::HardFault => 1,
            Self::Software(code) => code,
        }
    }
}

// TODO: This should be TryFrom.
impl From<u8> for Interrupt {
    #[allow(unreachable_code)]
    fn from(code: u8) -> Self {
        match code {
            0 => Self::Breakpoint,
            1 => Self::HardFault,
            _ => Self::Software(code),
        }
    }
}

/// Whether an [`Instruction`] is an I-type or R-type instruction.
#[non_exhaustive]
pub enum InstructionType {
    Register,
    Immediate,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum Register {
    // general purpose registers
    Rg0,
    Rg1,
    Rg2,
    Rg3,
    Rg4,
    Rg5,
    Rg6,
    Rg7,
    Rg8,
    Rg9,
    Rga,
    Rgb,
    Rgc,
    Rgd,
    Rge,
    Rgf,

    // special purpose registers
    Acc,
    Spr,
    Bpr,
    Ret,
    Idr,
    Mmr,
    Zero,

    #[default]
    Null, // Invalid - Triggers a fault if accessed

    // system registers - can't be written to by instructions.
    Mar,
    Mdr,
    Sts,
    Cir,
    Pcx,
}

impl Register {
    // this is here so clippy shuts up about the must_use tag.
    #[allow(clippy::must_use_candidate)]
    pub fn general() -> Vec<Self> {
        vec![
            Self::Rg0,
            Self::Rg1,
            Self::Rg2,
            Self::Rg3,
            Self::Rg4,
            Self::Rg5,
            Self::Rg6,
            Self::Rg7,
            Self::Rg8,
            Self::Rg9,
            Self::Rga,
            Self::Rgb,
            Self::Rgc,
            Self::Rgd,
            Self::Rge,
            Self::Rgf,
        ]
    }
}

impl TryFrom<u8> for Register {
    type Error = RegisterParseError;

    fn try_from(idx: u8) -> Result<Self, Self::Error> {
        if idx > 0x1C {
            return Err(RegisterParseError::InvalidIndex(idx));
        }

        Ok(match idx {
            // System registers are not indexable in the reg file so they cannot be
            // modified by instructions.
            0x0 => Self::Rg0,
            0x1 => Self::Rg1,
            0x2 => Self::Rg2,
            0x3 => Self::Rg3,
            0x4 => Self::Rg4,
            0x5 => Self::Rg5,
            0x6 => Self::Rg6,
            0x7 => Self::Rg7,
            0x8 => Self::Rg8,
            0x9 => Self::Rg9,
            0xA => Self::Rga,
            0xB => Self::Rgb,
            0xC => Self::Rgc,
            0xD => Self::Rgd,
            0xE => Self::Rge,
            0xF => Self::Rgf,
            0x10 => Self::Acc,
            0x11 => Self::Spr,
            0x12 => Self::Bpr,
            0x13 => Self::Ret,
            0x14 => Self::Idr,
            0x15 => Self::Mmr,
            0x16 => Self::Zero,
            0x17 => Self::Null,
            0x18 => Self::Mar,
            0x19 => Self::Mdr,
            0x1A => Self::Sts,
            0x1B => Self::Cir,
            0x1C => Self::Pcx,
            _ => unreachable!("This is already checked for in top `if` branch."),
        })
    }
}

impl TryFrom<&str> for Register {
    type Error = RegisterParseError;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        match value.to_lowercase().as_str() {
            "rg0" => Ok(Self::Rg0),
            "rg1" => Ok(Self::Rg1),
            "rg2" => Ok(Self::Rg2),
            "rg3" => Ok(Self::Rg3),
            "rg4" => Ok(Self::Rg4),
            "rg5" => Ok(Self::Rg5),
            "rg6" => Ok(Self::Rg6),
            "rg7" => Ok(Self::Rg7),
            "rg8" => Ok(Self::Rg8),
            "rg9" => Ok(Self::Rg9),
            "rga" => Ok(Self::Rga),
            "rgb" => Ok(Self::Rgb),
            "rgc" => Ok(Self::Rgc),
            "rgd" => Ok(Self::Rgd),
            "rge" => Ok(Self::Rge),
            "rgf" => Ok(Self::Rgf),
            "acc" => Ok(Self::Acc),
            "spr" => Ok(Self::Spr),
            "bpr" => Ok(Self::Bpr),
            "ret" => Ok(Self::Ret),
            "idr" => Ok(Self::Idr),
            "mmr" => Ok(Self::Mmr),
            "zero" => Ok(Self::Zero),
            "null" => Ok(Self::Null),
            "pcx" => Ok(Self::Pcx),
            _ => Err(RegisterParseError::InvalidName(value.to_string())),
        }
    }
}

impl std::fmt::Display for Register {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Rg0 => write!(f, "rg0"),
            Self::Rg1 => write!(f, "rg1"),
            Self::Rg2 => write!(f, "rg2"),
            Self::Rg3 => write!(f, "rg3"),
            Self::Rg4 => write!(f, "rg4"),
            Self::Rg5 => write!(f, "rg5"),
            Self::Rg6 => write!(f, "rg6"),
            Self::Rg7 => write!(f, "rg7"),
            Self::Rg8 => write!(f, "rg8"),
            Self::Rg9 => write!(f, "rg9"),
            Self::Rga => write!(f, "rga"),
            Self::Rgb => write!(f, "rgb"),
            Self::Rgc => write!(f, "rgc"),
            Self::Rgd => write!(f, "rgd"),
            Self::Rge => write!(f, "rge"),
            Self::Rgf => write!(f, "rgf"),
            Self::Acc => write!(f, "acc"),
            Self::Spr => write!(f, "spr"),
            Self::Bpr => write!(f, "bpr"),
            Self::Ret => write!(f, "ret"),
            Self::Idr => write!(f, "idr"),
            Self::Mmr => write!(f, "mmr"),
            Self::Zero => write!(f, "zero"),
            Self::Null => write!(f, "null"),
            Self::Mar => write!(f, "mar"),
            Self::Mdr => write!(f, "mdr"),
            Self::Sts => write!(f, "sts"),
            Self::Cir => write!(f, "cir"),
            Self::Pcx => write!(f, "pcx"),
        }
    }
}

#[derive(Debug, Clone, Copy, Eq)]
#[repr(u8)]
#[non_exhaustive]
/// A list of all current instructions in the DSA.
///
/// # Note
///
/// This is subject to change and is therefore marked non exhaustive.
pub enum Instruction {
    // No-op
    Nop = 0x0,

    // Data transfer instructions
    Mov(args::RTypeArgs) = 0x1,
    MovSigned(args::RTypeArgs) = 0x2,

    LoadByte(args::ITypeArgs) = 0x3,
    LoadByteSigned(args::ITypeArgs) = 0x4,
    LoadHalfword(args::ITypeArgs) = 0x5,
    LoadHalfwordSigned(args::ITypeArgs) = 0x6,
    LoadWord(args::ITypeArgs) = 0x7,

    StoreByte(args::ITypeArgs) = 0x8,
    StoreHalfword(args::ITypeArgs) = 0x9,
    StoreWord(args::ITypeArgs) = 0xA,

    LoadLowerImmediate(args::ITypeArgs) = 0xB,
    LoadUpperImmediate(args::ITypeArgs) = 0xC,

    // Jump Instructions
    Jump(args::ITypeArgs) = 0xD,
    JumpEq(args::ITypeArgs) = 0xE,
    JumpNeq(args::ITypeArgs) = 0xF,
    JumpGt(args::ITypeArgs) = 0x10,
    JumpGe(args::ITypeArgs) = 0x11,
    JumpLt(args::ITypeArgs) = 0x12,
    JumpLe(args::ITypeArgs) = 0x13,

    // Comparison
    Compare(args::RTypeArgs) = 0x14,

    // Arithmetic
    Add(args::RTypeArgs) = 0x19,
    Sub(args::RTypeArgs) = 0x1A,
    Increment(args::RTypeArgs) = 0x15,
    Decrement(args::RTypeArgs) = 0x16,
    ShiftLeft(args::RTypeArgs) = 0x17,
    ShiftRight(args::RTypeArgs) = 0x18,

    // Logical
    And(args::RTypeArgs) = 0x1B,
    Or(args::RTypeArgs) = 0x1C,
    Not(args::RTypeArgs) = 0x1D,
    Xor(args::RTypeArgs) = 0x1E,
    Nand(args::RTypeArgs) = 0x1F,
    Nor(args::RTypeArgs) = 0x20,
    Xnor(args::RTypeArgs) = 0x21,

    // Misc
    Interrupt(Interrupt) = 0x22,
    IntReturn = 0x23,
    Halt = 0x24,

    // Immediate Arithmetic
    AddImmediate(args::ITypeArgs) = 0x25,
    SubImmediate(args::ITypeArgs) = 0x26,

    // Fake Instructions
    Data(u32) = 0x3E,
    Segment(u32) = 0x3F,
}

impl PartialEq for Instruction {
    fn eq(&self, other: &Self) -> bool {
        self.encode() == other.encode()
    }
}

impl Instruction {
    /// Returns the opcode of an instruction.
    ///
    /// # Notes
    ///
    /// The top two bits shall be 0, opcodes are 6-bits long.
    #[must_use]
    pub const fn opcode(&self) -> u8 {
        unsafe { *std::ptr::from_ref::<Self>(self).cast::<u8>() }
    }

    /// Encodes an [`Instruction`] into a word.
    #[must_use]
    pub fn encode(&self) -> u32 {
        Encode::encode(*self, self.opcode())
    }

    /// Decodes an [`Instruction`] from a word `data`.
    pub fn decode(data: u32) -> Result<Self, InstructionDecodeError> {
        data.try_into()
    }

    /// Returns the mnemonic for a given [`Instruction`].
    #[must_use]
    pub const fn mnemonic(self) -> &'static str {
        match self {
            Self::Add(_) => "add",
            Self::Sub(_) => "sub",
            Self::Increment(_) => "inc",
            Self::Decrement(_) => "dec",
            Self::Compare(_) => "cmp",
            Self::Halt => "hlt",
            Self::And(_) => "and",
            Self::IntReturn => "intr",
            Self::Interrupt(_) => "int",
            Self::Jump(_) => "jmp",
            Self::JumpEq(_) => "jeq",
            Self::JumpNeq(_) => "jneq",
            Self::JumpGt(_) => "jgt",
            Self::JumpGe(_) => "jge",
            Self::JumpLt(_) => "jlt",
            Self::JumpLe(_) => "jle",
            Self::Mov(_) => "mov",
            Self::MovSigned(_) => "movs",
            Self::LoadByte(_) => "ldb",
            Self::LoadByteSigned(_) => "ldbs",
            Self::LoadHalfword(_) => "ldh",
            Self::LoadHalfwordSigned(_) => "ldhs",
            Self::LoadWord(_) => "ldw",
            Self::StoreByte(_) => "stb",
            Self::StoreHalfword(_) => "sth",
            Self::StoreWord(_) => "stw",
            Self::LoadLowerImmediate(_) => "lli",
            Self::LoadUpperImmediate(_) => "lui",
            Self::ShiftLeft(_) => "shl",
            Self::ShiftRight(_) => "shr",
            Self::Or(_) => "or",
            Self::Not(_) => "not",
            Self::Nop => "nop",
            Self::Xor(_) => "xor",
            Self::Nand(_) => "nand",
            Self::Nor(_) => "nor",
            Self::Xnor(_) => "xnor",
            Self::Data(_) => "data",
            Self::AddImmediate(_) => "addi",
            Self::SubImmediate(_) => "subi",
            Self::Segment(_) => "[SEGMENT]",
        }
    }

    /// Returns the [`InstructionType`] for the given [`Instruction`].
    #[must_use]
    pub const fn instruction_type(self) -> InstructionType {
        Self::instruction_type_from_opcode(self.opcode())
    }

    /// Returns the [`InstructionType`] for the given `opcode`.
    #[must_use]
    pub const fn instruction_type_from_opcode(opcode: u8) -> InstructionType {
        match opcode {
            0x3..=0x13 => InstructionType::Immediate,
            _ => InstructionType::Register,
        }
    }
}

// Instruction decoding logic goes here.
impl std::fmt::Display for Instruction {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.mnemonic())?;

        match self {
            Self::Mov(args) | Self::MovSigned(args) => {
                write!(f, " {}, {}", args.sr1, args.dr)
            }
            Self::LoadByte(args)
            | Self::LoadByteSigned(args)
            | Self::LoadHalfword(args)
            | Self::LoadHalfwordSigned(args)
            | Self::LoadWord(args)
            | Self::StoreByte(args)
            | Self::StoreHalfword(args)
            | Self::StoreWord(args) => {
                write!(
                    f,
                    " {}({:x}/{}), {}",
                    args.r1, args.immediate, args.immediate, args.r2
                )
            }
            Self::Jump(args)
            | Self::JumpEq(args)
            | Self::JumpNeq(args)
            | Self::JumpGt(args)
            | Self::JumpGe(args)
            | Self::JumpLt(args)
            | Self::JumpLe(args) => {
                write!(f, " 0x{:x}/{}({})", args.immediate, args.immediate, args.r1)
            }
            Self::LoadLowerImmediate(args) | Self::LoadUpperImmediate(args) => {
                write!(f, " 0x{:x}, {}, {}", args.immediate, args.r1, args.r2)
            }
            Self::Compare(args) | Self::Not(args) => {
                write!(f, " {}, {}", args.sr1, args.sr2)
            }

            Self::Add(args)
            | Self::Sub(args)
            | Self::Xor(args)
            | Self::Nand(args)
            | Self::Nor(args)
            | Self::Xnor(args)
            | Self::ShiftLeft(args)
            | Self::ShiftRight(args)
            | Self::And(args)
            | Self::Or(args) => {
                write!(f, " {}, {}, {}", args.sr1, args.sr2, args.dr)
            }

            Self::AddImmediate(args) | Self::SubImmediate(args) => {
                write!(f, " {}, {}, {}", args.r1, args.immediate, args.r2)
            }

            Self::Increment(a) | Self::Decrement(a) => write!(f, " {}", a.sr1),
            Self::Interrupt(a) => write!(f, " {}", a.as_u8()),
            Self::Data(a) => write!(f, " {a}"),
            Self::Segment(x) => write!(f, " [SEGMENT {x}]"),
            _ => Ok(()),
        }
    }
}

impl TryFrom<u32> for Instruction {
    type Error = InstructionDecodeError;

    /// Instruction decoding can be using using [`Instruction::try_from`]
    fn try_from(data: u32) -> Result<Self, Self::Error> {
        // Pull the opcode out so we can parse it correctly.
        let opcode = ((data >> 26) & 0x3F) as u8;

        match opcode {
            0x0 => Ok(Self::Nop),
            0x1 => Ok(Self::Mov(RTypeArgs::try_from(data)?)),
            0x2 => Ok(Self::MovSigned(RTypeArgs::try_from(data)?)),
            0x3 => Ok(Self::LoadByte(ITypeArgs::try_from(data)?)),
            0x4 => Ok(Self::LoadByteSigned(ITypeArgs::try_from(data)?)),
            0x5 => Ok(Self::LoadHalfword(ITypeArgs::try_from(data)?)),
            0x6 => Ok(Self::LoadHalfwordSigned(ITypeArgs::try_from(data)?)),
            0x7 => Ok(Self::LoadWord(ITypeArgs::try_from(data)?)),
            0x8 => Ok(Self::StoreByte(ITypeArgs::try_from(data)?)),
            0x9 => Ok(Self::StoreHalfword(ITypeArgs::try_from(data)?)),
            0xA => Ok(Self::StoreWord(ITypeArgs::try_from(data)?)),
            0xB => Ok(Self::LoadLowerImmediate(ITypeArgs::try_from(data)?)),
            0xC => Ok(Self::LoadUpperImmediate(ITypeArgs::try_from(data)?)),
            0xD => Ok(Self::Jump(ITypeArgs::try_from(data)?)),
            0xE => Ok(Self::JumpEq(ITypeArgs::try_from(data)?)),
            0xF => Ok(Self::JumpNeq(ITypeArgs::try_from(data)?)),
            0x10 => Ok(Self::JumpGt(ITypeArgs::try_from(data)?)),
            0x11 => Ok(Self::JumpGe(ITypeArgs::try_from(data)?)),
            0x12 => Ok(Self::JumpLt(ITypeArgs::try_from(data)?)),
            0x13 => Ok(Self::JumpLe(ITypeArgs::try_from(data)?)),
            0x14 => Ok(Self::Compare(RTypeArgs::try_from(data)?)),
            0x15 => Ok(Self::Increment(RTypeArgs::try_from(data)?)),
            0x16 => Ok(Self::Decrement(RTypeArgs::try_from(data)?)),
            0x17 => Ok(Self::ShiftLeft(RTypeArgs::try_from(data)?)),
            0x18 => Ok(Self::ShiftRight(RTypeArgs::try_from(data)?)),
            0x19 => Ok(Self::Add(RTypeArgs::try_from(data)?)),
            0x1A => Ok(Self::Sub(RTypeArgs::try_from(data)?)),
            0x1B => Ok(Self::And(RTypeArgs::try_from(data)?)),
            0x1C => Ok(Self::Or(RTypeArgs::try_from(data)?)),
            0x1D => Ok(Self::Not(RTypeArgs::try_from(data)?)),
            0x1E => Ok(Self::Xor(RTypeArgs::try_from(data)?)),
            0x1F => Ok(Self::Nand(RTypeArgs::try_from(data)?)),
            0x20 => Ok(Self::Nor(RTypeArgs::try_from(data)?)),
            0x21 => Ok(Self::Xnor(RTypeArgs::try_from(data)?)),
            0x22 => Ok(Self::Interrupt(Interrupt::from((data & 0xFF) as u8))),
            0x23 => Ok(Self::IntReturn),
            0x24 => Ok(Self::Halt),
            0x25 => Ok(Self::AddImmediate(ITypeArgs::try_from(data)?)),
            0x26 => Ok(Self::SubImmediate(ITypeArgs::try_from(data)?)),
            0x3F => Ok(Self::Segment(u32::from(data as u8))),
            _ => Err(InstructionDecodeError::InvalidOpcode(opcode)),
        }
    }
}

pub mod args;
mod encode;
pub mod errors;

#[cfg(test)]
mod tests;
