/*
 * Copyright (C) 2025 dwlr <dweller@cabin.digital>
 *
 * BSD 3-Clause License (BSD-3-Clause)
 * See LICENSE for details
 */


#define C8_RESET_VECTOR     0x200
#define C8_CYCLES_PER_FRAME 20

static const u8 fnt[] =
{
    0xF0, 0x90, 0x90, 0x90, 0xF0, /* 0 */
    0x20, 0x60, 0x20, 0x20, 0x70, /* 1 */
    0xF0, 0x10, 0xF0, 0x80, 0xF0, /* 2 */
    0xF0, 0x10, 0xF0, 0x10, 0xF0, /* 3 */
    0x90, 0x90, 0xF0, 0x10, 0x10, /* 4 */
    0xF0, 0x80, 0xF0, 0x10, 0xF0, /* 5 */
    0xF0, 0x80, 0xF0, 0x90, 0xF0, /* 6 */
    0xF0, 0x10, 0x20, 0x40, 0x40, /* 7 */
    0xF0, 0x90, 0xF0, 0x90, 0xF0, /* 8 */
    0xF0, 0x90, 0xF0, 0x10, 0xF0, /* 9 */
    0xF0, 0x90, 0xF0, 0x90, 0x90, /* A */
    0xE0, 0x90, 0xE0, 0x90, 0xE0, /* B */
    0xF0, 0x80, 0x80, 0x80, 0xF0, /* C */
    0xE0, 0x90, 0x90, 0x90, 0xE0, /* D */
    0xF0, 0x80, 0xF0, 0x80, 0xF0, /* E */
    0xF0, 0x80, 0xF0, 0x80, 0x80  /* F */
};


typedef struct
{
    u16 pc;
    u8  sp;

    u16 i;
    u16 prevkeys;
    u16 keys;

    u64 cycles;

            /* timers: */
    u8  dt; /* - delay */
    u8  st; /* - sound */

    bool running;

    union
    {
        struct
        {
            u8  v[16];
            u64 disp[32];
            u8  font[sizeof(fnt)];
            u16 stack[16];

        } sys;
        u8 ram[4*KB];
    } mem;

} chip8;

#define V       mem.sys.v
#define STACK   mem.sys.stack
#define DISPLAY mem.sys.disp
#define FONT    mem.sys.font
#define RAM     mem.ram

typedef enum
{
                                    C8_ILL,
/* Onnn = 0,1,2,A,B              */
/*    00E0 - CLS                 */ C8_CLS,
/*    00EE - RET                 */ C8_RET,
/*    0nnn - SYS  addr           */ C8_SYS,
/*    1nnn - JP   addr           */ C8_JP,
/*    2nnn - CALL addr           */ C8_CALL,
/*    Annn - LD   I,  addr       */ C8_LDI,
/*    Bnnn - JP   V0, addr       */ C8_JPV,
/* Oxkk = 3,4,6,7,C,E,F          */
/*    3xkk - SE   Vx, byte       */ C8_SEB,
/*    4xkk - SNE  Vx, byte       */ C8_SNEB,
/*    6xkk - LD   Vx, byte       */ C8_LD,
/*    7xkk - ADD  Vx, byte       */ C8_ADDB,
/*    Cxkk - RND  Vx, byte       */ C8_RND,
/*    Ex9E - SKP  Vx             */ C8_SKP,
/*    ExA1 - SKNP Vx             */ C8_SKNP,
/*    Fx07 - LD   Vx, DT         */ C8_MVDT,
/*    Fx0A - LD   Vx, K          */ C8_LDK,
/*    Fx15 - LD   DT, Vx         */ C8_LDDT,
/*    Fx18 - LD   ST, Vx         */ C8_LDST,
/*    Fx1E - ADD  I, Vx          */ C8_ADDI,
/*    Fx29 - LD   F, Vx          */ C8_HEX,
/*    Fx33 - LD   B, Vx          */ C8_BCD,
/*    Fx55 - LD   [I], Vx        */ C8_SAVE,
/*    Fx65 - LD   Vx, [I]        */ C8_RESTORE,
/* Oxyn = 5,8,9,D                */
/*    5xy0 - SE   Vx, Vy         */ C8_SE,
/*    8xy0 - LD   Vx, Vy         */ C8_MOVE,
/*    8xy1 - OR   Vx, Vy         */ C8_OR,
/*    8xy2 - AND  Vx, Vy         */ C8_AND,
/*    8xy3 - XOR  Vx, Vy         */ C8_XOR,
/*    8xy4 - ADD  Vx, Vy         */ C8_ADD,
/*    8xy5 - SUB  Vx, Vy         */ C8_SUB,
/*    8xy6 - SHR  Vx {, Vy}      */ C8_SHR,
/*    8xy7 - SUBN Vx, Vy         */ C8_SUBN,
/*    8xyE - SHL  Vx {, Vy}      */ C8_SHL,
/*    9xy0 - SNE  Vx, Vy         */ C8_SNE,
/*    Dxyn - DRW  Vx, Vy, nibble */ C8_DRW,
    C8_OP_CNT

} chip8_op;


void c8_reset(chip8* c8)
{
    assert(c8);
    assert(sizeof(c8->mem) > sizeof(fnt));
    assert(sizeof(c8->mem) >= 4*KB);

    memcpy(c8->FONT, fnt, sizeof(fnt));

    c8->pc = C8_RESET_VECTOR;
    c8->running = true;
}

void c8_dump_state(chip8* c8, bool stack)
{
    int i = 0;

    for(i = 0; i < (int)sizeof(c8->V); i += 4)
    {
        printf("      V%x: %02X (%3d) ",  i+0, c8->V[i+0], c8->V[i+0]);
        printf("V%x: %02X (%3d) ",  i+1, c8->V[i+1], c8->V[i+1]);
        printf("V%x: %02X (%3d) ",  i+2, c8->V[i+2], c8->V[i+2]);
        printf("V%x: %02X (%3d)\n", i+3, c8->V[i+3], c8->V[i+3]);
    }

    printf("      I:%04X SP: %02X DT: %02X ST: %02X CYCLES: %llu, KEYS: %X\n",
            c8->i, c8->sp, c8->dt, c8->st, c8->cycles, c8->keys);

    if(stack)
    {
        printf("stack:\n");
        for(i = 0; i < (int)sizeof(c8->STACK); i += 2)
        {
            printf("      %-2d: %04X ",  i+0, c8->STACK[i+0]);
            printf("%-2d: %04X\n", i+1, c8->STACK[i+1]);
        }
    }
}



#define c8_decode_generate(name)                    \
void c8_##name(u8* code, u16 offset, void* usrdat)  \
{                                                   \
    u16 word, instr, nnn;                           \
    u8  o, x, y, n, kk;                             \
                                                    \
    (void)usrdat;                                   \
                                                    \
    word  = *(u16*)(code + offset);                 \
    instr = be16toh(word);                          \
    o     = (instr & 0xF000) >> 12;                 \
    nnn   = (instr & 0x0FFF);                       \
    x     = (instr & 0x0F00) >> 8;                  \
    y     = (instr & 0x00F0) >> 4;                  \
    n     = (instr & 0x000F);                       \
    kk    = (instr & 0x00FF);                       \
                                                    \
    (void)o;                                        \
    (void)nnn;                                      \
    (void)x;                                        \
    (void)y;                                        \
    (void)n;                                        \
    (void)kk;                                       \
                                                    \
    X_C8_PRELUDE;                                   \
                                                    \
    if(o == 0 && nnn == 0x0E0)      X_C8_CLS;       \
    else if(o == 0 && nnn == 0x0EE) X_C8_RET;       \
    else if(o == 0)                 X_C8_SYS;       \
    else if(o == 1)                 X_C8_JP;        \
    else if(o == 2)                 X_C8_CALL;      \
    else if(o == 0xA)               X_C8_LDI;       \
    else if(o == 0xB)               X_C8_JPV;       \
    else if(o == 3)                 X_C8_SEB;       \
    else if(o == 4)                 X_C8_SNEB;      \
    else if(o == 6)                 X_C8_LD;        \
    else if(o == 7)                 X_C8_ADDB;      \
    else if(o == 0xC)               X_C8_RND;       \
    else if(o == 0xE && kk == 0x9E) X_C8_SKP;       \
    else if(o == 0xE && kk == 0xA1) X_C8_SKNP;      \
    else if(o == 0xF && kk == 0x07) X_C8_MVDT;      \
    else if(o == 0xF && kk == 0x0A) X_C8_LDK;       \
    else if(o == 0xF && kk == 0x15) X_C8_LDDT;      \
    else if(o == 0xF && kk == 0x18) X_C8_LDST;      \
    else if(o == 0xF && kk == 0x1E) X_C8_ADDI;      \
    else if(o == 0xF && kk == 0x29) X_C8_HEX;       \
    else if(o == 0xF && kk == 0x33) X_C8_BCD;       \
    else if(o == 0xF && kk == 0x55) X_C8_SAVE;      \
    else if(o == 0xF && kk == 0x65) X_C8_RESTORE;   \
    else if(o == 5 && n == 0)       X_C8_SE;        \
    else if(o == 8 && n == 0)       X_C8_MOVE;      \
    else if(o == 8 && n == 1)       X_C8_OR;        \
    else if(o == 8 && n == 2)       X_C8_AND;       \
    else if(o == 8 && n == 3)       X_C8_XOR;       \
    else if(o == 8 && n == 4)       X_C8_ADD;       \
    else if(o == 8 && n == 5)       X_C8_SUB;       \
    else if(o == 8 && n == 6)       X_C8_SHR;       \
    else if(o == 8 && n == 7)       X_C8_SUBN;      \
    else if(o == 8 && n == 0xE)     X_C8_SHL;       \
    else if(o == 9 && n == 0)       X_C8_SNE;       \
    else if(o == 0xD)               X_C8_DRW;       \
    else                            X_C8_ILL;       \
                                                    \
    X_C8_EPILOGUE;                                  \
}