/*---------------------------------------------------------*\
| PhilipsWizController.cpp                                  |
|                                                           |
|   Driver for Philips Wiz                                  |
|                                                           |
|   Adam Honse (calcprogrammer1@gmail.com)      03 Nov 2020 |
|                                                           |
|   This file is part of the OpenRGB project                |
|   SPDX-License-Identifier: GPL-2.0-or-later               |
\*---------------------------------------------------------*/

#include "PhilipsWizController.h"
#include <nlohmann/json.hpp>

using json = nlohmann::json;
using namespace std::chrono_literals;

PhilipsWizController::PhilipsWizController(std::string ip, bool use_cool, bool use_warm, std::string selected_white_strategy)
{
    /*-----------------------------------------------------------------*\
    | Fill in location string with device's IP address                  |
    \*-----------------------------------------------------------------*/
    location    = "IP: " + ip;

    /*-----------------------------------------------------------------*\
    | Fill in settings                                                  |
    \*-----------------------------------------------------------------*/
    use_cool_white = use_cool;
    use_warm_white = use_warm;
    white_strategy = selected_white_strategy;

    /*-----------------------------------------------------------------*\
    | Open a UDP client sending to the device's IP, port 38899          |
    \*-----------------------------------------------------------------*/
    port.udp_client(ip.c_str(), "38899");

    /*-----------------------------------------------------------------*\
    | Start a thread to handle responses received from the Wiz device   |
    \*-----------------------------------------------------------------*/
    ReceiveThreadRun = 1;
    ReceiveThread = new std::thread(&PhilipsWizController::ReceiveThreadFunction, this);

    /*-----------------------------------------------------------------*\
    | Request the system config (name, firmware version, MAC address)   |
    \*-----------------------------------------------------------------*/
    RequestSystemConfig();
}

PhilipsWizController::~PhilipsWizController()
{
    ReceiveThreadRun = 0;
    ReceiveThread->join();
    delete ReceiveThread;
}

std::string PhilipsWizController::GetLocation()
{
    return(location);
}

std::string PhilipsWizController::GetName()
{
    return("Wiz");
}

std::string PhilipsWizController::GetVersion()
{
    return(module_name + " " + firmware_version);
}

std::string PhilipsWizController::GetModuleName()
{
    return(module_name);
}

std::string PhilipsWizController::GetManufacturer()
{
    return("Philips");
}

std::string PhilipsWizController::GetUniqueID()
{
    return(module_mac);
}

void PhilipsWizController::SetColor(unsigned char red, unsigned char green, unsigned char blue, unsigned char brightness)
{
    json command;
    unsigned char white;

    /*-----------------------------------------------------------------*\
    | The official Wiz app also sends a warm white level with its       |
    | custom colours. Until we can figure out a way to account for it   |
    | correctly, set the white level based on selected strategy.        |
    \*-----------------------------------------------------------------*/
    if(white_strategy == "Average")
    {
        white      = (red + green + blue) / 3;
    }
    else if(white_strategy == "Minimum")
    {
        white      = std::min(std::min(red, green), blue);
        if(use_cool_white || use_warm_white)
        {
            red        = red - white;
            green      = green - white;
            blue       = blue - white;
        }
    }
    else
    {
        white      = 0;
    }

    if(use_cool_white)
    {
        command["params"]["c"] = white;
    }
    else
    {
        command["params"]["c"] = 0;
    }

    if(use_warm_white)
    {
        command["params"]["w"] = white;
    }
    else
    {
        command["params"]["w"] = 0;
    }


    /*-----------------------------------------------------------------*\
    | Fill in the setPilot command with RGB and brightness information. |
    | The bulb will not respond to 0, 0, 0, so if all channels are zero,|
    | set the state to off.  Otherwise, set it to on. As we're also     |
    | running direct the bulb needs to be set back to max brightness.   |
    \*-----------------------------------------------------------------*/
    command["method"]            = "setPilot";
    command["params"]["r"]       = red;
    command["params"]["g"]       = green;
    command["params"]["b"]       = blue;
    command["params"]["dimming"] = brightness;
    command["params"]["state"]   = !((red == 0) && (green == 0) && (blue == 0) && (white == 0));

    /*-----------------------------------------------------------------*\
    | Convert the JSON object to a string and write it                  |
    \*-----------------------------------------------------------------*/
    std::string command_str     = command.dump();

    port.udp_write((char *)command_str.c_str(), (int)command_str.length() + 1);
}

void PhilipsWizController::SetScene(int scene, unsigned char brightness)
{
    json command;

    /*------------------------------------------------------------*\
    | Fill in the setPilot command with Scene information.         |
    \*------------------------------------------------------------*/
    command["method"] = "setPilot";
    command["params"]["sceneId"] = scene;
    command["params"]["dimming"] = brightness;

    /*------------------------------------------------------------*\
    | Convert the JSON object to a string and write it             |
    \*------------------------------------------------------------*/
    std::string command_str = command.dump();

    port.udp_write((char*)command_str.c_str(), (int)command_str.length() + 1);
}

void PhilipsWizController::ReceiveThreadFunction()
{
    char recv_buf[1025];

    port.set_receive_timeout(1, 0);

    while(ReceiveThreadRun.load())
    {
        /*-----------------------------------------------------------------*\
        | Receive up to 1024 bytes from the device with a 1s timeout        |
        \*-----------------------------------------------------------------*/
        int size = port.udp_listen(recv_buf, 1024);

        if(size > 0)
        {
            /*-----------------------------------------------------------------*\
            | Responses are not null-terminated, so add termination             |
            \*-----------------------------------------------------------------*/
            recv_buf[size] = '\0';

            /*-----------------------------------------------------------------*\
            | Convert null-terminated response to JSON                          |
            \*-----------------------------------------------------------------*/
            json response = json::parse(recv_buf);

            /*-----------------------------------------------------------------*\
            | Check if the response contains the method name                    |
            \*-----------------------------------------------------------------*/
            if(response.contains("method"))
            {
                /*-------------------------------------------------------------*\
                | Handle responses for getSystemConfig method                   |
                | This method's response should contain a result object         |
                | containing fwVersion, moduleName, and mac, among others.      |
                \*-------------------------------------------------------------*/
                if(response["method"] == "getSystemConfig")
                {
                    if(response.contains("result"))
                    {
                        json result = response["result"];

                        if(result.contains("fwVersion"))
                        {
                            firmware_version = result["fwVersion"];
                        }

                        if(result.contains("moduleName"))
                        {
                            module_name = result["moduleName"];
                        }

                        if(result.contains("mac"))
                        {
                            module_mac = result["mac"];
                        }
                    }
                }
            }
        }
    }
}

void PhilipsWizController::RequestSystemConfig()
{
    json command;

    /*-----------------------------------------------------------------*\
    | Fill in the getSystemConfig command                               |
    \*-----------------------------------------------------------------*/
    command["method"]           = "getSystemConfig";

    /*-----------------------------------------------------------------*\
    | Convert the JSON object to a string and write it                  |
    \*-----------------------------------------------------------------*/
    std::string command_str     = command.dump();

    port.udp_write((char *)command_str.c_str(), (int)command_str.length() + 1);

    /*-----------------------------------------------------------------*\
    | Wait up to 1s to give it time to receive and process response     |
    \*-----------------------------------------------------------------*/
    for(unsigned int wait_count = 0; wait_count < 100; wait_count++)
    {
        if(firmware_version != "")
        {
            return;
        }

        std::this_thread::sleep_for(10ms);
    }
}
