use std::{
    hint::unlikely,
    ops::AddAssign,
    sync::{
        Arc,
        atomic::Ordering,
        mpsc::{self, TryRecvError},
    },
    thread,
    time::{Duration, Instant},
};

use common::{
    isa::instructions::{Instruction, Opcode},
    isa::register::Register
};
use crate::{
    memory::{
        mmu::MMU,
        ram::MemoryBank
    },
    processor::{
        interrupts::Interrupt,
        state::SharedState
    }
};
use crate::config::{MemoryMap, RegionType};
use crate::memory::ram::Page;
use crate::processor::state::{ProcessorSnapshot, RunningState};
use crate::processor::state::RunningState::{Halted, Paused, Running, Shutdown};

pub struct Emulator {
    internal_state: ProcessorSnapshot,
    shared_state: Arc<SharedState>,

    // Interrupts
    interrupts: mpsc::Receiver<Interrupt>,
    pending_fault: Option<Interrupt>,

    // memory
    mmu: MMU,
    mainstore: MemoryBank,

    // IO
    mmio_region: Option<(u32, u32)>, // start end end of segment

    // optionals - not necessarily set on boot.
    memory_map: Option<MemoryMap>,

    // Config params
    __mmap_configured: bool,

    time: Instant,
}

unsafe impl Send for Emulator {}
impl Emulator {
    pub fn new() -> Self {
        let (sender, receiver) = mpsc::channel::<Interrupt>();

        Self {
            interrupts: receiver,
            pending_fault: None,

            internal_state: ProcessorSnapshot::default(),
            shared_state: Arc::new(SharedState::new(sender)),

            memory_map: None,
            mmu: MMU::new(),
            mainstore: MemoryBank::new(),

            // mmio
            mmio_region: None,

            // Config params
            __mmap_configured: false,

            time: Instant::now(),
        }
    }

    pub fn state_handle(&self) -> Arc<SharedState> {
        self.shared_state.clone()
    }

    #[inline]
    pub fn mmu_mut(&mut self) -> &mut MMU {
        &mut self.mmu
    }

    #[inline]
    pub fn memory_mut(&mut self) -> &mut MemoryBank {
        &mut self.mainstore
    }

    #[must_use]
    #[inline]
    pub fn reg(&self, reg: Register) -> u32 {
        debug_assert!((reg as u8) < Register::COUNT);

        unsafe { *self.internal_state.registers.get_unchecked(reg as usize) }
    }

    #[inline]
    pub fn mut_reg(&mut self, reg: Register) -> &mut u32 {
        debug_assert!((reg as u8) < Register::COUNT);
        unsafe {
            self.internal_state
                .registers
                .get_unchecked_mut(reg as usize)
        }
    }

    #[cold]
    pub fn apply_memory_map(mut self, map: MemoryMap) -> Self {
        self.mmio_region = map
            .regions
            .iter()
            .find(|r| matches!(r.region_type, RegionType::MMIO))
            .map(|r| (r.base, r.base + r.size));

        map.apply(&mut self);
        self.memory_map = Some(map);
        self.__mmap_configured = true;
        self
    }

    #[cold]
    fn share_state(&mut self) {
        self.shared_state
            .proc
            .store(Arc::new(self.internal_state.clone()));
    }

    #[cold]
    fn boot(&mut self) -> Result<(), String> {
        if !(self.__mmap_configured) {
            return Err("Emulator<Mem> not configured".to_string());
        }

        if let Some(map) = &self.memory_map {
            MemoryMap::identity_map(&mut self.mmu, map.regions.get(0).unwrap());
        }

        self.internal_state.running = Running;
        Ok(())
    }


    #[cold]
    pub fn run_state_halted(&mut self, prior: RunningState) {
        self.internal_state.running = Halted;

        // Responsibility: keep track of the time spent in the running state across multiple hlt/interrupt cycles
        self.internal_state.time += self.time.elapsed();

        loop {
            // Transition: interrupt received
            if let Ok(int) = self.interrupts.recv_timeout(Duration::from_millis(100)) {
                self.internal_state.running = Running;
                self.interrupt(int);
                break;
            }

            // Transition: pause signal received
            if self.shared_state.paused.load(Ordering::Relaxed) {
                self.run_state_paused(Halted);
            }

            // Responsibility: update UI when requested
            if self.shared_state.update_req.load(Ordering::Relaxed) == true {
                self.share_state();
            }
        }

        // Responsibility: reset `private.time` on state exit
        self.time = Instant::now();
        self.internal_state.running = prior;
    }

    #[cold]
    pub fn run_state_paused(&mut self, prior: RunningState) {
        self.internal_state.running = Paused;

        // Responsibility: save the running-time since last pause state.
        self.internal_state.time = self.time.elapsed();
        self.share_state();

        loop {
            // Responsibility: update UI when requested
            if self.shared_state.update_req.load(Ordering::Relaxed) == true {
                self.share_state();
            }

            // Responsibility: reset when requested by the UI
            if self.shared_state.reseting.load(Ordering::Relaxed) == true {
                self.internal_state.registers = [0; Register::COUNT as usize];
                self.internal_state.clock = 0;

                while self.shared_state.reseting.load(Ordering::Relaxed) == true {
                    thread::sleep(Duration::from_millis(100));
                }
            }

            // Transition: run signal received
            if self.shared_state.paused.load(Ordering::Relaxed) == false {
                break;
            }
        }

        // Responsibility: reset private.time, shared.time, shared.cycles on state exit
        self.time = Instant::now();
        self.internal_state.time = Duration::new(0, 0);
        self.internal_state.clock = 0;

        self.internal_state.running = prior;
    }
    
    pub fn run(&mut self) -> Result<(), String> {
        // wait for UI to initialise.
        if self.shared_state.paused.load(Ordering::Relaxed) {
            self.run_state_paused(Running)
        }

        self.boot()?;

        'emu: loop {
            // so that it always returns zero. this is more performance friendly than checking
            // for a zero register each access as it's branchless
            *self.mut_reg(Register::Zero) = 0;

            // IMPORTANT
            // do not change anything about this loop. it's fully optimised afaik.
            // Check for commands or hardware Interrupts every 32k cycles
            if unlikely(self.internal_state.clock & 0x7FFF == 0) {

                // Responsibility: keep the UI thread up to date periodically
                // Update UI thread (roughly every 512k cycles targeting 60 UPS)
                if unlikely(self.internal_state.clock & 0x7FFFF == 0) {
                    if self.shared_state.update_req.load(Ordering::Relaxed) {
                        self.share_state();
                    }
                }

                // Transition: Paused
                if unlikely(self.shared_state.paused.load(Ordering::Relaxed)) {
                    self.run_state_paused(Running)
                }

                // Responsibility: Handle interrupts
                match self.interrupts.try_recv() {
                    Ok(int) => self.interrupt(int),
                    Err(TryRecvError::Disconnected) => break 'emu,
                    Err(TryRecvError::Empty) => {}
                }
            }

            // Responsibility: run fetch-decode-execute cycle.
            let pc = self.reg(Register::Pcx);
            let instruction = self.mem_read_word(pc);

            if cfg!(debug_assertions) {
                println!(
                    "Clock: {} Executing {:?} PCX: {}, reg: {:?}",
                    self.internal_state.clock,
                    Instruction(instruction),
                    pc,
                    self.internal_state.registers
                );
                thread::sleep(Duration::from_micros(10));
            }

            // Check if the previous instruction caused a fault.
            if let Some(fault) = self.pending_fault {
                println!("WARN fault: {:?}", fault);
                self.pending_fault = None;
                self.interrupt(fault);
            }

            self.mut_reg(Register::Pcx).add_assign(4);
            self.execute(Instruction(instruction));

            // always increment clock.
            self.internal_state.clock += 1;
        }

        self.internal_state.running = Shutdown;
        Ok(())
    }

    #[inline]
    fn interrupt(&mut self, int: Interrupt) {
        println!("Interrupt while at address {:04X}", self.reg(Register::Pcx));

        self.push(Register::Pcx);
        *self.mut_reg(Register::Pcx) =
            self.mem_read_word(self.reg(Register::Idr) + int.code() as u32 * 4);

        println!(
            "Jumping to interrupt at address {:04X}",
            self.reg(Register::Pcx)
        );
    }

    #[inline]
    fn push(&mut self, reg: Register) {
        *self.mut_reg(Register::Spr) -= 4;
        self.mem_write_word(self.reg(Register::Spr), self.reg(reg));
    }

    #[inline]
    fn pop(&mut self, reg: Register) {
        *self.mut_reg(reg) = self.mem_read_word(self.reg(Register::Spr));
        *self.mut_reg(Register::Spr) += 4;
    }

    #[inline]
    fn execute(&mut self, word: Instruction) {
        // This needs to be unsafe as we're using word.xxxx_uc() functions for decoding which are unsafe
        // as they perform unchecked transmute operations (performance critical)
        unsafe {
            match Opcode::from_u8(word.opcode()) {
                // Nothing
                Some(Opcode::Nop) => {}

                // move
                Some(Opcode::Mov) => {
                    *self.mut_reg(word.dest_uc()) = self.reg(word.src1_uc());
                }
                Some(Opcode::CMov) => {
                    if self.reg(word.misc_uc()) != 0 {
                        *self.mut_reg(word.dest_uc()) = self.reg(word.src1_uc());
                    }
                }

                // load
                Some(Opcode::Ldbs) => todo!(),
                Some(Opcode::Ldhs) => todo!(),
                Some(Opcode::Ldb) => {
                    *self.mut_reg(word.dest_uc()) = u32::from(
                        self.mem_read_byte(self.reg(word.src1_uc()) + word.imm16() as u32),
                    )
                }
                Some(Opcode::Ldh) => {
                    let addr = self.reg(word.src1_uc()) + word.imm16() as u32;

                    let hi = self.mem_read_byte(addr) as u32;
                    let lo = self.mem_read_byte(addr + 1) as u32;
                    *self.mut_reg(word.dest_uc()) = (hi << 8) | lo;

                    // *self.mut_reg(word.dest_uc()) = u32::from(
                    //     self.mem_read_word(self.reg(word.src1_uc()) + word.imm16() as u32) >> 16,
                    // )
                }
                Some(Opcode::Ldw) => {
                    *self.mut_reg(word.dest_uc()) = u32::from(
                        self.mem_read_word(self.reg(word.src1_uc()) + word.imm16() as u32),
                    )
                }

                // store
                Some(Opcode::Stb) => {
                    self.mem_write_byte(
                        self.reg(word.dest_uc()) + word.imm16() as u32,
                        self.reg(word.src1_uc()) as u8,
                    );
                }
                Some(Opcode::Sth) => {
                    self.mem_write_byte(
                        self.reg(word.dest_uc()) + word.imm16() as u32,
                        (self.reg(word.src1_uc()) as u16 >> 8) as u8,
                    );
                    self.mem_write_byte(
                        self.reg(word.dest_uc()) + word.imm16() as u32 + 1,
                        self.reg(word.src1_uc()) as u8,
                    );
                }
                Some(Opcode::Stw) => {
                    self.mem_write_word(
                        self.reg(word.dest_uc()) + word.imm16() as u32,
                        self.reg(word.src1_uc()),
                    );
                }

                // load immediate
                Some(Opcode::Lli) => {
                    *self.mut_reg(word.dest_uc()) = word.imm16() as u32;
                }
                Some(Opcode::Lui) => {
                    *self.mut_reg(word.dest_uc()) =
                        (word.imm16() as u32) << 16 | self.reg(word.dest_uc());
                }

                // Comparison
                Some(Opcode::Ieq) => {
                    *self.mut_reg(word.dest_uc()) =
                        (self.reg(word.src1_uc()) == self.reg(word.src2_uc())) as u32;
                }
                Some(Opcode::Ine) => {
                    *self.mut_reg(word.dest_uc()) =
                        (self.reg(word.src1_uc()) != self.reg(word.src2_uc())) as u32;
                }
                Some(Opcode::Ilt) => {
                    *self.mut_reg(word.dest_uc()) =
                        (self.reg(word.src1_uc()) < self.reg(word.src2_uc())) as u32;
                }
                Some(Opcode::Ile) => {
                    *self.mut_reg(word.dest_uc()) =
                        (self.reg(word.src1_uc()) <= self.reg(word.src2_uc())) as u32;
                }
                Some(Opcode::Igt) => {
                    *self.mut_reg(word.dest_uc()) =
                        (self.reg(word.src1_uc()) > self.reg(word.src2_uc())) as u32;
                }
                Some(Opcode::Ige) => {
                    *self.mut_reg(word.dest_uc()) =
                        (self.reg(word.src1_uc()) >= self.reg(word.src2_uc())) as u32;
                }

                // Jump
                Some(Opcode::Jmp) => {
                    *self.mut_reg(Register::Pcx) = self.reg(word.dest_uc()) + word.imm16() as u32
                }

                Some(Opcode::Jez) => {
                    if self.reg(word.src1_uc()) == 0 {
                        *self.mut_reg(Register::Pcx) =
                            self.reg(word.dest_uc()) + word.imm16() as u32
                    }
                }
                Some(Opcode::Jnz) => {
                    if self.reg(word.src1_uc()) != 0 {
                        *self.mut_reg(Register::Pcx) =
                            self.reg(word.dest_uc()) + word.imm16() as u32
                    }
                }
                Some(Opcode::Jnc) => todo!(),
                Some(Opcode::Jic) => todo!(),

                // Bitwise
                Some(Opcode::And) => {
                    *self.mut_reg(word.dest_uc()) =
                        self.reg(word.src1_uc()) & self.reg(word.src2_uc());
                }
                Some(Opcode::Nand) => {
                    *self.mut_reg(word.dest_uc()) =
                        !(self.reg(word.src1_uc()) & self.reg(word.src2_uc()));
                }
                Some(Opcode::Or) => {
                    *self.mut_reg(word.dest_uc()) =
                        self.reg(word.src1_uc()) | self.reg(word.src2_uc());
                }
                Some(Opcode::Nor) => {
                    *self.mut_reg(word.dest_uc()) =
                        !(self.reg(word.src1_uc()) | self.reg(word.src2_uc()));
                }
                Some(Opcode::Xor) => {
                    *self.mut_reg(word.dest_uc()) =
                        self.reg(word.src1_uc()) ^ self.reg(word.src2_uc());
                }
                Some(Opcode::Xnor) => {
                    *self.mut_reg(word.dest_uc()) =
                        !(self.reg(word.src1_uc()) ^ self.reg(word.src2_uc()));
                }
                Some(Opcode::Not) => {
                    *self.mut_reg(word.dest_uc()) = !self.reg(word.src1_uc());
                }

                // Arithmetic
                Some(Opcode::Add) => {
                    *self.mut_reg(word.dest_uc()) =
                        self.reg(word.src1_uc()) + self.reg(word.src2_uc());
                }

                Some(Opcode::Sub) => {
                    *self.mut_reg(word.dest_uc()) =
                        self.reg(word.src1_uc()) - self.reg(word.src2_uc());
                }

                Some(Opcode::Shl) => {
                    *self.mut_reg(word.dest_uc()) = self.reg(word.src1_uc())
                        << (self.reg(word.src2_uc()) + word.shamt() as u32);
                }

                Some(Opcode::Shr) => {
                    *self.mut_reg(word.dest_uc()) = self.reg(word.src1_uc())
                        >> (self.reg(word.src2_uc()) + word.shamt() as u32);
                }

                Some(Opcode::Addi) => {
                    *self.mut_reg(word.dest_uc()) = self.reg(word.src1_uc()) + word.imm16() as u32;
                }

                Some(Opcode::Subi) => {
                    *self.mut_reg(word.dest_uc()) = self.reg(word.src1_uc()) - word.imm16() as u32;
                }

                // Utility
                Some(Opcode::Push) => {
                    self.push(word.src1_uc());
                }
                Some(Opcode::Pop) => {
                    self.pop(word.dest_uc());
                }

                // Function
                Some(Opcode::Call) => {
                    self.push(Register::Pcx);
                    *self.mut_reg(Register::Pcx) = self.reg(word.dest_uc()) + word.imm16() as u32
                }
                Some(Opcode::Ret) => {
                    self.pop(Register::Ret);
                    *self.mut_reg(Register::Pcx) = self.reg(Register::Ret);
                }

                Some(Opcode::Int) => {
                    self.interrupt(Interrupt::Software(word.imm16() as u8));
                }
                Some(Opcode::Hlt) => {
                    self.run_state_halted(Running);
                },
                Some(Opcode::IRet) => {
                    self.pop(Register::Ret);
                    *self.mut_reg(Register::Pcx) = self.reg(Register::Ret);

                    println!(
                        "Returning from interrupt to address {:04X}",
                        self.reg(Register::Pcx)
                    );
                }
                None => {}
            }
        }
    }

    #[inline]
    fn mem_read_byte(&mut self, addr: u32) -> u8 {
        self.mainstore.read_byte(addr)
    }

    #[inline]
    fn mem_write_byte(&mut self, addr: u32, val: u8) {
        if addr == 0x40000 || addr == 0x40001 || addr == 0x40002 || addr == 0x40003 {
            eprintln!("ui wrote 0x{:08x} to uart + {}", val, addr - 0x40000);

            if addr == 0x40001 || addr == 0x40003 {
                eprintln!("writing char {} to uart + {}", val as char, addr - 0x40000);
            }
        }
        self.mainstore.write_byte(addr, val);
    }

    #[inline]
    fn mem_read_word(&mut self, addr: u32) -> u32 {
        self.mainstore.read_word(addr)
    }

    #[inline]
    fn mem_write_word(&mut self, addr: u32, val: u32) {
        self.mainstore.write_word(addr, val);
    }

    #[inline]
    fn mem_read_page(&mut self, addr: u32) -> &Page {
        self.mainstore.read_page(addr)
    }

    #[inline]
    fn mem_write_page(&mut self, addr: u32, val: &Page) {
        self.mainstore.write_page(addr, val);
    }

    fn fault(&mut self, interrupt: Interrupt) {
        self.pending_fault = Some(interrupt);
    }
}

