use std::{
    hint::{likely, unlikely},
    io::Read,
    process::exit,
    sync::{Mutex, atomic::AtomicU32},
    thread::sleep,
    time::Duration,
};

use pixels::Pixels;

enum Op2 {
    Direct(u32),
    Register(u8),
}

struct Reg(u8);
enum Cond {
    Ifeq,
    Ifne,
    Iflt,
    Ifge,
    Ifgt,
    Ifle,
    Ifult,
    Ifuge,
    Ifugt,
    Ifule,
}

impl From<u8> for Cond {
    fn from(value: u8) -> Self {
        match value {
            0b0000 => Cond::Ifeq,
            0b0001 => Cond::Ifne,
            0b1000 => Cond::Iflt,
            0b1001 => Cond::Ifge,
            0b1010 => Cond::Ifgt,
            0b1011 => Cond::Ifle,
            0b1100 => Cond::Ifult,
            0b1101 => Cond::Ifuge,
            0b1110 => Cond::Ifugt,
            0b1111 => Cond::Ifule,
            _ => {
                unreachable!()
            }
        }
    }
}

impl Cond {
    fn eval(self, a: u32, b: u32) -> bool {
        match self {
            Cond::Ifeq => a == b,
            Cond::Ifne => a != b,
            Cond::Iflt => (a as i32) < (b as i32),
            Cond::Ifge => (a as i32) >= (b as i32),
            Cond::Ifgt => (a as i32) > (b as i32),
            Cond::Ifle => (a as i32) <= (b as i32),
            Cond::Ifult => a < b,
            Cond::Ifuge => a >= b,
            Cond::Ifugt => a > b,
            Cond::Ifule => a <= b,
        }
    }
}

enum Instruction {
    Copy(Reg, Op2),
    Add(Reg, Reg, Op2),
    Sub(Reg, Reg, Op2),
    Or(Reg, Reg, Op2),
    And(Reg, Reg, Op2),
    Xor(Reg, Reg, Op2),
    Lsl(Reg, Reg, Op2),
    Lsr(Reg, Reg, Op2),
    Asr(Reg, Reg, Op2),
    Store(Reg, Op2, Reg),
    Load(Reg, Reg, Op2),
    Push(Op2),
    Pop(Reg),
    Skip(u8, Cond, Reg, Op2),
    Jump(u32), //address / 4
    Call(u32), //address / 4
    Ret(),
    GetStack(Reg),
    SetStack(Op2),
}

impl From<u32> for Instruction {
    fn from(value: u32) -> Self {
        match value >> 30 {
            0b00 => {
                let t = value & (1 << 29); // 3rd bit set
                let value = value - t;
                let t = t != 0;
                if t {
                    Self::Call(value)
                } else {
                    Self::Jump(value)
                }
            }
            fmt => {
                let imediate = (value & (1 << 28)) != 0;
                let s = (value & 1 << 27) != 0;
                const MASK: u32 = 0b1111;
                let opcode = (value >> 24) & MASK;
                let rd = Reg(((value >> 20) & MASK) as u8);
                let rx = Reg(((value >> 16) & MASK) as u8);
                let op2 = {
                    if imediate {
                        let value = if s {
                            value | 0xFFFF0000
                        } else {
                            value & 0x0000FFFF
                        };
                        Op2::Direct(value)
                    } else {
                        Op2::Register((value >> 12 & MASK) as u8)
                    }
                };
                match (fmt, opcode) {
                    (1, 0b0000) => Self::Copy(rd, op2),
                    (1, 0b0001) => Self::Add(rd, rx, op2),
                    (1, 0b0010) => Self::Sub(rd, rx, op2),
                    (1, 0b0011) => Self::Or(rd, rx, op2),
                    (1, 0b0100) => Self::And(rd, rx, op2),
                    (1, 0b0101) => Self::Xor(rd, rx, op2),
                    (1, 0b0110) => Self::Lsl(rd, rx, op2),
                    (1, 0b0111) => Self::Lsr(rd, rx, op2),
                    (1, 0b1000) => Self::Asr(rd, rx, op2),
                    (2, 0b0000) => Self::Store(rx, op2, rd),
                    (2, 0b0001) => Self::Load(rd, rx, op2),
                    (2, 0b0010) => Self::Push(op2),
                    (2, 0b0011) => Self::Pop(rd),
                    (2, 0b1000) => Self::Ret(),
                    (2, 0b1101) => Self::GetStack(rd),
                    (2, 0b1110) => Self::SetStack(op2),
                    (3, skip) => Self::Skip(rd.0, (skip as u8).into(), rx, op2),
                    _ => unreachable!(),
                }
            }
        }
    }
}

pub struct Computer<'a, 'b> {
    ram: Box<[u32; 0x01000000 / 4]>,
    regs: [u32; 16],
    pc: usize,
    sp: usize,
    screen: &'b Mutex<Pixels<'a>>,
    key: &'b AtomicU32,
}

fn iot() -> ! {
    exit(1);
}

impl<'a, 'b> Computer<'a, 'b> {
    pub fn new(filename: String, screen: &'b Mutex<Pixels<'a>>, key: &'b AtomicU32) -> Self {
        let mut new = Self {
            ram: Box::new([0; 0x01000000 / 4]),
            regs: [0; 16],
            pc: 0,
            sp: 0,
            screen,
            key,
        };
        let mut buf = String::new();
        std::fs::File::open(filename)
            .unwrap()
            .read_to_string(&mut buf)
            .unwrap();
        for (i, line) in buf.lines().enumerate() {
            match u32::from_str_radix(line, 16) {
                Ok(val) => new.ram[i] = val,
                Err(_) => break,
            }
        }
        new
    }
    pub fn step(&mut self) {
        let next_opcode = self.ram[self.pc];
        match Instruction::from(next_opcode) {
            Instruction::Copy(reg, op2) => {
                self.rg_wr(reg, self.resolve(op2));
                self.pc += 1;
            }
            Instruction::Add(reg, reg1, op2) => {
                self.rg_wr(reg, self.rg_r(reg1) + self.resolve(op2));
                self.pc += 1;
            }
            Instruction::Sub(reg, reg1, op2) => {
                self.rg_wr(reg, self.rg_r(reg1) - self.resolve(op2));
                self.pc += 1;
            }
            Instruction::Or(reg, reg1, op2) => {
                self.rg_wr(reg, self.rg_r(reg1) | self.resolve(op2));
                self.pc += 1;
            }
            Instruction::And(reg, reg1, op2) => {
                self.rg_wr(reg, self.rg_r(reg1) & self.resolve(op2));
                self.pc += 1;
            }
            Instruction::Xor(reg, reg1, op2) => {
                self.rg_wr(reg, self.rg_r(reg1) ^ self.resolve(op2));
                self.pc += 1;
            }
            Instruction::Lsl(reg, reg1, op2) => {
                self.rg_wr(reg, self.rg_r(reg1) << self.resolve(op2));
                self.pc += 1;
            }
            Instruction::Lsr(reg, reg1, op2) => {
                self.rg_wr(reg, self.rg_r(reg1) >> self.resolve(op2));
                self.pc += 1;
            }
            Instruction::Asr(reg, reg1, op2) => {
                self.rg_wr(reg, (self.rg_r(reg1) as i32 >> self.resolve(op2)) as u32);
                self.pc += 1;
            }
            Instruction::Store(reg, op2, reg1) => {
                let addr = (self.rg_r(reg) + self.resolve(op2)) as usize;
                if !addr.is_multiple_of(4) {
                    iot();
                }
                if addr <= 0x00ffffff {
                    self.ram[addr / 4] = self.rg_r(reg1);
                } else if addr <= 0x00ffffff + 480 * 640 * 4 {
                    let addr_screen = addr - 0x01000000;
                    let ubgr = self.rg_r(reg1);
                    let rgba = [ubgr as u8, (ubgr >> 8) as u8, (ubgr >> 16) as u8, 0xff];
                    let mut pixels = self.screen.lock().unwrap();
                    let screen = pixels.frame_mut();
                    for i in 0..4 {
                        screen[addr_screen + i] = rgba[i];
                    }
                } else {
                    iot();
                }
                self.pc += 1;
            }
            Instruction::Load(reg, reg1, op2) => {
                let addr = (self.rg_r(reg1) + self.resolve(op2)) as usize;
                if !addr.is_multiple_of(4) {
                    iot();
                }
                self.rg_wr(
                    reg,
                    if addr <= 0x00ffffff {
                        self.ram[addr / 4]
                    } else if addr <= 0x00ffffff + 480 * 640 * 4 {
                        let pixels = self.screen.lock().unwrap();
                        let buffer = pixels.frame();
                        let mut res = 0;
                        let addr_screen = addr - 0x01000000;
                        for i in 0..3 {
                            res += (buffer[addr_screen + i] as u32) << (i as u32 * 8)
                        }
                        res
                    } else if addr == 0x01200000 {
                        self.key.load(std::sync::atomic::Ordering::Relaxed)
                    } else {
                        iot();
                    },
                );
                self.pc += 1;
            }
            Instruction::Push(op2) => {
                self.sp -= 1;
                self.ram[self.sp] = self.resolve(op2);
                self.pc += 1;
            }
            Instruction::Pop(reg) => {
                self.rg_wr(reg, self.ram[self.sp]);
                self.sp += 1;
                self.pc += 1;
            }
            Instruction::Skip(d, cond, reg, op2) => {
                self.pc += 1;
                if cond.eval(self.rg_r(reg), self.resolve(op2)) {
                    self.pc += d as usize
                }
            }
            Instruction::Jump(mut addr) => {
                if addr & (1 << 28) != 0 {
                    addr += 7 << 29;
                } else if unlikely(addr == 0) {
                    sleep(Duration::from_hours(1));
                }
                self.pc = (addr + self.pc as u32) as usize;
            }
            Instruction::Call(addr) => {
                self.sp -= 1;
                self.ram[self.sp] = ((self.pc << 2) + 4) as u32;
                self.pc += addr as usize;
                self.pc &= 0x3FFFFFFF; //wrapping on 30 bit pc
            }
            Instruction::Ret() => {
                self.pc = (self.ram[self.sp] >> 2) as usize;
                self.sp += 1;
            }
            Instruction::GetStack(reg) => {
                self.rg_wr(reg, (self.sp << 2) as u32);
                self.pc += 1;
            }
            Instruction::SetStack(op2) => {
                let v = self.resolve(op2);
                if likely(v.is_multiple_of(4)) {
                    self.sp = (v >> 2) as usize;
                } else {
                    self.sp = usize::MAX //Yes, that means that clever program using sp to store information wont work on my emulator. Deal with it
                }
                self.pc += 1;
            }
        };
    }
    fn resolve(&self, op2: Op2) -> u32 {
        match op2 {
            Op2::Direct(v) => v,
            Op2::Register(r) => self.regs[r as usize],
        }
    }
    fn rg_wr(&mut self, reg: Reg, v: u32) {
        self.regs[reg.0 as usize] = v
    }
    fn rg_r(&self, reg: Reg) -> u32 {
        self.regs[reg.0 as usize]
    }
}
