/*-------------------------------------------------------------------*\
|  RGBController_CMMKController.cpp                                   |
|                                                                     |
|  Driver for Coolermaster MasterKeys keyboards                       |
|                                                                     |
|  Lukas N (chmod222)          28th Jun 2020                          |
|  Tam D (too.manyhobbies)     25th Apr 2021                          |
|                                                                     |
\*-------------------------------------------------------------------*/

#include "RGBController_CMMKController.h"

#include <sstream>

using namespace std::chrono_literals;

#define CMMK_SPEED_MIN          CMMK_SPEED0
#define CMMK_SPEED_MID          CMMK_SPEED2
#define CMMK_SPEED_MAX          CMMK_SPEED4
#define CMMK_MODE_FIRMWARE      0xFF
#define CMMK_MODE_MANUAL        0x7F

/**------------------------------------------------------------------*\
    @name Coolermaster Masterkeys Keyboards
    @category Keyboard
    @type USB
    @save :robot:
    @direct :white_check_mark:
    @effects :white_check_mark:
    @detectors DetectCoolerMasterKeyboards
    @comment
\*-------------------------------------------------------------------*/

RGBController_CMMKController::RGBController_CMMKController(CMMKController* controller_ptr)
{
    controller                  = controller_ptr;

    name                        = controller->GetDeviceName();
    vendor                      = controller->GetDeviceVendor();
    type                        = DEVICE_TYPE_KEYBOARD;
    description                 = "Cooler Master MasterKeys Device";
    version                     = controller->GetFirmwareVersion();
    serial                      = "";
    location                    = controller->GetLocation();

    mode Direct;
    Direct.name                 = "Direct";
    Direct.value                = CMMK_MODE_MANUAL;
    Direct.flags                = MODE_FLAG_HAS_PER_LED_COLOR;
    Direct.color_mode           = MODE_COLORS_PER_LED;
    modes.push_back(Direct);

    mode Static;
    Static.name                 = "Static";
    Static.value                = CMMK_EFFECT_FULLY_LIT;
    Static.flags                = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
    Static.color_mode           = MODE_COLORS_MODE_SPECIFIC;
    Static.colors_min           = 1;
    Static.colors_max           = 1;
    Static.colors.resize(1);
    modes.push_back(Static);

    mode Breathing;
    Breathing.name              = "Breathing";
    Breathing.value             = CMMK_EFFECT_BREATHE;
    Breathing.flags             = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_SPEED;
    Breathing.color_mode        = MODE_COLORS_MODE_SPECIFIC;
    Breathing.speed_min         = CMMK_SPEED_MIN;
    Breathing.speed_max         = CMMK_SPEED_MAX;
    Breathing.speed             = CMMK_SPEED_MID;
    Breathing.colors_min        = 1;
    Breathing.colors_max        = 1;
    Breathing.colors.resize(1);
    modes.push_back(Breathing);

    mode Cycle;
    Cycle.name                  = "Spectrum Cycle";
    Cycle.value                 = CMMK_EFFECT_CYCLE;
    Cycle.flags                 = MODE_FLAG_HAS_SPEED;
    Cycle.color_mode            = MODE_COLORS_NONE;
    Cycle.speed_min             = 2 * CMMK_SPEED_MIN; //Spectrum Cycle uses a unique speed range
    Cycle.speed_max             = 2 * CMMK_SPEED_MAX;
    Cycle.speed                 = 2 * CMMK_SPEED_MID;
    modes.push_back(Cycle);

    mode Reactive;
    Reactive.name               = "Reactive";
    Reactive.value              = CMMK_EFFECT_SINGLE;
    Reactive.flags              = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_SPEED;
    Reactive.color_mode         = MODE_COLORS_MODE_SPECIFIC;
    Reactive.speed_min          = CMMK_SPEED_MIN;
    Reactive.speed_max          = CMMK_SPEED_MAX;
    Reactive.speed              = CMMK_SPEED_MID;
    Reactive.colors_min         = 2;
    Reactive.colors_max         = 2;
    Reactive.colors.resize(2);
    modes.push_back(Reactive);

    mode Wave;
    Wave.name                   = "Rainbow Wave";
    Wave.value                  = CMMK_EFFECT_WAVE;
    Wave.flags                  = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_DIRECTION_LR | MODE_FLAG_HAS_DIRECTION_UD;
    Wave.color_mode             = MODE_COLORS_MODE_SPECIFIC;
    Wave.speed_min              = CMMK_SPEED_MIN;
    Wave.speed_max              = CMMK_SPEED_MAX;
    Wave.speed                  = CMMK_SPEED_MID;
    Wave.direction              = MODE_DIRECTION_LEFT;
    Wave.colors_min             = 1;
    Wave.colors_max             = 1;
    Wave.colors.resize(1);
    modes.push_back(Wave);

    mode Ripple;
    Ripple.name                 = "Ripple Effect";
    Ripple.value                = CMMK_EFFECT_RIPPLE;
    Ripple.flags                = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED;
    Ripple.color_mode           = MODE_COLORS_MODE_SPECIFIC;
    Ripple.speed_min            = CMMK_SPEED_MIN;
    Ripple.speed_max            = CMMK_SPEED_MAX;
    Ripple.speed                = CMMK_SPEED_MID;
    Ripple.colors_min           = 2;
    Ripple.colors_max           = 2;
    Ripple.colors.resize(2);
    modes.push_back(Ripple);

    mode Cross;
    Cross.name                  = "Cross";
    Cross.value                 = CMMK_EFFECT_CROSS;
    Cross.flags                 = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_SPEED;
    Cross.color_mode            = MODE_COLORS_MODE_SPECIFIC;
    Cross.speed_min             = CMMK_SPEED_MIN;
    Cross.speed_max             = CMMK_SPEED_MAX;
    Cross.speed                 = CMMK_SPEED_MID;
    Cross.colors_min            = 2;
    Cross.colors_max            = 2;
    Cross.colors.resize(2);
    modes.push_back(Cross);

    mode Raindrops;
    Raindrops.name              = "Raindrops";
    Raindrops.value             = CMMK_EFFECT_RAINDROPS;
    Raindrops.flags             = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_SPEED;
    Raindrops.color_mode        = MODE_COLORS_MODE_SPECIFIC;
    Raindrops.speed_min         = CMMK_SPEED_MIN;
    Raindrops.speed_max         = CMMK_SPEED_MAX;
    Raindrops.speed             = CMMK_SPEED_MID;
    Raindrops.colors_min        = 2;
    Raindrops.colors_max        = 2;
    Raindrops.colors.resize(2);
    modes.push_back(Raindrops);

    mode Stars;
    Stars.name                  = "Starfield";
    Stars.value                 = CMMK_EFFECT_STARS;
    Stars.flags                 = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_SPEED;
    Stars.color_mode            = MODE_COLORS_MODE_SPECIFIC;
    Stars.speed_min             = CMMK_SPEED_MIN;
    Stars.speed_max             = CMMK_SPEED_MAX;
    Stars.speed                 = CMMK_SPEED_MID;
    Stars.colors_min            = 2;
    Stars.colors_max = 2;
    Stars.colors.resize(2);
    modes.push_back(Stars);

    mode Snake;
    Snake.name                  = "Snake";
    Snake.value                 = CMMK_EFFECT_SNAKE;
    Snake.flags                 = MODE_FLAG_HAS_SPEED;
    Snake.color_mode            = MODE_COLORS_NONE;
    Snake.speed_min             = CMMK_SPEED_MIN;
    Snake.speed_max             = CMMK_SPEED_MAX;
    Snake.speed                 = CMMK_SPEED_MID;
    modes.push_back(Snake);

    mode FirmwareControl;
    FirmwareControl.name        = "Firmware Controlled";
    FirmwareControl.value       = 0xFF;
    FirmwareControl.flags       = 0;
    FirmwareControl.color_mode  = MODE_COLORS_NONE;
    modes.push_back(FirmwareControl);

    SetupZones();
}

RGBController_CMMKController::~RGBController_CMMKController()
{
    delete controller;
}

void RGBController_CMMKController::SetupMatrixMap()
{
    for(int y = 0; y < CMMK_ROWS_MAX; y++)
    {
        for(int x = 0; x < CMMK_COLS_MAX; x++)
        {
            matrix_map[y][x] = 0xFFFFFFFF;
        }
    }

    for(size_t i = 0; i < leds.size(); i++)
    {
        led& l  = leds[i];

        int y   = (l.value & 0xFF00) >> 8;
        int x   = (l.value & 0xFF);

        matrix_map[y][x] = i;
    }
}

void RGBController_CMMKController::SetupZones()
{
    uint8_t row_count       = controller->GetRowCount();
    uint8_t column_count    = controller->GetColumnCount();

    for(int y = 0; y < row_count; y++)
    {
        for(int x = 0; x < column_count; x++)
        {
            if(!controller->PositionValid(y, x))
            {
                continue;
            }

            std::stringstream namestrm;

            led key;

            namestrm << "Key @ Row " << (y + 1) << ", Column" << (x + 1);

            key.name = namestrm.str();
            key.value = (y & 0xFF) << 8 | (x & 0xFF);

            leds.push_back(key);
        }
    }

    zone KeyboardZone;
    KeyboardZone.name               = "Keyboard";
    KeyboardZone.type               = ZONE_TYPE_MATRIX;
    KeyboardZone.leds_min           = leds.size();
    KeyboardZone.leds_max           = leds.size();
    KeyboardZone.leds_count         = leds.size();
    KeyboardZone.matrix_map         = new matrix_map_type;
    KeyboardZone.matrix_map->height = row_count;
    KeyboardZone.matrix_map->width  = column_count;
    KeyboardZone.matrix_map->map    = (unsigned int *)&matrix_map;

    zones.push_back(KeyboardZone);

    SetupMatrixMap();
    SetupColors();
}

void RGBController_CMMKController::ResizeZone(int /*zone*/, int /*new_size*/)
{
    /*---------------------------------------------------------*\
    | This device does not support resizing zones               |
    \*---------------------------------------------------------*/
}

struct rgb map_to_cmmk_rgb(RGBColor input)
{
    return rgb
    {
        (uint8_t)RGBGetRValue(input),
        (uint8_t)RGBGetGValue(input),
        (uint8_t)RGBGetBValue(input)
    };
}

enum cmmk_wave_direction map_to_cmmk_dir(int input)
{
    switch(input)
    {
        case MODE_DIRECTION_LEFT:
            return CMMK_RIGHT_TO_LEFT;

        case MODE_DIRECTION_RIGHT:
            return CMMK_LEFT_TO_RIGHT;

        case MODE_DIRECTION_UP:
            return CMMK_FRONT_TO_BACK;

        case MODE_DIRECTION_DOWN:
            return CMMK_BACK_TO_FRONT;

        default:
            return CMMK_RIGHT_TO_LEFT;
    }
}

void copy_buffers(led* buf,  RGBColor* colbuf, size_t n, struct cmmk_color_matrix& mat, std::atomic<bool>& dirty)
{
    dirty.store(false);

    for(size_t i = 0; i < n; i++)
    {
        led const& selected_led = buf[i];

        int y = (selected_led.value & 0xFF00) >> 8;
        int x = selected_led.value & 0xFF;

        struct rgb col = map_to_cmmk_rgb(colbuf[i]);
        struct rgb ecol = mat.data[y][x];

        if(ecol.R != col.R || ecol.G != col.G || ecol.B != col.B)
        {
            dirty.store(true);

            mat.data[y][x] = col;
        }
    }
}

void RGBController_CMMKController::DeviceUpdateLEDs()
{
    copy_buffers(leds.data(), colors.data(), leds.size(), current_matrix, dirty);

    if(force_update.load() || dirty.load())
    {
        controller->SetAll(current_matrix);

        force_update.store(false);
    }
}

void RGBController_CMMKController::UpdateZoneLEDs(int zone_idx)
{
    zone& z = zones[zone_idx];

    copy_buffers(z.leds, z.colors, z.leds_count, current_matrix, dirty);

    if(force_update.load() || dirty.load())
    {
        controller->SetAll(current_matrix);

        force_update.store(false);
    }
}

void RGBController_CMMKController::UpdateSingleLED(int led_idx)
{
    led& selected_led = leds[led_idx];

    int y = (selected_led.value & 0xFF00) >> 8;
    int x = selected_led.value & 0xFF;

    current_matrix.data[y][x] = map_to_cmmk_rgb(colors[led_idx]);

    controller->SetSingle(y, x, map_to_cmmk_rgb(colors[led_idx]));
    dirty.store(false);
}

void RGBController_CMMKController::SetCustomMode()
{
    active_mode = 1;
}

void RGBController_CMMKController::DeviceUpdateMode()
{
    force_update.store(true);

    switch(modes[active_mode].value)
    {
        case CMMK_MODE_FIRMWARE:
            controller->SetFirmwareControl();
            break;

        case CMMK_MODE_MANUAL:
            controller->SetManualControl();
            break;

        case CMMK_EFFECT_FULLY_LIT:
            {
                cmmk_effect_fully_lit fully_lit_effect;

                fully_lit_effect.color  = map_to_cmmk_rgb(modes[active_mode].colors[0]);

                controller->SetMode(fully_lit_effect);
            }
            break;

        case CMMK_EFFECT_BREATHE:
            {
                cmmk_effect_breathe breathe_effect;

                breathe_effect.speed    = (uint8_t)modes[active_mode].speed;
                breathe_effect.color    = map_to_cmmk_rgb(modes[active_mode].colors[0]);

                controller->SetMode(breathe_effect);
            }
            break;

        case CMMK_EFFECT_CYCLE:
            {
                cmmk_effect_cycle cycle_effect;

                cycle_effect.speed  = (uint8_t)modes[active_mode].speed;

                controller->SetMode(cycle_effect);
            }
            break;

        case CMMK_EFFECT_SINGLE:
            {
                cmmk_effect_single single_effect;

                single_effect.speed     = (uint8_t)modes[active_mode].speed;
                single_effect.active    = map_to_cmmk_rgb(modes[active_mode].colors[0]);
                single_effect.rest      = map_to_cmmk_rgb(modes[active_mode].colors[1]);

                controller->SetMode(single_effect);
            }
            break;

        case CMMK_EFFECT_WAVE:
            {
                cmmk_effect_wave wave_effect;

                wave_effect.speed       = (uint8_t)modes[active_mode].speed;
                wave_effect.direction   = map_to_cmmk_dir(modes[active_mode].direction);
                wave_effect.start       = map_to_cmmk_rgb(modes[active_mode].colors[0]);

                controller->SetMode(wave_effect);
            }
            break;

        case CMMK_EFFECT_RIPPLE:
            {
                cmmk_effect_ripple ripple_effect;

                ripple_effect.speed             = (uint8_t)modes[active_mode].speed;
                ripple_effect.active            = map_to_cmmk_rgb(modes[active_mode].colors[0]);
                ripple_effect.rest              = map_to_cmmk_rgb(modes[active_mode].colors[1]);

                if(modes[active_mode].color_mode == MODE_COLORS_RANDOM)
                {
                    ripple_effect.ripple_type   = CMMK_RIPPLE_RANDOM_COLOR;
                }
                else
                {
                    ripple_effect.ripple_type   = CMMK_RIPPLE_GIVEN_COLOR;
                }

                controller->SetMode(ripple_effect);
            }
            break;

        case CMMK_EFFECT_CROSS:
            {
                cmmk_effect_cross cross_effect;

                cross_effect.speed  = (uint8_t)modes[active_mode].speed;
                cross_effect.active = map_to_cmmk_rgb(modes[active_mode].colors[0]);
                cross_effect.rest   = map_to_cmmk_rgb(modes[active_mode].colors[1]);

                controller->SetMode(cross_effect);
            }
            break;

        case CMMK_EFFECT_RAINDROPS:
            {
                cmmk_effect_raindrops raindrops_effect;

                raindrops_effect.speed      = (uint8_t)modes[active_mode].speed;
                raindrops_effect.interval   = CMMK_SPEED_MID;
                raindrops_effect.active     = map_to_cmmk_rgb(modes[active_mode].colors[0]);
                raindrops_effect.rest       = map_to_cmmk_rgb(modes[active_mode].colors[1]);

                controller->SetMode(raindrops_effect);
            }
            break;

        case CMMK_EFFECT_STARS:
            {
                cmmk_effect_stars stars_effect;

                stars_effect.speed      = (uint8_t)modes[active_mode].speed;
                stars_effect.interval   = CMMK_SPEED_MID;
                stars_effect.active     = map_to_cmmk_rgb(modes[active_mode].colors[0]);
                stars_effect.rest       = map_to_cmmk_rgb(modes[active_mode].colors[1]);

                controller->SetMode(stars_effect);
            }
            break;

        case CMMK_EFFECT_SNAKE:
            {
                cmmk_effect_snake snake_effect;

                snake_effect.speed = (uint8_t)modes[active_mode].speed;

                controller->SetMode(snake_effect);
            }
            break;
    }
}
