/*---------------------------------------------------------*\
| CMKeyboardV2Controller.cpp                                |
|                                                           |
|   Driver for Cooler Master V2 keyboards                   |
|                                                           |
|   Tam D (too.manyhobbies)                     30 Nov 2023 |
|                                                           |
|   This file is part of the OpenRGB project                |
|   SPDX-License-Identifier: GPL-2.0-or-later               |
\*---------------------------------------------------------*/

#include <locale>
#include <codecvt>
#include <string>
#include "CMKeyboardV2Controller.h"
#include "LogManager.h"
#include "StringUtils.h"

CMKeyboardV2Controller::CMKeyboardV2Controller(hid_device* dev_handle, hid_device_info* dev_info, std::string dev_name) : CMKeyboardAbstractController(dev_handle, dev_info, dev_name)
{
    m_sFirmwareVersion  = _GetFirmwareVersion();
    m_bMoreFFs          = false;
}

CMKeyboardV2Controller::~CMKeyboardV2Controller()
{
}

/*-----------------------------------------------------------------*\
|  Firmware version string is in the format:                        |
|    V<layout>.<minor>.<major>                                      |
|  Where <layout> is:                                               |
|   UNK = 0, ANSI/US = 1, ISO/EU = 2, JP = 3                        |
\*-----------------------------------------------------------------*/
KEYBOARD_LAYOUT CMKeyboardV2Controller::GetKeyboardLayout()
{
    KEYBOARD_LAYOUT layout = KEYBOARD_LAYOUT_DEFAULT;

    if(m_sFirmwareVersion.empty())
    {
        LOG_WARNING("[%s] _GetKeyboardLayout() empty firmware string detected. Assuming ANSI layout as default..", m_deviceName.c_str());

        layout = KEYBOARD_LAYOUT_ANSI_QWERTY;
    }
    else
    {
        switch(m_sFirmwareVersion.c_str()[1])
        {
            case '3':
            layout = KEYBOARD_LAYOUT_JIS;
            break;

            case '2':
            layout = KEYBOARD_LAYOUT_ISO_QWERTY;
            break;

            default:
            case '1':
            layout = KEYBOARD_LAYOUT_ANSI_QWERTY;
            break;
        }
    }

    /*------------------------------------------------------------*\
    | Double check keyboard type per #3592, EU keyboard reported   |
    | with version ID in serial number.                            |
    \*------------------------------------------------------------*/
    if(m_serialNumber.find("JP") != std::string::npos)
    {
        layout = KEYBOARD_LAYOUT_JIS;
    }

    if(m_serialNumber.find("EU") != std::string::npos)
    {
        layout = KEYBOARD_LAYOUT_ISO_QWERTY;
    }

    return(layout);
}

/*-----------------------------------------------------------------*\
| Gets the firmware version.                                        |
|   Strings are stored as UTF-16 so need to be converted to string  |
|   Firmware format is VX.YY.ZZ .                                   |
\*-----------------------------------------------------------------*/
std::string CMKeyboardV2Controller::_GetFirmwareVersion()
{
    std::vector<uint8_t> read;

    SetControlMode(MODE_FIRMWARE);
    read = SendCommand({0x12, 0x20});

    uint8_t cVersionStr[CM_KEYBOARD_WRITE_SIZE];

    size_t i = 0;
    for(uint8_t it : read)
    {
        cVersionStr[i++] = it;
    }

    std::u16string usFirmwareVersion(reinterpret_cast<const char16_t*>(cVersionStr+8));
    std::string sFirmwareVersion(StringUtils::u16string_to_string(usFirmwareVersion));

    LOG_VERBOSE("[%s] GetFirmwareVersion(): [%s]", m_deviceName.c_str(), sFirmwareVersion.c_str());

    return sFirmwareVersion;
}

void CMKeyboardV2Controller::Initialize()
{
    switch(m_productId)
    {
        case COOLERMASTER_KEYBOARD_SK620B_PID:
        case COOLERMASTER_KEYBOARD_SK620W_PID:
        MagicCommand(0x09);
        break;

        case COOLERMASTER_KEYBOARD_SK622B_PID:
        case COOLERMASTER_KEYBOARD_SK622W_PID:
        MagicStartupPacket();
        MagicCommand(0x09);
        break;

        case COOLERMASTER_KEYBOARD_SK630_PID:
        case COOLERMASTER_KEYBOARD_SK650_PID:
        SetActiveProfile(0x0C);
        m_bMoreFFs = true;
        break;

        case COOLERMASTER_KEYBOARD_SK652_PID:
        case COOLERMASTER_KEYBOARD_SK653_PID:
        MagicStartupPacket();
        MagicCommand(0x0C);
        m_bMoreFFs = true;
        break;

        case COOLERMASTER_KEYBOARD_CK530_PID:
        case COOLERMASTER_KEYBOARD_CK530_V2_PID:
        SetActiveProfile(0x05);
        MagicCommand(0x0A);
        break;

        case COOLERMASTER_KEYBOARD_CK550_V2_PID:
        SetActiveProfile(0x05);
        MagicCommand(0x09);
        break;

        case COOLERMASTER_KEYBOARD_MK730_PID:
        SetActiveProfile(0x05);
        MagicCommand(0x0A);
        m_bMoreFFs = true;
        break;

        case COOLERMASTER_KEYBOARD_CK552_V2_PID:
        SetControlMode(0x80);
        MagicCommand(0x01);
        break;

        default:
        SendCommand({0x56, 0x81, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0xBB, 0xBB, 0xBB});
        break;
    }
}

void CMKeyboardV2Controller::Shutdown()
{

}

/*---------------------------------------------------------*\
| Required for some keyboards. No idea what it does.        |
\*---------------------------------------------------------*/
void CMKeyboardV2Controller::MagicStartupPacket()
{
    std::lock_guard<std::mutex> guard(m_mutex);

    SendCommand({0x12});
    SendCommand({0x12, 0x20});
    SendCommand({0x12, 0x01});
    SendCommand({0x12, 0x22});
    SendCommand({0x42, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01});
    SendCommand({0x43, 0x00, 0x00, 0x00, 0x01});
};

void CMKeyboardV2Controller::SetLEDControl(bool bManual)
{
    uint8_t modeId = 0;

    if(bManual)
    {
        modeId = 0x05;
    }

    SetControlMode(modeId);
}

void CMKeyboardV2Controller::SetLeds(std::vector<led> leds, std::vector<RGBColor> colors)
{
    SetLEDControl(true);
    Initialize();

    RGBColor rgbColorMap[CM_MAX_LEDS];
    memset(rgbColorMap, 0, sizeof(RGBColor)*CM_MAX_LEDS);

    for(size_t i = 0; i < leds.size(); i++)
    {
        rgbColorMap[leds[i].value] = colors[i];
    }

    /*---------------------------------------------------------*\
    | Convert color array to linear map of RGB positions.       |
    | Map is sequential layout of RGB for each LED. i indicates |
    | the position in the map (i*3),                            |
    \*---------------------------------------------------------*/
    uint8_t  linearColorMap[CM_MAX_LEDS*3] = {0,};

    for(size_t i = 0; i < CM_MAX_LEDS; i++)
    {
        linearColorMap[i*3]   = RGBGetRValue(rgbColorMap[i]);
        linearColorMap[i*3+1] = RGBGetGValue(rgbColorMap[i]);
        linearColorMap[i*3+2] = RGBGetBValue(rgbColorMap[i]);
    }

    /*---------------------------------------------------------*\
    | For future reference, some keyboares may designate a      |
    | different number of max LEDs.                             |
    \*---------------------------------------------------------*/
    const uint8_t nLEDs = 0xC1;

    /*---------------------------------------------------------*\
    | Build initial packet.                                     |
    | This specifies some of the initial parameters such as the |
    | number of LEDs, and the first grouping of LEDs.           |
    | The packet structure seems to be fixed for these first 24 |
    | bytes.                                                    |
    \*---------------------------------------------------------*/
    std::vector<uint8_t> data =
    {
        0x56, 0x83, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
        0x80, 0x01, 0x00, nLEDs, 0x00, 0x00, 0x00, 0x00,
        0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00
    };

    size_t pktIdx;
    for(pktIdx = 0; pktIdx < 40; pktIdx++)
    {
        data.push_back(linearColorMap[pktIdx]);
    }

    std::lock_guard<std::mutex> guard(m_mutex);

    SendCommand(data);

    /*---------------------------------------------------------*\
    | Build subsequent packets.                                 |
    | Aproximately 9 packets of 60 bytes each. Total may change |
    | if we get a keyboard with an insane number of LEDs.       |
    \*---------------------------------------------------------*/
    for(size_t i = 1; i < 10; ++i)
    {
        data.clear();
        data.push_back(0x56);
        data.push_back(0x83);
        data.push_back((uint8_t)i);
        data.push_back(0x00);

        for(size_t j = 0; j < 60; j++)
        {
            data.push_back(linearColorMap[pktIdx++]);
        }

        SendCommand(data);
    }

    SendApplyPacket(0xFF);
}

void CMKeyboardV2Controller::SetSingleLED(uint8_t /*in_led*/, RGBColor /*in_color*/)
{
}

void CMKeyboardV2Controller::MagicCommand(uint8_t profileId)
{
    SendCommand(
        {
            0x56,      0x81, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
            profileId, 0x00, 0x00, 0x00, 0xBB, 0xBB, 0xBB, 0xBB}
        );
};

void CMKeyboardV2Controller::_SetEffectMode(uint8_t effectId)
{
    std::vector<uint8_t> data;

    switch(effectId)
    {
        case STATIC:
        case DIRECT:
        case CYCLE:
        case BREATHE:
        case CIRCLE_SPECTRUM:
        case REACTIVE_TORNADO:
            data = std::vector<uint8_t>
            {
                0x56, 0x81, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
                0x01, 0x00, 0x00, 0x00, 0x88, 0x88, 0x88, 0x88,
            };
            break;

        case WAVE:
            data = std::vector<uint8_t>
            {
                0x56, 0x81, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
                0x01, 0x00, 0x00, 0x00, 0x99, 0x99, 0x99, 0x99
            };
            break;

        case STARS:
        case RAINDROPS:
        case SNOW:
        case CROSS:
        case RIPPLE:
        case REACTIVE_PUNCH:
        case REACTIVE_FADE:
        case HEARTBEAT:
        case FIREBALL:
        case WATER_RIPPLE:
            data = std::vector<uint8_t>
            {
                0x56, 0x81, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
                0x02, 0x00, 0x00, 0x00, 0x88, 0x88, 0x88, 0x88,
            };
            break;

        case CUSTOMIZED:
            data = std::vector<uint8_t>{
                0x56, 0x81, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
                0x09, 0x00, 0x00, 0x00, 0xBB, 0xBB, 0xBB, 0xBB,
            };
            break;

        default:
            break;
    }

    SendCommand(data);
}

void CMKeyboardV2Controller::SendApplyPacket(uint8_t mode)
{
    SendCommand({0x51, 0x28, 0x00, 0x00, mode});
}


/*---------------------------------------------------------*\
| Sets the speed for each mode. Seems like almost every mode|
| has a different set of speed settings.                    |
\*---------------------------------------------------------*/
void CMKeyboardV2Controller::_UpdateSpeed(mode selectedMode, uint8_t &cSpeed1, uint8_t &cSpeed2)
{
    std::vector<uint8_t>vSpeed1{0x00, 0x00, 0x00, 0x00, 0x00};
    std::vector<uint8_t>vSpeed2{0x00, 0x00, 0x00, 0x00, 0x00};

    switch(selectedMode.value)
    {
        case CROSS:
        case REACTIVE_FADE:
            vSpeed1 = std::vector<uint8_t>{0x17, 0x0E, 0x0B, 0x0A, 0x04};
            vSpeed2 = std::vector<uint8_t>{0x01, 0x01, 0x02, 0x05, 0x04};
            break;

        case WAVE:
            vSpeed1 = std::vector<uint8_t>{0x17, 0x0D, 0x07, 0x09, 0x08};
            vSpeed2 = std::vector<uint8_t>{0x01, 0x04, 0x06, 0x0C, 0x11};
            break;

        case REACTIVE_PUNCH:
            vSpeed1 = std::vector<uint8_t>{0x0E, 0x0A, 0x04, 0x07, 0x01};
            break;

        case BREATHE:
            vSpeed1 = std::vector<uint8_t>{0x08, 0x0A, 0x0C, 0x07, 0x09};
            vSpeed2 = std::vector<uint8_t>{0x01, 0x02, 0x04, 0x04, 0x09};
            break;

        case CIRCLE_SPECTRUM:
        case REACTIVE_TORNADO:
            switch (selectedMode.direction)
            {
                case MODE_DIRECTION_LEFT:
                vSpeed1 = std::vector<uint8_t>{0xFF, 0xFE, 0xFD, 0xFC, 0xFC};
                vSpeed2 = std::vector<uint8_t>{0x04, 0x08, 0x08, 0x0C, 0x00};
                break;

                default:
                case MODE_DIRECTION_RIGHT:
                vSpeed1 = std::vector<uint8_t>{0x00, 0x01, 0x02, 0x03, 0x04};
                vSpeed2 = std::vector<uint8_t>{0x0C, 0x08, 0x08, 0x04, 0x00};
                break;
            }
            break;

        case CYCLE:
            vSpeed1 = std::vector<uint8_t>{0x10, 0x0C, 0x08, 0x04, 0x00};
            break;

        case RIPPLE:
        case WATER_RIPPLE:
            vSpeed1 = std::vector<uint8_t>{0x36, 0x18, 0x0C, 0x06, 0x02};
            break;

        case FIREBALL:
        case HEARTBEAT:
            vSpeed1 = std::vector<uint8_t>{0x01, 0x02, 0x03, 0x05, 0x09};
            break;

        case RAINDROPS:
        case SNOW:
            vSpeed1 = std::vector<uint8_t>{0x0B, 0x08, 0x05, 0x02, 0x00};
            vSpeed2 = std::vector<uint8_t>{0x08, 0x18, 0x30, 0x38, 0x40};
            break;

        case STARS:
            vSpeed1 = std::vector<uint8_t>{0x17, 0x0E, 0x08, 0x0A, 0x0A};
            vSpeed2 = std::vector<uint8_t>{0x01, 0x01, 0x01, 0x02, 0x04};
            break;

        case OFF:
        default:
            break;
    }

    if(selectedMode.speed >= 1 && selectedMode.speed <= 5)
    {
        cSpeed1 = vSpeed1[selectedMode.speed-1];
        cSpeed2 = vSpeed2[selectedMode.speed-1];
    }
}

void CMKeyboardV2Controller::SetCustomMode()
{
    std::lock_guard<std::mutex> guard(m_mutex);

    SendCommand({0x41, 0x80});
    SendCommand({0x52});
}

void CMKeyboardV2Controller::SetMode(mode selectedMode)
{
    std::vector<uint8_t> data(64, 0);
    std::vector<uint8_t> data1(64, 0);
    std::vector<uint8_t> data2(64, 0);

    uint8_t cColor1_R   = 0;
    uint8_t cColor1_G   = 0;
    uint8_t cColor1_B   = 0;
    uint8_t cColor2_R   = 0;
    uint8_t cColor2_G   = 0;
    uint8_t cColor2_B   = 0;

    RGBColor color1     = 0;
    RGBColor color2     = 0;
    uint8_t cSpeed1     = 0;
    uint8_t cSpeed2     = 0;

    _UpdateSpeed(selectedMode, cSpeed1, cSpeed2);

    uint8_t cBright     = (uint8_t)selectedMode.brightness;
    uint8_t cDirection  = 0;
    uint8_t effectId    = selectedMode.value;

    if(selectedMode.colors.size() >= 1)
    {
        color1          = selectedMode.colors[0];
        cColor1_R       = (uint8_t)RGBGetRValue(color1);
        cColor1_G       = (uint8_t)RGBGetGValue(color1);
        cColor1_B       = (uint8_t)RGBGetBValue(color1);
    }

    if(selectedMode.colors.size() >= 2)
    {
        color2          = selectedMode.colors[1];
        cColor2_R       = (uint8_t)RGBGetRValue(color2);
        cColor2_G       = (uint8_t)RGBGetGValue(color2);
        cColor2_B       = (uint8_t)RGBGetBValue(color2);
    }

    switch(selectedMode.direction)
    {
        case MODE_DIRECTION_LEFT:
        case MODE_DIRECTION_HORIZONTAL:
            cDirection      = 0x04;
            break;

        case MODE_DIRECTION_RIGHT:
            cDirection      = 0x00;
            break;

        case MODE_DIRECTION_UP:
        case MODE_DIRECTION_VERTICAL:
            cDirection      = 0x06;
            break;

        case MODE_DIRECTION_DOWN:
            cDirection      = 0x02;
            break;

        default:
            break;
    }

    SetCustomMode();

    std::lock_guard<std::mutex> guard(m_mutex);
    _SetEffectMode(effectId);

    switch(effectId)
    {
        case OFF:
            break;

        case DIRECT:
            break;

        case STATIC:
            {
                data = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
                    0x00, 0x01, 0x00, 0xC1, 0x00, 0x00, 0x00, 0x00,
                    cColor1_R, cColor1_G, cColor1_B, cBright, 0x00, 0x00, 0x00, 0x00,
                };
                SendCommand(data);
            }
            break;

        case WAVE:
            {
                data = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
                    0x00, 0x32, 0x00, 0xC1, cSpeed1, 0x00, 0x00, 0x00,
                    0xFF, 0xFF, 0xFF, cBright, 0x00, cDirection, cSpeed2, 0x00,
                    0x00, 0x04, 0x08
                };
                SendCommand(data);
            }
            break;

        case STARS:
            {
                data = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x00, 0x00, 0x0D, 0x00, 0x0D, 0x00,
                    0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x01, 0x00,
                    0x00, 0x01, 0x00, 0xC1, 0x00, 0x00, 0x00, 0x00,
                    cColor2_R, cColor2_G, cColor2_B, cBright, 0x00, 0x00, 0x00, 0x00,
                    0x00, 0x40, 0x00, 0x80, cSpeed1, 0x10, 0x00, 0x00,
                    cColor1_R, cColor1_G, cColor1_B, cBright, 0x00, 0x00, cSpeed2, 0x01,
                    0x01, 0x10, 0x08, 0x01, 0x10, 0x00, 0x00, 0x00,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
                };
                SendCommand(data);

                data1 = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF
                };

                data2 = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF
                };

                data = m_bMoreFFs ? data2 : data1;

                SendCommand(data);
            }
            break;

        case RAINDROPS:
            {
                data = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x00, 0x00, 0x0D, 0x00, 0x0D, 0x00,
                    0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x01, 0x00,
                    0x00, 0x01, 0x00, 0xC1, 0x00, 0x00, 0x00, 0x00,
                    cColor2_R, cColor2_G, cColor2_B, cBright, 0x00, 0x00, 0x00, 0x00,
                    0x81, 0x40, 0x00, 0x80, cSpeed1, 0x10, 0x00, 0x00,
                    cColor1_R, cColor1_G, cColor1_B, cBright, 0x00, 0x00, 0x03, 0x00,
                    cSpeed2, 0x18, 0x04, 0x10, 0x01, 0x00, 0x00, 0x00,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
                };
                SendCommand(data);

                data1 = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF
                };

                data2 = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF
                };

                data = m_bMoreFFs ? data2 : data1;

                SendCommand(data);
            }
            break;

        case SNOW:
            {
                data = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x00, 0x00, 0x0D, 0x00, 0x0D, 0x00,
                    0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x01, 0x00,
                    0x00, 0x01, 0x00, 0xC1, 0x00, 0x00, 0x00, 0x00,
                    cColor2_R, cColor2_G, cColor2_B, cBright, 0x00, 0x00, 0x00, 0x00,
                    0x81, 0x40, 0x00, 0x80, cSpeed1, 0x10, 0x00, 0x00,
                    cColor1_R, cColor1_G, cColor1_B, cBright, 0x00, 0x00, 0x03, 0x00,
                    cSpeed2, 0xFF, 0x10, 0x10, 0x01, 0x40, 0x00, 0x00,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
                };
                SendCommand(data);

                data = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF
                };
                SendCommand(data);
            }
            break;

        case CYCLE:
            {
                data = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
                    0x00, 0x31, 0x00, 0xC1, cSpeed1, 0x00, 0x00, 0x00,
                    0x40, 0x00, 0xFF, cBright, 0x00, 0x00, 0x03, 0x00,
                };

                SendCommand(data);
            }
            break;

        case BREATHE:
            {
                data = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
                    0x00, 0x30, 0x00, 0xC1, cSpeed1, 0x00, 0x00, 0x00,
                    cColor1_R, cColor1_G, cColor1_B, cBright, 0x01, 0x00, cSpeed2, 0x00,
                };
                SendCommand(data);
            }
            break;

        case CIRCLE_SPECTRUM:
            {
                /*---------------------------------------------------*\
                | Speed and directions are interlinked. Probably has  |
                | to do how speed is computed within the firmware.    |
                | The speed values for this mode take into account    |
                | the direction.                                      |
                \*---------------------------------------------------*/
                data = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
                    0x00, 0x34, 0x00, 0xC1, 0x04, 0x00, 0x00, 0x00,
                    0x00, 0x00, 0xFF, cBright, 0x00, 0x00, cSpeed1, 0x00,
                    0x00, cSpeed2, 0x00, 0x04, 0xA0, 0x00, 0x30, 0x00,
                };
                SendCommand(data);
            }
            break;

        case CROSS:
            {
                data = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x00, 0x00, 0x0C, 0x00, 0x0C, 0x00,
                    0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x01, 0x00,
                    0x00, 0x01, 0x00, 0xC1, 0x00, 0x00, 0x00, 0x00,
                    cColor2_R, cColor2_G, cColor2_B, cBright, 0x00, 0x00, 0x00, 0x00,
                    0x00, 0x80, 0x00, 0x80, cSpeed1, 0x10, 0x00, 0x00,
                    cColor1_R, cColor1_G, cColor1_B, cBright, 0x00, 0x00, cSpeed2, 0x01,
                    0x03, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
                };
                SendCommand(data);

                data1 = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                };

                data2 = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                };

                data = m_bMoreFFs ? data2 : data1;
                SendCommand(data);
            }
            break;

        case SINGLE:
            break;

        case RIPPLE:
            {
                data = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x00, 0x00, 0x0C, 0x00, 0x0C, 0x00,
                    0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x01, 0x00,
                    0x00, 0x01, 0x00, 0xC1, 0x00, 0x00, 0x00, 0x00,
                    cColor2_R, cColor2_G, cColor2_B, cBright, 0x00, 0x00, 0x00, 0x00,
                    0x01, 0x82, 0x00, 0x80, cSpeed1, 0x10, 0x00, 0x00,
                    cColor1_R, cColor1_G, cColor1_B, cBright, 0x00, 0x00, 0x01, 0x00,
                    0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
                };
                SendCommand(data);

                data = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
                };
                SendCommand(data);
            }
            break;

        case REACTIVE_PUNCH:
            {
                data = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x00, 0x00, 0x0B, 0x00, 0x0B, 0x00,
                    0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x01, 0x00,
                    0x00, 0x01, 0x00, 0xC1, 0x00, 0x00, 0x00, 0x00,
                    cColor2_R, cColor2_G, cColor2_B, cBright, 0x00, 0x00, 0x00, 0x00,
                    0x28, 0x80, 0x00, 0x80, cSpeed1, 0x10, 0x00, 0x00,
                    cColor1_R, cColor1_G, cColor1_B, cBright, 0x00, 0x00, 0x01, 0x00,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
                };

                SendCommand(data);

                data1 = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF
                };

                data2 = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF
                };

                data = m_bMoreFFs ? data2 : data1;

                SendCommand(data);
            }
            break;

        case REACTIVE_FADE:
            {
                data = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x00, 0x00, 0x0C, 0x00, 0x0C, 0x00,
                    0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x01, 0x00,
                    0x00, 0x01, 0x00, 0xC1, 0x00, 0x00, 0x00, 0x00,
                    cColor2_R, cColor2_G, cColor2_B, cBright, 0x00, 0x00, 0x00, 0x00,
                    0x00, 0x80, 0x00, 0x80, cSpeed1, 0x10, 0x00, 0x00,
                    cColor1_R, cColor1_G, cColor1_B, cBright, 0x00, 0x00, 0x01, 0x00,
                    0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
                };
                SendCommand(data);

                data1 = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                };

                data2 = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                };

                data = m_bMoreFFs ? data2 : data1;

                SendCommand(data);
            }
            break;

        case REACTIVE_TORNADO:
            {
                /*---------------------------------------------------*\
                | Speed and directions are interlinked. Probably has  |
                | to do how speed is computed within the firmware.    |
                | The speed values for this mode take into account    |
                | the direction.                                      |
                \*---------------------------------------------------*/
                data = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
                    0x00, 0x83, 0x00, 0xC1, 0x04, 0x00, 0x00, 0x00,
                    0x00, 0x00, 0xFF, cBright, 0x00, 0x00, cSpeed1, 0x00,
                    0x00, cSpeed2, 0x00, 0x00, 0x30, 0x00, 0x10, 0x00,
                };
                SendCommand(data);
            }
            break;

            case HEARTBEAT:
            {
                data = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x00, 0x00, 0x0C, 0x00, 0x0C, 0x00,
                    0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x01, 0x00,
                    0x00, 0x01, 0x00, 0xC1, 0x00, 0x00, 0x00, 0x00,
                    cColor2_R, cColor2_G, cColor2_B, cBright, 0x00, 0x00, 0x00, 0x00,
                    0x20, 0xA0, 0x00, 0x80, 0x20, 0x10, 0x00, 0x00,
                    cColor1_R, cColor1_G, cColor1_B, cBright, 0x00, 0x00, cSpeed1, 0x00,
                    0x02, 0xFF, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
                };
                SendCommand(data);

                data = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                };
                SendCommand(data);
            }
            break;

        case FIREBALL:
            {
                data = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x00, 0x00, 0x0C, 0x00, 0x0C, 0x00,
                    0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x01, 0x00,
                    0x00, 0x01, 0x00, 0xC1, 0x00, 0x00, 0x00, 0x00,
                    cColor2_R, cColor2_G, cColor2_B, cBright, 0x00, 0x00, 0x00, 0x00,
                    0x20, 0xA0, 0x00, 0x80, 0x10, 0x10, 0x00, 0x00,
                    cColor1_R, cColor1_G, cColor1_B, cBright, 0x00, 0x00, cSpeed1, 0x00,
                    0x01, 0x08, 0x09, 0x04, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
                };
                SendCommand(data);

                data = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                };
                SendCommand(data);
            }
            break;

        case WATER_RIPPLE:
            {
                data = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x00, 0x00, 0x0C, 0x00, 0x0C, 0x00,
                    0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x01, 0x00,
                    0x00, 0x01, 0x00, 0xC1, 0x00, 0x00, 0x00, 0x00,
                    cColor2_R, cColor2_G, cColor2_B, cBright, 0x00, 0x00, 0x00, 0x00,
                    0x01, 0x82, 0x00, 0x80, cSpeed1, 0x10, 0x00, 0x00,
                    cColor1_R, cColor1_G, cColor1_B, cBright, 0x00, 0x00, 0x06, 0x00,
                    0x00, 0x90, 0x14, 0x20, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
                };
                SendCommand(data);

                data = std::vector<uint8_t>
                {
                    0x56, 0x83, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                };
                SendCommand(data);
            }
            break;

        case SNAKE:
        default:
            break;
    }

    switch(effectId)
    {
        case OFF:
            SendCommand({0x51, 0x28, 0x00, 0x00, 0x10});
            SendCommand({0x41, 0x80});
            break;

        default:
            SendCommand({0x41, 0x80});
            SendCommand({0x51, 0x28, 0x00, 0x00, 0xFF});
            break;
    }
}

struct stCMKeyboardV2_mode CMKeyboardV2_modes[] =
{
/*----------------------------------------------------------------------------------------------------------------------------------------*\
|                             Speed         Brightness                                                                                     |
|    NAME          VALUE      MIN  MAX SET   MAX   DIR  Number of Colors      COLOR_MODE                  FLAGS                            |
\*----------------------------------------------------------------------------------------------------------------------------------------*/
    {"Direct",      DIRECT,    1,  5,   3,  0xFF,  0,                  0,   MODE_COLORS_PER_LED,        MODE_FLAG_HAS_PER_LED_COLOR},
    {"Static",      STATIC,    1,  5,   3,  0xFF,  0,                  1,   MODE_COLORS_MODE_SPECIFIC,  MODE_FLAG_HAS_MODE_SPECIFIC_COLOR |
                                                                                                         MODE_FLAG_HAS_BRIGHTNESS },
    {"Wave",        WAVE,      1,  5,   3,  0xFF,  MODE_DIRECTION_LEFT,1,   MODE_COLORS_NONE,           MODE_FLAG_HAS_BRIGHTNESS |
                                                                                                         MODE_FLAG_HAS_SPEED |
                                                                                                         MODE_FLAG_HAS_DIRECTION_LR |
                                                                                                         MODE_FLAG_HAS_DIRECTION_UD},
    {"Crosshair",   CROSS,     1,  5,   3,  0xFF,  0,                  2,   MODE_COLORS_MODE_SPECIFIC,  MODE_FLAG_HAS_MODE_SPECIFIC_COLOR |
                                                                                                         MODE_FLAG_HAS_BRIGHTNESS |
                                                                                                         MODE_FLAG_HAS_SPEED },
    {"Reactive Fade",REACTIVE_FADE,1,5,  3,  0xFF,  0,                 2,   MODE_COLORS_MODE_SPECIFIC,  MODE_FLAG_HAS_MODE_SPECIFIC_COLOR |
                                                                                                         MODE_FLAG_HAS_BRIGHTNESS |
                                                                                                         MODE_FLAG_HAS_SPEED },
    {"Stars",       STARS,     1,  5,   3,  0xFF,  0,                  2,   MODE_COLORS_MODE_SPECIFIC,  MODE_FLAG_HAS_MODE_SPECIFIC_COLOR |
                                                                                                         MODE_FLAG_HAS_BRIGHTNESS |
                                                                                                         MODE_FLAG_HAS_SPEED },
    {"Raindrops",   RAINDROPS, 1,  5,   3,  0xFF,  0,                  2,   MODE_COLORS_MODE_SPECIFIC,  MODE_FLAG_HAS_MODE_SPECIFIC_COLOR |
                                                                                                         MODE_FLAG_HAS_BRIGHTNESS |
                                                                                                         MODE_FLAG_HAS_SPEED },
    {"Color Cycle", CYCLE,     1,  5,   3,  0xFF,  0,                  0,   MODE_COLORS_NONE,           MODE_FLAG_HAS_BRIGHTNESS |
                                                                                                         MODE_FLAG_HAS_SPEED },
    {"Breathing",   BREATHE,   1,  5,   3,  0xFF,  0,                  1,   MODE_COLORS_MODE_SPECIFIC,  MODE_FLAG_HAS_MODE_SPECIFIC_COLOR |
                                                                                                         MODE_FLAG_HAS_BRIGHTNESS |
                                                                                                         MODE_FLAG_HAS_SPEED },
    {"Ripple",      RIPPLE,    1,  5,   3,  0xFF,  0,                  2,   MODE_COLORS_MODE_SPECIFIC,  MODE_FLAG_HAS_MODE_SPECIFIC_COLOR |
                                                                                                         MODE_FLAG_HAS_BRIGHTNESS |
                                                                                                         MODE_FLAG_HAS_SPEED },
    {"Fireball",    FIREBALL,  1,  5,   3,  0xFF,  0,                  2,   MODE_COLORS_MODE_SPECIFIC,  MODE_FLAG_HAS_MODE_SPECIFIC_COLOR |
                                                                                                         MODE_FLAG_HAS_BRIGHTNESS |
                                                                                                         MODE_FLAG_HAS_SPEED },
    {"Water Ripple",WATER_RIPPLE,1,5,   3,  0xFF,  0,                  2,   MODE_COLORS_MODE_SPECIFIC,  MODE_FLAG_HAS_MODE_SPECIFIC_COLOR |
                                                                                                         MODE_FLAG_HAS_BRIGHTNESS |
                                                                                                         MODE_FLAG_HAS_SPEED },
    {"Reactive Punch",REACTIVE_PUNCH,1,5,3, 0xFF,  0,                  2,   MODE_COLORS_MODE_SPECIFIC,  MODE_FLAG_HAS_MODE_SPECIFIC_COLOR |
                                                                                                         MODE_FLAG_HAS_BRIGHTNESS |
                                                                                                         MODE_FLAG_HAS_SPEED },
    {"Snowing",     SNOW,      1,  5,   3,  0xFF,  0,                  2,   MODE_COLORS_MODE_SPECIFIC,  MODE_FLAG_HAS_MODE_SPECIFIC_COLOR |
                                                                                                         MODE_FLAG_HAS_BRIGHTNESS |
                                                                                                         MODE_FLAG_HAS_SPEED },
    {"Heartbeat",   HEARTBEAT, 1,  5,   3,  0xFF,  0,                  2,   MODE_COLORS_MODE_SPECIFIC,  MODE_FLAG_HAS_MODE_SPECIFIC_COLOR |
                                                                                                         MODE_FLAG_HAS_BRIGHTNESS |
                                                                                                         MODE_FLAG_HAS_SPEED },
    {"Circle Spectrum",CIRCLE_SPECTRUM,1,5,3,0xFF, MODE_DIRECTION_LEFT,0,   MODE_COLORS_NONE,           MODE_FLAG_HAS_BRIGHTNESS |
                                                                                                         MODE_FLAG_HAS_SPEED |
                                                                                                         MODE_FLAG_HAS_DIRECTION_LR},
    {"Reactive Tornado",REACTIVE_TORNADO,1,5,3,0xFF,MODE_DIRECTION_LEFT,0,  MODE_COLORS_NONE,           MODE_FLAG_HAS_BRIGHTNESS |
                                                                                                         MODE_FLAG_HAS_SPEED |
                                                                                                         MODE_FLAG_HAS_DIRECTION_LR},
    {"Off",         OFF,       0,  0,   0,  0xFF,  MODE_DIRECTION_LEFT, 0,  MODE_COLORS_NONE,           0},
    { 0,            NONE,      0,  0,   0,     0,  0,                   0,  0,                          0},
};

void CMKeyboardV2Controller::InitializeModes(std::vector<mode> &modes)
{
    stCMKeyboardV2_mode * pCurrentMode = CMKeyboardV2_modes;

    while(pCurrentMode->value != NONE)
    {
        mode m;

        m.name           = std::string(pCurrentMode->name);
        m.value          = pCurrentMode->value;
        m.flags          = pCurrentMode->flags;
        m.color_mode     = pCurrentMode->color_mode;
        m.speed_min      = pCurrentMode->speed_min;
        m.speed_max      = pCurrentMode->speed_max;
        m.speed          = pCurrentMode->speed;
        m.brightness_min = 0x00;
        m.brightness_max = 0xFF;
        m.brightness     = 0xFF;
        m.colors_min     = pCurrentMode->nColors;
        m.colors_max     = pCurrentMode->nColors;
        m.colors.resize(pCurrentMode->nColors);
        m.direction      = pCurrentMode->direction;

        modes.push_back(m);

        pCurrentMode++;
    }
}
