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

mod instructions;

pub struct EmulatorState {
    a: u8,
    b: u8,
    c: u8,
    d: u8,
    e: u8,
    h: u8,
    l: u8,

    cc: ConditionCodes,

    /// Stack pointer
    sp: u16,
    /// Memory pointer
    pc: u16,
    memory: [u8; 8000],
}

pub struct ConditionCodes {
    /// Zero (Z), set if the result is zero.
    z: bool,
    /// Sign (S), set if the result is negative.
    s: bool,
    /// Parity (P), set if the number of 1 bits in the result is even.
    p: bool,
    /// Carry (C), set if the last addition operation resulted in a carry or if the last subtraction operation required a borrow.
    c: bool,
    /// Auxiliary carry (AC or H), used for binary-coded decimal arithmetic (BCD).
    ac: bool,
}

#[derive(PartialEq)]
pub enum Register {
    B, C, D, E,
    H, L, M, A,
}

/// Returns a Register enum based on the input number 0..7 in the order B,C,D,E,H,L,M,A
fn register_from_num(b: u8) -> Register {
    match b {
        0 => Register::B,
        1 => Register::C,
        2 => Register::D,
        3 => Register::E,
        4 => Register::H,
        5 => Register::L,
        6 => Register::M,
        7 => Register::A,
        _ => panic!("'{b}' cannot be converted to register enum"),
    }
}

fn get_register(register: Register, state: &EmulatorState) -> u16 {
    match register {
        Register::B => state.b as u16,
        Register::C => state.c as u16,
        Register::D => state.d as u16,
        Register::E => state.e as u16,
        Register::H => state.h as u16,
        Register::L => state.l as u16,
        Register::A => state.a as u16,
        Register::M => (state.memory[(state.h as usize)] as u16) << 8 | (state.memory[state.l as usize] as u16),
    }
}

fn set_register(register: Register, value: u8, state: &mut EmulatorState) {
    match register {
        Register::B => state.b = value,
        Register::C => state.c = value,
        Register::D => state.d = value,
        Register::E => state.e = value,
        Register::H => state.h = value,
        Register::L => state.l = value,
        Register::A => state.a = value,
        Register::M => panic!("Cannot set pseudoregister 'M'"),
    };
}

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, ac: true },
        pc: 0,
        memory: [0; 8000],
    };

    // 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..8000 {
        state.memory[i] = file[i];
    }

    while state.pc < 8000 {
        tick(&mut 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
        0x80..=0x87 => arithmetic::add(register_from_num(instruction & 0xf), state), // ADD
        0xc6        => arithmetic::adi(next_byte(), state), // ADI
        _           => 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);
}
