use instructions::arithmetic;
use std::{env, fs};

use crate::structs::*;

mod instructions;
mod structs;

pub const MEMORY_SIZE: usize = 8192;

fn main() {
    let mut state = EmulatorState {
        a: 0,
        b: 0,
        c: 0,
        d: 0,
        e: 0,
        h: 0,
        l: 0,
        sp: 0,
        cc: ConditionCodes {
            z: true,
            s: true,
            p: true,
            c: true,
        },
        pc: 0,
        ei: true,
        memory: [0; MEMORY_SIZE],
    };

    // Load the ROM into memory
    let mut args = env::args();
    let filename = args
        .nth(1)
        .expect("Provide a path to a ROM file to emulate as an argument");
    let file = fs::read(filename).expect("where file");

    let to_copy = MEMORY_SIZE.min(file.len());
    state.memory[..to_copy].copy_from_slice(&file[..to_copy]);

    while state.pc < MEMORY_SIZE as u16 {
        tick(&mut state);
    }

    println!("Program counter reached end of memory; exiting");
    print_state(&state);
}

fn tick(state: &mut EmulatorState) {
    let instruction = state.memory[state.pc as usize];

    let mut next_byte = || {
        state.pc += 1;
        state.memory[state.pc as usize]
    };

    match instruction {
        0x00 => {} // NOP

        /* Special */
        0xfb => state.ei = true,  // EI
        0xf3 => state.ei = false, // DI
        0x76 => {
            // HLT
            if state.ei {
                todo!()
            } else {
                println!("HLT called after DI; exiting.");
                print_state(state);
                std::process::exit(0);
            }
        }

        /* Maths */
        // INR
        0x04 => arithmetic::inr(Register::B, state),
        0x0c => arithmetic::inr(Register::C, state),
        0x14 => arithmetic::inr(Register::D, state),
        0x1c => arithmetic::inr(Register::E, state),
        0x24 => arithmetic::inr(Register::H, state),
        0x2c => arithmetic::inr(Register::L, state),
        0x34 => arithmetic::inr(Register::M, state),
        0x3c => arithmetic::inr(Register::A, state),

        // DCR
        0x05 => arithmetic::dcr(Register::B, state),
        0x0d => arithmetic::dcr(Register::C, state),
        0x15 => arithmetic::dcr(Register::D, state),
        0x1d => arithmetic::dcr(Register::E, state),
        0x25 => arithmetic::dcr(Register::H, state),
        0x2d => arithmetic::dcr(Register::L, state),
        0x35 => arithmetic::dcr(Register::M, state),
        0x3d => arithmetic::dcr(Register::A, state),

        // DAD
        0x09 => arithmetic::dad(Register::B, state),
        0x19 => arithmetic::dad(Register::D, state),
        0x29 => arithmetic::dad(Register::H, state),
        0x39 => arithmetic::dad(Register::SP, state),

        // INX
        0x03 => arithmetic::inx(Register::B, state),
        0x13 => arithmetic::inx(Register::D, state),
        0x23 => arithmetic::inx(Register::H, state),
        0x33 => arithmetic::inx(Register::SP, state),

        // DCX
        0x0b => arithmetic::dcx(Register::B, state),
        0x1b => arithmetic::dcx(Register::D, state),
        0x2b => arithmetic::dcx(Register::H, state),
        0x3b => arithmetic::dcx(Register::SP, state),

        0x80..=0x87 => arithmetic::add_reg(register_from_num(instruction & 0xf), false, state), // ADD
        0x88..=0x8f => arithmetic::add_reg(register_from_num(instruction & 0xf), state.cc.c, state), // ADC
        0xc6 => arithmetic::add(next_byte(), false, state), // ADI
        0xce => arithmetic::add(next_byte(), state.cc.c, state), // ACI

        0x90..=0x97 => arithmetic::sub_reg(register_from_num(instruction & 0xf), false, state), // SUB
        0x98..=0x9f => arithmetic::sub_reg(register_from_num(instruction & 0xf), state.cc.c, state), // SBB
        0xd6 => arithmetic::sub(next_byte(), false, state), // SUI
        0xde => arithmetic::sub(next_byte(), state.cc.c, state), // SBI

        0xa0..=0xa7 => arithmetic::and_reg(register_from_num(instruction & 0xf), state), // ANA
        0xa8..=0xaf => arithmetic::xor_reg(register_from_num(instruction & 0xf), state), // XRA
        0xb0..=0xb7 => arithmetic::or_reg(register_from_num(instruction & 0xf), state),  // ORA
        0xb8..=0xbf => arithmetic::cmp_reg(register_from_num(instruction & 0xf), state), // CMP
        0xe6 => arithmetic::and(next_byte(), state),                                     // ANI
        0xee => arithmetic::xor(next_byte(), state),                                     // XRI
        0xf6 => arithmetic::or(next_byte(), state),                                      // ORI
        0xfe => arithmetic::cmp(next_byte(), state),                                     // CPI

        _ => not_implemented(state),
    }

    state.pc += 1;
}

fn not_implemented(state: &EmulatorState) {
    let instruction = state.memory[state.pc as usize];
    panic!(
        "Unimplemented instruction {:#02X} at {:#04X}",
        instruction, state.pc
    );
}
