use std::ops::{Deref, DerefMut};

use crate::isa::instructions::{Instruction, Opcode};
use crate::isa::register::Register;

impl From<u32> for Instruction {
    #[inline]
    fn from(word: u32) -> Self {
        Self(word)
    }
}

impl Deref for Instruction {
    type Target = u32;

    #[inline]
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for Instruction {
    #[inline]
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl Instruction {
    // ---- R Type Builders ----

    #[inline]
    fn build_r(
        opcode: Opcode,
        src1: Register,
        dest: Register,
        src2: Register,
        misc: Register,
        shamt: u8,
    ) -> Self {
        Self(
            ((opcode as u32) << 26)
                | ((src1 as u32) << 21)
                | ((dest as u32) << 16)
                | ((src2 as u32) << 11)
                | ((misc as u32) << 6)
                | ((shamt as u32) & 0x3F),
        )
    }

    #[inline]
    fn build_i(opcode: Opcode, src: Register, dest: Register, imm: u16) -> Self {
        Self(((opcode as u32) << 26) | ((src as u32) << 21) | ((dest as u32) << 16) | (imm as u32))
    }

    #[inline]
    fn build_noarg(opcode: Opcode) -> Self {
        Self((opcode as u32) << 26)
    }

    /// No operation.
    pub fn nop() -> Self {
        Self::build_noarg(Opcode::Nop)
    }

    // ---- Move ----

    /// Move a value from `src` to `dest`.
    pub fn mov(src: Register, dest: Register) -> Self {
        Self::build_r(Opcode::Mov, src, dest, Register::Zero, Register::Zero, 0)
    }

    /// Conditional move: if `cmp` is true then move `src` to `dest`.
    pub fn cmov(src: Register, dest: Register, cmp: Register) -> Self {
        Self::build_r(Opcode::CMov, src, dest, cmp, Register::Zero, 0)
    }

    // ---- Load ----

    /// Load byte from memory at `src + offset` into `dest`.
    pub fn ldb(src: Register, dest: Register, offset: u16) -> Self {
        Self::build_i(Opcode::Ldb, src, dest, offset)
    }

    /// Load signed byte (sign‑extended) from memory at `src + offset` into `dest`.
    pub fn ldbs(src: Register, dest: Register, offset: u16) -> Self {
        Self::build_i(Opcode::Ldbs, src, dest, offset)
    }

    /// Load halfword from memory at `src + offset` into `dest`.
    pub fn ldh(src: Register, dest: Register, offset: u16) -> Self {
        Self::build_i(Opcode::Ldh, src, dest, offset)
    }

    /// Load signed halfword (sign‑extended) from memory at `src + offset` into `dest`.
    pub fn ldhs(src: Register, dest: Register, offset: u16) -> Self {
        Self::build_i(Opcode::Ldhs, src, dest, offset)
    }

    /// Load word from memory at `src + offset` into `dest`.
    pub fn ldw(src: Register, dest: Register, offset: u16) -> Self {
        Self::build_i(Opcode::Ldw, src, dest, offset)
    }

    // ---- Store ----

    /// Store byte from `src` to memory at `dest + offset`.
    pub fn stb(src: Register, dest: Register, offset: u16) -> Self {
        Self::build_i(Opcode::Stb, src, dest, offset)
    }

    /// Store halfword from `src` to memory at `dest + offset`.
    pub fn sth(src: Register, dest: Register, offset: u16) -> Self {
        Self::build_i(Opcode::Sth, src, dest, offset)
    }

    /// Store word from `src` to memory at `dest + offset`.
    pub fn stw(src: Register, dest: Register, offset: u16) -> Self {
        Self::build_i(Opcode::Stw, src, dest, offset)
    }

    // ---- Load Immediate ----

    /// Load lower 16 bits of an immediate into `dest`.
    pub fn lli(dest: Register, imm: u16) -> Self {
        Self::build_i(Opcode::Lli, Register::Zero, dest, imm)
    }

    /// Load upper 16 bits of an immediate into `dest`.
    pub fn lui(dest: Register, imm: u16) -> Self {
        Self::build_i(Opcode::Lui, Register::Zero, dest, imm)
    }

    /// Load a full 32‑bit immediate using an `lli` + `lui` pair.
    pub fn load_imm32(dest: Register, imm: u32) -> [Self; 2] {
        [
            Self::lli(dest, imm as u16),
            Self::lui(dest, (imm >> 16) as u16),
        ]
    }

    // ---- Comparison ----

    /// Set `dest` to 1 if `sr1 == sr2`, else 0.
    pub fn ieq(sr1: Register, sr2: Register, dest: Register) -> Self {
        Self::build_r(Opcode::Ieq, sr1, dest, sr2, Register::Zero, 0)
    }

    /// Set `dest` to 1 if `sr1 != sr2`, else 0.
    pub fn ine(sr1: Register, sr2: Register, dest: Register) -> Self {
        Self::build_r(Opcode::Ine, sr1, dest, sr2, Register::Zero, 0)
    }

    /// Set `dest` to 1 if `sr1 < sr2`, else 0.
    pub fn ilt(sr1: Register, sr2: Register, dest: Register) -> Self {
        Self::build_r(Opcode::Ilt, sr1, dest, sr2, Register::Zero, 0)
    }

    /// Set `dest` to 1 if `sr1 <= sr2`, else 0.
    pub fn ile(sr1: Register, sr2: Register, dest: Register) -> Self {
        Self::build_r(Opcode::Ile, sr1, dest, sr2, Register::Zero, 0)
    }

    /// Set `dest` to 1 if `sr1 > sr2`, else 0.
    pub fn igt(sr1: Register, sr2: Register, dest: Register) -> Self {
        Self::build_r(Opcode::Igt, sr1, dest, sr2, Register::Zero, 0)
    }

    /// Set `dest` to 1 if `sr1 >= sr2`, else 0.
    pub fn ige(sr1: Register, sr2: Register, dest: Register) -> Self {
        Self::build_r(Opcode::Ige, sr1, dest, sr2, Register::Zero, 0)
    }

    // ---- Jump ----

    /// Unconditional jump to address in `addr` with offset.
    pub fn jmp(addr: Register, offset: u16) -> Self {
        Self::build_i(Opcode::Jmp, Register::Zero, addr, offset)
    }

    /// Jump if zero flag set (`jez`).
    pub fn jez(cmp: Register, addr: Register, offset: u16) -> Self {
        Self::build_i(Opcode::Jez, cmp, addr, offset)
    }

    /// Jump if zero flag clear (`jnz`).
    pub fn jnz(cmp: Register, addr: Register, offset: u16) -> Self {
        Self::build_i(Opcode::Jnz, cmp, addr, offset)
    }

    /// Jump if carry set (`jic`).
    pub fn jic(addr: Register, offset: u16) -> Self {
        Self::build_i(Opcode::Jic, Register::Zero, addr, offset)
    }

    /// Jump if carry clear (`jnc`).
    pub fn jnc(addr: Register, offset: u16) -> Self {
        Self::build_i(Opcode::Jnc, Register::Zero, addr, offset)
    }

    // ---- Bitwise ----

    /// Bitwise AND of `sr1` and `sr2`, result in `dest`.
    pub fn and(sr1: Register, sr2: Register, dest: Register) -> Self {
        Self::build_r(Opcode::And, sr1, dest, sr2, Register::Zero, 0)
    }

    /// Bitwise NAND of `sr1` and `sr2`, result in `dest`.
    pub fn nand(sr1: Register, sr2: Register, dest: Register) -> Self {
        Self::build_r(Opcode::Nand, sr1, dest, sr2, Register::Zero, 0)
    }

    /// Bitwise OR of `sr1` and `sr2`, result in `dest`.
    pub fn or(sr1: Register, sr2: Register, dest: Register) -> Self {
        Self::build_r(Opcode::Or, sr1, dest, sr2, Register::Zero, 0)
    }

    /// Bitwise NOR of `sr1` and `sr2`, result in `dest`.
    pub fn nor(sr1: Register, sr2: Register, dest: Register) -> Self {
        Self::build_r(Opcode::Nor, sr1, dest, sr2, Register::Zero, 0)
    }

    /// Bitwise XOR of `sr1` and `sr2`, result in `dest`.
    pub fn xor(sr1: Register, sr2: Register, dest: Register) -> Self {
        Self::build_r(Opcode::Xor, sr1, dest, sr2, Register::Zero, 0)
    }

    /// Bitwise XNOR of `sr1` and `sr2`, result in `dest`.
    pub fn xnor(sr1: Register, sr2: Register, dest: Register) -> Self {
        Self::build_r(Opcode::Xnor, sr1, dest, sr2, Register::Zero, 0)
    }

    /// Bitwise NOT of `src`, result in `dest`.
    pub fn not(src: Register, dest: Register) -> Self {
        Self::build_r(Opcode::Not, src, dest, Register::Zero, Register::Zero, 0)
    }

    // ---- Arithmetic ----

    /// Add `sr1` and `sr2`, store result in `dest`.
    pub fn add(sr1: Register, sr2: Register, dest: Register) -> Self {
        Self::build_r(Opcode::Add, sr1, dest, sr2, Register::Zero, 0)
    }

    /// Subtract `sr2` from `sr1`, store result in `dest`.
    pub fn sub(sr1: Register, sr2: Register, dest: Register) -> Self {
        Self::build_r(Opcode::Sub, sr1, dest, sr2, Register::Zero, 0)
    }

    /// Shift left logical: shift `src` by (`rshamt` + `ishamt`) bits into `dest`.
    pub fn shl(src: Register, rshamt: Register, dest: Register, ishamt: u8) -> Self {
        debug_assert!(ishamt < 64);
        Self::build_r(Opcode::Shl, src, dest, rshamt, Register::Zero, ishamt)
    }

    /// Shift right logical: shift `src` by (`rshamt` + `ishamt`) bits into `dest`.
    pub fn shr(src: Register, rshamt: Register, dest: Register, ishamt: u8) -> Self {
        debug_assert!(ishamt < 64);
        Self::build_r(Opcode::Shr, src, dest, rshamt, Register::Zero, ishamt)
    }

    /// Add immediate `imm` to `sr1`, store result in `dest`.
    pub fn addi(sr1: Register, dest: Register, imm: u16) -> Self {
        Self::build_i(Opcode::Addi, sr1, dest, imm)
    }

    /// Subtract immediate `imm` from `sr1`, store result in `dest`.
    pub fn subi(sr1: Register, dest: Register, imm: u16) -> Self {
        Self::build_i(Opcode::Subi, sr1, dest, imm)
    }

    // ---- Util -----
    pub fn call(addr: Register, offset: u16) -> Self {
        Self::build_i(Opcode::Call, Register::Zero, addr, offset)
    }

    pub fn ret() -> Self {
        Self::build_noarg(Opcode::Ret)
    }

    pub fn push(reg: Register) -> Self {
        Self::build_r(
            Opcode::Push,
            reg,
            Register::Zero,
            Register::Zero,
            Register::Zero,
            0,
        )
    }

    pub fn pop(reg: Register) -> Self {
        Self::build_r(
            Opcode::Pop,
            Register::Zero,
            reg,
            Register::Zero,
            Register::Zero,
            0,
        )
    }

    // ---- System ----

    /// Trigger an interrupt with the given 6‑bit code.
    pub fn int(code: u8) -> Self {
        debug_assert!(code < 64);
        Self::build_i(Opcode::Int, Register::Zero, Register::Zero, code as u16)
    }

    /// Return from interrupt.
    pub fn irt() -> Self {
        Self::build_noarg(Opcode::IRet)
    }

    /// Halt execution.
    pub fn hlt() -> Self {
        Self::build_r(
            Opcode::Hlt,
            Register::Zero,
            Register::Zero,
            Register::Zero,
            Register::Zero,
            0,
        )
    }

    /// Raw data (not an instruction)
    pub fn data(value: u32) -> Self {
        Self(value)
    }
}
