//#include "log.h"
#include <assert.h>
#include <string.h>
#include "../include/cpu.h"
#include "cpu.h"
#include "memory.h"
#include "op.h"
#include "decoding.h"
#include "log.h"

/*
 * =====================================================================================
 *
 *       Filename:  cpu_state.c
 *
 *    Description:  6502 CPU emulator 
 *
 *        Version:  1.0
 *        Created:  2023-09-21 10:10:26 PM
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  William Nolin, 
 *   Organization:  
 *
 * =====================================================================================
 */

CPU cpu_state;

void cpu_init() {
    cpu_state.program_counter = 0x8000;
    cpu_state.stack_pointer = 0xfd;
    cpu_state.accumulator = 0x00;
    cpu_state.x = 0x00;
    cpu_state.y = 0x00;
    cpu_state.status = 0x04;
    cpu_state.oam_dma_triggered = false;
    cpu_state.nmi_requested = false;
    cpu_state.busy_cycle_count = 0;
}

void print_registers(byte op, unsigned long cycle_count) {
    log_trace("%#02x %#02x %s \t A:%#02x X:%#02x Y:%#02x F:%#02x SP:%#02x \t [%d]",
              cpu_state.program_counter - 1,    // The PC as been incremented when printing
              op,
              get_op_code_name(op),
              cpu_state.accumulator,
              cpu_state.x,
              cpu_state.y,
              cpu_state.status,
              cpu_state.stack_pointer,
              cycle_count);
}

void cpu_process_nmi() {
    cpu_stack_push_context();

    address handler_addr = mem_get_word(0xfffa);
    log_debug("NMI %#04x", handler_addr);

    cpu_state.nmi_requested = false;
    cpu_state.program_counter = handler_addr;
}

void oam_dma_upload() {
    byte page_high_addr = ppu_get_state()->oam_dma_register;    // TODO
    address page_addr = ((address) page_high_addr) << 8;
    byte n = 0xff;

    byte *ram_source = mem_get_ptr(page_addr);
    byte *oam_destination = ppu_get_state()->oam;

    memcpy(oam_destination, ram_source, n);

    log_debug("OAM DMA %#04x", page_addr);
    cpu_add_cycles(513);    // TODO
}

void cpu_cycle() {
    if (cpu_state.busy_cycle_count > 0) {
        // The last operation is not done yet
        cpu_state.busy_cycle_count--;
        return;
    }

    if (cpu_state.nmi_requested) {
        cpu_process_nmi();
    }

    if (cpu_state.oam_dma_triggered) {
        oam_dma_upload();
        cpu_state.oam_dma_triggered = false;
        return;
    }

    byte op = cpu_get_next_byte();
    print_registers(op, system_get_cycles());
    process_op_code(op);
}

void cpu_add_cycles(unsigned int cycle_count) {
    cpu_state.busy_cycle_count += cycle_count;
}

// === Registers ===
bool cpu_get_flag(byte mask) {
    return cpu_state.status & mask;
}

void cpu_set_flag(byte mask, bool set) {
    if (set) {
        cpu_state.status |= mask;
    } else {
        cpu_state.status &= ~mask;
    }
}

byte cpu_get_next_byte() {
    byte next_byte = mem_get_byte(cpu_state.program_counter);
    cpu_state.program_counter++;
    return next_byte;
}

word cpu_get_next_word() {
    word next_word = mem_get_word(cpu_state.program_counter);
    cpu_state.program_counter += 2;
    return next_word;
}

void cpu_stack_push(byte value) {
    assert(cpu_state.stack_pointer > 0);

    address mem_addr = CPU_STACK_ADDR | cpu_state.stack_pointer;
    mem_set_byte(mem_addr, value);
    cpu_state.stack_pointer--;
}

byte cpu_stack_pop() {
    assert(cpu_state.stack_pointer < 0xff);

    cpu_state.stack_pointer++;
    address mem_addr = CPU_STACK_ADDR | cpu_state.stack_pointer;
    byte value = mem_get_byte(mem_addr);
    return value;
}

void cpu_stack_push_context() {
    cpu_stack_push(cpu_state.program_counter >> 8);
    cpu_stack_push(cpu_state.program_counter & 0xff);
    cpu_stack_push(cpu_state.status);
}

void cpu_stack_pop_context() {
    byte value = cpu_stack_pop();
    value &= 0xef;  // The B mask cannot be set as it is a CPU signal
    value |= 0x20;  // This value is always set
    cpu_state.status = value;

    byte lo = cpu_stack_pop();
    address pc = cpu_stack_pop() << 8;
    pc += lo;
    cpu_state.program_counter = pc;
}

void cpu_trigger_oam_dma() {
    cpu_state.oam_dma_triggered = true;
}

void cpu_trigger_nmi() {
    cpu_state.nmi_requested = true;
}

CPU *cpu_get_state() {
    return &cpu_state;
}