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

#include <limits>

#include "QtPropertyFieldEditor.h"
#include "ListFieldEditor.h"
#include "ExpandableFieldEditor.h"


// -------------------- constructor --------------------
CamiTKPropertyFieldEditor::CamiTKPropertyFieldEditor(CamiTKExtensionModelPresenter* presenter, VariantDataModel& dataModel, const QString& name, const QString& description) : FieldEditor(presenter, dataModel, name, description) {
    separatorLine = nullptr;
}

// -------------------- deleteButtonClicked --------------------
void CamiTKPropertyFieldEditor::deleteButtonClicked() {
    if (separatorLine != nullptr) {
        separatorLine->hide();
        delete separatorLine;
        separatorLine = nullptr;
    }
    FieldEditor::deleteButtonClicked();
}

// -------------------- getWidget --------------------
QWidget* CamiTKPropertyFieldEditor::getWidget() {
    if (myWidget == nullptr) {
        QWidget* parameterWidget = new QWidget();
        mainGridLayout = new QGridLayout(parameterWidget);
        // compact space
        parameterWidget->setContentsMargins(0, 0, 0, 0);
        mainGridLayout->setContentsMargins(0, 0, 0, 0);
        mainGridLayout->setSpacing(0);
        mainGridLayout->setContentsMargins(0, 0, 0, 0);

        QWidget* expandableWidget = new QWidget();
        expandableGridLayout = new QGridLayout(expandableWidget);
        // compact space
        expandableWidget->setContentsMargins(0, 0, 0, 0);
        expandableGridLayout->setContentsMargins(0, 0, 0, 0);
        expandableGridLayout->setSpacing(0);
        expandableGridLayout->setContentsMargins(0, 0, 0, 0);

        // nameFieldEditor is used to update the label/
        // modification of this field will also modify the row label
        nameFieldEditor = new QtPropertyFieldEditor(presenter, dataModel["name"], "name", "Parameter name (must be unique among parameters and begin with an uppercase letter or number). It will be automatically formatted to title case.", "New Parameter");
        nameFieldEditor->setPlaceHolderText("Parameter Name");
        nameFieldEditor->setMandatory(true);
        nameFieldEditor->setTitleCase(true);
        addToGrid(mainGridLayout, nameFieldEditor, 0, 0);
        nameFieldEditor->getWidget()->setFixedWidth(200);

        QStringList typeEnum = {"int", "double", "bool", "QString", "QVector3D", "QColor", "QChar", "QPoint", "enum"};
        // other possible values (but not really useful for action, so not shown in the possible values) are: "QDate", "QTime", "QDateTime", "QPointF", "QKeySequence", "QLocale", "QSize", "QSizeF", "QRect", "QFont", "QRectF", "QSizePolicy", "QFont", "QCursor", "QVariant::Cursor"
        typeFieldEditor = new QtPropertyFieldEditor(presenter, dataModel["type"], "Type", "<p>Data type of the parameter.</p><p>For enum enter the default values as a <tt>QStringList</tt>, i.e. <tt>{ \"Value 1\", \"Value 2\", ... }</tt>, to populate the possible enum values.</p><p>Some less useful types are omitted in this enum, please check the documentation for more information.</p>", "int");
        typeFieldEditor->setEnumValues(typeEnum);
        addToGrid(mainGridLayout, typeFieldEditor, 0, 1);
        typeFieldEditor->getWidget()->setFixedWidth(100);

        defaultValueEditor = nullptr;
        minMaxWidget = nullptr;
        minimumValueEditor = nullptr;
        maximumValueEditor = nullptr;
        singleStepEditor = nullptr;
        decimalsEditor = nullptr;
        regExpEditor = nullptr;

        buildTypeDependentEditors(dataModel["type"], false /* first call: do not overwrite, use the value found in the model */);

        // defaultValueEditor depends on the value of typeFieldEditor → set the connection
        connect(typeFieldEditor, &FieldEditor::valueChanged, this, [ this ](VariantDataModel & dataModelType) {
            if (defaultValueEditor == nullptr) {
                // do nothing as already in the middle of the process
                return;
            }
            buildTypeDependentEditors(dataModelType);
        });

        QtPropertyFieldEditor* descriptionFieldEditor = new QtPropertyFieldEditor(presenter, dataModel["description"], "description", "Description of the parameter. You can use rich text (simple html).<br/><b>NOTE:</b> The double quote character must be escaped (e.g. <tt>\\\"ABC\\\"</tt>)", "");
        descriptionFieldEditor->setPlaceHolderText("Description");
        addToGrid(mainGridLayout, descriptionFieldEditor, 1, 0, 3);

        //-- advanced constraints on parameters
        QtPropertyFieldEditor* advancedQtPropertyFieldEditor;

        advancedQtPropertyFieldEditor = new QtPropertyFieldEditor(presenter, dataModel["readOnly"], "read only", "If checked this parameter will not be editable by the user", false);
        advancedQtPropertyFieldEditor->setCheckBoxText("Read Only");
        addToGrid(expandableGridLayout, advancedQtPropertyFieldEditor, 0, 0);

        advancedQtPropertyFieldEditor = new QtPropertyFieldEditor(presenter, dataModel["unit"], "unit", "Unit in which this parameter is expressed", "");
        advancedQtPropertyFieldEditor->setPlaceHolderText("Unit");
        addToGrid(expandableGridLayout, advancedQtPropertyFieldEditor, 0, 1);

        advancedQtPropertyFieldEditor = new QtPropertyFieldEditor(presenter, dataModel["group"], "group", "Parameters can be grouped together to simplify the UI.", "");
        advancedQtPropertyFieldEditor->setPlaceHolderText("Group");
        addToGrid(expandableGridLayout, advancedQtPropertyFieldEditor, 0, 2);

        ExpandableFieldEditor* advancedEditor = new ExpandableFieldEditor(presenter, expandableWidget, dataModel, "Parameter Constraints", true);
        addToGrid(mainGridLayout, advancedEditor, 2, 0, 3);

        myWidget = parameterWidget;
    }
    return myWidget;
}

// -------------------- appendRowTo --------------------
void CamiTKPropertyFieldEditor::appendRowTo(QGridLayout* layout, bool canBeDeleted) {
    FieldEditor::appendRowTo(layout, canBeDeleted);
    nameFieldEditor->setLabelToUpdateWithFieldValue(rowLabel);
    // add an horizontal visible line
    separatorLine = new QFrame();
    separatorLine->setFrameShape(QFrame::HLine);
    separatorLine->setFrameShadow(QFrame::Sunken);
    layout->addWidget(separatorLine, layout->rowCount(), 0, 1, 2);
}

// -------------------- addToGrid --------------------
void CamiTKPropertyFieldEditor::addToGrid(QGridLayout* gridLayout, FieldEditor* field, int row, int column, int columnSpan) {
    if (field->getWidget() != nullptr) {
        gridLayout->addWidget(field->getWidget(), row, column, 1, columnSpan);
    }
}

// -------------------- buildTypeDependentEditors --------------------
void CamiTKPropertyFieldEditor::buildTypeDependentEditors(VariantDataModel& dataModelType, bool overwriteDefault) {
    // get the QVariant type
    blockSignals(true);

    //-- delete all previous
    if (defaultValueEditor != nullptr) {
        // remove defaultValueEditor from the grid
        mainGridLayout->removeWidget(defaultValueEditor->getWidget());
        delete defaultValueEditor;
        defaultValueEditor = nullptr;
        dataModel["defaultValue"].remove();
    }

    if (minMaxWidget != nullptr) {
        expandableGridLayout->removeWidget(minMaxWidget);
        delete minimumValueEditor;
        delete maximumValueEditor;
        delete minMaxWidget;
        minMaxWidget = nullptr;
        minimumValueEditor = nullptr;
        maximumValueEditor = nullptr;
        // delete previous data model value
        dataModel["minimum"].remove();
        dataModel["maximum"].remove();
    }

    if (singleStepEditor != nullptr) {
        expandableGridLayout->removeWidget(singleStepEditor->getWidget());
        delete singleStepEditor;
        singleStepEditor = nullptr;
        dataModel["singleStep"].remove();
    }

    if (decimalsEditor != nullptr) {
        expandableGridLayout->removeWidget(decimalsEditor->getWidget());
        delete decimalsEditor;
        decimalsEditor = nullptr;
        dataModel["decimals"].remove();
    }

    if (regExpEditor != nullptr) {
        expandableGridLayout->removeWidget(regExpEditor->getWidget());
        delete regExpEditor;
        regExpEditor = nullptr;
        dataModel["regExp"].remove();
    }

    //-- build all required editors

    // Determine the real type
    QVariant::Type realType;
    if (dataModelType.toString() == "enum") {
        realType = QVariant::StringList;
    }
    else {
        realType = QVariant::nameToType(dataModelType.toString().toUtf8().constData());
    }

    // Determine GUI type (type known by the QtPropertyFieldEditor), current value,
    // default (new) value, edited field (data model field edited in the GUI)
    // and regExp (if required)
    QVariant newValue;
    QString editedDataField = "defaultValue";
    QString regExp;
    QString description = QString("Default value for type <tt>%1</tt>").arg(dataModelType);
    bool dataModelHasValue = (dataModel[editedDataField].isValid());
    switch (realType) {
        case QVariant::Invalid:
            // unsupported (should never happen!)
            newValue = 0;
            break;
        case QVariant::Vector3D:
            newValue = "QVector3D(0.0, 0.0, 0.0)";
            regExp = R"(QVector3D\(\s*[-]?[\d\.]+\s*(?:,\s*[-]?[\d\.]+\s*){2}\))";
            break;
        case QVariant::Point:
            newValue = "QPoint(0, 0)";
            regExp = R"(QPoint\(\s*[-]?[\d]+\s*(?:,\s*[-]?[\d]+\s*)\))";
            break;
        case QVariant::Color:
            newValue = "QColor(0, 0, 0)";
            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*\))";
            break;
        case QVariant::StringList:
            newValue = QVariantList({"First Enum Value"});
            // As the edited field is in fact the enumValues field,
            // set the parameter default value to 0 (which corresponds to the first enum value)
            dataModel[editedDataField] = 0;
            // ⚠ edited field is the enum values itself, not the default value, which is set automatically to 0
            editedDataField = "enumValues";
            // check if enumValues already exist and has a value
            dataModelHasValue = (dataModel[editedDataField].isValid());
            // also tested:  \{(\"[A-Za-zÀ-ÖØ-öø-ÿçÇ\s\d\w']+\",?\s*)+\}
            regExp = R"(\{(?:\s*\"[^\"]*\"\s*,)*\s*\"[^\"]*\"\s*\})";
            description = "List of enum values.<p>Press <tt>Insert</tt> or <tt>+</tt> to insert new tags, <tt>Delete</tt> to remove the current tag, and click on a tag to edit the text.";
            break;
        case QVariant::String:
            newValue = R"("")";
            regExp = R"("(?:[^"\\]|\\.)*")";
            break;
        default:
            // other types are directly supported
            newValue = QVariant(realType);
            break;
    }

    // overwrite previous data if required (i.e. when the type changes)
    if (overwriteDefault) {
        dataModel[editedDataField] = newValue;
    }
    else {
        // if the data model already has a value, use it for setting the UI
        // otherwise use the default value
        // +make sure double are treated as double not int
        if (realType == QVariant::Double) {
            dataModel[editedDataField] = (dataModelHasValue) ? dataModel[editedDataField].getValue().toDouble() : newValue.toDouble();
        }
        else {
            dataModel[editedDataField] = (dataModelHasValue) ? dataModel[editedDataField].getValue() : newValue;
        }
    }

    if (dataModelType.toString() == "enum") {
        // create the new field depending on the gui type, using the default value / enum values
        defaultValueEditor = new ListFieldEditor(presenter, dataModel[editedDataField], "Enum Value", description);
    }
    else {
        QtPropertyFieldEditor* defaultValueQtPropertyFieldEditor = new QtPropertyFieldEditor(presenter, dataModel[editedDataField], "Default Value", description, newValue);
        // add constraints for specific cases
        if (!regExp.isEmpty()) {
            defaultValueQtPropertyFieldEditor->setRegExp(regExp);
        }
        defaultValueEditor = defaultValueQtPropertyFieldEditor;
    }

    addToGrid(mainGridLayout, defaultValueEditor, 0, 2);

    // -- parameter constraints field editors
    if (dataModelType.toString() == "QString") {
        regExpEditor = new QtPropertyFieldEditor(presenter, dataModel["regExp"], "RegExp", "If set this will constrain the parameter value to match this regular expression", "");
        regExpEditor->setPlaceHolderText("RegExp");
        addToGrid(expandableGridLayout, regExpEditor, 1, 0, 3);
    }

    QtPropertyFieldEditor* qtPropertyFieldEditor = dynamic_cast<QtPropertyFieldEditor*>(defaultValueEditor);
    if (dataModelType.toString() == "int" || dataModelType.toString() == "double") {
        // create min max area (holds both minimumValueEditor and maximumValueEditor)
        minMaxWidget = new QWidget();
        QHBoxLayout* minMaxLayout = new QHBoxLayout(minMaxWidget);

        // build minimumValueEditor
        QVariant minValue = (dataModelType.toString() == "int") ? std::numeric_limits<int>::lowest() : std::numeric_limits<double>::lowest();
        minimumValueEditor = new QtPropertyFieldEditor(presenter, dataModel["minimum"], "Minimum Value", "Minimum possible value", minValue);
        minMaxLayout->addWidget(minimumValueEditor->getWidget());

        // update the default value minimal value when minimumValueEditor changes
        connect(minimumValueEditor, &FieldEditor::valueChanged, this, [ = ](VariantDataModel & dataModelType) {
            if (qtPropertyFieldEditor != nullptr) {
                qtPropertyFieldEditor->setMinimum(dataModelType.getValue());
            }
        });
        qtPropertyFieldEditor->setMinimum(dataModel["minimum"].getValue());

        // build maximumValueEditor
        QVariant maxValue = (dataModelType.toString() == "int") ? std::numeric_limits<int>::max() : std::numeric_limits<double>::max();
        maximumValueEditor = new QtPropertyFieldEditor(presenter, dataModel["maximum"], "Maximum Value", "Maximum possible value", maxValue);
        minMaxLayout->addWidget(maximumValueEditor->getWidget());

        expandableGridLayout->addWidget(minMaxWidget, 1, 0, 1, 1);

        // update the default value maximal value when maximalValueEditor changes
        connect(maximumValueEditor, &FieldEditor::valueChanged, this, [ = ](VariantDataModel & dataModelType) {
            if (qtPropertyFieldEditor != nullptr) {
                qtPropertyFieldEditor->setMaximum(dataModelType.getValue());
            }
        });
        qtPropertyFieldEditor->setMaximum(dataModel["maximum"].getValue());

        // build singleStepEditor
        QVariant defaultStepValue = ((dataModelType.toString() == "double") ? (double) 1.0 : (int) 1);
        singleStepEditor = new QtPropertyFieldEditor(presenter, dataModel["singleStep"], "Single Step", "Default increment between two values", defaultStepValue);
        addToGrid(expandableGridLayout, singleStepEditor, 1, 1);
    }

    if (dataModelType.toString() == "double") {
        decimalsEditor = new QtPropertyFieldEditor(presenter, dataModel["decimals"], "Number of Decimals", "Number of digits after the decimal point", QVariant(2));
        addToGrid(expandableGridLayout, decimalsEditor, 1, 2);

        // update the default value decimals when decimalsEditor changes
        connect(decimalsEditor, &FieldEditor::valueChanged, this, [ =, this ](VariantDataModel & dataModelType) {
            if (qtPropertyFieldEditor != nullptr) {
                int newDecimals = dataModel["decimals"].getValue().toInt();
                qtPropertyFieldEditor->setDecimals(newDecimals);
                minimumValueEditor->setDecimals(newDecimals);
                maximumValueEditor->setDecimals(newDecimals);
                singleStepEditor->setDecimals(newDecimals);
            }
        });
        int currentDecimals = dataModel["decimals"].getValue().toInt();
        qtPropertyFieldEditor->setDecimals(currentDecimals);
        minimumValueEditor->setDecimals(currentDecimals);
        maximumValueEditor->setDecimals(currentDecimals);
        singleStepEditor->setDecimals(currentDecimals);
    }
    else {
        // enforce no decimals for int
        if (dataModelType.toString() == "int") {
            qtPropertyFieldEditor->setDecimals(0);
            minimumValueEditor->setDecimals(0);
            maximumValueEditor->setDecimals(0);
            singleStepEditor->setDecimals(0);
        }
    }

    blockSignals(false);
}