/*---------------------------------------------------------*\
| LightSaltController.cpp                                   |
|                                                           |
|   Driver for LightSalt Peripherals                        |
|                                                           |
|   James Buren (braewoods)                     23 Jul 2024 |
|                                                           |
|   This file is part of the OpenRGB project                |
|   SPDX-License-Identifier: GPL-2.0-or-later               |
\*---------------------------------------------------------*/

#include <cstring>
#include "LightSaltController.h"
#include "StringUtils.h"

static const int mode_map[LIGHTSALT_MODE_MAXIMUM][6] =
{
    {0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
    {0x0100, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
    {0x0400, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
    {0x0700, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
    {0x0A00, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
    {0x0D00, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
    {0x0E00, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
    {0x0F00, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
    {0x1100, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
    {0x0000, 0x0000, 0x0000, 0x0000, 0x3301, 0x3302},
    {0x3402, 0x3401, 0x0000, 0x0000, 0x0000, 0x0000},
    {0x3502, 0x3501, 0x0000, 0x0000, 0x0000, 0x0000},
    {0x3602, 0x3601, 0x0000, 0x0000, 0x0000, 0x0000},
    {0x3802, 0x3801, 0x0000, 0x0000, 0x0000, 0x0000},
    {0x0000, 0x0000, 0x3901, 0x3902, 0x0000, 0x0000},
    {0x0000, 0x0000, 0x3A01, 0x3A02, 0x0000, 0x0000},
    {0x0000, 0x0000, 0x0000, 0x0000, 0x3702, 0x3701},
    {0x3B02, 0x3B01, 0x0000, 0x0000, 0x0000, 0x0000}
};

LightSaltController::LightSaltController(hid_device* dev_handle, const hid_device_info& info)
{
    wchar_t usb_string[128];

    dev             = dev_handle;
    device_location = info.path;

    if(hid_get_manufacturer_string(dev, usb_string, 128) == 0)
    {
        manufacturer = StringUtils::wstring_to_string(usb_string);
    }
    else
    {
        manufacturer = "";
    }

    if(hid_get_product_string(dev, usb_string, 128) == 0)
    {
        product = StringUtils::wstring_to_string(usb_string);
    }
    else
    {
        product = "";
    }

    if(strstr(manufacturer.c_str(), "Light&Salt") && strstr(product.c_str(), "Light&Salt"))
    {
        QueryDeviceModel();
        QueryDeviceClass();
        SetDeviceType();
    }
    else
    {
        device_model     = "";
        device_class     = "";
        firmware_version = "";
        device_type      = LIGHTSALT_TYPE_MAXIMUM;
    }
}

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

std::string LightSaltController::GetDeviceLocation()
{
    return("HID: " + device_location);
}

std::string LightSaltController::GetSerial()
{
    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 LightSaltController::GetManufacturer()
{
    return(manufacturer);
}

std::string LightSaltController::GetProduct()
{
    return(product);
}

std::string LightSaltController::GetDeviceModel()
{
    return(device_model);
}

std::string LightSaltController::GetDeviceClass()
{
    return(device_class);
}

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

int LightSaltController::GetDeviceType()
{
    return(device_type);
}

void LightSaltController::SetColors(RGBColor* colors, int sets, int rows, int columns)
{
    uint8_t write_buffer[LIGHTSALT_WRITE_LENGTH];
    int color_idx = 0;

    write_buffer[0] = 0x00;

    for(int set_idx = 0; set_idx < sets; set_idx++)
    {
        write_buffer[1] = 0x03 + set_idx;

        for(int row_idx = 0; row_idx < rows; row_idx++)
        {
            int write_idx = 2;

            write_buffer[write_idx++] = 0x01 + row_idx * columns;

            for(int column_idx = 0; column_idx < columns; column_idx++)
            {
                RGBColor color = colors[color_idx++];
                write_buffer[write_idx++] = RGBGetRValue(color);
                write_buffer[write_idx++] = RGBGetGValue(color);
                write_buffer[write_idx++] = RGBGetBValue(color);
            }

            hid_write(dev, write_buffer, sizeof(write_buffer));
        }
    }
}

void LightSaltController::ApplyColors(int sets)
{
    uint8_t write_buffer[LIGHTSALT_WRITE_LENGTH];
    int write_idx = 1;

    memset(write_buffer, 0x00, sizeof(write_buffer));
    write_buffer[write_idx++] = 0xFE;
    write_buffer[write_idx++] = 0x01;

    for(int set_idx = 0; set_idx < sets; set_idx++)
    {
        write_buffer[write_idx++] = 0x03 + set_idx;
    }

    hid_write(dev, write_buffer, sizeof(write_buffer));
}

void LightSaltController::SaveColors(int sets)
{
    uint8_t write_buffer[LIGHTSALT_WRITE_LENGTH];
    int write_idx = 1;

    memset(write_buffer, 0x00, sizeof(write_buffer));
    write_buffer[write_idx++] = 0xFE;
    write_buffer[write_idx++] = 0xF0;

    for(int set_idx = 0; set_idx < sets; set_idx++)
    {
        write_buffer[write_idx++] = 0x03 + set_idx;
    }

    hid_write(dev, write_buffer, sizeof(write_buffer));
}

void LightSaltController::SetMode(int mode, int direction, int speed)
{
    uint8_t write_buffer[LIGHTSALT_WRITE_LENGTH];
    int actual_mode = mode_map[mode][direction];

    memset(write_buffer, 0x00, sizeof(write_buffer));
    write_buffer[1] = 0xFE;
    write_buffer[2] = 0x03;
    write_buffer[3] = (actual_mode & 0xFF00) >> 8;
    write_buffer[4] = (actual_mode & 0x00FF) >> 0;
    write_buffer[5] = speed;

    hid_write(dev, write_buffer, sizeof(write_buffer));
}

void LightSaltController::SetFilter(RGBColor color)
{
    uint8_t write_buffer[LIGHTSALT_WRITE_LENGTH];

    memset(write_buffer, 0x00, sizeof(write_buffer));
    write_buffer[1] = 0xFE;
    write_buffer[2] = 0x04;
    write_buffer[3] = RGBGetRValue(color);
    write_buffer[4] = RGBGetGValue(color);
    write_buffer[5] = RGBGetBValue(color);

    hid_write(dev, write_buffer, sizeof(write_buffer));
}

void LightSaltController::SetBrightness(uint8_t brightness)
{
    uint8_t write_buffer[LIGHTSALT_WRITE_LENGTH];

    memset(write_buffer, 0x00, sizeof(write_buffer));
    write_buffer[1] = 0xFE;
    write_buffer[2] = 0x05;
    write_buffer[3] = brightness;

    hid_write(dev, write_buffer, sizeof(write_buffer));
}

void LightSaltController::QueryDeviceModel()
{
    uint8_t write_buffer[LIGHTSALT_WRITE_LENGTH];
    uint8_t read_buffer[LIGHTSALT_READ_LENGTH];

    memset(write_buffer, 0x00, sizeof(write_buffer));
    write_buffer[1] = 0xFE;
    write_buffer[2] = 0xF5;
    write_buffer[3] = 0x06;

    if(hid_write(dev, write_buffer, sizeof(write_buffer)) != sizeof(write_buffer))
    {
        device_model = "";
        return;
    }

    if(hid_read_timeout(dev, read_buffer, sizeof(read_buffer), 100) != sizeof(read_buffer))
    {
        device_model = "";
        return;
    }

    device_model = (char *)(read_buffer + 3);
}

void LightSaltController::QueryDeviceClass()
{
    uint8_t write_buffer[LIGHTSALT_WRITE_LENGTH];
    uint8_t read_buffer[LIGHTSALT_READ_LENGTH];

    memset(write_buffer, 0x00, sizeof(write_buffer));
    write_buffer[1] = 0xFE;
    write_buffer[2] = 0xFF;

    if(hid_write(dev, write_buffer, sizeof(write_buffer)) != sizeof(write_buffer))
    {
        device_class = "";
        firmware_version = "";
        return;
    }

    if(hid_read_timeout(dev, read_buffer, sizeof(read_buffer), 100) != sizeof(read_buffer))
    {
        device_class = "";
        firmware_version = "";
        return;
    }

    device_class     = (char *)(read_buffer                          );
    firmware_version = (char *)(read_buffer + device_class.size() + 1);
}

void LightSaltController::SetDeviceType()
{
    struct device_class_table
    {
        std::string device_class;
        int device_type;
    };
    static const device_class_table table[] =
    {
        {"Light&Salt_keyboard1", LIGHTSALT_TYPE_KEYBOARD},
        {"Light&Salt_KEY_PAD",   LIGHTSALT_TYPE_KEYPAD  }
    };

    device_type = LIGHTSALT_TYPE_MAXIMUM;

    for(const device_class_table& match : table)
    {
        if(match.device_class == device_class)
        {
            device_type = match.device_type;
            break;
        }
    }
}
