#ifndef __CPU____H__
#define __CPU____H__

#include <SDL.h>

#define CPU_FLAG_S_SIGN                     0x80
#define CPU_FLAG_Z_ZERO                     0x40
#define CPU_FLAG_H_HALF_CARRY               0x10
#define CPU_FLAG_PV_PARITY_OVERFLOW         0x04
#define CPU_FLAG_N_SUBTRACT                 0x02
#define CPU_FLAG_C_CARRY                    0x01
#define CPU_FLAG_ALL                        CPU_FLAG_S_SIGN | CPU_FLAG_Z_ZERO | CPU_FLAG_H_HALF_CARRY | CPU_FLAG_PV_PARITY_OVERFLOW | CPU_FLAG_N_SUBTRACT | CPU_FLAG_C_CARRY

#define CPU_IFF1_BIT                        0x01
#define CPU_IFF2_BIT                        0x02

#define CPU_MAX_ON_INSTRUCTION_EXECUTED_CALLBACKS 32

enum prefixType { NoPrefix, DDCB, DD, FDCB, FD, ED, CB };
enum immediateType { NoImmediate = 0, EightBit = 1, SixteenBit = 2 };

struct prefix {
    enum prefixType type;
    // GUARANTEED to point to static memory, therefore MUST never be free'd
    const char* humanReadable;
};

struct opcode {
    Uint8 value;
};

union immediateValue {
    Uint16 sixteenBit;
    Uint8 eightBit;
};

struct immediate {
    union immediateValue value;
};

enum executionType { DecodedOnly = 0, UnknownInstruction = 1, Executed = 2, SkippedDueToHalt = 3 };

struct regs {
    Uint8 F;
    Uint8 A;

    Uint8 C;
    Uint8 B;

    Uint8 E;
    Uint8 D;

    Uint8 L;
    Uint8 H;

    Uint16* AF;
    Uint16* BC;
    Uint16* DE;
    Uint16* HL;

    Uint8 IXL;
    Uint8 IXH;
    Uint16* IX;

    Uint8 IYL;
    Uint8 IYH;
    Uint16* IY;

    Uint8 R;
    Uint8 I;
    Uint16* IR;

    Uint16 SP;
    Uint16 PC;

    Uint8 IFF;

    Uint8 F_alt;
    Uint8 A_alt;

    Uint8 C_alt;
    Uint8 B_alt;

    Uint8 E_alt;
    Uint8 D_alt;

    Uint8 L_alt;
    Uint8 H_alt;

    Uint16* AF_alt;
    Uint16* BC_alt;
    Uint16* DE_alt;
    Uint16* HL_alt;
};

struct regNames {
    const char* A; const char* F; const char* B; const char* C; const char* D; const char* E; const char* H; const char* L; const char* AF; const char* BC; const char* DE; const char* HL; const char* IXL; const char* IXH; const char* IX; const char* IYL; const char* IYH; const char* IY; const char* R; const char* I; const char* IR; const char* SP; const char* PC; const char* IFF; const char* F_alt; const char* A_alt; const char* B_alt; const char* C_alt; const char* D_alt; const char* E_alt; const char* H_alt; const char* L_alt;
};

// represents a resolved instruction, which has been interpreted
// Z80 instructions are of one of two formats:
// 
// [0-1 prefix bytes]  [1 opcode byte]  [0-1 displacement bytes]  [0-2 bytes of immediate data]
//  or
// [2 prefix bytes]  [1 displacement byte]  [1 opcode byte]
struct instruction {
    // what has been done for this instruction
    enum executionType outcome;

    char* dissassembledName;
    Uint8 disassembledNameReadonly;

    // how many CPU tstates were needed for this instruction
    Uint32 tstatesElapsed;

    // address of first and last byte of this instruction
    // everything is counted, including potentially skipped DD prefix bytes
    Uint16 startPc;
    Uint16 endPc;

    struct prefix prefix;
    Uint8 displacementValue;
    Uint8 opcodeValue;
    struct immediate immediate;
};

enum interruptMode { Mode0 = 0, Mode1 = 1, Mode2 = 2 };

typedef void func_cpu_instruction_executed_callback(struct instruction*, Uint64);
typedef void func_cpu_on_tstates_elapsed_callback(Uint64);

/// <summary>
/// ARGUMENTS:
///     instruction - guaranteed to not be NULL; caller does NOT OWN pointer
/// RETURNS:
///     tstate adjustment to be applied due to the instruction that was just executed
/// </summary>
typedef Uint32(*func_cpu_instruction_executed_tstate_adjustment_callback)(const struct instruction* instruction);

/// <summary>
/// ARGUMENTS:
///     tstatesElapsedInBatch - in the batch started by cpu_run_many()
/// </summary>
typedef void(*func_cpu_instruction_before_execution_callback)(Uint64 tstatesElapsedInBatch);

/// <summary>
/// RETURNS:
///     tstate adjustment to be applied so far to an in-flight instruction
/// </summary>
typedef Uint32(*func_cpu_inflight_instruction_get_tstate_adjustment_callback)(const struct instruction* instruction);

void cpu_start(
    Uint8 logEveryInstruction, 
    func_cpu_instruction_executed_callback onInstructionExecutedCallback, 
    func_cpu_on_tstates_elapsed_callback onTstatesElapsedCallback,
    func_cpu_inflight_instruction_get_tstate_adjustment_callback inFlightInstructionGetTStateAdjustmentCallback,
    Uint8 generateMnemonics);

void cpu_destroy();

void cpu_request_interrupt();
Uint8 cpu_is_interrupt_requested();

void cpu_set_interrupt_mode(enum interruptMode mode);
enum interruptMode cpu_get_interrupt_mode();

Uint8 cpu_can_continue_string_operation(Uint32 currentTStatesConsumedByInstruction);

// runs CPU until about the specified tstate target has been reached or exceeded,
// whichever is closer
Uint64 cpu_run_many(
    Uint64 tstateTarget,
    func_cpu_instruction_before_execution_callback onBeforeInstructionExecutedCallback,
    func_cpu_instruction_executed_tstate_adjustment_callback onInstructionExecutedTStateAdjustmentCallback
);

void cpu_begin_decode_only_run();
void cpu_end_decode_only_run();

// gets a human-readable name for a prefix
const char* cpu_prefix_to_string(enum prefixType prefix);

// get a pointer to the CPU's registers
struct regs* cpu_regs();

Uint8 cpu_mode();

void cpu_halt();
Uint8 cpu_is_halted();
void cpu_prepare_after_state_load(Uint8 isHalted, Uint8 isInterruptRequested);

void cpu_log_instruction(struct instruction* instruction);

#endif // !__CPU____H__
