/*---------------------------------------------------------*\
| RGBController_ZotacBlackwellGPU.cpp                       |
|                                                           |
|   RGBController for ZOTAC Blackwell (RTX 50 series) GPU   |
|                                                           |
|   Eder Sánchez                                27 Mar 2026 |
|                                                           |
|   This file is part of the OpenRGB project                |
|   SPDX-License-Identifier: GPL-2.0-or-later               |
\*---------------------------------------------------------*/

#include "RGBController_ZotacBlackwellGPU.h"
#include "LogManager.h"
#include "pci_ids.h"

/**------------------------------------------------------------------*\
    @name ZOTAC RTX 50 series GPU
    @category GPU
    @type I2C
    @save :robot:
    @direct :x:
    @effects :tools:
    @detectors DetectZotacBlackwellGPUControllersPCI
    @comment
        Supports ZOTAC Blackwell (RTX 50 series) GPUs. The zone layout
        varies per card and is resolved from a static table keyed on
        PCI device and sub-device IDs.

        The controller uses individual SMBus byte writes (registers
        0x20-0x2F) with a 3ms delay between each transaction.

        To add new cards, add PCI ID entries in `pci_ids/pci_ids.h`,
        a zone config entry in `device_zone_configs` below, and a
        `REGISTER_I2C_PCI_DETECTOR` line in
        `Controllers/ZotacBlackwellGPUController/ZotacBlackwellGPUControllerDetect.cpp`.
\*-------------------------------------------------------------------*/

const RGBController_ZotacBlackwellGPU::DeviceZoneConfig RGBController_ZotacBlackwellGPU::device_zone_configs[] =
{
    { NVIDIA_RTX5080_DEV, ZOTAC_RTX5080_AMP_EXTREME_SUB_DEV, { "Logo", "Side Bar", "Infinity Mirror" }, 3 },
    { NVIDIA_RTX5090_DEV, ZOTAC_RTX5090_SOLID_OC_SUB_DEV,     { "ZOTAC Gaming", "Logo" },                2 },
    { 0, 0, { nullptr }, 0 }
};

const RGBController_ZotacBlackwellGPU::DeviceZoneConfig* RGBController_ZotacBlackwellGPU::FindZoneConfig(uint16_t device, uint16_t subdevice)
{
    for(const DeviceZoneConfig* cfg = device_zone_configs; cfg->zone_count != 0; cfg++)
    {
        if(cfg->device == device && cfg->subdevice == subdevice)
        {
            return cfg;
        }
    }
    return nullptr;
}

RGBController_ZotacBlackwellGPU::RGBController_ZotacBlackwellGPU(ZotacBlackwellGPUController* controller_ptr,
                                                                   uint16_t device, uint16_t subdevice)
{
    controller              = controller_ptr;

    const DeviceZoneConfig* cfg = FindZoneConfig(device, subdevice);
    if(cfg == nullptr)
    {
        LOG_ERROR("[%s] Unrecognized PCI device/subdevice: %04X/%04X. Falling back to three generic zones.",
                  controller->GetName().c_str(), device, subdevice);
        zone_names.push_back("Zone 0");
        zone_names.push_back("Zone 1");
        zone_names.push_back("Zone 2");
    }
    else
    {
        for(uint8_t z = 0; z < cfg->zone_count; z++)
        {
            zone_names.push_back(cfg->zones[z]);
        }
    }

    name                    = controller->GetName();
    vendor                  = "ZOTAC";
    description             = "ZOTAC RTX 50 series RGB GPU Device (" + controller->GetVersion() + ")";
    location                = controller->GetDeviceLocation();
    type                    = DEVICE_TYPE_GPU;
    version                 = controller->GetVersion();

    /*---------------------------------------------------------*\
    | Static mode                                               |
    \*---------------------------------------------------------*/
    mode STATIC;
    STATIC.name             = "Static";
    STATIC.value            = ZOTAC_BLACKWELL_GPU_MODE_STATIC;
    STATIC.flags            = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_PER_LED_COLOR;
    STATIC.brightness_min   = 0;
    STATIC.brightness_max   = 100;
    STATIC.brightness       = 100;
    STATIC.color_mode       = MODE_COLORS_PER_LED;
    modes.push_back(STATIC);

    /*---------------------------------------------------------*\
    | Breathe mode                                              |
    \*---------------------------------------------------------*/
    mode BREATHE;
    BREATHE.name            = "Breathe";
    BREATHE.value           = ZOTAC_BLACKWELL_GPU_MODE_BREATHE;
    BREATHE.flags           = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_PER_LED_COLOR;
    BREATHE.brightness_min  = 0;
    BREATHE.brightness_max  = 100;
    BREATHE.brightness      = 100;
    BREATHE.speed_min       = 0;
    BREATHE.speed_max       = 100;
    BREATHE.speed           = 20;
    BREATHE.color_mode      = MODE_COLORS_PER_LED;
    modes.push_back(BREATHE);

    /*---------------------------------------------------------*\
    | Fade mode                                                 |
    \*---------------------------------------------------------*/
    mode FADE;
    FADE.name               = "Fade";
    FADE.value              = ZOTAC_BLACKWELL_GPU_MODE_FADE;
    FADE.flags              = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_SPEED;
    FADE.speed_min          = 0;
    FADE.speed_max          = 100;
    FADE.speed              = 20;
    FADE.color_mode         = MODE_COLORS_NONE;
    modes.push_back(FADE);

    /*---------------------------------------------------------*\
    | Wink mode                                                 |
    \*---------------------------------------------------------*/
    mode WINK;
    WINK.name               = "Wink";
    WINK.value              = ZOTAC_BLACKWELL_GPU_MODE_WINK;
    WINK.flags              = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_PER_LED_COLOR;
    WINK.brightness_min     = 0;
    WINK.brightness_max     = 100;
    WINK.brightness         = 100;
    WINK.speed_min          = 0;
    WINK.speed_max          = 100;
    WINK.speed              = 20;
    WINK.color_mode         = MODE_COLORS_PER_LED;
    modes.push_back(WINK);

    /*---------------------------------------------------------*\
    | Glide mode                                                |
    \*---------------------------------------------------------*/
    mode GLIDE;
    GLIDE.name              = "Glide";
    GLIDE.value             = ZOTAC_BLACKWELL_GPU_MODE_GLIDE;
    GLIDE.flags             = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_DIRECTION_LR | MODE_FLAG_HAS_PER_LED_COLOR;
    GLIDE.brightness_min    = 0;
    GLIDE.brightness_max    = 100;
    GLIDE.brightness        = 100;
    GLIDE.speed_min         = 0;
    GLIDE.speed_max         = 100;
    GLIDE.speed             = 20;
    GLIDE.color_mode        = MODE_COLORS_PER_LED;
    modes.push_back(GLIDE);

    /*---------------------------------------------------------*\
    | Prism mode (called "Rainbow" in older firmware)           |
    \*---------------------------------------------------------*/
    mode PRISM;
    PRISM.name              = "Prism";
    PRISM.value             = ZOTAC_BLACKWELL_GPU_MODE_PRISM;
    PRISM.flags             = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_DIRECTION_LR;
    PRISM.speed_min         = 0;
    PRISM.speed_max         = 100;
    PRISM.speed             = 20;
    PRISM.color_mode        = MODE_COLORS_NONE;
    modes.push_back(PRISM);

    /*---------------------------------------------------------*\
    | Bokeh mode                                                |
    \*---------------------------------------------------------*/
    mode BOKEH;
    BOKEH.name              = "Bokeh";
    BOKEH.value             = ZOTAC_BLACKWELL_GPU_MODE_BOKEH;
    BOKEH.flags             = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_PER_LED_COLOR;
    BOKEH.brightness_min    = 0;
    BOKEH.brightness_max    = 100;
    BOKEH.brightness        = 100;
    BOKEH.speed_min         = 0;
    BOKEH.speed_max         = 100;
    BOKEH.speed             = 20;
    BOKEH.color_mode        = MODE_COLORS_PER_LED;
    modes.push_back(BOKEH);

    /*---------------------------------------------------------*\
    | Beacon mode                                               |
    \*---------------------------------------------------------*/
    mode BEACON;
    BEACON.name             = "Beacon";
    BEACON.value            = ZOTAC_BLACKWELL_GPU_MODE_BEACON;
    BEACON.flags            = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_PER_LED_COLOR;
    BEACON.brightness_min   = 0;
    BEACON.brightness_max   = 100;
    BEACON.brightness       = 100;
    BEACON.speed_min        = 0;
    BEACON.speed_max        = 100;
    BEACON.speed            = 20;
    BEACON.color_mode       = MODE_COLORS_PER_LED;
    modes.push_back(BEACON);

    /*---------------------------------------------------------*\
    | Tandem mode                                               |
    \*---------------------------------------------------------*/
    mode TANDEM;
    TANDEM.name             = "Tandem";
    TANDEM.value            = ZOTAC_BLACKWELL_GPU_MODE_TANDEM;
    TANDEM.flags            = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_PER_LED_COLOR;
    TANDEM.brightness_min   = 0;
    TANDEM.brightness_max   = 100;
    TANDEM.brightness       = 100;
    TANDEM.speed_min        = 0;
    TANDEM.speed_max        = 100;
    TANDEM.speed            = 20;
    TANDEM.color_mode       = MODE_COLORS_PER_LED;
    modes.push_back(TANDEM);

    /*---------------------------------------------------------*\
    | Tidal mode                                                |
    \*---------------------------------------------------------*/
    mode TIDAL;
    TIDAL.name              = "Tidal";
    TIDAL.value             = ZOTAC_BLACKWELL_GPU_MODE_TIDAL;
    TIDAL.flags             = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_DIRECTION_LR | MODE_FLAG_HAS_PER_LED_COLOR;
    TIDAL.brightness_min    = 0;
    TIDAL.brightness_max    = 100;
    TIDAL.brightness        = 100;
    TIDAL.speed_min         = 0;
    TIDAL.speed_max         = 100;
    TIDAL.speed             = 20;
    TIDAL.color_mode        = MODE_COLORS_PER_LED;
    modes.push_back(TIDAL);

    /*---------------------------------------------------------*\
    | Astra mode                                                |
    \*---------------------------------------------------------*/
    mode ASTRA;
    ASTRA.name              = "Astra";
    ASTRA.value             = ZOTAC_BLACKWELL_GPU_MODE_ASTRA;
    ASTRA.flags             = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_PER_LED_COLOR;
    ASTRA.brightness_min    = 0;
    ASTRA.brightness_max    = 100;
    ASTRA.brightness        = 100;
    ASTRA.speed_min         = 0;
    ASTRA.speed_max         = 100;
    ASTRA.speed             = 20;
    ASTRA.color_mode        = MODE_COLORS_PER_LED;
    modes.push_back(ASTRA);

    /*---------------------------------------------------------*\
    | Cosmic mode                                               |
    \*---------------------------------------------------------*/
    mode COSMIC;
    COSMIC.name             = "Cosmic";
    COSMIC.value            = ZOTAC_BLACKWELL_GPU_MODE_COSMIC;
    COSMIC.flags            = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_PER_LED_COLOR;
    COSMIC.brightness_min   = 0;
    COSMIC.brightness_max   = 100;
    COSMIC.brightness       = 100;
    COSMIC.speed_min        = 0;
    COSMIC.speed_max        = 100;
    COSMIC.speed            = 20;
    COSMIC.color_mode       = MODE_COLORS_PER_LED;
    modes.push_back(COSMIC);

    /*---------------------------------------------------------*\
    | Volta mode                                                |
    \*---------------------------------------------------------*/
    mode VOLTA;
    VOLTA.name              = "Volta";
    VOLTA.value             = ZOTAC_BLACKWELL_GPU_MODE_VOLTA;
    VOLTA.flags             = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_PER_LED_COLOR;
    VOLTA.brightness_min    = 0;
    VOLTA.brightness_max    = 100;
    VOLTA.brightness        = 100;
    VOLTA.speed_min         = 0;
    VOLTA.speed_max         = 100;
    VOLTA.speed             = 20;
    VOLTA.color_mode        = MODE_COLORS_PER_LED;
    modes.push_back(VOLTA);

    SetupZones();
}

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

void RGBController_ZotacBlackwellGPU::SetupZones()
{
    /*---------------------------------------------------------*\
    | One single-LED zone per name from the device_zone_configs |
    | table.  The zone's index is its position here, which is   |
    | written verbatim to the zone register (0x21) on update.   |
    \*---------------------------------------------------------*/
    for(const std::string& zone_name : zone_names)
    {
        zone new_zone;
        new_zone.name       = zone_name;
        new_zone.type       = ZONE_TYPE_SINGLE;
        new_zone.leds_min   = 1;
        new_zone.leds_max   = 1;
        new_zone.leds_count = 1;
        new_zone.matrix_map = NULL;
        zones.push_back(new_zone);

        led new_led;
        new_led.name        = zone_name + " LED";
        leds.push_back(new_led);
    }

    SetupColors();
}

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

void RGBController_ZotacBlackwellGPU::DeviceUpdateLEDs()
{
    DeviceUpdateMode();
}

void RGBController_ZotacBlackwellGPU::UpdateZoneLEDs(int zone)
{
    DeviceUpdateZone(zone);
}

void RGBController_ZotacBlackwellGPU::UpdateSingleLED(int led)
{
    DeviceUpdateZone(led);
}

void RGBController_ZotacBlackwellGPU::DeviceUpdateZone(int zone)
{
    unsigned int mode_val   = modes[active_mode].value;
    unsigned int brightness = modes[active_mode].brightness;
    unsigned int speed      = modes[active_mode].speed;
    unsigned int direction  = modes[active_mode].direction == MODE_DIRECTION_RIGHT
                            ? ZOTAC_BLACKWELL_GPU_DIR_RIGHT
                            : ZOTAC_BLACKWELL_GPU_DIR_LEFT;

    RGBColor color1;
    RGBColor color2 = ToRGBColor(0, 0, 0);

    switch(modes[active_mode].color_mode)
    {
        case MODE_COLORS_PER_LED:
            color1 = colors[zone];
            break;

        case MODE_COLORS_MODE_SPECIFIC:
            color1 = (modes[active_mode].colors.size() >= 1)
                   ? modes[active_mode].colors[0]
                   : ToRGBColor(0, 0, 0);
            color2 = (modes[active_mode].colors.size() >= 2)
                   ? modes[active_mode].colors[1]
                   : ToRGBColor(0, 0, 0);
            break;

        default:
            color1 = ToRGBColor(0, 0, 0);
            break;
    }

    controller->SetMode(zone, mode_val, color1, color2, brightness, speed, direction);
    controller->Commit();
}

void RGBController_ZotacBlackwellGPU::DeviceUpdateMode()
{
    for(unsigned int zone_idx = 0; zone_idx < zones.size(); zone_idx++)
    {
        DeviceUpdateZone(zone_idx);
    }
}
