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

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

    for i in 0..(MEMORY_SIZE.min(file.len())) {
        state.memory[i] = file[i];
    }

    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;
        return state.memory[state.pc as usize];
    };

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

        /* ADD */

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

        0x80..=0x87 => arithmetic::add(register_from_num(instruction & 0xf), state), // ADD
        0x88..=0x8f => arithmetic::adc(register_from_num(instruction & 0xf), state), // ADC
        0xc6        => arithmetic::adi(next_byte(), state), // ADI
        0xce        => arithmetic::aci(next_byte(), state), // ACI


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

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