/*****************************************************************************
 *
 * 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 "ParameterTableModel.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>
#include <cstdio>

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

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

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

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

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

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

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

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

void ParameterTableModel::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 ParameterTableModel::toJson(bool withValue = true) const
{
    QJsonArray array;

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

    return array;
}

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

int ParameterTableModel::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 ParameterTableModel::columnCount(const QModelIndex &) const
{
    return 4; // name,current value, saved value, url
}

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

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

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

        switch (role) {
            case Qt::DisplayRole:
            case Qt::EditRole:
                switch (index.column()) {
                    case 0: // Name
                        ret = parameter->name;
                        break;
                    case 1: // Current value
                        if (parameter->hasData()) {
                            ret = QLocale().toString(
                                    parameter->getCurrentValue());
                        }
                        break;
                    case 2: // Saved value
                        if (parameter->hasSavedData()) {
                            ret = QLocale().toString(
                                    parameter->getSavedValue());
                        }
                        break;
                    case 3: // Variable URL
                        ret = parameter->url;
                        break;
                }
                break;

            case Qt::DecorationRole:
                switch (index.column()) {
                    case 2: // Saved Value
                        if (parameter->hasData() and
                                (parameter->getCurrentValue() !=
                                 parameter->getSavedValue()
                                 or not parameter->hasSavedData())) {
                            ret = QIcon(":images/dialog-warning.svg");
                        }
                        break;
                    case 3: // Variable URL
                        ret = parameter->getIcon();
                        break;
                }
                break;

            default:
                break;
        }
    }

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

    return ret;
}

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

QVariant ParameterTableModel::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("Current value");
                break;
            case 2:
                ret = tr("Stored value");
                break;
            case 3:
                ret = tr("Variable");
                break;
        }
    }

    return ret;
}

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

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

    if (index.isValid()) {
        if (index.column() == 0 or index.column() == 2) {
            ret |= Qt::ItemIsEditable;
        }
    }
    else {
        ret |= Qt::ItemIsDropEnabled;
    }

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

    return ret;
}

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

bool ParameterTableModel::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:
                parameter->name = value.toString();
                ret = true;
                break;
            case 2:
                if (not value.isNull()) {
                    auto str(value.toString().trimmed());
                    if (not str.isEmpty()) {
                        bool ok;
                        double val(QLocale().toDouble(value.toString(), &ok));
                        if (ok) {
                            parameter->setSavedValue(val);
                            ret = true;
                        }
                    }
                }
                break;
            default:
                break;
        }
    }

    return ret;
}

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

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

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

    return types;
}

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


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

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

void ParameterTableModel::applyParameters()
{
    for (int row = 0; row < parameters.size(); row++) {
        auto val(parameters[row]->getSavedValue());
        auto sub(parameters[row]->getSubscription());
        if (not sub->empty()) {
            sub->getVariable().setValue(val);
        }
    }
}

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

void ParameterTableModel::saveParameters()
{
    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 ParameterTableModel::saveParametersAs()
{
    saveDialog();
    saveParameters();
}

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

void ParameterTableModel::loadParameters()
{
    QString path = loadDialog();

    QFile file(path);
    if (!file.open(QIODevice::ReadOnly)) {
        QString msg(tr("Failed to open %1.").arg(path));
        mainWindow->statusBar()->showMessage(msg, 5000);
        qWarning() << msg;
        return;
    }

    QByteArray ba = file.readAll();
    file.close();

    QJsonParseError err;
    QJsonDocument doc(QJsonDocument::fromJson(ba, &err));

    if (err.error != QJsonParseError::NoError) {
        qCritical() << "Layout parsing error (" << err.error
            << ") at offset " << err.offset << ": "
            << err.errorString();
        return;
    }

    parameterPath = path;
    QFileInfo fi(parameterPath);
    QString searchPath(fi.absoluteDir().path());
    QDir::setSearchPaths("parameters", QStringList(searchPath));

    QJsonObject layoutObject(doc.object());
    QJsonArray parameterArray(layoutObject["parameters"].toArray());

    clear();

    fromJson(parameterArray);
}

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

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

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

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

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

    return urls;
}

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

void ParameterTableModel::notify(Parameter *parameter)
{

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

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

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

    return parameters[index.row()];
}

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

void ParameterTableModel::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 ParameterTableModel::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];

    // store directory for opening the dialog next time
    QFileInfo file(parameterPath);
    QString searchPath(file.absoluteDir().path());
    QDir::setSearchPaths("parameters", QStringList(searchPath));
}

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

QString ParameterTableModel::loadDialog()
{
    QFileDialog dialog(mainWindow);
    dialog.setAcceptMode(QFileDialog::AcceptOpen);

    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 QString();
    }

    return dialog.selectedFiles()[0];
}

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


