/*---------------------------------------------------------*\
| RGBController_Debug.cpp                                   |
|                                                           |
|   Debug RGBController that can mimic various devices for  |
|   development and test purposes                           |
|                                                           |
|   Adam Honse (CalcProgrammer1)                31 Jul 2025 |
|                                                           |
|   This file is part of the OpenRGB project                |
|   SPDX-License-Identifier: GPL-2.0-or-later               |
\*---------------------------------------------------------*/

#include <algorithm>
#include <cstring>
#include "KeyboardLayoutManager.h"
#include "RGBController_Debug.h"

/**------------------------------------------------------------------*\
    @name Debug
    @category Unknown
    @type I2C
    @save :x:
    @direct :x:
    @effects :x:
    @detectors DetectDebugControllers
    @comment
\*-------------------------------------------------------------------*/

//0xFFFFFFFF indicates an unused entry in matrix
#define NA  0xFFFFFFFF

#define NUM_LAYOUTS 6

static const std::string layout_names[] =
{
    "Default",
    "ANSI QWERTY",
    "ISO QWERTY",
    "ISO QWERTZ",
    "ISO AZERTY",
    "JIS"
};

static unsigned int debug_keyboard_underglow_map[3][10] =
    { {    0,   1,   2,   3,   4,   5,   6,   7,   8,   9 },
      {   10,  11,  12,  13,  14,  15,  16,  17,  18,  19 },
      {   20,  21,  22,  23,  24,  25,  26,  27,  28,  29 } };

RGBController_Debug::RGBController_Debug(bool custom_controller, json debug_settings)
{
    if(custom_controller)
    {
        /*-------------------------------------------------*\
        | Set the name                                      |
        \*-------------------------------------------------*/
        name = debug_settings["DeviceName"];

        /*-------------------------------------------------*\
        | Find the device type                              |
        \*-------------------------------------------------*/
        if      (debug_settings["DeviceType"] == "motherboard")   type = DEVICE_TYPE_MOTHERBOARD;
        else if (debug_settings["DeviceType"] == "dram")          type = DEVICE_TYPE_DRAM;
        else if (debug_settings["DeviceType"] == "gpu")           type = DEVICE_TYPE_GPU;
        else if (debug_settings["DeviceType"] == "cooler")        type = DEVICE_TYPE_COOLER;
        else if (debug_settings["DeviceType"] == "led_strip")     type = DEVICE_TYPE_LEDSTRIP;
        else if (debug_settings["DeviceType"] == "keyboard")      type = DEVICE_TYPE_KEYBOARD;
        else if (debug_settings["DeviceType"] == "mouse")         type = DEVICE_TYPE_MOUSE;
        else if (debug_settings["DeviceType"] == "mousemat")      type = DEVICE_TYPE_MOUSEMAT;
        else if (debug_settings["DeviceType"] == "headset")       type = DEVICE_TYPE_HEADSET;
        else if (debug_settings["DeviceType"] == "headset_stand") type = DEVICE_TYPE_HEADSET_STAND;
        else if (debug_settings["DeviceType"] == "gamepad")       type = DEVICE_TYPE_GAMEPAD;
        else if (debug_settings["DeviceType"] == "light")         type = DEVICE_TYPE_LIGHT;
        else if (debug_settings["DeviceType"] == "speaker")       type = DEVICE_TYPE_SPEAKER;
        else if (debug_settings["DeviceType"] == "unknown")       type = DEVICE_TYPE_UNKNOWN;

        /*-------------------------------------------------*\
        | Set description, location, version, and serial    |
        \*-------------------------------------------------*/
        description                             = debug_settings["DeviceDescription"];
        location                                = debug_settings["DeviceLocation"];
        version                                 = debug_settings["DeviceVersion"];
        serial                                  = debug_settings["DeviceSerial"];

        /*-------------------------------------------------*\
        | Create the mode                                   |
        \*-------------------------------------------------*/
        mode Direct;

        Direct.name                             = "Direct";
        Direct.value                            = 0;
        Direct.flags                            = MODE_FLAG_HAS_PER_LED_COLOR;
        Direct.color_mode                       = MODE_COLORS_PER_LED;

        modes.push_back(Direct);

        /*-------------------------------------------------*\
        | Fill in zones                                     |
        \*-------------------------------------------------*/
        for(int ZoneID = 0; ZoneID < (int)debug_settings["DeviceZones"].size(); ZoneID++)
        {
            json ZoneJson = debug_settings["DeviceZones"][ZoneID];

            if
            (
                !ZoneJson.contains("name")       ||
                !ZoneJson.contains("type")       ||
                !ZoneJson.contains("leds_min")   ||
                !ZoneJson.contains("leds_max")   ||
                !ZoneJson.contains("leds_count")
            )
            {
                continue;
            }
            zone custom_zone;

            custom_zone.name = ZoneJson["name"];

            if      (ZoneJson["type"] == "linear") custom_zone.type = ZONE_TYPE_LINEAR;
            else if (ZoneJson["type"] == "matrix") custom_zone.type = ZONE_TYPE_MATRIX;
            else if (ZoneJson["type"] == "single") custom_zone.type = ZONE_TYPE_SINGLE;
            else
            {
                continue;
            }

            custom_zone.leds_min   = ZoneJson["leds_min"];
            custom_zone.leds_max   = ZoneJson["leds_max"];
            custom_zone.leds_count = ZoneJson["leds_count"];

            /*---------------------------------------------*\
            | Fill in the matrix map                        |
            \*---------------------------------------------*/
            bool BadVal = false;

            if(custom_zone.type == ZONE_TYPE_MATRIX)
            {
                if
                (
                    !ZoneJson.contains("matrix_height") ||
                    !ZoneJson.contains("matrix_width")  ||
                    !ZoneJson.contains("matrix_map")
                )
                {
                    /*-------------------------------------*\
                    | If there is no map then the zone      |
                    | can't be valid. Don't add it          |
                    \*-------------------------------------*/
                    continue;
                }

                custom_zone.matrix_map          = new matrix_map_type;

                custom_zone.matrix_map->width   = ZoneJson["matrix_width"];
                custom_zone.matrix_map->height  = ZoneJson["matrix_height"];

                int H                           = custom_zone.matrix_map->height;
                int W                           = custom_zone.matrix_map->width;

                BadVal                          = (ZoneJson["matrix_map"].size() != custom_zone.matrix_map->height);

                unsigned int* MatrixARR         = new unsigned int[H * W];

                for(int MatrixMapRow = 0; MatrixMapRow < H; MatrixMapRow++)
                {
                    /*-------------------------------------*\
                    | If something went wrong then make no  |
                    | attempt to recover and just move on   |
                    | in a way that doesn't crash.  Even 1  |
                    | bad row can corrupt the map so skip   |
                    | the zone entirely                     |
                    \*-------------------------------------*/
                    if((custom_zone.matrix_map->width != ZoneJson["matrix_map"][MatrixMapRow].size()) || BadVal)
                    {
                        BadVal = true;
                        break;
                    }

                    for(int MatrixMapCol = 0; MatrixMapCol < W; MatrixMapCol++)
                    {
                        int Val = ZoneJson["matrix_map"][MatrixMapRow][MatrixMapCol];

                        if((signed)Val == -1)
                        {
                            MatrixARR[MatrixMapRow * W + MatrixMapCol] = NA;
                        }
                        else
                        {
                            MatrixARR[MatrixMapRow * W + MatrixMapCol] = (unsigned)Val;
                        }
                    }
                }

                custom_zone.matrix_map->map = MatrixARR;
            }

            /*---------------------------------------------*\
            | Don't add the zone if it is invalid           |
            \*---------------------------------------------*/
            if(BadVal)
            {
                continue;
            }

            bool UseCustomLabels = false;
            if(ZoneJson.contains("custom_labels"))
            {
                /*-----------------------------------------*\
                | If the count is correct and the zone is   |
                | non-resizeable                            |
                \*-----------------------------------------*/
                if((ZoneJson["custom_labels"].size() == custom_zone.leds_count) && (custom_zone.leds_min == custom_zone.leds_max))
                {
                    UseCustomLabels = true;
                }
            }

            /*---------------------------------------------*\
            | Set the LED names                             |
            \*---------------------------------------------*/
            for(int LED_ID = 0; LED_ID < (int)custom_zone.leds_count; LED_ID++)
            {
                led custom_led;
                if(UseCustomLabels)
                {
                    /*-------------------------------------*\
                    | Set the label to the user defined     |
                    | label                                 |
                    \*-------------------------------------*/
                    custom_led.name = ZoneJson["custom_labels"][LED_ID];
                }
                else
                {
                    /*-------------------------------------*\
                    | Set default labels because something  |
                    | went wrong                            |
                    \*-------------------------------------*/
                    custom_led.name = ("Custom LED. Zone " + std::to_string(ZoneID) + ", LED " + std::to_string(LED_ID));
                }

                leds.push_back(custom_led);
            }

            zones.push_back(custom_zone);
        }

        SetupColors();
    }
    else
    {
        bool        zone_single     = true;
        bool        zone_linear     = true;
        bool        zone_resizable  = false;
        bool        zone_keyboard   = false;
        bool        zone_underglow  = false;
        std::string name_setting    = "";
        std::string type_setting    = "keyboard";

        if(debug_settings.contains("name"))
        {
            name_setting = debug_settings["name"];
        }

        if(debug_settings.contains("type"))
        {
            type_setting = debug_settings["type"];
        }

        if(debug_settings.contains("single"))
        {
            zone_single = debug_settings["single"];
        }

        if(debug_settings.contains("linear"))
        {
            zone_linear = debug_settings["linear"];
        }

        if(debug_settings.contains("resizable"))
        {
            zone_resizable = debug_settings["resizable"];
        }

        if(debug_settings.contains("keyboard"))
        {
            zone_keyboard = debug_settings["keyboard"];
        }

        if(debug_settings.contains("underglow"))
        {
            zone_underglow = debug_settings["underglow"];
        }

        if(type_setting == "motherboard")
        {
            name                = "Debug Motherboard";
            type                = DEVICE_TYPE_MOTHERBOARD;
        }
        else if(type_setting == "dram")
        {
            name                = "Debug DRAM";
            type                = DEVICE_TYPE_DRAM;
        }
        else if(type_setting == "gpu")
        {
            name                = "Debug GPU";
            type                = DEVICE_TYPE_GPU;
        }
        else if(type_setting == "keyboard")
        {
            name                = "Debug Keyboard";
            type                = DEVICE_TYPE_KEYBOARD;
        }
      else if(type_setting == "mouse")
        {
            name                = "Debug Mouse";
            type                = DEVICE_TYPE_MOUSE;
        }
        else if(type_setting == "argb")
        {
            name                = "Debug ARGB Controller";
            type                = DEVICE_TYPE_LEDSTRIP;
        }

        /*---------------------------------------------------------*\
        | Fill in debug controller information                      |
        \*---------------------------------------------------------*/
        description                     = name + " Device";
        vendor                          = name + " Vendor String";
        location                        = name + " Location String";
        version                         = name + " Version String";
        serial                          = name + " Serial String";

        if(name_setting != "")
        {
            name                        = name_setting;
        }

        /*---------------------------------------------------------*\
        | Create a direct mode                                      |
        \*---------------------------------------------------------*/
        mode Direct;

        Direct.name                     = "Direct";
        Direct.value                    = 0;
        Direct.flags                    = MODE_FLAG_HAS_PER_LED_COLOR;
        Direct.color_mode               = MODE_COLORS_PER_LED;

        modes.push_back(Direct);

        /*---------------------------------------------------------*\
        | Create a single zone/LED                                  |
        \*---------------------------------------------------------*/
        if(zone_single)
        {
            zone single_zone;

            single_zone.name            = "Single Zone";
            single_zone.type            = ZONE_TYPE_SINGLE;
            single_zone.leds_min        = 1;
            single_zone.leds_max        = 1;
            single_zone.leds_count      = 1;
            single_zone.matrix_map      = NULL;

            zones.push_back(single_zone);

            led single_led;

            single_led.name             = "Single LED";

            leds.push_back(single_led);

            led_alt_names.push_back("");
        }

        /*---------------------------------------------------------*\
        | Create a linear zone                                      |
        \*---------------------------------------------------------*/
        if(zone_linear)
        {
            zone linear_zone;

            linear_zone.name            = "Linear Zone";
            linear_zone.type            = ZONE_TYPE_LINEAR;
            linear_zone.leds_min        = 10;
            linear_zone.leds_max        = 10;
            linear_zone.leds_count      = 10;
            linear_zone.matrix_map      = NULL;

            zones.push_back(linear_zone);

            for(std::size_t led_idx = 0; led_idx < 10; led_idx++)
            {
                led linear_led;

                linear_led.name         = "Linear LED " + std::to_string(led_idx);

                leds.push_back(linear_led);

                led_alt_names.push_back("");
            }
        }
        /*---------------------------------------------------------*\
        | Create a keyboard matrix zone                             |
        \*---------------------------------------------------------*/
        if(zone_keyboard)
        {
            KEYBOARD_LAYOUT layout              = KEYBOARD_LAYOUT::KEYBOARD_LAYOUT_ANSI_QWERTY;
            KEYBOARD_SIZE   size                = KEYBOARD_SIZE::KEYBOARD_SIZE_FULL;

            if(debug_settings.contains("layout"))
            {
                KEYBOARD_LAYOUT temp_layout = debug_settings["layout"];

                if(temp_layout < NUM_LAYOUTS)
                {
                    layout = temp_layout;
                }
            }

            if(debug_settings.contains("size"))
            {
                size = debug_settings["size"];
            }

            KeyboardLayoutManager new_kb(layout, size);

            description                        += ", Layout: " + layout_names[layout] + ", Size: " + new_kb.GetName();

            /*-----------------------------------------------------*\
            | Check for custom key inserts and swaps                |
            \*-----------------------------------------------------*/
            const char* change_keys             = "change_keys";

            if(debug_settings.contains(change_keys))
            {
                std::vector<keyboard_led> change;

                const char* ins_row             = "ins_row";
                const char* rmv_key             = "rmv_key";
                const char* rmv_row             = "rmv_row";
                const char* swp_key             = "swp_key";

                const char* dbg_zone            = "Zone";
                const char* dbg_row             = "Row";
                const char* dbg_col             = "Col";
                const char* dbg_val             = "Val";
                const char* dbg_name            = "Name";
                const char* dbg_opcode          = "Opcode";

                for(size_t i = 0; i < debug_settings[change_keys].size(); i++)
                {
                    keyboard_led* key           = new keyboard_led;

                    key->zone                   = debug_settings[change_keys][i][dbg_zone];
                    key->row                    = debug_settings[change_keys][i][dbg_row];
                    key->col                    = debug_settings[change_keys][i][dbg_col];
                    key->value                  = debug_settings[change_keys][i][dbg_val];
                    key->name                   = debug_settings[change_keys][i][dbg_name].get_ref<const std::string&>().c_str();

                    if(debug_settings[change_keys][i][dbg_opcode] == ins_row)
                    {
                        key->opcode             = KEYBOARD_OPCODE_INSERT_ROW;
                    }
                    else if(debug_settings[change_keys][i][dbg_opcode] == rmv_key)
                    {
                        key->opcode             = KEYBOARD_OPCODE_REMOVE_SHIFT_LEFT;
                    }
                    else if(debug_settings[change_keys][i][dbg_opcode] == rmv_row)
                    {
                        key->opcode             = KEYBOARD_OPCODE_REMOVE_ROW;
                    }
                    else if(debug_settings[change_keys][i][dbg_opcode] == swp_key)
                    {
                        key->opcode             = KEYBOARD_OPCODE_SWAP_ONLY;
                    }
                    else
                    {
                        key->opcode             = KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT;
                    }

                    change.push_back(*key);
                }

                new_kb.ChangeKeys(change);
            }

            zone keyboard_zone;

            keyboard_zone.name                  = "Keyboard Zone";
            keyboard_zone.type                  = ZONE_TYPE_MATRIX;
            keyboard_zone.leds_min              = new_kb.GetKeyCount();
            keyboard_zone.leds_max              = new_kb.GetKeyCount();
            keyboard_zone.leds_count            = new_kb.GetKeyCount();
            keyboard_zone.matrix_map            = new matrix_map_type;
            keyboard_zone.matrix_map->height    = new_kb.GetRowCount();
            keyboard_zone.matrix_map->width     = new_kb.GetColumnCount();
            keyboard_zone.matrix_map->map       = new unsigned int[keyboard_zone.matrix_map->height * keyboard_zone.matrix_map->width];

            new_kb.GetKeyMap(keyboard_zone.matrix_map->map, KEYBOARD_MAP_FILL_TYPE_COUNT);

            zones.push_back(keyboard_zone);

            for(unsigned int led_idx = 0; led_idx < keyboard_zone.leds_count; led_idx++)
            {
                led keyboard_led;

                keyboard_led.name = new_kb.GetKeyNameAt(led_idx);

                leds.push_back(keyboard_led);

                led_alt_names.push_back(new_kb.GetKeyAltNameAt(led_idx));
            }
        }
        /*---------------------------------------------------------*\
        | Create an underglow matrix zone                           |
        \*---------------------------------------------------------*/
        if(zone_underglow)
        {
            zone underglow_zone;

            underglow_zone.name                 = "Underglow Zone";
            underglow_zone.type                 = ZONE_TYPE_MATRIX;
            underglow_zone.leds_min             = 30;
            underglow_zone.leds_max             = 30;
            underglow_zone.leds_count           = 30;
            underglow_zone.matrix_map           = new matrix_map_type;
            underglow_zone.matrix_map->height   = 3;
            underglow_zone.matrix_map->width    = 10;
            underglow_zone.matrix_map->map      = (unsigned int*)&debug_keyboard_underglow_map;

            zones.push_back(underglow_zone);

            for(std::size_t led_idx = 0; led_idx < underglow_zone.leds_count; led_idx++)
            {
                led underglow_led;

                underglow_led.name              = "Underglow LED "  + std::to_string(led_idx);;

                leds.push_back(underglow_led);

                led_alt_names.push_back("");
            }
        }
        /*---------------------------------------------------------*\
        | Create a resizable linear zone                            |
        \*---------------------------------------------------------*/
        if(zone_resizable)
        {
            zone resizable_zone;

            resizable_zone.name         = "Resizable Zone";
            resizable_zone.type         = ZONE_TYPE_LINEAR;
            resizable_zone.leds_min     = 0;
            resizable_zone.leds_max     = 100;
            resizable_zone.leds_count   = 0;
            resizable_zone.matrix_map   = NULL;

            zones.push_back(resizable_zone);
        }
    }

    SetupColors();
}

RGBController_Debug::~RGBController_Debug()
{

}

void RGBController_Debug::SetupZones()
{

}

void RGBController_Debug::ResizeZone(int index, int new_size)
{
    //Make sure that it isn't out of bounds (negative numbers)
    if(new_size < int(zones[index].leds_min))
    {
         new_size = zones[index].leds_min;
    }

    // Same thing as the above line except for over 100
    if(new_size > int(zones[index].leds_max))
    {
        new_size = zones[index].leds_max;
    }

    // Store the previous amount of LEDs
    int old_size = zones[index].leds_count;

    // Set the LED count in the zone to the new ammount
    zones[index].leds_count = new_size;

    // Set the new ammount of LEDs for to the new size
    size_t old_leds_size = leds.size();

    // Add the new ammount of LEDs to the old ammount
    size_t new_leds_size = leds.size() - old_size + new_size;

    leds.resize(std::max(old_leds_size, new_leds_size));

    memmove((void *)(&leds[zones[index].start_idx] + old_leds_size), (const void *)(&leds[zones[index].start_idx] + new_leds_size), (old_leds_size - zones[index].start_idx - old_size) * sizeof(led));

    leds.resize(new_leds_size);

    for(int i = 0; i < new_size; ++i)
    {
        leds[zones[index].start_idx + i].name = "Linear LED " + std::to_string(i);
    }

    SetupColors();
}

void RGBController_Debug::DeviceUpdateLEDs()
{

}

void RGBController_Debug::UpdateZoneLEDs(int /*zone*/)
{

}

void RGBController_Debug::UpdateSingleLED(int /*led*/)
{

}

void RGBController_Debug::DeviceUpdateMode()
{

}
