/*****************************************************************************
 *
 * Testmanager - Graphical Automation and Visualisation Tool
 *
 * Copyright (C) 2023  Daniel Ramirez <dr@igh.de>
 *
 * This file is part of Testmanager.
 *
 * Testmanager 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, either version 3 of the License, or (at your option)
 * any later version.
 *
 * Testmanager 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 Testmanager. If not, see <http://www.gnu.org/licenses/>.
 *
 ****************************************************************************/

#include "Parameter.h"

#include "ParameterModel.h"
#include "ParameterTableModel.h"
#include "TabPage.h"
#include "MainWindow.h"
#include "DataNode.h"
#include "DataModel.h"

#include <QJsonObject>
#include <QDebug>
#include <chrono>
#include <pdcom5/Subscriber.h>
#include <pdcom5/Subscription.h>
#include <qabstractitemmodel.h>

#include <exception>

/*****************************************************************************
 * public
 ****************************************************************************/

Parameter::Parameter(ParameterModel *model):
    PdCom::Subscriber(PdCom::event_mode),
    model(model),
    mainWindow(model->getMainWindow()),
    dataNode(nullptr),
    currentValue(0.0),
    dataPresent(false),
    savedValue(0.0),
    savedDataPresent(false)
{
}

/****************************************************************************/

Parameter::Parameter(ParameterTableModel *model):
    PdCom::Subscriber(PdCom::event_mode),
    model((ParameterModel*)model),
    mainWindow(model->getMainWindow()),
    dataNode(nullptr),
    currentValue(0.0),
    dataPresent(false),
    savedValue(0.0),
    savedDataPresent(false)
{
}

/****************************************************************************/

Parameter::~Parameter()
{
    if (dataNode) {
        dataNode->notifyUnsubscribed();
        dataNode = nullptr;
    }
}

/****************************************************************************/

QIcon Parameter::getIcon() const
{
    if (dataNode) {
        return dataNode->getIcon();
    }
    else {
        return QIcon();
    }
}

/****************************************************************************/

void Parameter::fromJson(const QJsonValue &value)
{
    QJsonObject obj(value.toObject());

    if (obj.contains("name")) {
        name = obj["name"].toString();
    }
    else {
        name = QString();
    }

    url = obj["url"].toString();

    double scale(1.0);
    if (obj.contains("scale")) {
        auto scaleValue(obj["scale"]);
        if (not scaleValue.isDouble()) {
            throw(std::invalid_argument(
                        "scale must be a scalar double value."));
        }

        scale = obj["scale"].toDouble();
        if (scale == 0.0) {
            throw(std::invalid_argument("scale must not be zero"));
        }
    }

    if (obj.contains("value")){
        auto value(obj["value"]);
        if (not value.isDouble()) {
            throw(std::invalid_argument(
                        "Non-double value types not yet implemented."));
        }
        savedValue = value.toDouble() / scale;
        savedDataPresent = true;
    }
    else {
        savedValue = 0.0;
        savedDataPresent = false;
    }
}

/****************************************************************************/

QJsonObject Parameter::toJson(bool withValue) const
{
    QJsonObject obj;

    obj["name"] = name;
    obj["url"] = url.toString();
    if (withValue and savedDataPresent) {
        obj["value"] = savedValue;
    }

    return obj;
}

/****************************************************************************/

void Parameter::replaceUrl(const QUrl &oldUrl, const QUrl &newUrl)
{
    QUrl dataSourceUrl(url.adjusted(QUrl::RemovePassword |
                QUrl::RemovePath | QUrl::RemoveQuery |
                QUrl::RemoveFragment));
    if (dataSourceUrl != oldUrl) {
        return;
    }

    url.setScheme(newUrl.scheme());
    url.setAuthority(newUrl.authority()); // user, pass, host, port
}

/****************************************************************************/

void Parameter::setSavedValue(double val)
{
    savedValue = val;
    savedDataPresent = true;
    model->notify(this);
}

/****************************************************************************/

void Parameter::connectParameters()
{
    if (dataNode) {
        dataNode->notifyUnsubscribed();
        dataNode = nullptr;
    }

    DataModel *dataModel(mainWindow->getDataModel());
    dataNode = dataModel->findDataNode(url);

    if (dataNode) {
        subscription = PdCom::Subscription(*this, dataNode->getVariable());
        try {
            subscription.poll();
        }
        catch (PdCom::Exception &e) {
            QString msg(tr("Parameter poll failed: %1")
                    .arg(e.what()));
            mainWindow->statusBar()->showMessage(msg, 5000);
            qWarning() << msg;
            dataNode->notifyUnsubscribed();
            dataNode = nullptr;
        }
    }
    else {
        QString msg(tr("URL not found: %1").arg(url.toString()));
        mainWindow->statusBar()->showMessage(msg, 5000);
        qWarning() << msg;
    }
}

/*****************************************************************************
 * private
 ****************************************************************************/

void Parameter::newValues(std::chrono::nanoseconds)
{
    subscription.getValue(currentValue);
    dataPresent = true;
    model->notify(this);
}

/****************************************************************************/
