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

use crate::structs::*;

mod instructions;
mod structs;

pub const MEMORY_SIZE: usize = 8192;

fn main() {
    let mut state = EmulatorState::default();

    // 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.next_byte();

    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);
            }
        }

        /* Data Transfer */
        // MVI
        0x06 | 0x0e | 0x16 | 0x1e | 0x26 | 0x2e | 0x36 | 0x3e => transfer::mvi(
            state.next_byte(),
            register_from_num((instruction & 0x38) >> 3),
            state,
        ),

        // MOV
        0x40..=0x7f => transfer::mov(
            register_from_num(instruction & 0x7),
            register_from_num((instruction & 0x38) >> 3),
            state,
        ),

        // Accumulator transfer instructions
        0x02 => transfer::stax(Register::B, state), // STAX B
        0x0a => transfer::ldax(Register::B, state), // LDAX B
        0x12 => transfer::stax(Register::D, state), // STAX D
        0x1a => transfer::ldax(Register::D, state), // LDAX D
        0x32 => state.memory[state.next_word() as usize] = state.a, // STA
        0x3a => state.a = state.memory[state.next_word() as usize], // LDA

        // 16-bit transfer instructions
        0x01 => set_register_pair(Register::B, state.next_word(), state), // LXI B
        0x11 => set_register_pair(Register::D, state.next_word(), state), // LXI D
        0x21 => set_register_pair(Register::H, state.next_word(), state), // LXI H
        0x31 => set_register_pair(Register::SP, state.next_word(), state), // LXI SP
        0x22 => transfer::shld(state.next_word(), state),                 // SHLD
        0x2a => transfer::lhld(state.next_word(), state),                 // LHLD

        // Stack instructions
        0xc1 => transfer::pop_reg(Register::B, state), // POP B
        0xc5 => transfer::push_reg(Register::B, state), // PUSH B
        0xd1 => transfer::pop_reg(Register::D, state), // POP D
        0xd5 => transfer::push_reg(Register::D, state), // PUSH D
        0xe1 => transfer::pop_reg(Register::H, state), // POP H
        0xe5 => transfer::push_reg(Register::H, state), // PUSH H
        0xf1 => transfer::pop_reg(Register::A, state), // POP PSW
        0xf5 => transfer::push_reg(Register::A, state), // PUSH PSW

        /* 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),

        // Accumulator rotates
        0x07 => arithmetic::rlc(state),
        0x0f => arithmetic::rrc(state),
        0x17 => arithmetic::ral(state),
        0x1f => arithmetic::rar(state),

        // Carry and complements
        0x27 => panic!("Auxiliary Carry not implemented, unable to execute DAA instruction"),
        0x2f => state.a = !state.a,       // CMA
        0x37 => state.cc.c = true,        // STC
        0x3f => state.cc.c = !state.cc.c, // CMC

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

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

        0xa0..=0xa7 => arithmetic::and_reg(register_from_num(instruction & 0x7), state), // ANA
        0xa8..=0xaf => arithmetic::xor_reg(register_from_num(instruction & 0x7), state), // XRA
        0xb0..=0xb7 => arithmetic::or_reg(register_from_num(instruction & 0x7), state),  // ORA
        0xb8..=0xbf => arithmetic::cmp_reg(register_from_num(instruction & 0x7), state), // CMP
        0xe6 => arithmetic::and(state.next_byte(), state),                               // ANI
        0xee => arithmetic::xor(state.next_byte(), state),                               // XRI
        0xf6 => arithmetic::or(state.next_byte(), state),                                // ORI
        0xfe => arithmetic::cmp(state.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
    );
}
