/*---------------------------------------------------------*\
| GigabyteRGBFusion2USBController.h                         |
|                                                           |
|   Driver for Gigabyte Aorus RGB Fusion 2 USB motherboard  |
|                                                           |
|   jackun                                      08 Jan 2020 |
|   megadjc                                     31 Jul 2025 |
|                                                           |
|   This file is part of the OpenRGB project                |
|   SPDX-License-Identifier: GPL-2.0-or-later               |
\*---------------------------------------------------------*/

#pragma once

#include <algorithm>
#include <cstring>
#include <hidapi.h>
#include <map>
#include "RGBController.h"
#include "GigabyteFusion2USB_Devices.h"

#define FUSION2_USB_BUFFER_SIZE   64

/*--------------------------------------------------------*\
| Gen2 scan/info opcode base                               |
\*--------------------------------------------------------*/
#define GEN2_LED_BASE_SCAN      0x38

/*---------------------------------------------------------*\
| Effects mode list                                         |
\*---------------------------------------------------------*/
enum EffectType
{
    EFFECT_NONE                 = 0,
    EFFECT_STATIC               = 1,
    EFFECT_PULSE                = 2,
    EFFECT_BLINKING             = 3,
    EFFECT_COLORCYCLE           = 4,
    EFFECT_WAVE                 = 6,
    EFFECT_RANDOM               = 8,
    EFFECT_WAVE1                = 9,
    EFFECT_WAVE2                = 10,
    EFFECT_WAVE3                = 11,
    EFFECT_WAVE4                = 12,
    EFFECT_DFLASH               = 15,
    // to be continued...
};

/*---------------------------------------------------------*\
| Low level strip length divisions                          |
\*---------------------------------------------------------*/
enum LEDCount
{
    LEDS_32                     = 0,
    LEDS_64,
    LEDS_256,
    LEDS_512,
    LEDS_1024,
};

/*---------------------------------------------------------*\
| Defines the RGB led data structure.                       |
\*---------------------------------------------------------*/
struct LEDs
{
    uint8_t r;
    uint8_t g;
    uint8_t b;
};

/*---------------------------------------------------------*\
| Defines structure for low level calibration data.         |
\*---------------------------------------------------------*/
struct CalibrationData
{
    uint32_t dled[4]   = {0, 0, 0, 0};
    uint32_t spare[4]  = {0, 0, 0, 0};
    uint32_t mainboard = 0;
};

/*---------------------------------------------------------*\
| Defines structure for high level calibration data.        |
\*---------------------------------------------------------*/
struct EncodedCalibration
{
    std::string dled[4];
    std::string spare[4];
    std::string mainboard;
};

/*---------------------------------------------------------*\
| High level struct to contain Gen2 Header Data             |
\*---------------------------------------------------------*/
struct Gen2StripInfo
{
    uint8_t                 numStrip   = 0;
    std::vector<uint16_t>   LedsOfStrip;
    uint32_t                totalLeds  = 0;
};

#pragma pack(push, 1)

/*---------------------------------------------------------*\
| Packet structure for applying effects                     |
\*---------------------------------------------------------*/
union PktEffectApply
{
    unsigned char buffer[FUSION2_USB_BUFFER_SIZE];
    struct apply_data
    {
        uint8_t  report_id  = 0xCC;
        uint8_t  command_id = 0x28;
        uint32_t zone_sel0  = 0;
        uint32_t zone_sel1  = 0;
        uint8_t  padding[54];
    } a;

    PktEffectApply() : a {}
    {
        std::memset(a.padding, 0, sizeof(a.padding));
    }
};

/*---------------------------------------------------------*\
| Single LED Calibration struct                             |
\*---------------------------------------------------------*/
struct RGBA
{
    union
    {
        uint8_t raw[4];
        struct
        {
            uint8_t blue;
            uint8_t green;
            uint8_t red;
            uint8_t alpha;
        };
    };
};

typedef std::map< std::string, RGBA >       RGBCalibration;
typedef std::map< std::string, std::string> calibration;

/*---------------------------------------------------------*\
| Packet structure for ARGB headers (addressable)           |
\*---------------------------------------------------------*/
union PktRGB
{
    unsigned char buffer[FUSION2_USB_BUFFER_SIZE];
    struct RGBData
    {
        uint8_t     report_id;
        uint8_t     header;
        uint16_t    boffset;
        uint8_t     bcount;
        LEDs        leds[19];
        uint16_t    padding0;
    } s;

    PktRGB() : s {}
    {
    }

    void Init(uint8_t header, uint8_t report_id)
    {
        switch(header)
        {
            case LED4:
            case HDR_D_LED2:
                header = HDR_D_LED2_ARGB;
                break;
            case HDR_D_LED3:
                header = HDR_D_LED3_ARGB;
                break;
            case HDR_D_LED4:
                header = HDR_D_LED4_ARGB;
                break;
            default:
                header = HDR_D_LED1_ARGB;
                break;
        }
        s.report_id = report_id;
        s.header    = header;
        s.boffset   = 0;
        s.bcount    = 0;
        memset(s.leds, 0, sizeof(s.leds));
    }
};

/*---------------------------------------------------------*\
| Packet structure for hardware effects                     |
| Default values for Hardware Effects mode.                 |
| Old init values.                                          |
| (All values 0 unless otherwise noted below)               |
| e.color0        = 0x00FF2100;    //orange                 |
| e.period1       = 1200;                                   |
| e.period2       = 200;                                    |
| e.period3       = 200;                                    |
| e.effect_param2 = 1;                                      |
\*---------------------------------------------------------*/
union PktEffect
{
    unsigned char buffer[FUSION2_USB_BUFFER_SIZE];
    struct Effect
    {
        uint8_t report_id       = 0;
        uint8_t header          = 0;
        uint32_t zone0          = 0;    // RGB Fusion sets it to pow(2, led)
        uint32_t zone1          = 0;
        uint8_t reserved0       = 0;
        uint8_t effect_type     = EFFECT_STATIC;
        uint8_t max_brightness  = 255;
        uint8_t min_brightness  = 0;
        uint32_t color0         = 0;
        uint32_t color1         = 0;
        uint16_t period0        = 0;    // Fade in - Rising Timer - Needs to be 0 for "Direct"
        uint16_t period1        = 0;    // Fade out
        uint16_t period2        = 0;    // Hold
        uint16_t period3        = 0;
        uint8_t effect_param0   = 0;    // ex color count to cycle through (max seems to be 7)
        uint8_t effect_param1   = 0;
        uint8_t effect_param2   = 0;    // ex flash repeat count
        uint8_t effect_param3   = 0;
        uint8_t padding0[30];
    } e;

    PktEffect() : e {}
    {
    }

    void Init(int led, uint8_t report_id, uint16_t pid)
    {
        memset(buffer, 0, sizeof(buffer));

        e.report_id         = report_id;
        if(led == -1)
        {
            e.zone0  = (pid == 0x5711) ? 0x07FF : 0xFF;
            e.header = 0x20;
        }
        else if(led < 8)
        {
            e.zone0  = 1U << led;
            e.header = 0x20 + led;
        }
        else if(led < 11)
        {
            e.zone0  = 1U << led;
            e.header = 0x90 + (led - 8);
        }
        else
        {
            e.zone0  = 0;
            e.header = 0;
        }
    }
};

/*---------------------------------------------------------*\
| Basic Controller Init Struct                              |
\*---------------------------------------------------------*/
struct IT8297Report
{
    uint8_t  report_id;
    uint8_t  product;
    uint8_t  device_num;
    uint8_t  strip_detect;
    uint32_t fw_ver;
    uint8_t  curr_led_count_high;
    uint8_t  curr_led_count_low;
    uint8_t  strip_ctrl_length1;
    uint8_t  support_cmd_flag;
    char     str_product[28];
    uint32_t cal_spare0;
    uint32_t cal_strip0;
    uint32_t cal_strip1;
    uint32_t rgb_cali;
    uint32_t chip_id;
    uint32_t cal_spare1;
};

/*---------------------------------------------------------*\
| CC61 Calibration Struct (For IT5711)                      |
\*---------------------------------------------------------*/
struct IT5711Calibration
{
    uint8_t  report_id;
    uint8_t  reserved[3];
    uint32_t cal_strip2;
    uint32_t cal_strip3;
    uint32_t cal_spare2;
    uint32_t cal_spare3;
    uint8_t  padding[44];
};

/*---------------------------------------------------------*\
| CC33 Set Calibration Struct                               |
\*---------------------------------------------------------*/
union CMD_0x33
{
    unsigned char buffer[FUSION2_USB_BUFFER_SIZE];
    struct Calibration
    {
        uint8_t  report_id      = 0xCC;
        uint8_t  command_id     = 0x33;
        uint32_t d_strip_c0     = 0;
        uint32_t d_strip_c1     = 0;
        uint32_t rgb_cali       = 0;
        uint32_t c_spare0       = 0;
        uint32_t c_spare1       = 0;
        uint32_t d_strip_c2     = 0;
        uint32_t d_strip_c3     = 0;
        uint32_t c_spare2       = 0;
        uint32_t c_spare3       = 0;
        uint8_t  reserved[25];
    } c;

    CMD_0x33() : c{}
    {
        memset(c.reserved, 0, sizeof(c.reserved));
    }
};

#pragma pack(pop)

class RGBFusion2USBController
{
public:
    RGBFusion2USBController(hid_device* handle, const char *path, std::string mb_name, uint16_t pid);
    ~RGBFusion2USBController();

    bool                    ApplyEffect(bool batch_commit = false);
    bool                    SetCalibration(const EncodedCalibration& cal, bool refresh_from_hw);
    void                    SetLedCount(unsigned int c0, unsigned int c1, unsigned int c2, unsigned int c3);
    void                    SetLEDEffect(int led, int mode, unsigned int speed, unsigned char brightness, bool random, uint32_t* color);
    bool                    SetStripBuiltinEffectState(int hdr, bool enable);
    void                    SetStripColors(unsigned int hdr, RGBColor * colors, unsigned int num_colors, int single_led = -1);
    bool                    SupportsGen2() const;
    bool                    ScanGen2Strips();

    EncodedCalibration      GetCalibration(bool refresh_from_hw = false);
    std::string             GetDeviceName();
    uint8_t                 GetDeviceNum();
    std::string             GetDeviceDescription();
    std::string             GetDeviceLocation();
    std::string             GetFWVersion();
    uint16_t                GetProductID();
    std::string             GetSerial();
    std::vector<Gen2StripInfo> ExportGen2Strips() const;
    Gen2StripInfo           g2_strip_info[4];

private:
    std::string             DecodeCalibrationBuffer(uint32_t value) const;
    bool                    EnableLampArray(bool enable);
    bool                    EnableBeat(bool enable);
    uint32_t                EncodeCalibrationBuffer(const std::string& rgb_order);
    bool                    RefreshHardwareInfo();
    void                    ResetController();
    bool                    SaveLEDState(bool enable);
    bool                    SaveCalState();
    bool                    SendCCReport(uint8_t a, uint8_t b, uint8_t c = 0);
    bool                    SendReport(uint8_t id, uint8_t a, uint8_t b, uint8_t c = 0);
    int                     SendPacket(unsigned char* packet);

    hid_device*             dev;
    uint8_t                 device_num;
    uint16_t                product_id;
    uint32_t                effect_zone_mask    = 0;
    int                     mode;
    IT8297Report            report;
    CalibrationData         cal_data;
    std::string             name;
    std::string             description;
    std::string             location;
    std::string             version;
    std::string             chip_id;
    int                     effect_disabled     = -1;
    int                     report_id           = 0xCC;
    bool                    report_loaded       = false;
    bool                    cali_loaded         = false;
    LEDCount                D_LED1_count;
    LEDCount                D_LED2_count;
    LEDCount                D_LED3_count;
    LEDCount                D_LED4_count;
};
