pub mod decode;
pub mod encode;

#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct Instruction(pub u32);

#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Opcode {
    Nop = 0,

    // move
    Mov = 1,
    CMov = 2,

    // load
    Ldb = 3,
    Ldbs = 4,
    Ldh = 5,
    Ldhs = 6,
    Ldw = 7,

    // store
    Stb = 8,
    Sth = 9,
    Stw = 10,

    // load immediate
    Lli = 11,
    Lui = 12,

    // comparison
    Ieq = 13,
    Ine = 14,
    Ilt = 15,
    Ile = 16,
    Igt = 17,
    Ige = 18,

    // jump
    Jmp = 19,
    Jez = 20,
    Jnz = 21,
    Jic = 22,
    Jnc = 23,

    // bitwise
    And = 24,
    Nand = 25,
    Or = 26,
    Nor = 27,
    Xor = 28,
    Xnor = 29,
    Not = 30,

    // arithmetic
    Add = 31,
    Sub = 32,
    Shl = 33,
    Shr = 34,
    Addi = 35,
    Subi = 36,

    // utility
    Push = 37,
    Pop = 38,
    Call = 39,
    Ret = 40,

    // system
    Int = 41,
    IRet = 42,
    Hlt = 43,
}

impl Opcode {
    #[inline]
    pub fn from_u8(val: u8) -> Option<Self> {
        if val <= Self::Hlt as u8 {
            Some(unsafe { std::mem::transmute(val) })
        } else {
            None
        }
    }
}

use std::fmt;

impl fmt::Debug for Instruction {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let opcode = Opcode::from_u8(self.opcode());

        match opcode {
            None => write!(f, "Instruction(INVALID OPCODE {:#04x})", self.opcode()),

            Some(op @ (Opcode::Nop | Opcode::Hlt | Opcode::Ret | Opcode::IRet)) => {
                write!(f, "{op:?}")
            }

            Some(op @ Opcode::Push) => {
                write!(f, "{op:?} {src1:?}", op = op, src1 = self.src1_checked())
            }

            Some(op @ Opcode::Pop) => {
                write!(f, "{op:?} {dest:?}", op = op, dest = self.dest_checked())
            }

            // I-type: single reg + immediate
            Some(op @ (Opcode::Lli | Opcode::Lui)) => {
                write!(
                    f,
                    "{op:?} {dest:?}, {imm:#06x}",
                    op = op,
                    dest = self.dest_checked(),
                    imm = self.imm16()
                )
            }

            // I-type: no reg, immediate only
            Some(op @ Opcode::Int) => {
                write!(f, "{op:?} {imm:#06x}", op = op, imm = self.imm16())
            }

            // I-type: src + dest + immediate
            Some(
                op @ (Opcode::Ldb
                | Opcode::Ldbs
                | Opcode::Ldh
                | Opcode::Ldhs
                | Opcode::Ldw
                | Opcode::Stb
                | Opcode::Sth
                | Opcode::Stw
                | Opcode::Addi
                | Opcode::Subi),
            ) => {
                write!(
                    f,
                    "{op:?} {src:?}, {dest:?}, {imm:#06x}",
                    op = op,
                    src = self.src1_checked(),
                    dest = self.dest_checked(),
                    imm = self.imm16()
                )
            }

            // I-type: cmp + addr + immediate (conditional jumps)
            Some(op @ (Opcode::Jez | Opcode::Jnz)) => {
                write!(
                    f,
                    "{op:?} {cmp:?}, {addr:?}, {imm:#06x}",
                    op = op,
                    cmp = self.src1_checked(),
                    addr = self.dest_checked(),
                    imm = self.imm16()
                )
            }

            // I-type: addr + immediate only (unconditional/carry jumps)
            Some(op @ (Opcode::Jmp | Opcode::Call | Opcode::Jic | Opcode::Jnc)) => {
                write!(
                    f,
                    "{op:?} {addr:?}, {imm:#06x}",
                    op = op,
                    addr = self.dest_checked(),
                    imm = self.imm16()
                )
            }

            // R-type: src + dest (two reg)
            Some(op @ (Opcode::Mov | Opcode::Not)) => {
                write!(
                    f,
                    "{op:?} {src:?}, {dest:?}",
                    op = op,
                    src = self.src1_checked(),
                    dest = self.dest_checked()
                )
            }

            // R-type: src + dest + cmp (cmov)
            Some(op @ Opcode::CMov) => {
                write!(
                    f,
                    "{op:?} {src:?}, {dest:?}, {cmp:?}",
                    op = op,
                    src = self.src1_checked(),
                    dest = self.dest_checked(),
                    cmp = self.src2_checked()
                )
            }

            // R-type: sr1 + sr2 + dest (arithmetic/bitwise/comparison)
            Some(
                op @ (Opcode::Add
                | Opcode::Sub
                | Opcode::And
                | Opcode::Nand
                | Opcode::Or
                | Opcode::Nor
                | Opcode::Xor
                | Opcode::Xnor
                | Opcode::Ieq
                | Opcode::Ine
                | Opcode::Ilt
                | Opcode::Ile
                | Opcode::Igt
                | Opcode::Ige),
            ) => {
                write!(
                    f,
                    "{op:?} {sr1:?}, {sr2:?}, {dest:?}",
                    op = op,
                    sr1 = self.src1_checked(),
                    sr2 = self.src2_checked(),
                    dest = self.dest_checked()
                )
            }

            // R-type: src + rshamt + dest + ishamt
            Some(op @ (Opcode::Shl | Opcode::Shr)) => {
                write!(
                    f,
                    "{op:?} {src:?}, {dest:?}, {rshamt:?} + {ishamt}",
                    op = op,
                    src = self.src1_checked(),
                    dest = self.dest_checked(),
                    rshamt = self.src2_checked(),
                    ishamt = self.shamt()
                )
            }
        }
    }
}
