#include "doctest.h"

#include <cpu/cpu.h>
#include <memory/ram.h>


TEST_CASE("simple load and add")
{
  u8 test_ram[] = {
    0x3E, 0x10, // LD A, $0x10
    0x87,       // ADD A, A
  };
  RAM r(test_ram, 3, true);
  Cpu cpu(&r);

  CHECK(cpu.state.PC == 0x0);

  cpu.step();

  CHECK(cpu.state.PC == 0x2);
  CHECK(cpu.state.A == 0x10);

  cpu.step();

  CHECK(cpu.state.PC == 0x3);
  CHECK(cpu.state.A == 0x20);
}

TEST_CASE("direct jump")
{
  u8 test_ram[] = {
    0xC3, 0x05, 0x00, // JMP $0x0005
    0x00, 0x00, // NOP NOP
    0xC3, 0x00, 0x00, // JMP $0x0000
  };

  RAM r(test_ram, 8, true);
  Cpu cpu(&r);

  CHECK(cpu.state.PC == 0x0000);

  cpu.step();

  CHECK(cpu.state.PC == 0x0005);

  cpu.step();

  CHECK(cpu.state.PC == 0x0000);
}

TEST_CASE("LD HL, nn; LD A, [HL]; LD A, n; LD [HL], A")
{
  u8 test_ram[] = {
    0x21, 0x20, 0x00, // LD HL, $0x0020
    0x7E,             // LD A, [HL]
    0x3E,0x5A,        // LD A, $0x5A
    0x77,             // LD [HL], A
    0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0xA5, // . = 0x0020
  };

  RAM r(test_ram, 0x0021, false);
  Cpu cpu(&r);

  CHECK(cpu.state.PC == 0x0);

  cpu.step();

  CHECK(cpu.state.PC == 0x3);
  CHECK(cpu.state.HL == 0x0020);
  CHECK(cpu.state.A == 0x0);

  cpu.step();

  CHECK(cpu.state.PC == 0x4);
  CHECK(cpu.state.A == 0xA5);

  cpu.step();

  CHECK(cpu.state.PC == 0x6);
  CHECK(cpu.state.A == 0x5A);

  cpu.step();

  CHECK(cpu.state.PC == 0x7);
  CHECK(test_ram[0x20] == 0x5A);
}

TEST_CASE("PUSH rr ; POP rr")
{
  u8 test_ram[] = {
    /* 0x0000 */ 0x31, 0x10, 0x00, // LD SP, $0x0010
    /* 0x0003 */ 0x01, 0xAA, 0x55, // LD BC, $0x55AA
    /* 0x0006 */ 0xC5, // PUSH BC
    /* 0x0007 */ 0x01, 0x55, 0xAA, // LD BC, $0xAA55
    /* 0x000A */ 0xD1, // POP DE
    /* 0x000B */ 0x00, 0x00, 0x00, 0x00, 0x00,
    /* 0x0010 */ 0x12, 0x34,
  };

  RAM r(test_ram, 0x0012, false);
  Cpu cpu(&r);

  cpu.step();

  CHECK(cpu.state.PC == 0x0003);
  CHECK(cpu.state.SP == 0x0010);

  cpu.step();

  CHECK(cpu.state.PC == 0x0006);
  CHECK(cpu.state.BC == 0x55AA);

  cpu.step();

  CHECK(cpu.state.PC == 0x0007);
  CHECK(cpu.state.SP == 0x000E);
  CHECK(test_ram[0x000E] == 0xAA);
  CHECK(test_ram[0x000F] == 0x55);

  cpu.step();

  CHECK(cpu.state.PC == 0x000A);
  CHECK(cpu.state.BC == 0xAA55);

  cpu.step();

  CHECK(cpu.state.PC == 0x000B);
  CHECK(cpu.state.DE == 0x55AA);
}

TEST_CASE("CALL ; RET")
{
  u8 test_ram[] = {
    /* 0x0000 */ 0x31, 0x10, 0x00, // LD SP, $0x0010
    /* 0x0003 */ 0xCD, 0x0A, 0x00, // CALL $0x000A
    /* 0x0006 */ 0x00, 0x00, 0x00, 0x00, // 4 x NOP
    /* 0x000A */ 0x00, // NOP
    /* 0x000B */ 0xC9, // RET
    /* 0x000C */ 0xaa, 0xaa, 0xaa, 0xaa,
    /* 0x000D */ 0xaa, 0xaa, 0xaa,
    /* 0x0010 */
  };

  RAM r(test_ram, 0x10, false);
  Cpu cpu(&r);

  cpu.step(); // LD SP, $0x0010

  CHECK(cpu.state.PC == 0x0003);
  CHECK(cpu.state.SP == 0x0010);

  cpu.step(); // CALL $0x000A

  CHECK(cpu.state.PC == 0x000A);
  CHECK(cpu.state.SP == 0x000E);
  CHECK(test_ram[0x000F] == 0x00);
  CHECK(test_ram[0x000E] == 0x06);

  cpu.step(); // NOP

  CHECK(cpu.state.PC == 0x000B);
  CHECK(cpu.state.SP == 0x000E);
  CHECK(test_ram[0x000F] == 0x00);
  CHECK(test_ram[0x000E] == 0x06);

  cpu.step(); // RET

  CHECK(cpu.state.PC == 0x0006);
  CHECK(cpu.state.SP == 0x0010);
}

TEST_CASE("JR e")
{
  u8 test_ram[] = {
    0x18, 0x02,
    0x00, 0x00,
    0x18, (u8)(-0x06),
  };

  RAM r(test_ram, 0x6, true);
  Cpu cpu(&r);

  CHECK(cpu.state.PC == 0x0);

  cpu.step();

  CHECK(cpu.state.PC == 0x4);

  cpu.step();

  CHECK(cpu.state.PC == 0x0);
}

TEST_CASE("RST op leads to correct call")
{
  u8 test_ram[] = { 0x00 };
  RAM r(test_ram, 0x1, true);

  Cpu cpu(&r);

  u16 expected_pc;

  SUBCASE("RST $00") { test_ram[0] = 0xC7; expected_pc = 0x00; }
  SUBCASE("RST $08") { test_ram[0] = 0xCF; expected_pc = 0x08; }
  SUBCASE("RST $10") { test_ram[0] = 0xD7; expected_pc = 0x10; }
  SUBCASE("RST $18") { test_ram[0] = 0xDF; expected_pc = 0x18; }
  SUBCASE("RST $20") { test_ram[0] = 0xE7; expected_pc = 0x20; }
  SUBCASE("RST $28") { test_ram[0] = 0xEF; expected_pc = 0x28; }
  SUBCASE("RST $30") { test_ram[0] = 0xF7; expected_pc = 0x30; }
  SUBCASE("RST $38") { test_ram[0] = 0xFF; expected_pc = 0x38; }

  cpu.step();

  CHECK(cpu.state.PC == expected_pc);
}