/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *                                                                         *
 *  Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru>                *
 *                                                                         *
 *  This file is part of guh.                                              *
 *                                                                         *
 *  Guh is free software: you can redistribute it and/or modify            *
 *  it under the terms of the GNU General Public License as published by   *
 *  the Free Software Foundation, version 2 of the License.                *
 *                                                                         *
 *  Guh is distributed in the hope that it will be useful,                 *
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
 *  GNU General Public License for more details.                           *
 *                                                                         *
 *  You should have received a copy of the GNU General Public License      *
 *  along with guh. If not, see <http://www.gnu.org/licenses/>.            *
 *                                                                         *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "jsonrpcserver.h"
#include "extern-plugininfo.h"

JsonRpcServer::JsonRpcServer(QObject *parent) :
    QObject(parent),
    m_id(0)
{
    m_manager = new TuneManager(31337, this);

    connect(m_manager, &TuneManager::tuneConnectionStatusChanged, this, &JsonRpcServer::connectionStatusChanged);
    connect(m_manager, &TuneManager::dataReady, this, &JsonRpcServer::processData);

}

void JsonRpcServer::start()
{
    m_manager->start();
}

void JsonRpcServer::stop()
{
    m_manager->stop();
}

bool JsonRpcServer::tuneAvailable()
{
    return m_manager->tuneAvailable();
}

bool JsonRpcServer::sync(QList<Device *> deviceList)
{
    if (!tuneAvailable()) {
        return false;
    }

    QVariantMap message;
    QVariantMap params;
    QVariantList moods;
    QVariantMap tune;
    foreach (Device* device, deviceList) {
        if (device->deviceClassId() == moodDeviceClassId) {
            QVariantMap mood;
            mood.insert("name", device->paramValue("name"));
            mood.insert("deviceId", device->id());
            mood.insert("position", device->paramValue("position"));
            mood.insert("icon", device->paramValue("icon"));
            QVariantMap states;
            states.insert("value", device->stateValue(valueStateTypeId).toInt());
            states.insert("active", device->stateValue(activeStateTypeId).toBool());
            mood.insert("states", states);
            moods.append(mood);
        }
        if (device->deviceClassId() == tuneDeviceClassId) {
            tune.insert("name", device->paramValue("name"));
            tune.insert("deviceId", device->id());
            tune.insert("brightness", device->stateValue(brightnessStateTypeId).toInt());
            tune.insert("power", device->stateValue(powerStateTypeId).toBool());
        }
    }
    m_id++;
    params.insert("moods", moods);
    params.insert("tune", tune);
    message.insert("method", "Items.Sync");
    message.insert("id", m_id);
    message.insert("params", params);

    m_requests.insert(m_id, message);

    QJsonDocument jsonDoc = QJsonDocument::fromVariant(message);
    qCDebug(dcTune) << jsonDoc.toJson();
    m_manager->sendData(jsonDoc.toJson(QJsonDocument::Compact));
    return true;
}

void JsonRpcServer::executeAction(Device *device, const Action &action)
{
    QVariantMap message;
    QVariantMap params;

    params.insert("deviceId", device->id());
    params.insert("actionId", action.id());

    if (device->deviceClassId() == moodDeviceClassId) {
        message.insert("method", "Mood.ExecuteAction");
        if (action.actionTypeId() == valueActionTypeId) {
            params.insert("value", action.param("value").value().toInt());
            params.insert("active", device->stateValue(activeStateTypeId).toBool());
        } else if (action.actionTypeId() == activeActionTypeId) {
            params.insert("value", device->stateValue(valueStateTypeId).toInt());
            params.insert("active", action.param("active").value().toBool());
        }
    } else if(device->deviceClassId() == tuneDeviceClassId) {
        message.insert("method", "Tune.ExecuteAction");
        if (action.actionTypeId() == brightnessActionTypeId) {
            params.insert("brightness", action.param("brightness").value().toInt());
            params.insert("power", device->stateValue(powerStateTypeId).toBool());
        } else if (action.actionTypeId() == powerActionTypeId) {
            params.insert("brightness", device->stateValue(brightnessStateTypeId).toInt());
            params.insert("power", action.param("power").value().toBool());
        }
    }

    m_id++;
    message.insert("id", m_id);
    message.insert("params", params);

    m_requests.insert(m_id, message);

    QJsonDocument jsonDoc = QJsonDocument::fromVariant(message);
    qCDebug(dcTune) << jsonDoc.toJson();
    m_manager->sendData(jsonDoc.toJson(QJsonDocument::Compact));
}

QByteArray JsonRpcServer::formatResponse(int commandId, const QVariantMap &responseParams)
{
    QVariantMap responseMap;
    responseMap.insert("id", commandId);
    responseMap.insert("success", true);
    responseMap.insert("params", responseParams);
    QJsonDocument jsonDoc = QJsonDocument::fromVariant(responseMap);
    return jsonDoc.toJson(QJsonDocument::Compact);
}

QByteArray JsonRpcServer::formatErrorResponse(int commandId, const QString &errorMessage)
{
    QVariantMap responseMap;
    responseMap.insert("id", commandId);
    responseMap.insert("success", false);
    responseMap.insert("error", errorMessage);
    QJsonDocument jsonDoc = QJsonDocument::fromVariant(responseMap);
    return jsonDoc.toJson(QJsonDocument::Compact);
}

void JsonRpcServer::handleResponse(const QVariantMap &response)
{
    int responseId = response.value("id").toInt();

    if (!m_requests.contains(responseId)) {
        qCWarning(dcTune) << "got a response without a corresponding request!!!!";
        return;
    }

    // remove it request because we have a response now...
    QVariantMap request = m_requests.take(responseId);

    // Note: maby we have to do something if any request fails
    if (!response.value("success").toBool()) {
        qCWarning(dcTune) << "error for request:" << request << response.value("error").toString();
    }

    // check if this is a response to an action execution
    if (response.contains("actionId")) {
        emit gotActionResponse(response);
    }
}

void JsonRpcServer::processData(const QByteArray &data)
{
    QJsonParseError error;
    QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);

    if(error.error != QJsonParseError::NoError) {
        qCWarning(dcTune) << "failed to parse data" << data << ":" << error.errorString();
        return;
    }

    QVariantMap message = jsonDoc.toVariant().toMap();

    // check if the message has an id
    if (!message.contains("id")) {
        qCWarning(dcTune) << "message does not contain a valid id" << message;
        return;
    }

    // if this is a response message to a request
    if (message.contains("success") && !message.contains("method")) {
        handleResponse(message);
        return;
    }

    // otherwise we need a method
    if (!message.contains("method")) {
        qCWarning(dcTune) << "message does not contain a valid method" << message;
        return;
    }

    int commandId = message.value("id").toInt();

    if (message.value("method").toString() == "Mood.SyncStates") {
        emit gotMoodSync(message.value("params").toMap());
        m_manager->sendData(formatResponse(commandId));
    }

    if (message.value("method").toString() == "Tune.SyncStates") {
        emit gotTuneSync(message.value("params").toMap());
        m_manager->sendData(formatResponse(commandId));
    }

}
