use std::{
    cmp::{max, min},
    sync::Arc,
};

use crate::emulator::system::{
    cache::Cache,
    constants::{DISPLAY_ADDRESS, SERIAL_ADDRESS},
    memory::MemoryUnit,
    model::{IODevice, ProcessorError, RegFile},
};

use common::instructions::{Instruction, Interrupt, Register};

pub struct Processor {
    pub memory: Box<dyn MemoryUnit>,
    pub registers: RegFile,
    pub halted: bool,
    pub io_devices: Vec<Arc<dyn IODevice>>,

    pub void: u32,
    pub cache: Cache,

    pub serial_buff: Vec<u8>,
}

impl Processor {
    #[must_use]
    pub fn new(memory: Box<dyn MemoryUnit>, io_devices: Vec<Arc<dyn IODevice>>) -> Self {
        Self {
            memory,
            registers: RegFile::default(),
            halted: false,
            io_devices,
            void: 0,
            cache: Cache::new(),

            serial_buff: Vec::with_capacity(32768),
        }
    }

    pub const fn reset(&mut self) {
        // set all registers to zero
        // run memory.reset()
        self.registers.reset();
    }

    pub fn clear(&mut self) {
        self.memory.reset();
    }

    pub fn cycle(&mut self) -> Result<(u32, Instruction), ProcessorError> {
        self.halted = false;

        // Get value from PCX.
        let addr = self.fetch()?;
        // Increment PCX.
        self.advance()?;

        // Set MAR to the previous value of PCX.
        *self.reg(Register::Mar)? = addr;

        let encoded = if let Some(val) = self.cache.lookup_value(addr) {
            val
        } else {
            let block = self.memory.read_block(addr);
            self.cache.set(addr, block);
            self.cache
                .lookup_value(addr)
                .expect("Failed to lookup value!")
        };

        // Set CIR to the value of RAM[MAR].
        *self.reg(Register::Cir)? = encoded;

        let decoded = if let Some(val) = self.cache.lookup_instruction(addr) {
            val
        } else {
            let decoded = Instruction::decode(encoded)
                .map_err(|_| ProcessorError::InvalidInstruction(encoded))?;
            self.cache.insert(addr, decoded);
            decoded
        };

        decoded.execute(self)?;
        Ok((addr, decoded))
    }

    const fn fetch(&self) -> Result<u32, ProcessorError> {
        self.get(Register::Pcx)
    }

    pub const fn get(&self, reg: Register) -> Result<u32, ProcessorError> {
        self.registers.get(reg)
    }

    pub const fn reg(&mut self, reg: Register) -> Result<&mut u32, ProcessorError> {
        match reg {
            Register::Zero => Ok(&mut self.void),
            _ => self.registers.reg(reg),
        }
    }

    pub fn display(&mut self) -> Result<Vec<u8>, ProcessorError> {
        Ok(self.memory.read_range(DISPLAY_ADDRESS, 2000))
    }

    pub fn cmp(&mut self, a: u32, b: u32) {
        self.set_flag(Flag::Equal, a == b);
        self.set_flag(Flag::GreaterThan, a > b);
        self.set_flag(Flag::LessThan, a < b);
    }

    // functions to set new state
    fn set_flag(&mut self, flag: Flag, value: bool) {
        if value {
            *self
                .reg(Register::Sts)
                .expect("STS should never be invalid") |= flag as u32;
        } else {
            *self
                .reg(Register::Sts)
                .expect("STS should never be invalid") &= !(flag as u32);
        }
    }

    fn get_flag(&self, flag: Flag) -> Result<bool, ProcessorError> {
        Ok(self.get(Register::Sts)? & (flag as u32) != 0)
    }

    fn advance(&mut self) -> Result<(), ProcessorError> {
        // increment PCX
        *self.reg(Register::Pcx)? += 4;
        Ok(())
    }

    fn jump(&mut self, reg: Register, offset: u16) -> Result<(), ProcessorError> {
        *self.reg(Register::Pcx)? = self.get(reg)? + u32::from(offset);
        Ok(())
    }

    pub fn begin_interrupt(
        &mut self,
        interrupt: Interrupt,
    ) -> Result<(), ProcessorError> {
        let idt = self.get(Register::Idr)?;

        let addr = self
            .memory
            .read_word(idt + u32::from(interrupt.as_u8()) * 4)?;
        println!("INFO: Interrupt {interrupt:?} addr: {addr}");

        self.push(self.get(Register::Pcx)?)?;
        *self.reg(Register::Pcx)? = addr;
        Ok(())
    }

    fn push(&mut self, val: u32) -> Result<(), ProcessorError> {
        *self.reg(Register::Spr)? -= 4;
        let reg = *self.reg(Register::Spr)?;
        self.memory.write_word(reg, val)
    }

    fn pop(&mut self) -> Result<u32, ProcessorError> {
        let reg = *self.reg(Register::Spr)?;
        let val = self.memory.read_word(reg)?;
        *self.reg(Register::Spr)? += 4;
        Ok(val)
    }

    // TODO: remove this once implemented
    #[allow(clippy::needless_pass_by_ref_mut)]
    fn end_interrupt(&mut self) -> Result<(), ProcessorError> {
        let ret = self.pop()?;
        *self.reg(Register::Ret)? = ret;
        *self.reg(Register::Pcx)? = ret;
        Ok(())
    }

    pub fn get_stack(&mut self, n: u32) -> Result<Vec<u8>, ProcessorError> {
        let addr = self.get(Register::Spr)?;
        let size = n * 4;
        // returns the stack
        Ok(self.memory.read_range(
            max(addr, 0),    // ensures that we cannot read from a negative address
            min(size, addr), // ensures we don't read above the top of the stack
        ))
    }
}

#[derive(Debug)]
#[expect(dead_code)]
enum Flag {
    Equal = 1,
    GreaterThan = 2,
    LessThan = 4,
    Zero = 8,
    Positive = 16,
    Negative = 32,
    Carry = 64,
    UserMode = 128,
    InterruptsEnabled = 256,
}

trait Executable {
    fn execute(self, cpu: &mut Processor) -> Result<(), ProcessorError>;
}

impl Executable for Instruction {
    #[allow(clippy::too_many_lines)]
    fn execute(self, cpu: &mut Processor) -> Result<(), ProcessorError> {
        match self {
            // No operation - a blank line.
            // Copies from SrcReg to a.drReg.
            Self::Mov(a) => {
                *cpu.reg(a.dr)? = cpu.get(a.sr1)?;
            }

            // Copies from SrcReg to a.drReg, sign extending the value to take up a full
            // word.
            Self::MovSigned(a) => {
                *cpu.reg(a.dr)? = sign_extend(cpu.get(a.sr1)?);
            }

            // Loads a byte from memory address (base + offset) into a.drReg. The
            // effective address must be byte-aligned.
            Self::LoadByte(a) => {
                *cpu.reg(a.r2)? = u32::from(
                    cpu.memory
                        .read_byte(cpu.get(a.r1)? + u32::from(a.immediate)),
                );
            }

            // Loads a sign-extended byte from memory address (base + offset) into
            // a.drReg. The effective address must be byte-aligned.
            Self::LoadByteSigned(a) => {
                *cpu.reg(a.r2)? = sign_extend(u32::from(
                    cpu.memory
                        .read_byte(cpu.get(a.r1)? + u32::from(a.immediate)),
                ));
            }

            // Loads a half-word from memory address (base + offset) into a.drReg. The
            // effective address must be 2-byte-aligned.
            Self::LoadHalfword(a) => {
                // we read an entire word, then right shift so we only get the first half
                // of the word
                *cpu.reg(a.r2)? = cpu
                    .memory
                    .read_word(cpu.get(a.r1)? + u32::from(a.immediate))?
                    >> 16;
            }

            // Loads a sign-extended half-word from memory address (base + offset) into
            // a.drReg. The effective address must be 2-byte-aligned.
            Self::LoadHalfwordSigned(a) => {
                *cpu.reg(a.r2)? = sign_extend(
                    cpu.memory
                        .read_word(cpu.get(a.r1)? + u32::from(a.immediate))?
                        >> 16,
                );
            }

            // Loads a word from memory address (base + offset) into a.drReg. The
            // effective address must be 4-byte-aligned.
            Self::LoadWord(a) => {
                *cpu.reg(a.r2)? = cpu
                    .memory
                    .read_word(cpu.get(a.r1)? + u32::from(a.immediate))?;
            }

            // Stores a byte from SrcReg in memory address (base + offset) The effective
            // address must be byte-aligned.
            Self::StoreByte(a) => {
                let addr = cpu.get(a.r2)? + u32::from(a.immediate);
                let val = cpu.get(a.r1)? as u8;
                if addr == SERIAL_ADDRESS {
                    cpu.serial_buff.push(val);
                }
                cpu.memory.write_byte(addr, val);
            }

            // Stores a half-word from SrcReg in memory address (base + offset) The
            // effective address must be 2-byte-aligned.
            Self::StoreHalfword(a) => {
                let addr = cpu.get(a.r2)? + u32::from(a.immediate);
                let bytes = (cpu.get(a.r1)? as u16).to_le_bytes();

                if addr == SERIAL_ADDRESS {
                    cpu.serial_buff.extend(bytes);
                }

                // split the value into bytes and then write two bytes
                cpu.memory.write_byte(addr, bytes[0]);
                cpu.memory.write_byte(addr + 1, bytes[1]);
            }

            // Stores a word from SrcReg in memory address (base + offset) The effective
            // address must be 4-byte-aligned.
            Self::StoreWord(a) => {
                let addr = cpu.get(a.r2)? + u32::from(a.immediate);
                let val = cpu.get(a.r1)?;
                let bytes = val.to_le_bytes();

                if addr == SERIAL_ADDRESS {
                    cpu.serial_buff.extend(bytes);
                }

                cpu.memory.write_word(addr, val)?;
            }

            // Loads a 16-bit literal value into reg, setting the bottom 16 bits of the
            // word. To populate the upper 16 bits, see LUI.
            Self::LoadLowerImmediate(a) => {
                *cpu.reg(a.r1)? = u32::from(a.immediate);
            }

            // Loads a 16-bit literal value into reg, setting the top 16 bits of the word.
            // To populate the lower 16 bits, see LLI.
            Self::LoadUpperImmediate(a) => {
                *cpu.reg(a.r1)? =
                    (cpu.get(a.r1)? & 0x0000_FFFF) | (u32::from(a.immediate) << 16);
            }

            // Unconditionally jumps to the calculated address or direct address
            Self::Jump(a) => cpu.jump(a.r1, a.immediate)?,

            // Jumps to the calculated address or direct address if equal flag set.
            Self::JumpEq(a) => {
                if cpu.get_flag(Flag::Equal)? {
                    cpu.jump(a.r1, a.immediate)?;
                }
            }

            // Jumps to the calculated address or direct address if equal flag not set.
            Self::JumpNeq(a) => {
                if !cpu.get_flag(Flag::Equal)? {
                    cpu.jump(a.r1, a.immediate)?;
                }
            }

            // Jumps to the calculated address or direct address if greater than flag set.
            Self::JumpGt(a) => {
                if cpu.get_flag(Flag::GreaterThan)? {
                    cpu.jump(a.r1, a.immediate)?;
                }
            }

            // Jumps to the calculated address or direct address if greater than flag or
            // equal flag set.
            Self::JumpGe(a) => {
                if cpu.get_flag(Flag::GreaterThan)? || cpu.get_flag(Flag::Equal)? {
                    cpu.jump(a.r1, a.immediate)?;
                }
            }

            // Jumps to the calculated address or direct address if less than flag set.
            Self::JumpLt(a) => {
                if cpu.get_flag(Flag::LessThan)? {
                    cpu.jump(a.r1, a.immediate)?;
                }
            }

            // Jumps to the calculated address or direct address if less than flag or
            // equal flag set.
            Self::JumpLe(a) => {
                if cpu.get_flag(Flag::LessThan)? || cpu.get_flag(Flag::Equal)? {
                    cpu.jump(a.r1, a.immediate)?;
                }
            }

            // Increments the value in the given register
            Self::Increment(a) => *cpu.reg(a.sr1)? = inc(cpu.get(a.sr1)?),

            // Decrements the value in the given register
            Self::Decrement(a) => *cpu.reg(a.sr1)? = dec(cpu.get(a.sr1)?),

            // Left shifts the value in Reg by the given amount (either a register, or a
            // literal value)
            Self::ShiftLeft(a) => {
                *cpu.reg(a.dr)? = shl(cpu.get(a.sr1)?, a.shamt + cpu.get(a.sr2)? as u8);
            }

            // Right shifts the value in Reg by the given amount (either a register, or a
            // literal value).
            Self::ShiftRight(a) => {
                *cpu.reg(a.dr)? = shr(cpu.get(a.sr1)?, a.shamt + cpu.get(a.sr2)? as u8);
            }

            // Adds the value of Src2 to Src1 and writes the result to a.dr
            Self::Add(a) => {
                *cpu.reg(a.dr)? = add(cpu.get(a.sr1)?, cpu.get(a.sr2)?);
            }

            // Subtracts the value of Src2 from Src1 and writes the result to a.dr
            Self::Sub(a) => {
                *cpu.reg(a.dr)? = sub(cpu.get(a.sr1)?, cpu.get(a.sr2)?);
            }

            Self::AddImmediate(a) => {
                *cpu.reg(a.r2)? = add(cpu.get(a.r1)?, u32::from(a.immediate));
            }

            Self::SubImmediate(a) => {
                *cpu.reg(a.r2)? = sub(cpu.get(a.r1)?, u32::from(a.immediate));
            }

            // Performs bitwise AND on Src1 and Src2 storing the result in a.dr
            Self::And(a) => *cpu.reg(a.dr)? = and(cpu.get(a.sr1)?, cpu.get(a.sr2)?),

            // Performs bitwise OR on Src1 and Src2 storing the result in a.dr
            Self::Or(a) => *cpu.reg(a.dr)? = or(cpu.get(a.sr1)?, cpu.get(a.sr2)?),

            // Performs bitwise NOT on Src storing the result in a.dr
            Self::Not(a) => *cpu.reg(a.dr)? = not(cpu.get(a.sr1)?),

            // Performs bitwise XOR on Src1 and Src2 storing the result in a.dr
            Self::Xor(a) => *cpu.reg(a.dr)? = xor(cpu.get(a.sr1)?, cpu.get(a.sr2)?),

            // Performs bitwise NAND on Src1 and Src2 storing the result in a.dr
            Self::Nand(a) => *cpu.reg(a.dr)? = nand(cpu.get(a.sr1)?, cpu.get(a.sr2)?),

            // Performs bitwise NOR on Src1 and Src2 storing the result in a.dr
            Self::Nor(a) => *cpu.reg(a.dr)? = nor(cpu.get(a.sr1)?, cpu.get(a.sr2)?),

            // Performs bitwise XNOR on Src1 and Src2 storing the result in a.dr
            Self::Xnor(a) => *cpu.reg(a.dr)? = xnor(cpu.get(a.sr1)?, cpu.get(a.sr2)?),

            // Compares the value of Reg1 to the value in Reg2. The results of the
            // comparisons are set in the Status register.
            Self::Compare(a) => {
                cpu.cmp(cpu.get(a.sr1)?, cpu.get(a.sr2)?);
            }

            // Initiates an interrupt with the given 8 bit interrupt code.
            // Triggering an interrupt invokes the following behaviour:
            //      - The return address is saved to the RET register.
            //      - The stack base ptr is set to the kernel stack.
            Self::Interrupt(interrupt_code) => {
                cpu.begin_interrupt(interrupt_code)?;
            }

            // Returns from an interrupt,
            Self::IntReturn => {
                cpu.end_interrupt()?;
            }

            // Halts the processor.
            Self::Halt => {
                cpu.halted = true;
            }

            Self::Segment(_) | Self::Nop | Self::Data(_) => {}
            _ => {
                eprintln!("WARN: unimplemented instruction: {self}");
                todo!()
            }
        }
        Ok(())
    }
}

// mathematical and logical functions & other operations
const fn add(a: u32, b: u32) -> u32 {
    a.wrapping_add(b)
}

const fn sub(a: u32, b: u32) -> u32 {
    a.wrapping_sub(b)
}

const fn and(a: u32, b: u32) -> u32 {
    a & b
}

const fn inc(a: u32) -> u32 {
    a.wrapping_add(1)
}

const fn dec(a: u32) -> u32 {
    a.wrapping_sub(1)
}

const fn shl(a: u32, amount: u8) -> u32 {
    a << amount
}

const fn shr(a: u32, amount: u8) -> u32 {
    a >> amount
}

const fn or(a: u32, b: u32) -> u32 {
    a | b
}

const fn not(a: u32) -> u32 {
    !a
}

const fn xor(a: u32, b: u32) -> u32 {
    a ^ b
}

const fn nand(a: u32, b: u32) -> u32 {
    !(a & b)
}

const fn nor(a: u32, b: u32) -> u32 {
    !(a | b)
}

const fn xnor(a: u32, b: u32) -> u32 {
    !(a ^ b)
}

const fn sign_extend(val: u32) -> u32 {
    let (mask, sign_bit): (u32, u8) = match val {
        0..=0xFF => (0xFFFF_FF00, 7),
        // I presume this was the intended behaviour?
        0x100..=0xFFFF => (0xFFFF_0000, 15),
        _ => (0x0000_0000, 31),
    };

    if val & (1 << sign_bit) != 0 {
        val | mask
    } else {
        val
    }
}

#[cfg(test)]
mod tests;
