#include "bi2a.h"

#include "avs/game.h"
#include "games/ddr/io.h"
#include "games/sdvx/io.h"
#include "games/drs/io.h"
#include "games/drs/drs.h"
#include "misc/eamuse.h"
#include "util/logging.h"
#include "util/utils.h"

using namespace GameAPI;

// state
static uint8_t STATUS_BUFFER[272] {};
static bool STATUS_BUFFER_FREEZE = false;
static unsigned int BI2A_VOLL = 0;
static unsigned int BI2A_VOLR = 0;


static bool __cdecl ac_io_bi2a_init_is_finished() {
    return true;
}

static bool __cdecl ac_io_bi2a_get_control_status_buffer(void *buffer) {

    // copy buffer
    memcpy(buffer, STATUS_BUFFER, std::size(STATUS_BUFFER));
    return true;
}

static bool __cdecl ac_io_bi2a_update_control_status_buffer() {

    // check freeze
    if (STATUS_BUFFER_FREEZE) {
        return true;
    }

    // Sound Voltex
    if (avs::game::is_model("KFC")) {

        // clear buffer
        memset(STATUS_BUFFER, 0, std::size(STATUS_BUFFER));
        STATUS_BUFFER[0] = 1;

        /*
         * Unmapped Buttons
         *
         * Control      Bit
         * EX BUTTON 1  93
         * EX BUTTON 2  92
         * EX ANALOG 1  170-183
         * EX ANALOG 2  186-199
         */

        // get buttons
        auto &buttons = games::sdvx::get_buttons();

        if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Test))) {
            ARRAY_SETB(STATUS_BUFFER, 19);
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Service))) {
            ARRAY_SETB(STATUS_BUFFER, 18);
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::CoinMech))) {
            ARRAY_SETB(STATUS_BUFFER, 17);
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Start))) {
            ARRAY_SETB(STATUS_BUFFER, 85);
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_A))) {
            ARRAY_SETB(STATUS_BUFFER, 84);
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_B))) {
            ARRAY_SETB(STATUS_BUFFER, 83);
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_C))) {
            ARRAY_SETB(STATUS_BUFFER, 82);
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_D))) {
            ARRAY_SETB(STATUS_BUFFER, 81);
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::FX_L))) {
            ARRAY_SETB(STATUS_BUFFER, 80);
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::FX_R))) {
            ARRAY_SETB(STATUS_BUFFER, 95);
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Headphone))) {
            ARRAY_SETB(STATUS_BUFFER, 87);
        }

        // volume left
        if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left))) {
            BI2A_VOLL = (BI2A_VOLL - 16) & 1023;
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right))) {
            BI2A_VOLL = (BI2A_VOLL + 16) & 1023;
        }

        // volume right
        if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left))) {
            BI2A_VOLR = (BI2A_VOLR - 16) & 1023;
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right))) {
            BI2A_VOLR = (BI2A_VOLR + 16) & 1023;
        }

        // update volumes
        auto &analogs = games::sdvx::get_analogs();
        auto vol_left = BI2A_VOLL;
        auto vol_right = BI2A_VOLR;
        if (analogs.at(0).isSet() || analogs.at(1).isSet()) {
            vol_left += (unsigned int) (Analogs::getState(RI_MGR,
                    analogs.at(games::sdvx::Analogs::VOL_L)) * 1023.99f);
            vol_right += (unsigned int) (Analogs::getState(RI_MGR,
                    analogs.at(games::sdvx::Analogs::VOL_R)) * 1023.99f);
        }

        // proper loops
        vol_left %= 1024;
        vol_right %= 1024;

        // save volumes in buffer
        *((uint16_t*) &STATUS_BUFFER[17]) = (uint16_t) ((vol_left) << 2);
        *((uint16_t*) &STATUS_BUFFER[19]) = (uint16_t) ((vol_right) << 2);
    }

    // DanceDanceRevolution
    if (avs::game::is_model("MDX")) {

        // clear buffer
        memset(STATUS_BUFFER, 0, std::size(STATUS_BUFFER));
        STATUS_BUFFER[0] = 1;

        // get buttons
        auto &buttons = games::ddr::get_buttons();

        if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::COIN_MECH)) == Buttons::BUTTON_PRESSED) {
            STATUS_BUFFER[2] |= 1 << 1;
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::SERVICE)) == Buttons::BUTTON_PRESSED) {
            STATUS_BUFFER[2] |= 1 << 2;
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::TEST)) == Buttons::BUTTON_PRESSED) {
            STATUS_BUFFER[2] |= 1 << 3;
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P1_START)) == Buttons::BUTTON_PRESSED) {
            STATUS_BUFFER[10] |= 1 << 7;
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P1_MENU_UP)) == Buttons::BUTTON_PRESSED) {
            STATUS_BUFFER[10] |= 1 << 6;
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P1_MENU_DOWN)) == Buttons::BUTTON_PRESSED) {
            STATUS_BUFFER[10] |= 1 << 5;
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P1_MENU_LEFT)) == Buttons::BUTTON_PRESSED) {
            STATUS_BUFFER[10] |= 1 << 4;
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P1_MENU_RIGHT)) == Buttons::BUTTON_PRESSED) {
            STATUS_BUFFER[10] |= 1 << 3;
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P2_START)) == Buttons::BUTTON_PRESSED) {
            STATUS_BUFFER[11] |= 1 << 5;
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P2_MENU_UP)) == Buttons::BUTTON_PRESSED) {
            STATUS_BUFFER[11] |= 1 << 4;
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P2_MENU_DOWN)) == Buttons::BUTTON_PRESSED) {
            STATUS_BUFFER[11] |= 1 << 3;
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P2_MENU_LEFT)) == Buttons::BUTTON_PRESSED) {
            STATUS_BUFFER[11] |= 1 << 2;
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P2_MENU_RIGHT)) == Buttons::BUTTON_PRESSED) {
            STATUS_BUFFER[11] |= 1 << 1;
        }
    }

    // DANCERUSH
    if (avs::game::is_model("REC")) {

        // clear buffer
        memset(STATUS_BUFFER, 0, std::size(STATUS_BUFFER));
        STATUS_BUFFER[0] = 1;

        // get buttons
        auto &buttons = games::drs::get_buttons();

        // test
        if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::Test)) == Buttons::State::BUTTON_PRESSED) {
            ARRAY_SETB(STATUS_BUFFER, 19);
        }

        // service
        if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::Service)) == Buttons::State::BUTTON_PRESSED) {
            ARRAY_SETB(STATUS_BUFFER, 18);
        }

        // coin
        if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::CoinMech)) == Buttons::State::BUTTON_PRESSED) {
            ARRAY_SETB(STATUS_BUFFER, 17);
        }

        if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P1_Start)) == Buttons::State::BUTTON_PRESSED) {
            ARRAY_SETB(STATUS_BUFFER, 87);
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P1_Up)) == Buttons::State::BUTTON_PRESSED) {
            ARRAY_SETB(STATUS_BUFFER, 86);
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P1_Down)) == Buttons::State::BUTTON_PRESSED) {
            ARRAY_SETB(STATUS_BUFFER, 85);
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P1_Left)) == Buttons::State::BUTTON_PRESSED) {
            ARRAY_SETB(STATUS_BUFFER, 84);
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P1_Right)) == Buttons::State::BUTTON_PRESSED) {
            ARRAY_SETB(STATUS_BUFFER, 83);
        }

        if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P2_Start)) == Buttons::State::BUTTON_PRESSED) {
            ARRAY_SETB(STATUS_BUFFER, 93);
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P2_Up)) == Buttons::State::BUTTON_PRESSED) {
            ARRAY_SETB(STATUS_BUFFER, 92);
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P2_Down)) == Buttons::State::BUTTON_PRESSED) {
            ARRAY_SETB(STATUS_BUFFER, 91);
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P2_Left)) == Buttons::State::BUTTON_PRESSED) {
            ARRAY_SETB(STATUS_BUFFER, 90);
        }
        if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P2_Right)) == Buttons::State::BUTTON_PRESSED) {
            ARRAY_SETB(STATUS_BUFFER, 89);
        }
    }

    return true;
}

static bool __cdecl ac_io_bi2a_current_coinstock(size_t index, DWORD *coins) {

    // check index
    if (index > 1)
        return false;

    // get coins and return success
    *coins = (DWORD) eamuse_coin_get_stock();
    return true;
}

static bool __cdecl ac_io_bi2a_consume_coinstock(size_t index, int amount) {

    // check index
    if (index > 1)
        return false;

    // calculate new stock
    auto stock = eamuse_coin_get_stock();
    auto stock_new = stock - amount;

    // check new stock
    if (stock_new < 0)
        return false;

    // apply new stock
    eamuse_coin_set_stock(stock_new);
    return true;
}

static bool __cdecl ac_io_bi2a_lock_coincounter(size_t index) {

    // check index
    if (index > 1)
        return false;

    // enable coin blocker
    eamuse_coin_set_block(true);
    return true;
}

static bool __cdecl ac_io_bi2a_unlock_coincounter(size_t index) {

    // check index
    if (index > 1)
        return false;

    // disable coin blocker
    eamuse_coin_set_block(false);
    return true;
}

static void __cdecl ac_io_bi2a_control_coin_blocker_close(size_t index) {

    // check index
    if (index > 1)
        return;

    // enable coin blocker
    eamuse_coin_set_block(true);
}

static void __cdecl ac_io_bi2a_control_coin_blocker_open(size_t index) {

    // check index
    if (index > 1)
        return;

    // disable coin blocker
    eamuse_coin_set_block(false);
}

static long __cdecl ac_io_bi2a_control_led_bright(size_t index, uint8_t brightness) {

    // Sound Voltex
    if (avs::game::is_model("KFC")) {

        /*
         * Control      R   G   B
         * =======================
         * WING UP      28  29  30
         * WING LOW     31  32  33
         * WOOFER       0   1   3
         * CONTROLLER   4   5   6
         *
         * Values go up to 255.
         *
         *
         * Control      Index
         * ==================
         * START BUTTON 8
         * A BUTTON     9
         * B BUTTON     10
         * C BUTTON     11
         * D BUTTON     12
         * FX L BUTTON  13
         * FX R BUTTON  14
         * POP          24
         * TITLE LEFT   25
         * TITLE RIGHT  26
         *
         * Values go up to 127.
         */

        static const struct {
            int light1, light2;
            float max;
        } mapping[] = {
                { games::sdvx::Lights::WOOFER_R, -1, 255 },
                { games::sdvx::Lights::WOOFER_G, -1, 255 },
                { -1, -1, 0 },
                { games::sdvx::Lights::WOOFER_B, -1, 255 },
                { games::sdvx::Lights::CONTROLLER_R, -1, 255 },
                { games::sdvx::Lights::CONTROLLER_G, -1, 255 },
                { games::sdvx::Lights::CONTROLLER_B, -1, 255 },
                { -1, -1, 0 },
                { games::sdvx::Lights::START, -1, 127 },
                { games::sdvx::Lights::BT_A, -1, 127 },
                { games::sdvx::Lights::BT_B, -1, 127 },
                { games::sdvx::Lights::BT_C, -1, 127 },
                { games::sdvx::Lights::BT_D, -1, 127 },
                { games::sdvx::Lights::FX_L, -1, 127 },
                { games::sdvx::Lights::FX_R, -1, 127 },
                { -1, -1, 0 }, { -1, -1, 0 }, { -1, -1, 0 },
                { games::sdvx::Lights::GENERATOR_R, -1, 255 },
                { games::sdvx::Lights::GENERATOR_G, -1, 255 },
                { games::sdvx::Lights::GENERATOR_B, -1, 255 },
                { -1, -1, 0 }, { -1, -1, 0 }, { -1, -1, 0 },
                { games::sdvx::Lights::POP, -1, 127 },
                { games::sdvx::Lights::TITLE_LEFT, -1, 127 },
                { games::sdvx::Lights::TITLE_RIGHT, -1, 127 },
                { -1, -1, 0 },
                { games::sdvx::Lights::WING_RIGHT_UP_R, games::sdvx::Lights::WING_LEFT_UP_R, 255 },
                { games::sdvx::Lights::WING_RIGHT_UP_G, games::sdvx::Lights::WING_LEFT_UP_G, 255 },
                { games::sdvx::Lights::WING_RIGHT_UP_B, games::sdvx::Lights::WING_LEFT_UP_B, 255 },
                { games::sdvx::Lights::WING_RIGHT_LOW_R, games::sdvx::Lights::WING_LEFT_LOW_R, 255 },
                { games::sdvx::Lights::WING_RIGHT_LOW_G, games::sdvx::Lights::WING_LEFT_LOW_G, 255 },
                { games::sdvx::Lights::WING_RIGHT_LOW_B, games::sdvx::Lights::WING_LEFT_LOW_B, 255 },
        };

        // ignore index out of range
        if (index > std::size(mapping)) {
            return true;
        }

        // get lights
        auto &lights = games::sdvx::get_lights();

        // get light from mapping
        auto light = mapping[index];

        // write lights
        if (light.light1 >= 0) {
            Lights::writeLight(RI_MGR, lights[light.light1], brightness / light.max);
        } else {
            log_warning("sdvx", "light unset {} {}", index, (int) brightness);
        }
        if (light.light2 >= 0) {
            Lights::writeLight(RI_MGR, lights[light.light2], brightness / light.max);
        }
    }

    // return success
    return true;
}

static long __cdecl ac_io_bi2a_get_watchdog_time_min() {
    return -1;
}

static long __cdecl ac_io_bi2a_get_watchdog_time_now() {
    return -1;
}

static void __cdecl ac_io_bi2a_watchdog_off() {
}

static bool __cdecl ac_io_bi2a_init(uint8_t param) {
    return true;
}

static bool __cdecl ac_io_bi2a_set_watchdog_time(uint16_t time) {
    return true;
}

static bool __cdecl ac_io_bi2a_get_watchdog_status() {
    return true;
}

static bool __cdecl ac_io_bi2a_set_amp_volume(uint8_t a1, uint8_t a2) {
    return true;
}

static bool __cdecl ac_io_bi2a_tapeled_init(uint8_t a1, uint8_t a2) {
    return true;
}

static bool __cdecl ac_io_bi2a_tapeled_init_is_finished() {
    return true;
}

static bool __cdecl ac_io_bi2a_control_tapeled_rec_set(uint8_t* data, size_t x_sz, size_t y_sz) {

    // check dimensions
    if (x_sz != 38 || y_sz != 49) {
        log_fatal("drs", "DRS tapeled wrong dimensions");
    }

    // copy data into our buffer - 4 bytes per pixel BGR
    for (size_t i = 0; i < x_sz * y_sz; i++) {
        games::drs::DRS_TAPELED[i][0] = data[i*4+2];
        games::drs::DRS_TAPELED[i][1] = data[i*4+1];
        games::drs::DRS_TAPELED[i][2] = data[i*4];
    }

    // success
    return true;
}

// TODO: this controls the upright RGB bars on the sides
static bool __cdecl ac_io_bi2a_control_tapeled_bright(size_t off1, size_t off2,
        uint8_t r, uint8_t g, uint8_t b, uint8_t bank) {
    return true;
}

static bool __cdecl ac_io_bi2a_tapeled_send() {
    return true;
}

static int __cdecl ac_io_bi2a_get_exbio2_status(uint8_t *info) {
    // surely this meme never gets old
    info[5] = 5;
    info[6] = 7;
    info[7] = 3;
    return 0;
}

acio::BI2AModule::BI2AModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("BI2A", module, hookMode) {
    this->status_buffer = STATUS_BUFFER;
    this->status_buffer_size = sizeof(STATUS_BUFFER);
    this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}

void acio::BI2AModule::attach() {
    ACIOModule::attach();

    ACIO_MODULE_HOOK(ac_io_bi2a_init_is_finished);
    ACIO_MODULE_HOOK(ac_io_bi2a_get_control_status_buffer);
    ACIO_MODULE_HOOK(ac_io_bi2a_update_control_status_buffer);
    ACIO_MODULE_HOOK(ac_io_bi2a_current_coinstock);
    ACIO_MODULE_HOOK(ac_io_bi2a_consume_coinstock);
    ACIO_MODULE_HOOK(ac_io_bi2a_lock_coincounter);
    ACIO_MODULE_HOOK(ac_io_bi2a_unlock_coincounter);
    ACIO_MODULE_HOOK(ac_io_bi2a_control_coin_blocker_close);
    ACIO_MODULE_HOOK(ac_io_bi2a_control_coin_blocker_open);
    ACIO_MODULE_HOOK(ac_io_bi2a_control_led_bright);
    ACIO_MODULE_HOOK(ac_io_bi2a_get_watchdog_time_min);
    ACIO_MODULE_HOOK(ac_io_bi2a_get_watchdog_time_now);
    ACIO_MODULE_HOOK(ac_io_bi2a_watchdog_off);
    ACIO_MODULE_HOOK(ac_io_bi2a_init);
    ACIO_MODULE_HOOK(ac_io_bi2a_set_watchdog_time);
    ACIO_MODULE_HOOK(ac_io_bi2a_get_watchdog_status);
    ACIO_MODULE_HOOK(ac_io_bi2a_set_amp_volume);
    ACIO_MODULE_HOOK(ac_io_bi2a_tapeled_init);
    ACIO_MODULE_HOOK(ac_io_bi2a_tapeled_init_is_finished);
    ACIO_MODULE_HOOK(ac_io_bi2a_get_exbio2_status);
    ACIO_MODULE_HOOK(ac_io_bi2a_control_tapeled_rec_set);
    ACIO_MODULE_HOOK(ac_io_bi2a_control_tapeled_bright);
    ACIO_MODULE_HOOK(ac_io_bi2a_tapeled_send);
}
