/*-----------------------------------------*\
|  RGBController_ASRockPolychromeUSB.cpp    |
|                                           |
|  Generic RGB Interface for OpenRGB        |
|  ASRock Polychrome USB Driver             |
|                                           |
|  Ed Kambulow (Dredvard) 12/26/2020        |
\*-----------------------------------------*/

#include "RGBController_ASRockPolychromeUSB.h"
#include <string.h>

#define ASROCK_USB_MAX_ZONES        8
#define ASROCK_ADDRESSABLE_MAX_LEDS 100

RGBController_PolychromeUSB::RGBController_PolychromeUSB(PolychromeUSBController* polychrome_ptr)
{
    polychrome                  = polychrome_ptr;

    name                        = polychrome->GetDeviceName();
    description                 = "ASRock Polychrome USB Device";
    vendor                      = "ASRock";
    type                        = DEVICE_TYPE_MOTHERBOARD;
    location                    = polychrome->GetDeviceLocation();

    mode Off;
    Off.name                    = "Off";
    Off.value                   = POLYCHROME_USB_MODE_OFF;
    Off.flags                   = 0;
    Off.color_mode              = MODE_COLORS_NONE;
    modes.push_back(Off);

    mode Static;
    Static.name                 = "Static";
    Static.value                = POLYCHROME_USB_MODE_STATIC;
    Static.flags                = MODE_FLAG_HAS_PER_LED_COLOR;
    Static.color_mode           = MODE_COLORS_PER_LED;
    modes.push_back(Static);

    mode Breathing;
    Breathing.name              = "Breathing";
    Breathing.value             = POLYCHROME_USB_MODE_BREATHING;
    Breathing.flags             = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_PER_LED_COLOR;
    Breathing.speed_min         = POLYCHROME_USB_SPEED_MIN;
    Breathing.speed_max         = POLYCHROME_USB_SPEED_MAX;
    Breathing.speed             = POLYCHROME_USB_SPEED_DEFAULT;
    Breathing.color_mode        = MODE_COLORS_PER_LED;
    modes.push_back(Breathing);

    mode Strobe;
    Strobe.name                 = "Strobe";
    Strobe.value                = POLYCHROME_USB_MODE_STROBE;
    Strobe.flags                = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_PER_LED_COLOR;
    Strobe.speed_min            = POLYCHROME_USB_SPEED_MIN;
    Strobe.speed_max            = POLYCHROME_USB_SPEED_MAX;
    Strobe.speed                = POLYCHROME_USB_SPEED_DEFAULT;
    Strobe.color_mode           = MODE_COLORS_PER_LED;
    modes.push_back(Strobe);

    mode SpectrumCycle;
    SpectrumCycle.name          = "Spectrum Cycle";
    SpectrumCycle.value         = POLYCHROME_USB_MODE_SPECTRUM_CYCLE;
    SpectrumCycle.flags         = MODE_FLAG_HAS_SPEED;
    SpectrumCycle.speed_min     = POLYCHROME_USB_SPEED_MIN;
    SpectrumCycle.speed_max     = POLYCHROME_USB_SPEED_MAX;
    SpectrumCycle.speed         = POLYCHROME_USB_SPEED_DEFAULT;
    SpectrumCycle.color_mode    = MODE_COLORS_NONE;
    modes.push_back(SpectrumCycle);

    mode Random;
    Random.name                 = "Random";
    Random.value                = POLYCHROME_USB_MODE_RANDOM;
    Random.flags                = MODE_FLAG_HAS_SPEED;
    Random.speed_min            = POLYCHROME_USB_SPEED_MIN;
    Random.speed_max            = POLYCHROME_USB_SPEED_MAX;
    Random.speed                = POLYCHROME_USB_SPEED_DEFAULT;
    Random.color_mode           = MODE_COLORS_NONE;
    modes.push_back(Random);

    mode Music;
    Random.name                 = "Music";
    Random.value                = POLYCHROME_USB_MODE_RANDOM;
    Random.flags                = MODE_FLAG_HAS_BRIGHTNESS;
    Random.color_mode           = MODE_COLORS_NONE;
    modes.push_back(Random);

    mode Wave;
    Wave.name                   = "Wave";
    Wave.value                  = POLYCHROME_USB_MODE_WAVE;
    Wave.flags                  = MODE_FLAG_HAS_SPEED;
    Wave.speed_min              = POLYCHROME_USB_SPEED_MIN;
    Wave.speed_max              = POLYCHROME_USB_SPEED_MAX;
    Wave.speed                  = POLYCHROME_USB_SPEED_DEFAULT;
    Wave.color_mode             = MODE_COLORS_NONE;
    modes.push_back(Wave);

    mode Spring;
    Spring.name                 = "Spring";
    Spring.value                = POLYCHROME_USB_MODE_SPRING;
    Spring.flags                = MODE_FLAG_HAS_SPEED;
    Spring.speed_min            = POLYCHROME_USB_SPEED_MIN;
    Spring.speed_max            = POLYCHROME_USB_SPEED_MAX;
    Spring.speed                = POLYCHROME_USB_SPEED_DEFAULT;
    Spring.color_mode           = MODE_COLORS_NONE;
    modes.push_back(Spring);

    mode Stack;
    Stack.name                  = "Stack";
    Stack.value                 = POLYCHROME_USB_MODE_STACK;
    Stack.flags                 = MODE_FLAG_HAS_SPEED;
    Stack.speed_min             = POLYCHROME_USB_SPEED_MIN;
    Stack.speed_max             = POLYCHROME_USB_SPEED_MAX;
    Stack.speed                 = POLYCHROME_USB_SPEED_DEFAULT;
    Stack.color_mode            = MODE_COLORS_NONE;
    modes.push_back(Stack);

    mode Cram;
    Cram.name                   = "Cram";
    Cram.value                  = POLYCHROME_USB_MODE_CRAM;
    Cram.flags                  = MODE_FLAG_HAS_SPEED;
    Cram.speed_min              = POLYCHROME_USB_SPEED_MIN;
    Cram.speed_max              = POLYCHROME_USB_SPEED_MAX;
    Cram.speed                  = POLYCHROME_USB_SPEED_DEFAULT;
    Cram.color_mode             = MODE_COLORS_NONE;
    modes.push_back(Cram);

    mode Scan;
    Scan.name                   = "Scan";
    Scan.value                  = POLYCHROME_USB_MODE_SCAN;
    Scan.flags                  = MODE_FLAG_HAS_SPEED;
    Scan.speed_min              = POLYCHROME_USB_SPEED_MIN;
    Scan.speed_max              = POLYCHROME_USB_SPEED_MAX;
    Scan.speed                  = POLYCHROME_USB_SPEED_DEFAULT;
    Scan.color_mode             = MODE_COLORS_NONE;
    modes.push_back(Scan);

    mode Neon;
    Neon.name                   = "Neon";
    Neon.value                  = POLYCHROME_USB_MODE_NEON;
    Neon.flags                  = 0;
    Neon.color_mode             = MODE_COLORS_NONE;
    modes.push_back(Neon);

    mode Water;
    Water.name                  = "Water";
    Water.value                 = POLYCHROME_USB_MODE_WATER;
    Water.flags                 = MODE_FLAG_HAS_SPEED;
    Water.speed_min             = POLYCHROME_USB_SPEED_MIN;
    Water.speed_max             = POLYCHROME_USB_SPEED_MAX;
    Water.speed                 = POLYCHROME_USB_SPEED_DEFAULT;
    Water.color_mode            = MODE_COLORS_NONE;
    modes.push_back(Water);

    mode Rainbow;
    Rainbow.name                = "Rainbow";
    Rainbow.value               = POLYCHROME_USB_MODE_RAINBOW;
    Rainbow.flags               = MODE_FLAG_HAS_SPEED;
    Rainbow.speed_min           = POLYCHROME_USB_SPEED_MIN;
    Rainbow.speed_max           = POLYCHROME_USB_SPEED_MAX;
    Rainbow.speed               = POLYCHROME_USB_SPEED_DEFAULT;
    Rainbow.color_mode          = MODE_COLORS_NONE;
    modes.push_back(Rainbow);

    SetupZones();

    /*-------------------------------------------------*\
    | Initialize active mode                            |
    \*-------------------------------------------------*/
    active_mode = GetDeviceMode(1);
}

void RGBController_PolychromeUSB::SetupZones()
{
    /*-------------------------------------------------*\
    | Clear any existing color/LED configuration        |
    \*-------------------------------------------------*/
    leds.clear();
    colors.clear();
    zones.resize(polychrome->GetChannelCount());

    /*-------------------------------------------------*\
    | Set zones and leds                                |
    \*-------------------------------------------------*/
    for(unsigned int channel_idx = 0; channel_idx < zones.size(); channel_idx++)
    {
        PolychromeDeviceInfo device_info = polychrome->GetPolychromeDevices()[channel_idx];

        zones[channel_idx].type     = ZONE_TYPE_LINEAR;

        if(device_info.device_type== PolychromeDeviceType::ADDRESSABLE)
        {
            zones[channel_idx].name       = polychrome_USB_zone_names[channel_idx];
            zones[channel_idx].leds_min   = 0;
            zones[channel_idx].leds_max   = ASROCK_ADDRESSABLE_MAX_LEDS;
            zones[channel_idx].leds_count = device_info.num_leds; 
        }
        else if(device_info.device_type==PolychromeDeviceType::FIXED) 
        {
            zones[channel_idx].name       = polychrome_USB_zone_names[channel_idx];
            zones[channel_idx].leds_min   = device_info.num_leds;
            zones[channel_idx].leds_max   = device_info.num_leds;
            zones[channel_idx].leds_count = device_info.num_leds; 
        }       


        for(unsigned int led_ch_idx = 0; led_ch_idx < zones[channel_idx].leds_count; led_ch_idx++)
        {
            led new_led;
            new_led.name = zones[channel_idx].name;
            new_led.name.append(", LED ");
            new_led.name.append(std::to_string(led_ch_idx + 1));
            new_led.value = channel_idx;

            leds.push_back(new_led);
        }

        zones[channel_idx].matrix_map = NULL;
    }

    SetupColors();

    /*---------------------------------------------------------*\
    | Initialize colors for each LED                            |
    \*---------------------------------------------------------*/
    for(std::size_t led_idx = 0; led_idx < leds.size(); led_idx++)
    {
        unsigned char led = (unsigned char)leds[led_idx].value;

        colors[led_idx] = polychrome->GetZoneConfig(led).color;     // TODO Get addressable instead of zone idx
    }

    /*---------------------------------------------------------*\
    | Initialize zone info to track zone, speed, mode           |
    | B550 Boards have modes, speed for each zone               |
    \*---------------------------------------------------------*/
     for (unsigned int channel_idx = 0; channel_idx < zones.size(); channel_idx++)
    {
        PolychromeZoneInfo zoneinfo;
        zoneinfo = polychrome->GetZoneConfig(channel_idx); 
        zones_info.push_back(zoneinfo);
    }

}

void RGBController_PolychromeUSB::ResizeZone(int zone, int new_size)
{
     unsigned char zonecfg[ASROCK_USB_MAX_ZONES];

    /*---------------------------------------------------------*\
    | This device does not currently support resizing zones     |
    \*---------------------------------------------------------*/
    zones[zone].leds_count = (unsigned char) new_size;

    memset(zonecfg, POLYCHROME_USB_ZONE_UNAVAILABLE, ASROCK_USB_MAX_ZONES);

    for(unsigned int i = 0; i < zones.size(); i++)
    {
        zonecfg[i]=zones[i].leds_count;
    }

    // unsigned char zonecmd = POLYCHROME_USB_LEDCOUNT_CFG;
    // WriteHeader(zonecmd, zonecfg, sizeof(zonecfg));
}

void RGBController_PolychromeUSB::DeviceUpdateLEDs()
{
    for(std::size_t zone_idx = 0; zone_idx < zones.size(); zone_idx++)
    {
        unsigned char set_mode = zones_info[zone_idx].mode;

        if (set_mode>modes.size())
        {
            set_mode = active_mode;
        }

        polychrome->WriteZone(zone_idx, set_mode, zones_info[zone_idx].speed, zones[zone_idx].colors[0], false);
    }
}

void RGBController_PolychromeUSB::UpdateZoneLEDs(int zone)
{
    unsigned char set_mode=zones_info[zone].mode;

    if(set_mode > modes.size())
    {
        set_mode = active_mode;
    }

    polychrome->WriteZone(zone, set_mode, zones_info[zone].speed, zones[zone].colors[0], false);
}

void RGBController_PolychromeUSB::UpdateSingleLED(int led)
{
    unsigned int  channel  = leds[led].value;
    unsigned char set_mode = zones_info[channel].mode;

    if(set_mode > modes.size())
    {
        set_mode = active_mode;
    }

    polychrome->WriteZone(channel, set_mode, zones_info[channel].speed, zones[channel].colors[0], false);
}

unsigned char RGBController_PolychromeUSB::GetDeviceMode(unsigned char zone)
{
    int dev_mode;

    dev_mode    = polychrome->GetZoneConfig(zone).mode;
    active_mode = dev_mode;

    return(active_mode);  
}

void RGBController_PolychromeUSB::SetCustomMode()
{
    active_mode = POLYCHROME_USB_MODE_STATIC;
}

void RGBController_PolychromeUSB::DeviceUpdateMode()
{
    for(unsigned int zone_idx = 0; zone_idx < zones.size(); zone_idx++)
    {
        if(zones[zone_idx].leds_count > 0)
        {
            unsigned char set_mode      =(unsigned char) modes[active_mode].value;
            zones_info[zone_idx].mode   =(unsigned char) modes[active_mode].value;
            zones_info[zone_idx].speed  =(unsigned char) modes[active_mode].speed;
            
            if(set_mode > modes.size())
            {
                set_mode = active_mode;                    
            }

            polychrome->WriteZone(zone_idx, set_mode, zones_info[zone_idx].speed, zones[zone_idx].colors[0], false);
        }
    }
}
