/*---------------------------------------------------------*\
| FnaticStreakController.cpp                                |
|                                                           |
|   Driver for Fnatic Streak and miniStreak keyboards       |
|                                                           |
|   Based on leddy project by Hanna Czenczek                |
|                                                           |
|   This file is part of the OpenRGB project                |
|   SPDX-License-Identifier: GPL-2.0-or-later               |
\*---------------------------------------------------------*/

#include <cstring>
#include "FnaticStreakController.h"
#include "StringUtils.h"
#include "LogManager.h"

FnaticStreakController::FnaticStreakController(hid_device* dev_handle, hid_device_info* dev_info, std::string dev_name, FnaticStreakType kb_type)
{
    dev             = dev_handle;
    location        = dev_info->path;
    name            = dev_name;
    keyboard_type   = kb_type;
    profile         = 1;
    software_effect_mode = false;

    memset(color_buf, 0x00, sizeof(color_buf));

    /*-----------------------------------------------------*\
    | Get the firmware version from the device info         |
    \*-----------------------------------------------------*/
    char fw_version_buf[8];
    memset(fw_version_buf, '\0', sizeof(fw_version_buf));

    unsigned short version = dev_info->release_number;
    snprintf(fw_version_buf, 8, "%.2X.%.2X", (version & 0xFF00) >> 8, version & 0x00FF);

    firmware_version = fw_version_buf;
}

FnaticStreakController::~FnaticStreakController()
{
    hid_close(dev);
}

std::string FnaticStreakController::GetDeviceLocation()
{
    return("HID: " + location);
}

std::string FnaticStreakController::GetNameString()
{
    return(name);
}

std::string FnaticStreakController::GetSerialString()
{
    wchar_t serial_string[128];
    int ret = hid_get_serial_number_string(dev, serial_string, 128);

    if(ret != 0)
    {
        return("");
    }

    return(StringUtils::wstring_to_string(serial_string));
}

std::string FnaticStreakController::GetFirmwareVersion()
{
    return(firmware_version);
}

FnaticStreakType FnaticStreakController::GetKeyboardType()
{
    return(keyboard_type);
}

unsigned int FnaticStreakController::GetLEDCount()
{
    if(keyboard_type == FNATIC_STREAK_TYPE_MINI)
    {
        return 106;
    }
    else
    {
        return 124;
    }
}

void FnaticStreakController::SetProfile(unsigned char new_profile)
{
    if(new_profile >= 1 && new_profile <= 4)
    {
        profile = new_profile;
        SoftwareEffectEnd();
    }
}

void FnaticStreakController::SoftwareEffectStart()
{
    software_effect_mode = true;
}

void FnaticStreakController::SoftwareEffectEnd()
{
    software_effect_mode = false;
    RefreshProfile();
}

void FnaticStreakController::SendKeepalive()
{
    /*-----------------------------------------------------*\
    | Send keepalive packet (0x07 or 0xfe) to prevent       |
    | keyboard from reverting to profile effect during      |
    | direct/preview mode                                   |
    \*-----------------------------------------------------*/
    unsigned char prefix[] = { 0x07 };
    SendRequest(prefix, sizeof(prefix), nullptr, 0);
}

void FnaticStreakController::RefreshProfile()
{
    unsigned char data[] = { profile };
    unsigned char prefix[] = { 0x04 };
    SendRequest(prefix, sizeof(prefix), data, sizeof(data));
}

void FnaticStreakController::SendRequest(const unsigned char* prefix, size_t prefix_len, const unsigned char* raw_data, size_t data_len)
{
    size_t total_len = prefix_len + data_len;
    size_t offset = 0;
    unsigned char cmd = (prefix_len > 0) ? prefix[0] : raw_data[0];

    while(offset < total_len)
    {
        unsigned char packet[65];
        memset(packet, 0x00, sizeof(packet));

        /*-----------------------------------------------------*\
        | Packet format:                                        |
        | [0]     = Report ID (0x00)                            |
        | [1]     = Command                                     |
        | [2-4]   = Total length (24-bit little endian)         |
        | [5-7]   = Offset (24-bit little endian)               |
        | [8-64]  = Data (57 bytes max per packet)              |
        \*-----------------------------------------------------*/
        packet[0] = 0x00;
        packet[1] = cmd;
        packet[2] = (unsigned char)(total_len & 0xFF);
        packet[3] = (unsigned char)((total_len >> 8) & 0xFF);
        packet[4] = (unsigned char)((total_len >> 16) & 0xFF);
        packet[5] = (unsigned char)(offset & 0xFF);
        packet[6] = (unsigned char)((offset >> 8) & 0xFF);
        packet[7] = (unsigned char)((offset >> 16) & 0xFF);

        for(size_t i = offset; i < offset + 57 && i < total_len; i++)
        {
            if(i < prefix_len)
            {
                packet[i - offset + 8] = prefix[i];
            }
            else
            {
                packet[i - offset + 8] = raw_data[i - prefix_len];
            }
        }

        hid_write(dev, packet, 65);
        offset += 57;
    }

    /*-----------------------------------------------------*\
    | For command 0x05, save changes and refresh profile    |
    \*-----------------------------------------------------*/
    if(cmd == 0x05)
    {
        unsigned char save_prefix[] = { 0x13 };
        SendRequest(save_prefix, sizeof(save_prefix), nullptr, 0);

        unsigned char profile_data[] = { profile };
        unsigned char profile_prefix[] = { 0x04 };
        SendRequest(profile_prefix, sizeof(profile_prefix), profile_data, sizeof(profile_data));
    }
}

void FnaticStreakController::SetLEDsDirect(std::vector<led> leds, std::vector<RGBColor> colors, unsigned int brightness)
{
    unsigned int total_leds = GetLEDCount();

    /*-----------------------------------------------------*\
    | Clear the color buffer                                |
    \*-----------------------------------------------------*/
    memset(color_buf, 0x00, sizeof(color_buf));

    /*-----------------------------------------------------*\
    | Transfer colors to the buffer                         |
    | Format: sequential RGB triplets indexed by LED value  |
    | The LED value corresponds to the physical LED index   |
    | in the keyboard hardware (0-123 for full, 0-105 mini) |
    | Apply brightness scaling (0-100%)                     |
    \*-----------------------------------------------------*/
    unsigned int leds_to_set = (unsigned int)std::min(colors.size(), leds.size());

    for(unsigned int i = 0; i < leds_to_set; i++)
    {
        unsigned int led_idx = leds[i].value;
        if(led_idx < total_leds)
        {
            if(brightness >= 100)
            {
                /*-----------------------------------------*\
                | Full brightness - no scaling needed       |
                \*-----------------------------------------*/
                color_buf[led_idx * 3 + 0] = RGBGetRValue(colors[i]);
                color_buf[led_idx * 3 + 1] = RGBGetGValue(colors[i]);
                color_buf[led_idx * 3 + 2] = RGBGetBValue(colors[i]);
            }
            else
            {
                /*-----------------------------------------*\
                | Apply brightness scaling                  |
                \*-----------------------------------------*/
                color_buf[led_idx * 3 + 0] = (unsigned char)(RGBGetRValue(colors[i]) * brightness / 100);
                color_buf[led_idx * 3 + 1] = (unsigned char)(RGBGetGValue(colors[i]) * brightness / 100);
                color_buf[led_idx * 3 + 2] = (unsigned char)(RGBGetBValue(colors[i]) * brightness / 100);
            }
        }
    }
}

void FnaticStreakController::SendRGBToDevice()
{
    if(keyboard_type == FNATIC_STREAK_TYPE_65)
    {
        unsigned char data[64];
        memset(data, 0x00, sizeof(data));

        data[0]  = 0x0F;
        data[1]  = 0x15;

        data[7]  = 0x0F;
        data[8]  = 0x03;

        for(unsigned int i = 9; i < 25; i++)
        {
            data[i] = 0xFF;
        }

        data[25] = color_buf[0];
        data[26] = color_buf[1];
        data[27] = color_buf[2];

        hid_write(dev, data, sizeof(data));
    }
    else
    {
        unsigned int total_leds = GetLEDCount();
        unsigned int data_size = total_leds * 3;

        /*-----------------------------------------------------*\
        | For direct/software control, use command 0x0f         |
        | This bypasses the profile and allows immediate update |
        | The 0x03 subcommand indicates per-key color data      |
        \*-----------------------------------------------------*/
        unsigned char prefix[] = { 0x0f, 0x03 };
        SendRequest(prefix, sizeof(prefix), color_buf, data_size);
    }
}

void FnaticStreakController::SetPulse(unsigned char color_mode, unsigned char r, unsigned char g, unsigned char b, unsigned char speed)
{
    /*-----------------------------------------------------*\
    | Pulse effect - cmd 0x06                               |
    | Data: [mode, r, g, b, speed]                          |
    \*-----------------------------------------------------*/
    unsigned char prefix[] = { 0x05, profile, 0x02 };
    unsigned char data[] = { FNATIC_STREAK_CMD_PULSE, color_mode, r, g, b, speed };
    SendRequest(prefix, sizeof(prefix), data, sizeof(data));
}

void FnaticStreakController::SetWave(unsigned char color_mode, unsigned char r, unsigned char g, unsigned char b, unsigned char speed, unsigned char direction)
{
    /*-----------------------------------------------------*\
    | Wave effect - cmd 0x07                                |
    | Data: [mode, r, g, b, speed, direction]               |
    \*-----------------------------------------------------*/
    unsigned char prefix[] = { 0x05, profile, 0x02 };
    unsigned char data[] = { FNATIC_STREAK_CMD_WAVE, color_mode, r, g, b, speed, direction };
    SendRequest(prefix, sizeof(prefix), data, sizeof(data));
}

void FnaticStreakController::SetReactive(unsigned char color_mode, unsigned char r, unsigned char g, unsigned char b, unsigned char speed, bool keyup)
{
    /*-----------------------------------------------------*\
    | Reactive effect - cmd 0x09                            |
    | Data: [mode, r, g, b, speed, trigger]                 |
    | trigger: 0 = keydown, 1 = keyup                       |
    \*-----------------------------------------------------*/
    unsigned char prefix[] = { 0x05, profile, 0x02 };
    unsigned char data[] = { FNATIC_STREAK_CMD_REACTIVE, color_mode, r, g, b, speed, (unsigned char)(keyup ? 0 : 1) };
    SendRequest(prefix, sizeof(prefix), data, sizeof(data));
}

void FnaticStreakController::SetReactiveRipple(unsigned char color_mode, unsigned char r, unsigned char g, unsigned char b, unsigned char speed, bool keyup)
{
    /*-----------------------------------------------------*\
    | Reactive Ripple effect - cmd 0x0A                     |
    | Data: [mode, r, g, b, speed, trigger]                 |
    | trigger: 0 = keydown, 1 = keyup                       |
    \*-----------------------------------------------------*/
    unsigned char prefix[] = { 0x05, profile, 0x02 };
    unsigned char data[] = { FNATIC_STREAK_CMD_REACTIVE_RIPPLE, color_mode, r, g, b, speed, (unsigned char)(keyup ? 0 : 1) };
    SendRequest(prefix, sizeof(prefix), data, sizeof(data));
}

void FnaticStreakController::SetRain(unsigned char color_mode, unsigned char r, unsigned char g, unsigned char b, unsigned char speed, unsigned char direction)
{
    /*-----------------------------------------------------*\
    | Rain effect - cmd 0x0B                                |
    | Data: [mode, r, g, b, speed, direction]               |
    | Note: Does not support rainbow mode                   |
    \*-----------------------------------------------------*/
    unsigned char prefix[] = { 0x05, profile, 0x02 };
    unsigned char mode = (color_mode == FNATIC_STREAK_COLOR_MODE_RAINBOW) ? FNATIC_STREAK_COLOR_MODE_RANDOM : color_mode;
    unsigned char data[] = { FNATIC_STREAK_CMD_RAIN, mode, r, g, b, speed, direction };
    SendRequest(prefix, sizeof(prefix), data, sizeof(data));
}

void FnaticStreakController::SetGradient(unsigned char colors[][3], unsigned char positions[], unsigned int count)
{
    /*-----------------------------------------------------*\
    | Gradient effect - cmd 0x0C                            |
    | Data: [count, {r, g, b, pos} * 10]                    |
    | (always sends 10 color slots, unused are zeroed)      |
    \*-----------------------------------------------------*/
    unsigned char prefix[] = { 0x05, profile, 0x02 };
    unsigned char data[42];
    memset(data, 0x00, sizeof(data));

    data[0] = FNATIC_STREAK_CMD_GRADIENT;
    data[1] = (unsigned char)count;

    for(unsigned int i = 0; i < count && i < 10; i++)
    {
        data[2 + i * 4 + 0] = colors[i][0];
        data[2 + i * 4 + 1] = colors[i][1];
        data[2 + i * 4 + 2] = colors[i][2];
        data[2 + i * 4 + 3] = positions[i];
    }

    SendRequest(prefix, sizeof(prefix), data, sizeof(data));
}

void FnaticStreakController::SetFade(unsigned char color_mode, unsigned char colors[][3], unsigned char positions[], unsigned int count, unsigned char speed)
{
    /*-----------------------------------------------------*\
    | Fade effect - cmd 0x0D                                |
    | Data: [mode, count, {r, g, b, pos} * 10, speed]       |
    \*-----------------------------------------------------*/
    unsigned char prefix[] = { 0x05, profile, 0x02 };
    unsigned char data[44];
    memset(data, 0x00, sizeof(data));

    data[0] = FNATIC_STREAK_CMD_FADE;
    data[1] = color_mode;
    data[2] = (unsigned char)count;

    for(unsigned int i = 0; i < count && i < 10; i++)
    {
        data[3 + i * 4 + 0] = colors[i][0];
        data[3 + i * 4 + 1] = colors[i][1];
        data[3 + i * 4 + 2] = colors[i][2];
        data[3 + i * 4 + 3] = positions[i];
    }

    data[43] = speed;

    SendRequest(prefix, sizeof(prefix), data, sizeof(data));
}
