pub mod call_stack;
pub mod config;
pub mod regs;

use crate::bytecode::ops::Operations::*;
use crate::bytecode::ops::*;
use crate::bytecode::types::*;

use crate::engine::call_stack::FnCall;
use crate::HaltStatus;
use crate::RuntimeErrors;
use alloc::vec::Vec;
use config::EngineConfig;
use log::trace;
use regs::Registers;

use self::call_stack::CallStack;

pub struct Page {
    pub data: [u8; 4096 * 2],
}
pub type EnviromentCall = fn(Registers) -> Result<(), ()>;

pub fn empty_enviroment_call(reg: Registers) -> Result<(), ()> {
    Err(())
}

pub struct Engine {
    pub index: usize,
    program: Vec<u8>,
    registers: Registers,
    config: EngineConfig,

    /// BUG: This DOES NOT account for overflowing
    last_timer_count: u32,
    timer_callback: Option<fn() -> u32>,
    memory: Vec<Page>,
    pub enviroment_call_table: [EnviromentCall; 256],
    call_stack: CallStack,
}

impl Engine {
    pub fn read_mem_addr(&mut self, address: u64) -> u8 {
        // println!("{}", address);
        255
    }
    pub fn set_timer_callback(&mut self, func: fn() -> u32) {
        self.timer_callback = Some(func);
    }
}

impl Engine {
    pub fn new(program: Vec<u8>) -> Self {
        Self {
            index: 0,
            program,
            registers: Registers::new(),
            config: EngineConfig::default(),
            last_timer_count: 0,
            timer_callback: None,
            enviroment_call_table: [empty_enviroment_call; 256],
            memory: Vec::new(),
            call_stack: Vec::new(),
        }
    }

    pub fn dump(&self) {
        trace!("Registers");
        trace!(
            "A {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}",
            self.registers.a0,
            self.registers.a1,
            self.registers.a2,
            self.registers.a3,
            self.registers.a4,
            self.registers.a5,
            self.registers.a6,
            self.registers.a7,
            self.registers.a8,
            self.registers.a9,
        );
        trace!(
            "B {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}",
            self.registers.b0,
            self.registers.b1,
            self.registers.b2,
            self.registers.b3,
            self.registers.b4,
            self.registers.b5,
            self.registers.b6,
            self.registers.b7,
            self.registers.b8,
            self.registers.b9,
        );
        trace!(
            "C {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}",
            self.registers.c0,
            self.registers.c1,
            self.registers.c2,
            self.registers.c3,
            self.registers.c4,
            self.registers.c5,
            self.registers.c6,
            self.registers.c7,
            self.registers.c8,
            self.registers.c9,
        );
        trace!(
            "D0-D4 {:016X} {:016X} {:016X} {:016X} {:016X}
D5-D9 {:016X} {:016X} {:016X} {:016X} {:016X}",
            self.registers.d0,
            self.registers.d1,
            self.registers.d2,
            self.registers.d3,
            self.registers.d4,
            self.registers.d5,
            self.registers.d6,
            self.registers.d7,
            self.registers.d8,
            self.registers.d9,
        );
        trace!(
            "E0-E4 {:016X} {:016X} {:016X} {:016X} {:016X}
E5-E9 {:016X} {:016X} {:016X} {:016X} {:016X}",
            self.registers.e0,
            self.registers.e1,
            self.registers.e2,
            self.registers.e3,
            self.registers.e4,
            self.registers.e5,
            self.registers.e6,
            self.registers.e7,
            self.registers.e8,
            self.registers.e9,
        );
        trace!(
            "F0-F4 {:016X} {:016X} {:016X} {:016X} {:016X}
F5-F9 {:016X} {:016X} {:016X} {:016X} {:016X}",
            self.registers.f0,
            self.registers.f1,
            self.registers.f2,
            self.registers.f3,
            self.registers.f4,
            self.registers.f5,
            self.registers.f6,
            self.registers.f7,
            self.registers.f8,
            self.registers.f9,
        );
    }
    pub fn run(&mut self) -> Result<HaltStatus, RuntimeErrors> {
        use HaltStatus::*;
        use RuntimeErrors::*;
        loop {
            // Break out of the loop
            if self.index + 1 == self.program.len() {
                break;
            }
            let op = (self.program[self.index], self.program[self.index + 1]);
            // println!("OP {} INDEX {}", op.0, self.index);
            match op {
                (0, _) => {
                    trace!("NO OP");
                    self.index += 1;
                }
                // Add a 8 bit num
                (1, 1) => {
                    let lhs = self.program[self.index + 2];
                    // println!("LHS 8BIT {}", lhs);
                    let rhs = self.program[self.index + 3];
                    // println!("RHS 8BIT {}", rhs);

                    let ret = lhs + rhs;
                    let reg = self.program[self.index + 4];

                    match reg {
                        0xA0..=0xC9 => {
                            self.set_register_8(reg, ret);
                        }
                        0xD0..=0xF9 => {
                            panic!("Register oversized")
                        }
                        _ => {
                            panic!("Not a register.")
                        }
                    }

                    self.index += 4;
                }
                // Add a 64 bit num
                (1, 2) => {
                    let mut lhs_array = [0; 8];
                    let mut rhs_array = [0; 8];

                    for (index, byte) in self.program[self.index + 2..self.index + 10]
                        .into_iter()
                        .enumerate()
                    {
                        lhs_array[index] = *byte;
                    }
                    let lhs = u64::from_be_bytes(lhs_array);
                    // println!("LHS 64BIT {}", lhs);

                    for (index, byte) in self.program[self.index + 10..self.index + 18]
                        .into_iter()
                        .enumerate()
                    {
                        rhs_array[index] = *byte;
                    }

                    let rhs = u64::from_be_bytes(rhs_array);

                    // println!("RHS 64BIT {}", rhs);

                    let ret = lhs + rhs;

                    let reg = self.program[self.index + 18];
                    // println!("Store {} in {:02X}", ret, reg);

                    match reg {
                        0xA0..=0xC9 => {
                            panic!("Register undersized")
                        }
                        0xD0..=0xF9 => {
                            self.set_register_64(reg, ret);
                        }
                        _ => {
                            panic!("Not a register.")
                        }
                    }

                    self.index += 19;
                }
                (2, 1) => {
                    let lhs = self.program[self.index + 2];
                    // println!("LHS 8BIT {}", lhs);
                    let rhs = self.program[self.index + 3];
                    // println!("RHS 8BIT {}", rhs);
                    let ret = lhs - rhs;
                    let reg = self.program[self.index + 4];

                    match reg {
                        0xA0..=0xC9 => {
                            self.set_register_8(reg, ret);
                        }
                        0xD0..=0xF9 => {
                            panic!("Register oversized")
                        }
                        _ => {
                            panic!("Not a register.")
                        }
                    }

                    self.index += 4;
                }
                (2, 2) => {
                    let mut lhs_array = [0; 8];
                    let mut rhs_array = [0; 8];

                    for (index, byte) in self.program[self.index + 2..self.index + 10]
                        .into_iter()
                        .enumerate()
                    {
                        lhs_array[index] = *byte;
                    }
                    let lhs = u64::from_be_bytes(lhs_array);
                    // println!("LHS 64BIT {}", lhs);

                    for (index, byte) in self.program[self.index + 10..self.index + 18]
                        .into_iter()
                        .enumerate()
                    {
                        rhs_array[index] = *byte;
                    }

                    let rhs = u64::from_be_bytes(rhs_array);

                    // println!("RHS 64BIT {}", rhs);

                    let ret = lhs - rhs;

                    let reg = self.program[self.index + 18];
                    // println!("Store {} in {:02X}", ret, reg);

                    match reg {
                        0xA0..=0xC9 => {
                            panic!("Register undersized")
                        }
                        0xD0..=0xF9 => {
                            self.set_register_64(reg, ret);
                        }
                        _ => {
                            panic!("Not a register.")
                        }
                    }

                    self.index += 19;
                }
                (2, 3) => {
                    // 8 bit
                    self.index += 4;
                }
                // TODO: Implement 64 bit register to register subtraction
                (2, 4) => {
                    // 64 bit
                    self.index += 19;
                }
                // Read from address to register
                (5, 0) => {
                    let mut addr_array = [0; 8];

                    for (index, byte) in self.program[self.index + 2..self.index + 10]
                        .into_iter()
                        .enumerate()
                    {
                        addr_array[index] = *byte;
                    }
                    let addr = u64::from_be_bytes(addr_array);

                    trace!("addr {}", addr);

                    let ret = self.read_mem_addr(addr);
                    let reg = self.program[self.index + 10];
                    trace!("reg {}", reg);
                    self.set_register_8(reg, ret);
                    self.index += 9;
                }

                (10, int) => {
                    println!("Enviroment Call {}", int);
                    self.enviroment_call_table[int as usize](self.registers);
                    self.index += 2;
                }

                (100, _) => {
                    if self.call_stack.len() > self.config.call_stack_depth {
                        trace!("Callstack {}", self.call_stack.len());
                        break;
                    }

                    let mut addr_array = [0; 8];

                    for (index, byte) in self.program[self.index + 1..self.index + 9]
                        .into_iter()
                        .enumerate()
                    {
                        trace!("byte {}", byte);
                        addr_array[index] = *byte;
                    }
                    let addr = usize::from_be_bytes(addr_array);
                    if addr > self.program.len() {
                        panic!("Invalid jump address {}", addr)
                    } else {
                        let call = FnCall { ret: self.index };
                        self.call_stack.push(call);

                        self.index = addr;
                        trace!("Jumping to {}", addr);

                        self.dump();
                        // panic!();
                    }
                }

                _op_pair => {
                    // println!("OP Pair {}", op_pair.0);
                    self.index += 1;
                }
            }

            // Finish step

            if self.timer_callback.is_some() {
                let ret = self.timer_callback.unwrap()();
                if (ret - self.last_timer_count) >= self.config.quantum {
                    return Ok(Running);
                }
            }
        }
        Ok(Halted)
    }

    pub fn set_register_8(&mut self, register: u8, value: u8) {
        match register {
            0xA0 => self.registers.a0 = value,
            0xA1 => self.registers.a1 = value,
            0xA2 => self.registers.a2 = value,
            0xA3 => self.registers.a3 = value,
            0xA4 => self.registers.a4 = value,
            0xA5 => self.registers.a5 = value,
            0xA6 => self.registers.a6 = value,
            0xA7 => self.registers.a7 = value,
            0xA8 => self.registers.a8 = value,
            0xA9 => self.registers.a9 = value,
            //
            0xB0 => self.registers.b0 = value,
            0xB1 => self.registers.b1 = value,
            0xB2 => self.registers.b2 = value,
            0xB3 => self.registers.b3 = value,
            0xB4 => self.registers.b4 = value,
            0xB5 => self.registers.b5 = value,
            0xB6 => self.registers.b6 = value,
            0xB7 => self.registers.b7 = value,
            0xB8 => self.registers.b8 = value,
            0xB9 => self.registers.b9 = value,
            //
            0xC0 => self.registers.c0 = value,
            0xC1 => self.registers.c1 = value,
            0xC2 => self.registers.c2 = value,
            0xC3 => self.registers.c3 = value,
            0xC4 => self.registers.c4 = value,
            0xC5 => self.registers.c5 = value,
            0xC6 => self.registers.c6 = value,
            0xC7 => self.registers.c7 = value,
            0xC8 => self.registers.c8 = value,
            0xC9 => self.registers.c9 = value,

            _ => {
                panic!("Unlikely you are here if everyone behaved.\nThis register is not 8 bit")
            }
        }
    }
    pub fn set_register_64(&mut self, register: u8, value: u64) {
        match register {
            0xD0 => self.registers.d0 = value,
            0xD1 => self.registers.d1 = value,
            0xD2 => self.registers.d2 = value,
            0xD3 => self.registers.d3 = value,
            0xD4 => self.registers.d4 = value,
            0xD5 => self.registers.d5 = value,
            0xD6 => self.registers.d6 = value,
            0xD7 => self.registers.d7 = value,
            0xD8 => self.registers.d8 = value,
            0xD9 => self.registers.d9 = value,
            //
            0xE0 => self.registers.f0 = value,
            0xE1 => self.registers.f1 = value,
            0xE2 => self.registers.f2 = value,
            0xE3 => self.registers.f3 = value,
            0xE4 => self.registers.f4 = value,
            0xE5 => self.registers.f5 = value,
            0xE6 => self.registers.f6 = value,
            0xE7 => self.registers.f7 = value,
            0xE8 => self.registers.f8 = value,
            0xE9 => self.registers.f9 = value,

            //
            0xF0 => self.registers.f0 = value,
            0xF1 => self.registers.f1 = value,
            0xF2 => self.registers.f2 = value,
            0xF3 => self.registers.f3 = value,
            0xF4 => self.registers.f4 = value,
            0xF5 => self.registers.f5 = value,
            0xF6 => self.registers.f6 = value,
            0xF7 => self.registers.f7 = value,
            0xF8 => self.registers.f8 = value,
            0xF9 => self.registers.f9 = value,

            _ => {
                panic!("Unlikely you are here if everyone behaved.\nThis register is not 64 bit")
            }
        }
    }
}