/*****************************************************************************
 *
 * 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 "ScriptVariableModel.h"

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

#include <QMimeData>
#include <QDebug>

//#define DEBUG_MODEL

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

ScriptVariableModel::ScriptVariableModel(WidgetContainer *container):
    container(container)
{
}

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

ScriptVariableModel::~ScriptVariableModel()
{
    clear();
}

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

void ScriptVariableModel::clear()
{
    if (variables.isEmpty()) {
        return;
    }

    beginResetModel();
    foreach (ScriptVariable *var, variables) {
        delete var;
    }
    variables.clear();
    endResetModel();
    container->scriptVariablesChanged();
}

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

void ScriptVariableModel::fromJson(const QJsonArray &array)
{
    foreach (QJsonValue arrayValue, array) {
        ScriptVariable *var = new ScriptVariable(this);
        var->fromJson(arrayValue);

        beginInsertRows(QModelIndex(), variables.size(), variables.size());
        variables.append(var);
        endInsertRows();
        container->scriptVariablesChanged();
    }
}

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

QJsonArray ScriptVariableModel::toJson() const
{
    QJsonArray array;

    foreach (const ScriptVariable *var, variables) {
        array.append(var->toJson());
    }

    return array;
}

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

int ScriptVariableModel::rowCount(const QModelIndex &parent) const
{
    int ret = 0;

    if (parent.isValid()) {
        qWarning() << __func__ << parent << "inval";
    }
    else {
        ret = variables.size();
    }

#ifdef DEBUG_MODEL
    qDebug() << __func__ << parent << ret;
#endif

    return ret;
}

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

int ScriptVariableModel::columnCount(const QModelIndex &) const
{
    return 4; // name, period, value, url
}

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

QVariant ScriptVariableModel::data(const QModelIndex &index, int role) const
{
    QVariant ret;

    if (index.isValid()) {
        const ScriptVariable *var = variables[index.row()];

        switch (role) {

            case Qt::DisplayRole:
            case Qt::EditRole:
                switch (index.column()) {
                    case 0:
                        ret = var->name;
                        break;
                    case 1:
                        ret = QLocale().toString(var->period.count());
                        break;
                    case 2:
                        if (var->hasData()) {
                            ret = var->getValue();
                        }
                        break;
                    case 3:
                        ret = var->url;
                        break;
                }
                break;

            case Qt::DecorationRole:
                switch (index.column()) {
                    case 3:
                        ret = var->getIcon();
                        break;
                }
                break;

            default:
                break;
        }
    }

#ifdef DEBUG_MODEL
    if (role <= 1) {
        qDebug() << __func__ << index << role << ret;
    }
#endif

    return ret;
}

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

QVariant ScriptVariableModel::headerData(
        int section,
        Qt::Orientation orientation,
        int role
        ) const
{
    QVariant ret;

    if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
        switch (section) {
            case 0:
                ret = tr("Name");
                break;
            case 1:
                ret = tr("Period [s]");
                break;
            case 2:
                ret = tr("Value");
                break;
            case 3:
                ret = tr("Variable");
                break;
        }
    }

    return ret;
}

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

Qt::ItemFlags ScriptVariableModel::flags(const QModelIndex &index) const
{
    Qt::ItemFlags ret = QAbstractTableModel::flags(index);

    if (index.isValid()) {
        if (index.column() >= 0 and index.column() <= 1) {
            ret |= Qt::ItemIsEditable;
        }
    }
    else {
        ret |= Qt::ItemIsDropEnabled;
    }

#ifdef DEBUG_MODEL
    qDebug() << __func__ << index << ret;
#endif

    return ret;
}

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

bool ScriptVariableModel::setData(const QModelIndex &index,
        const QVariant &value, int role)
{
    bool ret = false;

    if (index.isValid() && role == Qt::EditRole) {
        ScriptVariable *var = variables[index.row()];

        switch (index.column()) {
            case 0:
                var->name = value.toString();
                container->updateScript();
                ret = true;
                break;

            case 1:
                var->period = std::chrono::duration<double>(QLocale().toDouble(value.toString()));
                var->connectVariables();
                ret = true;
                break;

            default:
                break;
        }
    }

    return ret;
}

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

QStringList ScriptVariableModel::mimeTypes() const
{
    QStringList types;
    types << "text/uri-list";

#ifdef DEBUG_MODEL
    qDebug() << __func__ << types;
#endif

    return types;
}

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

bool ScriptVariableModel::dropMimeData(const QMimeData *data,
        Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
#ifdef DEBUG_MODEL
    qDebug() << __func__ << data << action << row << column << parent;
#else
    Q_UNUSED(column);
    Q_UNUSED(parent);
#endif

    if (not data->hasFormat("text/uri-list")) {
        return false;
    }

    if (action != Qt::CopyAction) {
        return false;
    }

    MainWindow *mainWindow(container->getTabPage()->getMainWindow());
    int insertPos;
    if (row == -1) {
        insertPos = variables.size();
    }
    else {
        insertPos = row;
    }

    foreach (QUrl url, data->urls()) {
        DataNode *dataNode(mainWindow->getDataModel()->findDataNode(url));
        if (!dataNode) {
            mainWindow->statusBar()->showMessage(tr("Url not found: %1")
                    .arg(url.toString()), 2000);
            continue;
        }

        ScriptVariable *var = new ScriptVariable(this);
        var->url = url;

        if (dataNode->getPeriod().count() > 0.0) {
            var->period = std::chrono::milliseconds(200);
        }

        var->connectVariables();

        beginInsertRows(QModelIndex(), insertPos, insertPos);
        variables.insert(insertPos, var);
        endInsertRows();
        container->scriptVariablesChanged();

        insertPos++;
    }


    return true;
}

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

void ScriptVariableModel::connectVariables()
{
    for (int row = 0; row < variables.size(); row++) {
        variables[row]->connectVariables();
        QModelIndex i(createIndex(row, 3));
        QModelIndex j(createIndex(row, 3));
        emit QAbstractItemModel::dataChanged(i, j);
    }
}

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

void ScriptVariableModel::replaceUrl(const QUrl &oldUrl, const QUrl &newUrl)
{
    for (int row = 0; row < variables.size(); row++) {
        variables[row]->replaceUrl(oldUrl, newUrl);
        QModelIndex i(createIndex(row, 3));
        QModelIndex j(createIndex(row, 3));
        emit QAbstractItemModel::dataChanged(i, j);
    }
}

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

bool ScriptVariableModel::uses(const QUrl &varUrl) const
{
    for (int row = 0; row < variables.size(); row++) {
        if (variables[row]->url == varUrl) {
            return true;
        }
    }

    return false;
}

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

QSet<QUrl> ScriptVariableModel::getUrls() const
{
    QSet<QUrl> urls;

    for (int row = 0; row < variables.size(); row++) {
        urls.insert(variables[row]->url);
    }

    return urls;
}

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

void ScriptVariableModel::notify(ScriptVariable *var)
{
    for (int row = 0; row < variables.size(); row++) { // FIXME
        if (variables[row] == var) {
            QModelIndex i(createIndex(row, 2));
            QModelIndex j(createIndex(row, 2));
            emit QAbstractItemModel::dataChanged(i, j);
            break;
        }
    }
}

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

void ScriptVariableModel::appendToPythonDict(PyObject *dict) const
{
    foreach (const ScriptVariable *var, variables) {
        var->appendToPythonDict(dict);
    }
}

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

ScriptVariable *ScriptVariableModel::getScriptVariable(
        const QModelIndex &index) const
{
    if (index.row() < 0 or index.row() >= variables.size()) {
        return nullptr;
    }

    return variables[index.row()];
}

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

void ScriptVariableModel::remove(ScriptVariable *var)
{
    for (int row = 0; row < variables.size(); row++) { // FIXME
        if (variables[row] == var) {
            beginRemoveRows(QModelIndex(), row, row);
            variables.removeAt(row);
            endRemoveRows();
            delete var;
            return;
        }
    }
}

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