/*---------------------------------------------------------*\
| SteelSeriesArctisNova3Controller.cpp                      |
|                                                           |
|   Driver for SteelSeries Arctis Nova 3                    |
|                                                           |
|   Tomasz Gorczyca (eldiablo123)               18 Jul 2026 |
|                                                           |
|   This file is part of the OpenRGB project                |
|   SPDX-License-Identifier: GPL-2.0-or-later               |
\*---------------------------------------------------------*/
#include <string.h>
#include "SteelSeriesArctisNova3Controller.h"
#include "StringUtils.h"

SteelSeriesArctisNova3Controller::SteelSeriesArctisNova3Controller(hid_device* dev_handle, const hid_device_info& info, std::string dev_name)
{
    dev         = dev_handle;
    location    = info.path;
    name        = dev_name;
}

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

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

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

std::string SteelSeriesArctisNova3Controller::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));
}

void SteelSeriesArctisNova3Controller::SetSpeedBytes(unsigned char* usb_buf, unsigned char speed)
{
    switch(speed)
    {
        case 1:     usb_buf[0x0D] = 0x30; usb_buf[0x0E] = 0x75; break;
        case 2:     usb_buf[0x0D] = 0x2E; usb_buf[0x0E] = 0x68; break;
        case 3:     usb_buf[0x0D] = 0x2C; usb_buf[0x0E] = 0x5B; break;
        case 4:     usb_buf[0x0D] = 0x20; usb_buf[0x0E] = 0x4E; break;
        case 5:     usb_buf[0x0D] = 0x1E; usb_buf[0x0E] = 0x41; break;
        case 6:     usb_buf[0x0D] = 0x1C; usb_buf[0x0E] = 0x34; break;
        case 7:     usb_buf[0x0D] = 0x1A; usb_buf[0x0E] = 0x27; break;
        case 8:     usb_buf[0x0D] = 0x0E; usb_buf[0x0E] = 0x1A; break;
        case 9:     usb_buf[0x0D] = 0x0C; usb_buf[0x0E] = 0x0D; break;
        case 10:    usb_buf[0x0D] = 0x94; usb_buf[0x0E] = 0x02; break;
        default:    usb_buf[0x0D] = 0x0C; usb_buf[0x0E] = 0x0D; break;
    }
}

/*---------------------------------------------------------------------*\
| Select/enable packet (64 bytes): 06 A5 (01=left/02=right) (00/0A)     |
\*---------------------------------------------------------------------*/
void SteelSeriesArctisNova3Controller::SelectZone(unsigned char zone_id, unsigned char state)
{
    unsigned char usb_buf[ARCTIS_NOVA3_APPLY_REPORT_SIZE];
    memset(usb_buf, 0x00, ARCTIS_NOVA3_APPLY_REPORT_SIZE);

    usb_buf[0x00] = ARCTIS_NOVA3_REPORT_ID;
    usb_buf[0x01] = 0xA5;
    usb_buf[0x02] = (zone_id == 0) ? 0x02 : 0x01;
    usb_buf[0x03] = state;

    hid_write(dev, usb_buf, ARCTIS_NOVA3_APPLY_REPORT_SIZE);
}

/*---------------------------------------------------------------------*\
| Apply packet (64 bytes): 06 (A3=left/09=right)                        |
\*---------------------------------------------------------------------*/
void SteelSeriesArctisNova3Controller::ApplyZone(unsigned char zone_id)
{
    unsigned char usb_buf[ARCTIS_NOVA3_APPLY_REPORT_SIZE];
    memset(usb_buf, 0x00, ARCTIS_NOVA3_APPLY_REPORT_SIZE);

    usb_buf[0x00] = ARCTIS_NOVA3_REPORT_ID;
    usb_buf[0x01] = (zone_id == 0) ? 0x09 : 0xA3;

    hid_write(dev, usb_buf, ARCTIS_NOVA3_APPLY_REPORT_SIZE);
}

void SteelSeriesArctisNova3Controller::SendEffectPacket(unsigned char* usb_buf)
{
    hid_write(dev, usb_buf, ARCTIS_NOVA3_REPORT_SIZE);
}

void SteelSeriesArctisNova3Controller::SetOff(unsigned char zone_id)
{
    SelectZone(zone_id, 0x00);
    ApplyZone(zone_id);
}

/*---------------------------------------------------------------------*\
| Save                                                                  |
\*---------------------------------------------------------------------*/
void SteelSeriesArctisNova3Controller::Save(unsigned char zone_id)
{
    ApplyZone(zone_id);
}

/*---------------------------------------------------------------------*\
| Single Color / Direct                                                 |
\*---------------------------------------------------------------------*/
void SteelSeriesArctisNova3Controller::SetDirect(unsigned char zone_id, RGBColor color)
{
    unsigned char usb_buf[ARCTIS_NOVA3_REPORT_SIZE];
    memset(usb_buf, 0x00, ARCTIS_NOVA3_REPORT_SIZE);

    usb_buf[0x00] = ARCTIS_NOVA3_REPORT_ID;
    usb_buf[0x01] = 0xAA;
    usb_buf[0x02] = (zone_id == 0) ? 0x01 : 0x00;

    usb_buf[0x0D] = 0x98;
    usb_buf[0x0E] = 0x3A;

    usb_buf[0x15] = 0x55;
    usb_buf[0x17] = 0x96;

    usb_buf[0x1D] = 0x01;

    usb_buf[0x25] = 0x02;

    usb_buf[0x2D] = RGBGetRValue(color);
    usb_buf[0x2E] = RGBGetGValue(color);
    usb_buf[0x2F] = RGBGetBValue(color);
    usb_buf[0x30] = 0xFF;

    SendEffectPacket(usb_buf);

    SelectZone(zone_id, 0x0A);
    ApplyZone(zone_id);
}

/*---------------------------------------------------------------------*\
| Breathe                                                                |
\*---------------------------------------------------------------------*/
void SteelSeriesArctisNova3Controller::SetBreathe(unsigned char zone_id, RGBColor color, unsigned char speed)
{
    unsigned char usb_buf[ARCTIS_NOVA3_REPORT_SIZE];
    memset(usb_buf, 0x00, ARCTIS_NOVA3_REPORT_SIZE);

    usb_buf[0x00] = ARCTIS_NOVA3_REPORT_ID;
    usb_buf[0x01] = 0xAA;
    usb_buf[0x02] = (zone_id == 0) ? 0x01 : 0x00;

    SetSpeedBytes(usb_buf, speed);

    usb_buf[0x15] = 0x55;
    usb_buf[0x17] = 0x96;

    usb_buf[0x25] = 0x03;

    usb_buf[0x29] = RGBGetRValue(color);
    usb_buf[0x2A] = RGBGetGValue(color);
    usb_buf[0x2B] = RGBGetBValue(color);

    usb_buf[0x30] = 0x7F;
    usb_buf[0x31] = RGBGetRValue(color);
    usb_buf[0x32] = RGBGetGValue(color);
    usb_buf[0x33] = RGBGetBValue(color);
    usb_buf[0x34] = 0x7F;

    SendEffectPacket(usb_buf);

    SelectZone(zone_id, 0x0A);
    ApplyZone(zone_id);
}

/*---------------------------------------------------------------------*\
| Color Shift - Rainbow                                                 |
\*---------------------------------------------------------------------*/
void SteelSeriesArctisNova3Controller::SetColorShiftRainbow(unsigned char zone_id, unsigned char speed)
{
    unsigned char usb_buf[ARCTIS_NOVA3_REPORT_SIZE];
    memset(usb_buf, 0x00, ARCTIS_NOVA3_REPORT_SIZE);

    usb_buf[0x00] = ARCTIS_NOVA3_REPORT_ID;
    usb_buf[0x01] = 0xAA;
    usb_buf[0x02] = (zone_id == 0) ? 0x01 : 0x00;

    SetSpeedBytes(usb_buf, speed);

    usb_buf[0x15] = 0x55;
    usb_buf[0x17] = 0x96;

    usb_buf[0x25] = 0x07;

    static const unsigned char rainbow_stops[] =
    {
        0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00,
        0x2B, 0x00, 0xFF, 0x00, 0x2B, 0x00, 0xFF, 0xFF, 0x2B, 0x00,
        0x00, 0xFF, 0x2B, 0xFF, 0x00, 0xFF, 0x2B, 0xFF, 0x00, 0x00,
        0x26
    };

    memcpy(&usb_buf[0x26], rainbow_stops, sizeof(rainbow_stops));

    SendEffectPacket(usb_buf);

    SelectZone(zone_id, 0x0A);
    ApplyZone(zone_id);
}

/*---------------------------------------------------------------------*\
| Color Shift - Heat Orange                                             |
\*---------------------------------------------------------------------*/
void SteelSeriesArctisNova3Controller::SetColorShiftOrange(unsigned char zone_id, unsigned char speed)
{
    unsigned char usb_buf[ARCTIS_NOVA3_REPORT_SIZE];
    memset(usb_buf, 0x00, ARCTIS_NOVA3_REPORT_SIZE);

    usb_buf[0x00] = ARCTIS_NOVA3_REPORT_ID;
    usb_buf[0x01] = 0xAA;
    usb_buf[0x02] = (zone_id == 0) ? 0x01 : 0x00;

    SetSpeedBytes(usb_buf, speed);

    usb_buf[0x15] = 0x55;
    usb_buf[0x17] = 0x96;

    usb_buf[0x25] = 0x03;

    static const unsigned char orange_stops[] =
    {
        0xFF, 0xEA, 0x00, 0xFF, 0xEA, 0x00, 0x00, 0xFF, 0x4D, 0x00,
        0x7F, 0xFF, 0xEA, 0x00, 0x7F
    };

    memcpy(&usb_buf[0x26], orange_stops, sizeof(orange_stops));

    SendEffectPacket(usb_buf);

    SelectZone(zone_id, 0x0A);
    ApplyZone(zone_id);
}

/*---------------------------------------------------------------------*\
| Color Shift - Frost Blue                                              |
\*---------------------------------------------------------------------*/
void SteelSeriesArctisNova3Controller::SetColorShiftBlue(unsigned char zone_id, unsigned char speed)
{
    unsigned char usb_buf[ARCTIS_NOVA3_REPORT_SIZE];
    memset(usb_buf, 0x00, ARCTIS_NOVA3_REPORT_SIZE);

    usb_buf[0x00] = ARCTIS_NOVA3_REPORT_ID;
    usb_buf[0x01] = 0xAA;
    usb_buf[0x02] = (zone_id == 0) ? 0x01 : 0x00;

    SetSpeedBytes(usb_buf, speed);

    usb_buf[0x15] = 0x55;
    usb_buf[0x17] = 0x96;

    usb_buf[0x25] = 0x03;

    static const unsigned char blue_stops[] =
    {
        0x8C, 0x00, 0xFF, 0x8C, 0x00, 0xFF, 0x00, 0x3B, 0xD1, 0xFF,
        0x7F, 0x8C, 0x00, 0xFF, 0x7F
    };

    memcpy(&usb_buf[0x26], blue_stops, sizeof(blue_stops));

    SendEffectPacket(usb_buf);

    SelectZone(zone_id, 0x0A);
    ApplyZone(zone_id);
}
