/*****************************************************************************
 *
 * Testmanager - Graphical Automation and Visualisation Tool
 *
 * Copyright (C) 2019  Florian Pose <fp@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 <Python.h>

#include "ScriptVariable.h"

#include "ScriptVariableModel.h"
#include "WidgetContainer.h"
#include "TabPage.h"
#include "MainWindow.h"
#include "DataNode.h"
#include "DataModel.h"

#include <QJsonObject>
#include <QDebug>

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

typedef struct {
    PyObject_HEAD
    ScriptVariable *ptr;
} PyScriptVariable;

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

static PyObject *getValue(PyObject *self, PyObject * /* args */)
{
    ScriptVariable *var(((PyScriptVariable *) self)->ptr);
    return Py_BuildValue("d", var->getValue());
}

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

static PyMethodDef pyScriptVariableMethods[] = {
    { "getValue", getValue, METH_NOARGS,
        "Query the current value" },
    {NULL, NULL, 0, NULL}
};

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

static PyTypeObject pyScriptVariableType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "ScriptVariable",              /* tp_name */
    sizeof(PyScriptVariable),      /* tp_basicsize */
    0,                             /* tp_itemsize */
    0,                             /* tp_dealloc */
    0,                             /* tp_print */
    0,                             /* tp_getattr */
    0,                             /* tp_setattr */
    0,                             /* tp_reserved */
    0,                             /* tp_repr */
    0,                             /* tp_as_number */
    0,                             /* tp_as_sequence */
    0,                             /* tp_as_mapping */
    0,                             /* tp_hash */
    0,                             /* tp_call */
    0,                             /* tp_str */
    0,                             /* tp_getattro */
    0,                             /* tp_setattro */
    0,                             /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT,            /* tp_flags */
    "Script variables",            /* tp_doc */
    0,                             /* tp_traverse */
    0,                             /* tp_clear */
    0,                             /* tp_richcompare */
    0,                             /* tp_weaklistoffset */
    0,                             /* tp_iter */
    0,                             /* tp_iternext */
    pyScriptVariableMethods,       /* tp_methods */
    0,                             /* tp_members */
    0,                             /* tp_getset */
    0,                             /* tp_base */
    0,                             /* tp_dict */
    0,                             /* tp_descr_get */
    0,                             /* tp_descr_set */
    0,                             /* tp_dictoffset */
    0,                             /* tp_init */
    0,                             /* tp_alloc */
    0,                             /* tp_new */
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
};

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

void ScriptVariable::registerPythonType()
{
    if (PyType_Ready(&pyScriptVariableType) < 0) {
        qWarning() << "Failed to finalize Python type ScriptVariable!";
    }
}

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

ScriptVariable::ScriptVariable(ScriptVariableModel *model):
    period(0.0),
    model(model),
    dataNode(nullptr),
    variable(nullptr),
    dataPresent(false),
    value(0.0),
    pyObject((PyObject *) PyObject_New(PyScriptVariable,
                &pyScriptVariableType))
{
    ((PyScriptVariable *) pyObject)->ptr = this;
}

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

ScriptVariable::~ScriptVariable()
{
    if (variable) {
        variable->unsubscribe(this);
    }
    if (dataNode) {
        dataNode->notifyUnsubscribed();
    }

    PyObject_Del(pyObject);
}

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

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

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

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

    url = obj["url"].toString();
    period = obj["period"].toDouble();
    name = obj["name"].toString();
}

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

QJsonValue ScriptVariable::toJson() const
{
    QJsonObject obj;

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

    if (period) {
        obj["period"] = period;
    }

    obj["name"] = name;

    return obj;
}

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

void ScriptVariable::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 ScriptVariable::connectVariables()
{
    if (variable) {
        variable->unsubscribe(this);
        variable = nullptr;
    }
    if (dataNode) {
        dataNode->notifyUnsubscribed();
        dataNode = nullptr;
    }

    MainWindow *mainWindow(
            model->getContainer()->getTabPage()->getMainWindow());
    DataModel *dataModel(mainWindow->getDataModel());
    dataNode = dataModel->findDataNode(url);

    if (dataNode) {
        variable = dataNode->getVariable();
        if (variable) {
            try {
                variable->subscribe(this, period);
            }
            catch (PdCom::VariableException &e) {
                QString msg(tr("Script variable subscription failed: %1")
                        .arg(e.what()));
                mainWindow->statusBar()->showMessage(msg, 5000);
                qWarning() << msg;
                variable = nullptr;
                dataNode = nullptr;
                return;
            }
            dataNode->notifySubscribed(period);

            if (period == 0.0) {
                try {
                    variable->poll(this);
                }
                catch (PdCom::VariableException &e) {
                    QString msg(tr("Script variable poll failed: %1")
                            .arg(e.what()));
                    mainWindow->statusBar()->showMessage(msg, 5000);
                    qWarning() << msg;
                    variable->unsubscribe(this);
                    variable = nullptr;
                    dataNode->notifyUnsubscribed();
                    dataNode = nullptr;
                }
            }
        }
    }
    else {
        QString msg(tr("URL not found: %1").arg(url.toString()));
        mainWindow->statusBar()->showMessage(msg, 5000);
        qWarning() << msg;
    }
}

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

void ScriptVariable::appendToPythonDict(PyObject *dict) const
{
    if (name.isEmpty()) {
        return;
    }

    PyDict_SetItemString(dict, name.toUtf8().constData(), pyObject);
}

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

void ScriptVariable::notify(PdCom::Variable *variable)
{
    variable->getValue(&value, 1);
    dataPresent = true;
    model->notify(this);
    model->getContainer()->executeScript();
}

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

void ScriptVariable::notifyDelete(PdCom::Variable *)
{
    variable = nullptr;

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

    dataPresent = false;
    model->notify(this);
}

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