/*-----------------------------------------*\
|  SteelSeriesSenseiController.h            |
|                                           |
|  Definitions and types for SteelSeries    |
|  Sensei lighting controller               |
|  Based on Rival controller by             |
|  B Horn (bahorn) 13/5/2020                |
\*-----------------------------------------*/

#include "SteelSeriesSenseiController.h"
#include <cstring>
#include <stdio.h>
#include <stdlib.h>

static void send_usb_msg(hid_device* dev, char * data_pkt, unsigned int size)
{
    char* usb_pkt = new char[size + 1];
    
    usb_pkt[0] = 0x00;
    for(unsigned int i = 1; i < size + 1; i++)
    {
        usb_pkt[i] = data_pkt[i-1];
    }
    
    hid_write(dev, (unsigned char *)usb_pkt, size + 1);
    
    delete usb_pkt;
}

SteelSeriesSenseiController::SteelSeriesSenseiController
    (
    hid_device*         dev_handle,
    steelseries_type    proto_type,
    const char*         path
    )
{
    dev         = dev_handle;
    location    = path;
    proto       = proto_type;
}

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

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

char* SteelSeriesSenseiController::GetDeviceName()
{
    return device_name;
}

std::string SteelSeriesSenseiController::GetSerialString()
{
    wchar_t serial_string[128];
    int ret = hid_get_serial_number_string(dev, serial_string, 128);
    if (ret != 0)
    {
        return("");
    }

    std::wstring return_wstring = serial_string;
    std::string return_string(return_wstring.begin(), return_wstring.end());

    return(return_string);
}

steelseries_type SteelSeriesSenseiController::GetMouseType()
{
    return proto;
}

/* Saves to the internal configuration */
void SteelSeriesSenseiController::Save()
{
    /*-----------------------------------------------------*\
    | Saves to the internal configuration                   |
    \*-----------------------------------------------------*/
    char usb_buf[9];

    /*-----------------------------------------------------*\
    | Zero out buffer                                       |
    \*-----------------------------------------------------*/
    memset(usb_buf, 0x00, sizeof(usb_buf));

    /*-----------------------------------------------------*\
    | Set up Save packet                                    |
    \*-----------------------------------------------------*/
    usb_buf[0x00]       = 0x09;

    /*-----------------------------------------------------*\
    | Send packet                                           |
    \*-----------------------------------------------------*/
    send_usb_msg(dev, usb_buf, 9);
}


void SteelSeriesSenseiController::SetLightEffect
    (
    unsigned char   zone_id,
    unsigned char   effect,
    unsigned char   speed,
    unsigned char   red,
    unsigned char   green,
    unsigned char   blue
    )
{
    char usb_buf[65];

    /*-----------------------------------------------------*\
    | Zero out buffer                                       |
    \*-----------------------------------------------------*/
    memset(usb_buf, 0x00, sizeof(usb_buf));

    /*-----------------------------------------------------*\
    | Set up Light Effect packet                            |
    \*-----------------------------------------------------*/
    char dur1;
    char dur2;

    switch(effect)
    {
        case STEELSERIES_SENSEI_MODE_BREATHING:
            switch(speed)
            {
                case STEELSERIES_SENSEI_EFFECT_BREATHING_MIN:
                    dur1 = 0x27;
                    dur2 = 0x10;            //10 sec cycle
                    break;

                case STEELSERIES_SENSEI_EFFECT_BREATHING_MID:
                    dur1 = 0x13;
                    dur2 = 0x88;            //5 sec cycle
                    break;

                case STEELSERIES_SENSEI_EFFECT_BREATHING_MAX:
                    dur1 = 0x09;
                    dur2 = 0xc4;            //2.5 sec cycle
                    break;
            }
            usb_buf[0x00]       = 0x5B;     //command byte
            usb_buf[0x02]       = zone_id;
            usb_buf[0x04]       = dur1;     //duration in ms 1st byte
            usb_buf[0x03]       = dur2;     //duration in ms 2nd byte
            usb_buf[0x1B]       = 0x03;     //Number of colors

            /*---------------------------------------------*\
            | Original software duplicates these RGB bytes, |
            | but seems unnecessary                         |
            \*---------------------------------------------*/
            usb_buf[0x1C]       = red;
            usb_buf[0x1D]       = green;
            usb_buf[0x1E]       = blue;

            usb_buf[0x1F]       = red;
            usb_buf[0x20]       = green;
            usb_buf[0x21]       = blue;
            usb_buf[0x26]       = 0x7F;     //percent of duration out of 0xFF
            usb_buf[0x27]       = red;
            usb_buf[0x28]       = green;
            usb_buf[0x29]       = blue;
            usb_buf[0x2A]       = 0x7F;     //percent of duration out of 0xFF
            break;

        case STEELSERIES_SENSEI_MODE_RAINBOW:
            switch(speed)
            {
                case STEELSERIES_SENSEI_EFFECT_RAINBOW_MIN:
                    dur1 = 0x4E;
                    dur2 = 0x20;            //20 sec cycle
                    break;

                case STEELSERIES_SENSEI_EFFECT_RAINBOW_MID:
                    dur1 = 0x27;
                    dur2 = 0x10;            //10 sec cycle
                    break;

                case STEELSERIES_SENSEI_EFFECT_RAINBOW_MAX:
                    dur1 = 0x13;
                    dur2 = 0x88;            //5 sec cycle
                    break;
            }
            usb_buf[0x00]       = 0x5B;     //command byte
            usb_buf[0x02]       = zone_id;
            usb_buf[0x04]       = dur1;     //duration in ms 1st byte
            usb_buf[0x03]       = dur2;     //duration in ms 2nd byte
            usb_buf[0x1B]       = 0x07;     //Number of colors

            /*---------------------------------------------*\
            | Original software duplicates these RGB bytes, |
            | but seems unnecessary                         |
            \*---------------------------------------------*/
            usb_buf[0x1C]       = red;
            usb_buf[0x1D]       = green;
            usb_buf[0x1E]       = blue;

            usb_buf[0x1C]       = 0xFF;
            usb_buf[0x1F]       = 0xFF;
            usb_buf[0x22]       = 0x14;
            usb_buf[0x23]       = 0xFF;
            usb_buf[0x24]       = 0xFF;
            usb_buf[0x26]       = 0x2B;     //percent of duration out of 0xFF
            usb_buf[0x28]       = 0xFF;
            usb_buf[0x2A]       = 0x2B;
            usb_buf[0x2C]       = 0xFF;
            usb_buf[0x2D]       = 0xFF;     //percent of duration out of 0xFF
            usb_buf[0x2E]       = 0x28;
            usb_buf[0x31]       = 0xFF;
            usb_buf[0x32]       = 0x2B;
            usb_buf[0x33]       = 0xFF;
            usb_buf[0x35]       = 0xFF;
            usb_buf[0x36]       = 0x2B;
            usb_buf[0x37]       = 0xFF;
            usb_buf[0x3A]       = 0x14;
            break;
    }

    /*-----------------------------------------------------*\
    | Send packet                                           |
    \*-----------------------------------------------------*/
    send_usb_msg(dev, usb_buf, sizeof(usb_buf));
}


void SteelSeriesSenseiController::SetLightEffectAll
    (
    unsigned char   effect,
    unsigned char   speed,
    unsigned char   red,
    unsigned char   green,
    unsigned char   blue
    )
{
    SetLightEffect(0, effect, speed, red, green, blue);
    SetLightEffect(1, effect, speed, red, green, blue);
}


void SteelSeriesSenseiController::SetColor
    (
    unsigned char   zone_id,
    unsigned char   red,
    unsigned char   green,
    unsigned char   blue
    )
{
    char usb_buf[65];

    /*-----------------------------------------------------*\
    | Zero out buffer                                       |
    \*-----------------------------------------------------*/
    memset(usb_buf, 0x00, sizeof(usb_buf));

    /*-----------------------------------------------------*\
    | Set up Set Color packet                               |
    \*-----------------------------------------------------*/
    usb_buf[0x00]       = 0x5B;             //command byte
    usb_buf[0x02]       = zone_id;

    /*-----------------------------------------------------*\
    | Original software duplicates these RGB bytes,         |
    | but seems unnecessary                                 |
    \*-----------------------------------------------------*/
    usb_buf[0x1C]       = red;
    usb_buf[0x1D]       = green;
    usb_buf[0x1E]       = blue;

    usb_buf[0x1F]       = red;
    usb_buf[0x20]       = green;
    usb_buf[0x21]       = blue;
    usb_buf[0x13]       = 0x01;             //Static color flag
    usb_buf[0x1B]       = 0x01;             //Number of colors

    /*-----------------------------------------------------*\
    | Send packet                                           |
    \*-----------------------------------------------------*/
    send_usb_msg(dev, usb_buf, sizeof(usb_buf));
}

void SteelSeriesSenseiController::SetColorAll
    (
        unsigned char   red,
        unsigned char   green,
        unsigned char   blue
        )
{
    SetColor(0, red, green, blue);
    SetColor(1, red, green, blue);
}

