/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2025 Univ. Grenoble Alpes, CNRS, Grenoble INP - UGA, TIMC, 38000 Grenoble, France
 *
 * Visit http://camitk.imag.fr for more information
 *
 * This file is part of CamiTK.
 *
 * CamiTK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * CamiTK 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 Lesser General Public License version 3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with CamiTK.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $CAMITK_LICENCE_END$
 ****************************************************************************/

#include "HotPlugAction.h"
#include "HotPlugActionExtension.h"

// CamiTK includes
#include "Property.h"
#include "Log.h"

#include <TransformEngine.h>
#include <VariantDataModel.h>

#include <QVector3D>
#include <QPoint>
#include <QColor>

namespace camitk {

// ------------------- Constructor -------------------
HotPlugAction::HotPlugAction(HotPlugActionExtension* extension, const VariantDataModel& data) : Action(extension) {
    hotPlugExtension = extension;
    TransformEngine transformEngine;
    QJsonObject dataObject = data.getValue().toJsonObject();

    // Setting name, description and input component
    setName(transformEngine.transformToString("$title(name)$", dataObject));
    setDescription(data["description"]);
    setComponentClassName(data["componentClass"]);

    // Setting classification family and tags
    setFamily(data["classification"]["family"]);

    for (auto tag : data["classification"]["tags"].getValue().toList()) {
        addTag(tag.toString());
    }

    // block event management
    initializationPending = true;

    // Setting the action's parameters
    createParameters(data["parameters"]);

    // immediately take the property changes into account
    setAutoUpdateProperties(true);

    // initialization is finish, all events are unblocked
    initializationPending = false;

    if (data["notEmbedded"].isValid() && data["notEmbedded"].getValue().toBool()) {
        setEmbedded(false);
    }
}

// ------------------- needsUpdate -------------------
bool HotPlugAction::needsUpdate() {
    return false;
}

// ------------------- update -------------------
bool HotPlugAction::update() {
    // Do nothing by default
    return true;
}

// ------------------- event -------------------
bool HotPlugAction::event(QEvent* e) {
    if (e->type() == QEvent::DynamicPropertyChange && !initializationPending) {
        e->accept();
        QDynamicPropertyChangeEvent* changeEvent = dynamic_cast<QDynamicPropertyChangeEvent*>(e);

        if (changeEvent != nullptr) {
            // call user-defined parameterChanged()
            parameterChangedEvent(changeEvent->propertyName());
            return true;
        }
        else {
            return false;
        }
    }

    // this is important to continue the process if the event is a different one
    return QObject::event(e);
}

// ------------------- createParameters -------------------
void HotPlugAction::createParameters(VariantDataModel parameters) {
    TransformEngine transformEngine;
    if (parameters.isValid() && parameters.size() > 0) {

        for (const VariantDataModel& parameterData : parameters) {
            Property* param = createParameter(parameterData);

            setParameterAttributes(param, parameterData);

            // Finally, add the new parameter
            addParameter(param);
        }
    }
}

// ------------------- createParameter -------------------
Property* HotPlugAction::createParameter(VariantDataModel parameterData) {
    TransformEngine transformEngine;

    QString paramName = transformEngine.transformToString("$title(name)$", parameterData.getValue().toJsonObject());

    if (parameterData["type"].toString() == "enum" || parameterData["type"].toString() == "int") {
        return new Property(paramName, (int) parameterData["defaultValue"].getValue().toInt(), parameterData["description"], parameterData["unit"]);
    }

    if (parameterData["type"].toString() == "double") {
        return new Property(paramName, (double) parameterData["defaultValue"].getValue().toDouble(), parameterData["description"], parameterData["unit"]);
    }

    if (parameterData["type"].toString() == "bool") {
        return new Property(paramName, (bool) parameterData["defaultValue"].getValue().toBool(), parameterData["description"], parameterData["unit"]);
    }

    if (parameterData["type"].toString() == "QString") {
        QRegularExpression regExp(R"(\"(.*)\")");
        QRegularExpressionMatch match = regExp.match(parameterData["defaultValue"]);
        if (match.hasMatch()) {
            return new Property(paramName, match.captured(1), parameterData["description"], parameterData["unit"]);
        }
        else {
            CAMITK_WARNING(tr("Parameter %1 cannot convert default value %2 to QString (default value must be escaped inside quotes: \"\\\"default value\\\"\"). Using empty string instead.").arg(paramName).arg(parameterData["defaultValue"]));
            return new Property(paramName, "", parameterData["description"], parameterData["unit"]);
        }
    }

    if (parameterData["type"].toString() == "QVector3D") {
        QRegularExpression regExp(R"(QVector3D\((\s*[-]?[\d\.]+\s*),(\s*[-]?[\d\.]+\s*),(\s*[-]?[\d\.]+\s*)\))");
        QRegularExpressionMatch match = regExp.match(parameterData["defaultValue"]);
        if (match.hasMatch()) {
            return new Property(paramName, QVector3D(match.captured(1).toDouble(), match.captured(2).toDouble(), match.captured(3).toDouble()), parameterData["description"], parameterData["unit"]);
        }
        else {
            CAMITK_WARNING(tr("Parameter %1 cannot convert default value %2 to QVector3D (default value must look like: \"QVector3D(1.0344,42.034,-345.14)\"). Using (0,0,0) instead.").arg(paramName).arg(parameterData["defaultValue"]));
            return new Property(paramName, QVector3D(0.0, 0.0, 0.0), parameterData["description"], parameterData["unit"]);
        }
    }

    if (parameterData["type"].toString() == "QColor") {
        QRegularExpression regExp(R"(QColor\(\s*(25[0-5]|2[0-4]\d|[01]?\d{1,2})\s*,\s*(25[0-5]|2[0-4]\d|[01]?\d{1,2})\s*,\s*(25[0-5]|2[0-4]\d|[01]?\d{1,2})\s*\))");
        QRegularExpressionMatch match = regExp.match(parameterData["defaultValue"]);
        if (match.hasMatch()) {
            return new Property(paramName, QColor(match.captured(1).toInt(), match.captured(2).toInt(), match.captured(3).toInt()), parameterData["description"], parameterData["unit"]);
        }
        else {
            CAMITK_WARNING(tr("Parameter %1 cannot convert default value %2 to QColor (default value must look like: \"QColor(124,101,45)\"). Using (0,0,0) (black) instead.").arg(paramName).arg(parameterData["defaultValue"]));
            return new Property(paramName, QColor(0.0, 0.0, 0.0), parameterData["description"], parameterData["unit"]);
        }
    }

    if (parameterData["type"].toString() == "QChar") {
        return new Property(paramName, parameterData["defaultValue"].getValue().toChar(), parameterData["description"], parameterData["unit"]);
    }

    if (parameterData["type"].toString() == "QPoint") {
        QRegularExpression regExp(R"(QPoint\(\s*([-]?[\d]+)\s*,\s*([-]?[\d]+\s*)\))");
        QRegularExpressionMatch match = regExp.match(parameterData["defaultValue"]);
        if (match.hasMatch()) {
            return new Property(paramName, QPoint(match.captured(1).toInt(), match.captured(2).toInt()), parameterData["description"], parameterData["unit"]);
        }
        else {
            CAMITK_WARNING(tr("Parameter %1 cannot convert default value %2 to QPoint (default value must look like: \"QPoint(124,101)\"). Using (0,0) instead.").arg(paramName).arg(parameterData["defaultValue"]));
            return new Property(paramName, QPoint(0, 0), parameterData["description"], parameterData["unit"]);
        }
    }

    CAMITK_WARNING(tr("Parameter %1 has unsupported type %2. Using QString instead.").arg(paramName).arg(parameterData["type"]));
    return new Property(paramName, "", parameterData["description"], parameterData["unit"]);
}

// ------------------- setParameterAttributes -------------------
void HotPlugAction::setParameterAttributes(Property* parameter, VariantDataModel parameterData) {

    if (parameterData["group"].isValid() && parameterData["group"].toString() != "") {
        parameter->setGroupName(parameterData["group"]);
    }

    QStringList numericConstraints = { "minimum", "maximum", "decimals", "singleStep"};
    for (auto& e : numericConstraints) {
        if (parameterData[e].isValid() && parameterData[e].toString() != "") {
            parameter->setAttribute(e, parameterData[e].getValue().toDouble());
        }
    }

    if (parameterData["regExp"].isValid() && parameterData["regExp"].toString() != "") {
        parameter->setAttribute("regExp", QRegularExpression(parameterData["regExp"].toString()));
    }

    if (parameterData["readOnly"].isValid() && parameterData["readOnly"].getValue().toBool()) {
        parameter->setReadOnly(true);
    }

    if (parameterData["type"].toString() == "enum") {
        TransformEngine transformEngine;
        // Set the enum type
        QString enumTypeName = transformEngine.transformToString("$upperCamelCase(p.name)$Enum", parameterData.getValue().toJsonObject());
        parameter->setEnumTypeName(enumTypeName);
        // Populate the enum values
        parameter->setAttribute("enumNames", parameterData["enumValues"].getValue().toStringList());
    }
}

} // namespace camitk