#include "LedDisplay.h"

//LedDisplay::LedDisplay(String deviceName, Adafruit_NeoMatrix *ledDisplayMatrix)


void LedDisplay::loop()
{
    if (millis() - lastKeepAlive > this->maximumKeepAliveDelay)
    {
        this->lastKeepAlive = millis();
        this->bleServer->disconnectCurrentDevice();
    }
}

void LedDisplay::onDeviceConnectedChanged(bool deviceConnected)
{
    if (deviceConnected)
    {
        Serial.println("Device connected");
        this->lastKeepAlive = millis();
    }
    else
    {
        Serial.println("Device disconnected");
        this->lastKeepAlive = -1;
        this->sessionAuthorized = false;
    }
}

void LedDisplay::onDataReceived(String dataString)
{
    // process JSON
    const size_t capacity = JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(3) + 200;
    DynamicJsonDocument requestDoc(capacity);
    DeserializationError error = deserializeJson(requestDoc, dataString);

    // return on error
    if (error != DeserializationError::Ok)
        return;

    // get reuqest data
    DisplayCommand requestHeader = requestDoc["header"];
    JsonObject requestData = requestDoc["data"];

    // prepare reply data
    DynamicJsonDocument replyDoc(capacity);
    replyDoc["header"] = requestHeader;
    DisplayStatusCode replyStatus = InternalError;
    JsonObject replyData = replyDoc.createNestedObject("data");

    if (requestHeader > KeepAliveCommand && !this->sessionAuthorized)
        replyStatus = Unauthorized;
    else
        switch (requestHeader)
        {
        case AuthenticateCommand:
        {
            String combinedCode = String(this->properties.deviceCode); //this->bleServer->getDeviceAddress() + String(this->properties.deviceCode);
            String secret = this->sha256(combinedCode);

            if (this->sessionAuthorized)
            {
                replyStatus = Success;
            }
            else if (requestData["secret"] == secret)
            {
                replyStatus = Success;
                this->sessionAuthorized = true;
            }
            else
            {
                replyStatus = Unauthorized;
                this->sessionAuthorized = false;
                break;
            }

            // for future use: add some variables of the display
        }

        case KeepAliveCommand:
        {
            replyStatus = Success;
            this->lastKeepAlive = millis();
            break;
        }

        case GetAllTextSetsCommand:
        {
            replyStatus = Success;
            replyData["maximumTextSets"] = LedDisplayController::maximumTextSets;
            replyData["maximumTextLength"] = LedDisplayController::maximumTextLength;

            break;
        }
        case GetDisplayBrightnessCommand:
        {
            replyData["displayBrightness"] = this->ledDisplayController->getBrightness();
            replyData["automaticBrightnessAdjustment"] = this->ledDisplayController->getAutomaticBrightnessAdjustment();
            replyStatus = Success;
            break;
        }
        case GetTextSetParameterCommand:
        {
            int index = requestData["index"];
            LedDisplayController::DisplayTextSetParameter parameter = requestData["parameter"];
            // send each parameter to the client
            replyData["index"] = index;
            replyData["parameter"] = parameter;
            replyStatus = Success;

            replyData["value"] = this->ledDisplayController->getTextSetParameter(
                index,
                parameter);


            Serial.println("sending parameter: " + String(parameter) + " with value: '" + this->ledDisplayController->getTextSetParameter(
                index,
                parameter) + "'");
            break;
        }

        case SetTextSetParameterCommand:
        {
            int index = requestData["index"];
            int parameter = requestData["parameter"];
            String value = requestData["value"];

            LedDisplayController::GetSetTextSetParameterExitCode res = this->ledDisplayController->setTextSetParameter(index, LedDisplayController::DisplayTextSetParameter(parameter), value);

            if (res == LedDisplayController::Success)
                replyStatus = Success;
            else
            {
                replyStatus = DisplayControllerError;
                replyData["displayControllerError"] = res;
            }

            break;
        }
        case SetDisplayBrightnessCommand:
        {
            this->ledDisplayController->setBrightness(requestData["displayBrightness"]);
            this->ledDisplayController->setAutomaticBrightnessAdjustment(requestData["automaticBrightnessAdjustment"]);
            replyStatus = Success;
            break;
        }
        case SetDisplayCodeCommand: {
            String code = requestData["displayCode"];
            if(code.length() != 4) {
                replyStatus = BadRequestError;
                break;
            }

            strncpy(this->properties.deviceCode, code.c_str(), sizeof(this->properties.deviceCode));
            this->storeProperties();
            replyStatus = Success;
            break;
        }
        case SetDisplayNameCommand: {
            String name = requestData["displayName"];
            if(name.length() <= 0) {
                replyStatus = BadRequestError;
                break;
            }

            strncpy(this->properties.deviceName, name.c_str(), sizeof(this->properties.deviceName));
            this->storeProperties();
            replyStatus = Success;
            break;
        }
        default:
            break;
        }

    // reply to the client
    replyDoc["status"] = replyStatus;
    String json;
    serializeJson(replyDoc, json);
    Serial.println("Primary JSON: '" + json + "'");
    this->bleServer->sendData(json);



}

bool LedDisplay::loadProperties()
{
    if (this->eepromUnit == nullptr)
        return false;

    DisplayProperties buf = {};

    // read conf from EEPROM
    this->eepromUnit->read(buf);

    if (strcmp(buf.valid, "OK") == 0)
    {
        memcpy(&this->properties, &buf, sizeof(DisplayProperties));
    }
    else
    {
        // There was an error reading the properties -> rebuild with default values!
        DisplayProperties defaultProperties{
            "Led Display",
            "1234",
            "OK"};

        memcpy(&this->properties, &defaultProperties, sizeof(DisplayProperties));
        this->storeProperties();
    }

    return true;
}

bool LedDisplay::storeProperties()
{
    if (this->eepromUnit == nullptr)
        return false;

    strncpy(this->properties.valid, "OK", sizeof(this->properties.valid));
    this->eepromUnit->write(this->properties);
    return true;
}

String LedDisplay::sha256(String payload)
{
    byte shaResult[32];
    mbedtls_md_context_t ctx;
    mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
    const size_t payloadLength = strlen(payload.c_str());
    mbedtls_md_init(&ctx);
    mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 0);
    mbedtls_md_starts(&ctx);
    mbedtls_md_update(&ctx, (const unsigned char *)payload.c_str(), payloadLength);
    mbedtls_md_finish(&ctx, shaResult);
    mbedtls_md_free(&ctx);

    String resultString = "";

    for (int i = 0; i < sizeof(shaResult); i++)
    {
        char str[3];

        sprintf(str, "%02x", (int)shaResult[i]);
        resultString += str;
    }

    return resultString;
}