// This implementation is ported from the device.dll source graciously
// provided by zyp

#include <windows.h>

#include "bemanitools/jbio.h"
#include "imports/ch341.h"
#include "util/defs.h"

static uint16_t jb_io_panels;
static uint8_t jb_io_sys_buttons;

static bool is_initialized = false;

static log_formatter_t jb_io_log_misc;
static log_formatter_t jb_io_log_info;
static log_formatter_t jb_io_log_warning;
static log_formatter_t jb_io_log_fatal;

union magicbox_input {
    uint32_t dword;
    uint8_t bytes[4];
};

void jb_io_set_loggers(
    log_formatter_t misc,
    log_formatter_t info,
    log_formatter_t warning,
    log_formatter_t fatal)
{
    jb_io_log_misc = misc;
    jb_io_log_info = info;
    jb_io_log_warning = warning;
    jb_io_log_fatal = fatal;
}

bool jb_io_init(
    thread_create_t thread_create,
    thread_join_t thread_join,
    thread_destroy_t thread_destroy)
{
    if (CH341OpenDevice(0) < 0) {
        jb_io_log_warning("jbio", "Can't open CH341 device.\n");
        return false;
    }

    is_initialized = true;

    return true;
}

void jb_io_fini(void)
{
    CH341CloseDevice(0);
}

static const uint32_t magic_panel_mappings[] = {
    (1 << 0x08),
    (1 << 0x0a),
    (1 << 0x0c),
    (1 << 0x0e),
    (1 << 0x07),
    (1 << 0x05),
    (1 << 0x03),
    (1 << 0x01),
    (1 << 0x09),
    (1 << 0x0b),
    (1 << 0x0d),
    (1 << 0x0f),
    (1 << 0x06),
    (1 << 0x04),
    (1 << 0x02),
    (1 << 0x00),
};

static const uint32_t magic_sys_mappings[] = {
    (1 << 0x11), // TEST
    (1 << 0x10), // SERVICE
    (1 << 0x13), // COIN
};

bool jb_io_read_inputs(void)
{
    // Read IO board
    unsigned long size;
    union magicbox_input input;

    input.dword = -1;
    jb_io_panels = 0;
    jb_io_sys_buttons = 0;

    CH341EppSetAddr(0, 255);
    CH341EppSetAddr(0, 1);
    CH341EppSetAddr(0, 255);
    CH341EppSetAddr(0, 0);
    size = sizeof(uint8_t);
    CH341EppReadData(0, &input.bytes[0], &size);
    CH341EppSetAddr(0, 255);
    CH341EppSetAddr(0, 0);
    size = sizeof(uint8_t);
    CH341EppReadData(0, &input.bytes[1], &size);
    CH341EppSetAddr(0, 255);
    CH341EppSetAddr(0, 0);
    size = sizeof(uint8_t);
    CH341EppReadData(0, &input.bytes[2], &size);
    CH341EppSetAddr(0, 255);

    for (uint8_t i = 0; i < lengthof(magic_panel_mappings); i++) {
        if ((input.dword & magic_panel_mappings[i]) == 0) {
            jb_io_panels |= 1 << i;
        }
    }
    for (uint8_t i = 0; i < lengthof(magic_sys_mappings); i++) {
        if ((input.dword & magic_sys_mappings[i]) == 0) {
            jb_io_sys_buttons |= 1 << i;
        }
    }

    return true;
}

bool jb_io_write_lights(void)
{
    return true;
}

uint8_t jb_io_get_sys_inputs(void)
{
    return jb_io_sys_buttons;
}

uint16_t jb_io_get_panel_inputs(void)
{
    return jb_io_panels;
}

void jb_io_set_rgb_led(enum jb_io_rgb_led unit, uint8_t r, uint8_t g, uint8_t b)
{
    // I mean I guess there's reactive LEDs on the sides? I'm not going to the
    // effort to work out if they're controllable or not
}

bool jb_io_set_panel_mode(enum jb_io_panel_mode mode)
{
    // panel always returns merged input state, no corner support
    return true;
}

bool jb_io_set_coin_blocker(bool blocked)
{
    return true;
}
