use std::sync::Arc;
use std::sync::mpsc::{self, Receiver, Sender};

#[allow(unused_imports)]
use crate::emulator::misc::rpc::{Activity, RpcClient};

use crate::emulator::system::model::StateUpdate;
use crate::emulator::system::{
    model::{Command, Running},
    processor::Processor,
};

use common::prelude::*;

#[expect(clippy::too_many_lines)]
#[allow(unused_variables)]
pub fn run_emulator(
    cmd_rx: &Receiver<Command>,
    state_tx: &Sender<StateUpdate>,
    mut processor: Processor,
    rpc_client: Option<&Arc<RpcClient>>,
) {
    println!("INFO: Starting emulator.");

    let mut running = Running::Paused;
    let mut step = 0;
    let mut addr;
    let mut history = Vec::<(u32, u32)>::with_capacity(32768);
    let size = 256;

    let record_history = true;

    state_tx
        .send(StateUpdate::Running(Running::Paused))
        .expect("Failed to send initial state!");

    let mut instruction_count = 0;
    let mut update = false;

    loop {
        let cmd = if step > 0 {
            None
        } else if running == Running::Running && step == 0 {
            match cmd_rx.try_recv() {
                Ok(cmd) => Some(cmd),
                Err(mpsc::TryRecvError::Empty) => {
                    update = false;
                    None
                }
                Err(mpsc::TryRecvError::Disconnected) => break,
            }
        } else {
            match cmd_rx.recv() {
                Ok(cmd) => Some(cmd),
                Err(_) => break,
            }
        };

        if running == Running::Running && step == 0 {
            step = 32768;
        }

        if let Some(cmd) = cmd {
            match cmd {
                Command::Start => {
                    running = Running::Running;
                    step = 32768;

                    // Update RPC with current state. TODO: Make this only occur on state
                    // changes.
                    #[cfg(feature = "discord-rpc")]
                    if let Some(rpc_client) = rpc_client {
                        use std::{path::PathBuf, str::FromStr};

                        rpc_client.update(Activity::EditingFile(
                            PathBuf::from_str("test")
                                .expect("This is a valid path, WTF."),
                        ));
                    }
                }
                Command::Stop => {
                    running = Running::Paused;
                    step = 0;
                }
                Command::Reset(x) => {
                    running = Running::Paused;
                    step = 0;

                    match x {
                        0 => {
                            processor.clear();
                            processor.reset();
                            instruction_count = 0;
                        }
                        1 => {
                            processor.reset();
                            instruction_count = 0;
                        }
                        2 => {
                            processor.clear();
                        }
                        _ => unreachable!(),
                    }

                    processor.reset();
                }
                Command::Step(x) => {
                    step = x;
                    running = Running::Paused;
                }
                Command::Write(offset, data) => {
                    update = true;

                    processor.memory.write_range(offset, data);
                }
                Command::Interrupt(_interrupt) => {
                    // update = true;

                    todo!("implement interrupts")
                }
                Command::MemRequest(new, size) if update => {
                    addr = new;
                    let _ = state_tx.send(StateUpdate::MemoryView(
                        processor.memory.read_range(addr, size),
                    ));
                }
                Command::DisplayRequest if update => {
                    let _ = state_tx.send(StateUpdate::DisplayView(
                        processor.display().unwrap_or_else(|_| {
                            report_err(
                                state_tx,
                                "Failed to read display!",
                                &mut processor,
                            );
                            Vec::new()
                        }),
                    ));
                    let _ = state_tx.send(StateUpdate::Serial(
                        processor.serial_buff.drain(..).collect(),
                    ));
                }
                Command::StackRequest if update => {
                    let _ = state_tx.send(StateUpdate::StackView(
                        processor.get_stack(32).unwrap_or_else(|_| {
                            report_err(state_tx, "Failed to read stack!", &mut processor);
                            Vec::new()
                        }),
                    ));
                }
                Command::RegisterRequest if update => {
                    let _ = state_tx.send(StateUpdate::Registers(processor.registers));
                }
                Command::RunningRequest if update => {
                    let _ = state_tx.send(StateUpdate::Running(running));
                }
                Command::HistoryRequest if update => {
                    let hsc = history.clone();
                    history.clear();
                    let _ = state_tx.send(StateUpdate::InstructionHistory(hsc));
                }
                Command::InstructionCountRequest if update => {
                    let _ = state_tx.send(StateUpdate::Instructions(instruction_count));
                }
                Command::WriteBlock(addr, block) => {
                    processor.memory.write_range(addr, block.to_vec());
                }

                _ => {}
            }
        }

        if running == Running::Running {
            step += 1;
        }

        if step > 0 {
            step -= 1;
            update = true;

            // Execute one cycle.
            let instruction = match processor.cycle() {
                Ok(instruction) => instruction,
                Err(why) => {
                    let pcx = processor
                        .get(Register::Pcx)
                        .expect("PCX should never be invalid");
                    report_err(
                        state_tx,
                        &format!(
                            "Could not decode instruction at {pcx:x}. Reason: {why}"
                        ),
                        &mut processor,
                    );
                    (pcx, Instruction::Nop)
                }
            };

            if record_history {
                history.push((
                    instruction.0,
                    processor
                        .get(Register::Cir)
                        .expect("CIR should never be invalid"),
                ));
            }

            if matches!(instruction, (_, Instruction::Halt)) {
                running = Running::Halted;
                step = 0;
            }

            instruction_count += 1;
        }
    }
}

fn report_err(state_tx: &Sender<StateUpdate>, why: &str, processor: &mut Processor) {
    processor
        .begin_interrupt(Interrupt::HardFault)
        .expect("What kind of goofy ahh shenanigans did you do with your fault handler? At this point, the emulator can just crash. this is on you.");
    let _ = state_tx.send(StateUpdate::Error(why.to_string()));
}
