#include <SDL.h>
#include <stdio.h>
#include <sys\types.h> 
#include <sys\stat.h>

#include "Tests.h"
#include "Cpu.h"
#include "CpuUtilities.h"
#include "Memory.h"

static Uint8 _test__romImage[] = {
    0xc3, 0x00, 0x00 //infinite loop at 0x00
};

void _tests_dummy_callback(struct instruction* instruction, Uint64 b) {

}

void _tests_dummy_callback2(Uint64 b) {

}

void _tests_assert_instruction_opcode_resolution(struct instructionOpcode* expected, struct instructionOpcode* actual) {
    char success = 1;

    if (expected->opcode != actual->opcode) {
        success = 0;
    }

    if (expected->prefix != actual->prefix) {
        success = 0;
    }

#ifndef _____DISABLE_DEBUG_FUNCTIONALITY
    if (expected->opcodePc != actual->opcodePc) {
        success = 0;
    }
#endif

    if (expected->tstatesElapsed != actual->tstatesElapsed) {
        success = 0;
    }

    if (expected->startPc != actual->startPc) {
        success = 0;
    }

#ifndef _____DISABLE_DEBUG_FUNCTIONALITY
    if (!success) {
        printf("Failed instruction opcode resolution test:\n");
        printf("expected:  start PC %u   opcode PC %u   opcode 0x%x   prefix %s   tstates %u\n", expected->startPc, expected->opcodePc, expected->opcode, expected->prefixString, expected->tstatesElapsed);
        
        printf("           displacement 0x%x   displacement PC %u\n", expected->displacement, expected->displacementPc);
        printf("  actual:  start PC %u   opcode PC %u   opcode 0x%x   prefix %s   tstates %u\n", actual->startPc, actual->opcodePc, actual->opcode, actual->prefixString, actual->tstatesElapsed);
        printf("           displacement 0x%x   displacement PC %u\n", actual->displacement, actual->displacementPc);
        exit(1);
    }
#endif
}

void tests_screen_load() {
    struct stat info;
    const char* filename = "scr0.bin";
    if (stat(filename, &info) != 0) {
        return;
    }

    char* file = (Uint8*)malloc(info.st_size);
    if (file == NULL) {
        return;
    }
    FILE* fp;
    errno_t result = fopen_s(&fp, filename, "rb");
    if (result) {
        return;
    }
    /* Try to read a single block of info.st_size bytes */
    size_t blocks_read = fread(file, info.st_size, 1, fp);
    if (blocks_read != 1) {
        return;
    }
    fclose(fp);

    memory_load(file, 16384, 6912);
    free(file);
}

void tests_rom_load() {
    memory_load(_test__romImage, 0, sizeof(_test__romImage));
}

void _test_instruction_opcode_resolution() {
    static Uint8 _romStart[] = {
        0x13,
        0xdd, 0xcb, 0xF1, 0x14,             // 14 with prefix DDCB
        0x15,                               // 15 with prefix None
        0x16,

        // PC: 7
        0xfd, 0xcb, 0xF2, 0x17,             // 17 with prefix FDCB

        // PC: 11
        0xfd, 0x18,                         // 18 with prefix FD
        0x19,
        
        // PC: 14
        0xdd, 0xdd, 0xdd, 0x20,
        // PC: 18
        0xdd, 0x30,
        // PC: 20
        0xdd, 0xed, 0x31,
        // PC: 23
        0xdd, 0xfd, 0x32,

        // PC: 26
        0xdd, 0xdd, 0xcb, 0xF3, 0x21,       // 21 with prefix DDCB
        // PC: 31
        0xdd, 0xfd, 0x22,                   // 22 with prefix FD
        // PC: 34
        0xed, 0x23,                         // 23 with prefix ED
        0x24,

        // PC: 37
        0xdd, 0xed, 0x25,                   // 25 with prefix ED
        // PC: 40
        0xcb, 0x26,                         // 26 with prefix CB
        0x27,
    };
    memory_load(_romStart, 0, sizeof(_romStart));
    cpu_start(0, 0, _tests_dummy_callback, _tests_dummy_callback2, 1);

    struct instructionOpcode expected[] = {
        {.prefix = NoPrefix, .opcode = 0x13, .tstatesElapsed = 4, .startPc = 0, },
        {.prefix = DDCB, .opcode = 0x14, .tstatesElapsed = 16, .startPc = 1, .displacement = 0xF1, .displacementPc = 3},
        {.prefix = NoPrefix, .opcode = 0x15, .tstatesElapsed = 4, .startPc = 5, },
        {.prefix = NoPrefix, .opcode = 0x16, .tstatesElapsed = 4, .startPc = 6, },
        {.prefix = FDCB, .opcode = 0x17, .tstatesElapsed = 16, .startPc = 7, .displacement = 0xF2, .displacementPc = 9},
        {.prefix = FD, .opcode = 0x18, .tstatesElapsed = 8, .startPc = 11, },
        {.prefix = NoPrefix, .opcode = 0x19, .tstatesElapsed = 4,  .startPc = 13, },
        {.prefix = DD, .opcode = 0x20, .tstatesElapsed = 16,  .startPc = 14, },
        {.prefix = DD, .opcode = 0x30, .tstatesElapsed = 8,  .startPc = 18, },
        {.prefix = ED, .opcode = 0x31, .tstatesElapsed = 12,  .startPc = 20, },
        {.prefix = FD, .opcode = 0x32, .tstatesElapsed = 12,  .startPc = 23, },
        {.prefix = DDCB, .opcode = 0x21, .tstatesElapsed = 20,  .startPc = 26, .displacement = 0xF3, .displacementPc = 29},
        {.prefix = FD, .opcode = 0x22, .tstatesElapsed = 12, .startPc = 31, },
        {.prefix = ED, .opcode = 0x23, .tstatesElapsed = 8,  .startPc = 34, },
        {.prefix = NoPrefix, .opcode = 0x24, .tstatesElapsed = 4,  .startPc = 36, },
        {.prefix = ED, .opcode = 0x25, .tstatesElapsed = 12, .startPc = 37, },
        {.prefix = CB, .opcode = 0x26, .tstatesElapsed = 8,  .startPc = 40, },
        {.prefix = NoPrefix, .opcode = 0x27, .tstatesElapsed = 4,  .startPc = 42, },
    };

    for (int i = 0; i < sizeof(expected)/sizeof(struct instructionOpcode); i++) {
        struct instructionOpcode actual = cpu_fetch_instruction_opcode___tests();
        
        expected[i].prefixString = cpu_prefix_to_string(expected[i].prefix);    // populate string
        _tests_assert_instruction_opcode_resolution(&expected[i], &actual);
    }
}

// asserts that given that the two 8bit registers make up the value of the corresponding 16bit register
void _test_assert_regs_equal(Uint8* msb, Uint8* lsb, Uint16* sixteenBit) {
    if (
        *(Uint8*)sixteenBit != *lsb ||
        *((Uint8*)sixteenBit+1) != *msb
        ) {
        printf("Failed register equality test:\n");
        printf("MSB 0x%x    LSB 0x%x    differ from sixteen bit 0x%x", *msb, *lsb, *sixteenBit);
        exit(1);
    }
}

void _test_registers() {
    cpu_start(0, 0, _tests_dummy_callback, _tests_dummy_callback2, 1);
    struct regs* r = cpu_regs();

    r->A = 0x13; r->F = 0x37;
    r->B = 0x23; r->C = 0x38;
    r->D = 0x24; r->E = 0x39;
    r->H = 0x24; r->L = 0x39;
    r->IXH = 0x25; r->IXL = 0x3A;
    r->IYH = 0x26; r->IYL = 0x3B;
    r->I = 0x27; r->R = 0x3C;

    _test_assert_regs_equal(&r->A, &r->F, r->AF);
    _test_assert_regs_equal(&r->B, &r->C, r->BC);
    _test_assert_regs_equal(&r->D, &r->E, r->DE);
    _test_assert_regs_equal(&r->H, &r->L, r->HL);
    _test_assert_regs_equal(&r->IXH, &r->IXL, r->IX);
    _test_assert_regs_equal(&r->IYH, &r->IYL, r->IY);
    _test_assert_regs_equal(&r->I, &r->R, r->IR);
}

void _test_sixteenBit() {
    union immediateValue t;
    t.sixteenBit = 0x1338;
    if ( t.eightBit != 0x38 ) {
        printf("Failed union sixteenBit test:\n");
        exit(1);
    }
}

void tests_run_unit_tests() {
    _test_sixteenBit();
    _test_instruction_opcode_resolution();
    _test_registers();
}

void tests_run_execution_test(void* romStart, int size) {
    cpu_destroy();
    memory_load(romStart, 0, size);
    cpu_start(0, 0, _tests_dummy_callback, _tests_dummy_callback2, 1);

    *cpu_regs()->BC = 0x8221;
    *cpu_regs()->DE = 0xf040;
    *cpu_regs()->HL = 0xf234;

    //cpu_regs()->IFF = CPU_IFF2_BIT;

    cpu_regs()->A = 0x8f;
    cpu_regs()->C = 0xfe;

    cpu_regs()->F = 0;
    //cpu_regs()->F |= CPU_FLAG_Z_ZERO;
    //cpu_regs()->F |= CPU_FLAG_C_CARRY;
    //cpu_regs()->F |= CPU_FLAG_PV_PARITY_OVERFLOW;
    //cpu_regs()->F |= CPU_FLAG_S_SIGN;
    //cpu_regs()->F |= CPU_FLAG_N_SUBTRACT;
    //cpu_regs()->F |= CPU_FLAG_H_HALF_CARRY;
        *cpu_regs()->BC = 0x1345;
        *cpu_regs()->DE = 0x0678;
        *cpu_regs()->HL = 0xf234;
        cpu_regs()->A = 0x91;

        *cpu_regs()->IX = 0xf801;
        *cpu_regs()->IY = 0xf902;
    
    //memory_write16(0xF337, 0x1234);
    memory_write8(*cpu_regs()->HL, 0x00);
    memory_write8(*cpu_regs()->DE, 0x25);

    memory_write8(*cpu_regs()->IX - 0x05, 0x00);
    memory_write8(*cpu_regs()->IY + 0x12, 0x01);

    cpu_regs()->SP = 0xf100;
    //cpu_regs()->SP = 0xffff;
    memory_write16(0xf100, 0x1337);     // value at top of stack

    //cpu_begin_decode_only_run();
    while (cpu_regs()->PC < size) {
        cpu_print_regs();
        struct instruction* instruction = cpu_run_next_instruction();
        printf("%04x\t%s\n", instruction->startPc, instruction->dissassembledName);
        cpu_destroy_instruction(instruction);
    }
    cpu_print_regs();
    // we stop, since PC now points past the size passed in
    //printf("\n:::::::::::::: %x \n", memory_read16(0xff00));
    printf("\nPC %04x is past test program end... stopping\n\n", cpu_regs()->PC);
    //cpu_end_decode_only_run();
}

void tests_run_execution_tests() {
    memory_start( NULL );
    cpu_start(0, 0, _tests_dummy_callback, _tests_dummy_callback2, 1);

    static Uint8 data0[] = { 

        //0xfd, 0xfd, 0x09,
        //0xdd, 0xfd, 0x09,
        //0xfd, 0xdd, 0x09,

        0x01, 0x01, 0x00, // ld bc, nn
        0x21, 0x02, 0x00, // ld hl, nn

        0x11, 0x0a, 0x00, // ld de, nn

        0xed, 0xb0,         // ldir

        0x00,//nop
    };
    tests_run_execution_test(data0, sizeof(data0));
    printf("Entering infinite loop\n");
    while (1) {}
}