/*****************************************************************************
 *
 * 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 "ParameterModel.h"
#include "ParameterSetModel.h"

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

#include <QMimeData>
#include <QDebug>
#include <QFileDialog>
#include <QDialog>
#include <QFileInfo>
#include <QDir>
#include <QJsonDocument>
#include <QJsonArray>

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

ParameterModel::ParameterModel(MainWindow *mainWindow):
    mainWindow(mainWindow)
{}

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

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

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

void ParameterModel::clear()
{
    if (this->isEmpty()) {
        return;
    }

    beginResetModel();
    foreach (Parameter *parameter, parameters) {
        delete parameter;
    }
    parameters.clear();
    endResetModel();
}

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

void ParameterModel::fromJson(const QJsonArray &array)
{
    foreach (QJsonValue arrayValue, array) {
        Parameter *parameter = new Parameter(this);

        parameter->fromJson(arrayValue);

        beginInsertRows(QModelIndex(), parameters.size(), parameters.size());
        parameters.append(parameter);
        endInsertRows();
    }
}

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

QJsonArray ParameterModel::toJson(bool withValue = true) const
{
    QJsonArray array;

    foreach (const Parameter *parameter, parameters) {
        array.append(parameter->toJson(withValue));
    }

    return array;
}

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

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

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

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

    return ret;
}

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

int ParameterModel::columnCount(const QModelIndex &) const
{
    return 3;  // name, value, url
}

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

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

    if (index.isValid()) {
        const Parameter *parameter = parameters[index.row()];

        switch (role) {
            case Qt::DisplayRole:
            case Qt::EditRole:
                switch (index.column()) {
                    case 0:
                        ret = parameter->name;
                        break;
                    case 1:
                        if (parameter->hasData()) {
                            ret = QLocale().toString(
                                    parameter->getCurrentValue());
                        }
                        break;
                    case 2:
                        ret = parameter->url;
                        break;
                }
                break;

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

            default:
                break;
        }
    }

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

    return ret;
}

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

QVariant ParameterModel::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("Value");
                break;
            case 2:
                ret = tr("Variable");
                break;
        }
    }

    return ret;
}

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

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

    if (index.isValid()) {
        if (index.column() == 0) {
            ret |= Qt::ItemIsEditable;
        }
        if (index.column() == 1) {
            auto parameter(parameters[index.row()]);
            auto sub(parameter->getSubscription());
            if (!sub->empty()) {
                ret |= Qt::ItemIsEditable;
            }
        }
    }
    else {
        ret |= Qt::ItemIsDropEnabled;
    }

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

    return ret;
}

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

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

    if (index.isValid() && role == Qt::EditRole) {
        Parameter *parameter = parameters[index.row()];

        switch (index.column()) {
            case 0:  // Name
                parameter->name = value.toString();
                ret = true;
                break;

            case 1:  // Current value
                if (not value.isNull()) {
                    auto str(value.toString().trimmed());
                    if (not str.isEmpty()) {
                        bool ok;
                        double val = QLocale().toDouble(str, &ok);
                        auto sub(parameter->getSubscription());
                        if (ok and not sub->empty()) {
                            sub->getVariable().setValue(val);
                            parameter->setSavedValue(val);
                            ret = true;
                        }
                    }
                }
                break;

            default:
                break;
        }
    }

    return ret;
}

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

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

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

    return types;
}

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

bool ParameterModel::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 (!canDropMimeData(data, action, row, column, parent)) {
        QGuiApplication::restoreOverrideCursor();
        return false;
    }

    int insertPos;
    if (row == -1) {
        insertPos = parameters.size();
    }
    else {
        insertPos = row;
    }

    ParameterSetModel *parameterSetModel = mainWindow->getParameterSetModel();

    if (parameterSetModel->isEmpty()) {
        parameterSetModel->newParameterSet(true);
    }

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

        if (getUrls().contains(url)) {
            // already existing
            continue;
        }

        Parameter *parameter = new Parameter(this);
        parameter->url = url;

        parameter->connectParameters();

        // initialize saved value with current value FIXME remove?
        parameter->setSavedValue(parameter->getCurrentValue());

        beginInsertRows(QModelIndex(), insertPos, insertPos);
        parameters.insert(insertPos, parameter);
        endInsertRows();

        insertPos++;
    }

    QGuiApplication::restoreOverrideCursor();

    return true;
}

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

bool ParameterModel::canDropMimeData(
        const QMimeData *data,
        Qt::DropAction action,
        int row,
        int column,
        const QModelIndex &parent) const
{
    Q_UNUSED(row);
    Q_UNUSED(column);
    Q_UNUSED(parent);

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

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

    bool wrong = false;

    DataNode *node;

    foreach (QUrl url, data->urls()) {
        node = mainWindow->getDataModel()->findDataNode(url);
        if (!node->getVariable().isWriteable()) {
            mainWindow->statusBar()->showMessage(
                    tr("Process variable %1 must be writable!")
                            .arg(node->getName()),
                    5000);
            QGuiApplication::setOverrideCursor(Qt::ForbiddenCursor);
            wrong = true;
            break;
        }
    }
    if (wrong) {
        QGuiApplication::restoreOverrideCursor();
        return false;
    }

    mainWindow->statusBar()->clearMessage();

    return true;
}

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

void ParameterModel::connectParameters()
{
    for (int row = 0; row < parameters.size(); row++) {
        parameters[row]->connectParameters();
        QModelIndex i(createIndex(row, 0));
        QModelIndex j(createIndex(row, 2));
        emit QAbstractItemModel::dataChanged(i, j);
    }
}

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

void ParameterModel::saveParameters()
{
    foreach (Parameter *parameter, parameters) {
        // set saved to current before saving
        parameter->setSavedValue(parameter->getCurrentValue());
    }

    saveDialog();

    QJsonObject layoutObject;

    bool withValue = true;

    QJsonArray parameterArray = toJson(withValue);

    layoutObject["parameters"] = parameterArray;

    QJsonDocument saveDoc(layoutObject);

    QFile saveFile(parameterPath);
    if (!saveFile.open(QIODevice::WriteOnly)) {
        mainWindow->statusBar()->showMessage(
                tr("Failed to open %1").arg(parameterPath),
                2000);
        return;
    }

    saveFile.write(saveDoc.toJson());
    saveFile.close();
}

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

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

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

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

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

    return urls;
}

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

void ParameterModel::notify(Parameter *parameter)
{
    for (int row = 0; row < parameters.size(); row++) {  // FIXME
        if (parameters[row] == parameter) {
            QModelIndex i(createIndex(row, 1));
            QModelIndex j(createIndex(row, 1));
            emit QAbstractItemModel::dataChanged(i, j);
            break;
        }
    }
}

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

Parameter *ParameterModel::getParameter(const QModelIndex &index) const
{
    if (index.row() < 0 or index.row() >= parameters.size()) {
        return nullptr;
    }

    return parameters[index.row()];
}

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

void ParameterModel::remove(Parameter *parameter)
{
    for (int row = 0; row < parameters.size(); row++) {  // FIXME
        if (parameters[row] == parameter) {
            beginRemoveRows(QModelIndex(), row, row);
            parameters.removeAt(row);
            endRemoveRows();
            delete parameter;
            return;
        }
    }
}

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

void ParameterModel::saveDialog()
{
    QFileDialog dialog(mainWindow);
    dialog.setAcceptMode(QFileDialog::AcceptSave);

    QStringList filters;
    filters << tr("Parameter Files (*.par)") << tr("Any files (*)");

    dialog.setNameFilters(filters);
    dialog.setDefaultSuffix("par");

    int ret = dialog.exec();

    if (ret != QDialog::Accepted) {
        return;
    }

    parameterPath = dialog.selectedFiles()[0];
    QFileInfo file(parameterPath);
    QString searchPath(file.absoluteDir().path());
    QDir::setSearchPaths("parameters", QStringList(searchPath));
}
