#pragma once

#include "types.h"

#include "memory/mem_device.h"

typedef u8 opcode_t;

enum Flags {
  F_ZERO = 0x8,
  F_SUB = 0x4,
  F_HALF = 0x2,
  F_CARRY = 0x1,
};

enum AluOp : int {
  ADD = 0,
  ADC = 1,
  SUB = 2,
  SBC = 3,
  AND = 4,
  XOR = 5,
  OR  = 6,
  CP  = 7,
};

enum CC
{
  COND_NZ = 0,
  COND_Z = 1,
  COND_NC = 2,
  COND_C = 3,
};

enum InterruptType : u8
{
  INT_VBlank = 0x1,
  INT_LCDSTAT = 0x2,
  INT_Timer = 0x4,
  INT_Serial = 0x8,
  INT_Joypad = 0x10,

  INT_MASK = 0x1F,
};


/**
 IME - Interrupt Master Enable

 An EI instruction will enable the interrupts, but delayed. During the next instruction after EI,
 interrupts are still disabled. For this to be emulated we use a small state machine. which works as follows

 instruction EI - sets IME_SCHEDULED
 handleInterrupts -> IME_SCHEDULED to IME_ON (but no call to isr yet)
 instruction any - is IME_ON, but no chance for call to isr yet
 handleInterrupts -> is IME_ON, do a call to isr if necessary
 */
enum IME_state
{
  IME_OFF,
  IME_SCHEDULED,
  IME_ON,
};

struct Cpu_state {
  // Registers
  union {
    u16 BC;
    struct { u8 B; u8 C; };
  };
  union {
    u16 DE;
    struct { u8 D; u8 E; };
  };
  union {
    u16 HL;
    struct { u8 H; u8 L; };
  };

  u8 A;
  u16 SP;
  u16 PC;

  bool zero;
  bool subtract;
  bool halfcarry;
  bool carry;

  IME_state IME;

  u8 IE;
  u8 IF;

  void setAF(u16 v);
  u16 getAF();
};

class Cpu {
private:
  u8 readPC8();
  u16 readPC16();

  void pushStack8(u8 data);
  u8 popStack8();

  void pushStack16(u16 data);
  u16 popStack16();

  void aluop8(AluOp op, u8 lhs, u8 rhs, u8& out, bool update_carry = true);

  inline
  void aluop8(AluOp op, u8 rhs, bool update_carry = true)
  {
    aluop8(op, state.A, rhs, state.A, update_carry);
  }

  void doCall(u16 target);
  void doRet();

  bool decodeCond(u8 cc);

  bool handleInterrupts();
  void executeInstruction();

  void reset();

public:
  Cpu(Mem_device* bus);

  Cpu_state state;
  Mem_device* bus;
  unsigned long processed_mcycles;


  void signalInterrupt(InterruptType it);

  void step();
};