#include "bi2x.h"

#include "misc/eamuse.h"
#include "rawinput/rawinput.h"
#include "util/utils.h"

#include "iidx.h"
#include "io.h"

using namespace acioemu;

games::iidx::BI2XSerialHandle::BI2XDevice::BI2XDevice() {
    this->node_count = 1;
}

bool games::iidx::BI2XSerialHandle::BI2XDevice::parse_msg(
    MessageData *msg_in,
    circular_buffer<uint8_t> *response_buffer
) {
#ifdef ACIOEMU_LOG
    log_info("iidx", "BI2X ADDR: {}, CMD: 0x{:04x}", msg_in->addr, msg_in->cmd.code);
#endif

    // check command
    switch (msg_in->cmd.code) {
        case ACIO_CMD_GET_VERSION: {

            // send version data
            auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE);
            this->set_version(msg, 0x03, 0, 4, 2, 0, "BI2X");
            write_msg(msg, response_buffer);
            delete msg;
            break;
        }
        case 0x0153:
            if (DISABLE_ESPEC_IO) {
                return false;
            }
            [[fallthrough]]; // to 0x0152
        case 0x0152: { // STATUS

            // check input data length
            if (msg_in->cmd.data_size == 0x30) {

                // LED TICKER
                IIDX_LED_TICKER_LOCK.lock();
                if (!IIDXIO_LED_TICKER_READONLY)
                    memcpy(IIDXIO_LED_TICKER, &msg_in->cmd.raw[0x17], 9);
                IIDX_LED_TICKER_LOCK.unlock();

                // NEON LIGHT
                bool neon_light = msg_in->cmd.raw[0x24] != 0;
                write_top_neon(neon_light ? (uint8_t) 0xFF : (uint8_t) 0);

                // SPOT LIGHT
                uint8_t spot_light_bits = 0;
                for (size_t i = 0; i < 8; i++) {
                    auto index = i > 3 ? i + 1 : i;
                    if (msg_in->cmd.raw[0x20 + index] != 0)
                        spot_light_bits |= 1 << i;
                }
                write_top_lamp(spot_light_bits);

                // SWLED (DECK)
                uint16_t lamp_bits = 0;
                for (size_t i = 0; i < 14; i++) {
                    if (msg_in->cmd.raw[0x07 + i] != 0)
                        lamp_bits |= 1 << i;
                }
                write_lamp(lamp_bits);

                // SWLED (PANEL)
                uint8_t led_bits = 0;
                for (size_t i = 0; i < 4; i++) {
                    if (msg_in->cmd.raw[0x03 + i] != 0)
                        led_bits |= 1 << i;
                }
                write_led(led_bits);

                // flush device output
                RI_MGR->devices_flush_output();
            }

            // sleep - otherwise the IO thread will go too hard on the CPU
            Sleep(1);

            // generate message
            auto msg = this->create_msg(msg_in, 0x2E);

            // get buttons
            auto &buttons = get_buttons();

            // player 1 buttons
            if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_1)))
                ARRAY_SETB(msg->cmd.raw, 151);
            if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_2)))
                ARRAY_SETB(msg->cmd.raw, 167);
            if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_3)))
                ARRAY_SETB(msg->cmd.raw, 183);
            if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_4)))
                ARRAY_SETB(msg->cmd.raw, 199);
            if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_5)))
                ARRAY_SETB(msg->cmd.raw, 215);
            if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_6)))
                ARRAY_SETB(msg->cmd.raw, 231);
            if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_7)))
                ARRAY_SETB(msg->cmd.raw, 247);

            // player 2 buttons
            if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_1)))
                ARRAY_SETB(msg->cmd.raw, 263);
            if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_2)))
                ARRAY_SETB(msg->cmd.raw, 279);
            if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_3)))
                ARRAY_SETB(msg->cmd.raw, 295);
            if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_4)))
                ARRAY_SETB(msg->cmd.raw, 311);
            if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_5)))
                ARRAY_SETB(msg->cmd.raw, 327);
            if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_6)))
                ARRAY_SETB(msg->cmd.raw, 343);
            if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_7)))
                ARRAY_SETB(msg->cmd.raw, 359);

            // player 1 start
            if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_Start)))
                ARRAY_SETB(msg->cmd.raw, 79);

            // player 2 start
            if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_Start)))
                ARRAY_SETB(msg->cmd.raw, 78);

            // VEFX
            if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::VEFX)))
                ARRAY_SETB(msg->cmd.raw, 77);

            // EFFECT
            if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Effect)))
                ARRAY_SETB(msg->cmd.raw, 76);

            // service
            if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Service)))
                ARRAY_SETB(msg->cmd.raw, 10);

            // test
            if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Test)))
                ARRAY_SETB(msg->cmd.raw, 11);

            // turntables
            msg->cmd.raw[16] = get_tt(0, true);
            msg->cmd.raw[17] = get_tt(1, true);

            // slider
            msg->cmd.raw[0] |= get_slider(0) << 4;
            msg->cmd.raw[2] |= get_slider(1) << 4;
            msg->cmd.raw[4] |= get_slider(2) << 4;
            msg->cmd.raw[6] |= get_slider(3) << 4;
            msg->cmd.raw[7] |= get_slider(4) << 4;

            // coin
            this->coin_counter += eamuse_coin_consume_stock();
            msg->cmd.raw[8] |= this->coin_counter;

            /*
             * TODO
             * bit 9 appears to be the coin mech
             */

            // write message
            write_msg(msg, response_buffer);
            delete msg;
            break;
        }
        case ACIO_CMD_CLEAR:
        case ACIO_CMD_STARTUP:
        case 0x0120: // WD COMMAND
        case 0xFF: // BROADCAST
        {
            // send status 0
            auto msg = this->create_msg_status(msg_in, 0);
            write_msg(msg, response_buffer);
            delete msg;
            break;
        }
        default:
            return false;
    }

    // mark as handled
    return true;
}

bool games::iidx::BI2XSerialHandle::open(LPCWSTR lpFileName) {
    if (wcscmp(lpFileName, L"COM3") != 0) {
        return false;
    }

    log_info("iidx", "Opened COM3 (BIO2)");

    // ACIO device
    //acio_emu.add_device(new FMSerialDevice());

    return true;
}

int games::iidx::BI2XSerialHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
    //auto buffer = reinterpret_cast<uint8_t *>(lpBuffer);

    // read from emu
    /*
    DWORD bytes_read = 0;
    while (bytes_read < nNumberOfBytesToRead) {
        auto cur_byte = acio_emu.read();

        if (cur_byte.has_value()) {
            buffer[bytes_read++] = cur_byte.value();
        } else {
            break;
        }
    }
    */

    // return amount of bytes read
    //return (int) bytes_read;
    return (int) nNumberOfBytesToRead;
}

int games::iidx::BI2XSerialHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) {
    auto buffer = reinterpret_cast<const uint8_t *>(lpBuffer);

    log_info("iidx", "BIO2 Data: {}", bin2hex(buffer, nNumberOfBytesToWrite));

    // write to emu
    /*
    for (DWORD i = 0; i < nNumberOfBytesToWrite; i++) {
        acio_emu.write(buffer[i]);
    }
    */

    // return all data written
    return (int) nNumberOfBytesToWrite;
}

int games::iidx::BI2XSerialHandle::device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize,
                                               LPVOID lpOutBuffer, DWORD nOutBufferSize) {
    return -1;
}

bool games::iidx::BI2XSerialHandle::close() {
    log_info("iidx", "Closed COM3 (BIO2)");

    return true;
}
