////////////////////////////////////////////////////////////////////////////////////////
//
//  Copyright 2026 OVITO GmbH, Germany
//
//  This file is part of OVITO (Open Visualization Tool).
//
//  OVITO is free software; you can redistribute it and/or modify it either under the
//  terms of the GNU General Public License version 3 as published by the Free Software
//  Foundation (the "GPL") or, at your option, under the terms of the MIT License.
//  If you do not alter this notice, a recipient may use your version of this
//  file under either the GPL or the MIT License.
//
//  You should have received a copy of the GPL along with this program in a
//  file LICENSE.GPL.txt.  You should have received a copy of the MIT License along
//  with this program in a file LICENSE.MIT.txt
//
//  This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
//  either express or implied. See the GPL or the MIT License for the specific language
//  governing rights and limitations.
//
////////////////////////////////////////////////////////////////////////////////////////

#include <ovito/stdmod/gui/StdModGui.h>
#include <ovito/stdmod/modifiers/HistogramModifier.h>
#include <ovito/stdobj/gui/widgets/PropertyReferenceParameterUI.h>
#include <ovito/gui/desktop/properties/IntegerParameterUI.h>
#include <ovito/gui/desktop/properties/IntegerRadioButtonParameterUI.h>
#include <ovito/gui/desktop/properties/VariantComboBoxParameterUI.h>
#include <ovito/gui/desktop/properties/FloatParameterUI.h>
#include <ovito/gui/desktop/properties/BooleanParameterUI.h>
#include <ovito/gui/desktop/properties/DataObjectReferenceParameterUI.h>
#include <ovito/gui/desktop/properties/ObjectStatusDisplay.h>
#include <ovito/gui/desktop/properties/OpenDataInspectorButton.h>
#include <ovito/gui/desktop/mainwin/MainWindow.h>
#include <ovito/core/dataset/pipeline/ModificationNode.h>
#include "HistogramModifierEditor.h"

#include <qwt/qwt_plot_zoneitem.h>

namespace Ovito {

IMPLEMENT_CREATABLE_OVITO_CLASS(HistogramModifierEditor);
SET_OVITO_OBJECT_EDITOR(HistogramModifier, HistogramModifierEditor);

/******************************************************************************
* Sets up the UI widgets of the editor.
******************************************************************************/
void HistogramModifierEditor::createUI(const RolloutInsertionParameters& rolloutParams)
{
    // Create a rollout.
    QWidget* rollout = createRollout(tr("Histogram"), rolloutParams, "manual:particles.modifiers.histogram");

    // Create the rollout contents.
    QVBoxLayout* layout = new QVBoxLayout(rollout);
    layout->setContentsMargins(4,4,4,4);
    layout->setSpacing(4);

    DataObjectReferenceParameterUI* pclassUI = createParamUI<DataObjectReferenceParameterUI>(PROPERTY_FIELD(GenericPropertyModifier::subject), PropertyContainer::OOClass());
    layout->addWidget(new QLabel(tr("Operate on:")));
    layout->addWidget(pclassUI->comboBox());

    PropertyReferenceParameterUI* sourcePropertyUI = createParamUI<PropertyReferenceParameterUI>(PROPERTY_FIELD(HistogramModifier::sourceProperty));
    sourcePropertyUI->setContainerField(PROPERTY_FIELD(GenericPropertyModifier::subject));
    layout->addWidget(new QLabel(tr("Property:")));
    layout->addWidget(sourcePropertyUI->comboBox());

    QGridLayout* gridlayout = new QGridLayout();
    gridlayout->setContentsMargins(4,4,4,4);
    gridlayout->setColumnStretch(1, 1);

    // Number of bins parameter.
    IntegerParameterUI* numBinsPUI = createParamUI<IntegerParameterUI>(PROPERTY_FIELD(HistogramModifier::numberOfBins));
    gridlayout->addWidget(numBinsPUI->label(), 0, 0);
    gridlayout->addLayout(numBinsPUI->createFieldLayout(), 0, 1);

    // Normalization mode parameter.
    VariantComboBoxParameterUI* normalizationModeUI = createParamUI<VariantComboBoxParameterUI>(PROPERTY_FIELD(HistogramModifier::normalizationMode));
    normalizationModeUI->comboBox()->addItem(tr("Absolute count"), QVariant::fromValue((int)HistogramModifier::NormalizationMode::AbsoluteCount));
    normalizationModeUI->comboBox()->addItem(tr("Relative frequency"), QVariant::fromValue((int)HistogramModifier::NormalizationMode::RelativeFrequency));
    normalizationModeUI->comboBox()->addItem(tr("Probability density"), QVariant::fromValue((int)HistogramModifier::NormalizationMode::ProbabilityDensity));
    gridlayout->addWidget(new QLabel(tr("Normalization mode:")), 1, 0);
    gridlayout->addWidget(normalizationModeUI->comboBox(), 1, 1);

    layout->addLayout(gridlayout);

    _plotWidget = new DataTablePlotWidget();
    _plotWidget->setMinimumHeight(240);
    _plotWidget->setMaximumHeight(240);
    _selectionRangeIndicator = new QwtPlotZoneItem();
    _selectionRangeIndicator->setOrientation(Qt::Vertical);
    _selectionRangeIndicator->setZ(1);
    _selectionRangeIndicator->attach(_plotWidget);
    _selectionRangeIndicator->hide();

    layout->addWidget(_plotWidget);

    OpenDataInspectorButton* openDataInspectorBtn = new OpenDataInspectorButton(this, tr("Show in data inspector"));
    layout->addWidget(openDataInspectorBtn);

    // Input.
    QGroupBox* inputBox = new QGroupBox(tr("Input"), rollout);
    QVBoxLayout* sublayout = new QVBoxLayout(inputBox);
    sublayout->setContentsMargins(4,4,4,4);
    layout->addWidget(inputBox);

    BooleanParameterUI* onlySelectedUI = createParamUI<BooleanParameterUI>(PROPERTY_FIELD(HistogramModifier::onlySelectedElements));
    sublayout->addWidget(onlySelectedUI->checkBox());

    // Create selection.
    QGroupBox* selectionBox = new QGroupBox(tr("Create selection"), rollout);
    sublayout = new QVBoxLayout(selectionBox);
    sublayout->setContentsMargins(4,4,4,4);
    layout->addWidget(selectionBox);

    BooleanParameterUI* selectInRangeUI = createParamUI<BooleanParameterUI>(PROPERTY_FIELD(HistogramModifier::selectInRange));
    sublayout->addWidget(selectInRangeUI->checkBox());

    QHBoxLayout* hlayout = new QHBoxLayout();
    sublayout->addLayout(hlayout);
    FloatParameterUI* selRangeStartPUI = createParamUI<FloatParameterUI>(PROPERTY_FIELD(HistogramModifier::selectionRangeStart));
    FloatParameterUI* selRangeEndPUI = createParamUI<FloatParameterUI>(PROPERTY_FIELD(HistogramModifier::selectionRangeEnd));
    hlayout->addWidget(new QLabel(tr("From:")));
    hlayout->addLayout(selRangeStartPUI->createFieldLayout());
    hlayout->addSpacing(12);
    hlayout->addWidget(new QLabel(tr("To:")));
    hlayout->addLayout(selRangeEndPUI->createFieldLayout());
    selRangeStartPUI->setEnabled(false);
    selRangeEndPUI->setEnabled(false);
    connect(selectInRangeUI->checkBox(), &QCheckBox::toggled, selRangeStartPUI, &FloatParameterUI::setEnabled);
    connect(selectInRangeUI->checkBox(), &QCheckBox::toggled, selRangeEndPUI, &FloatParameterUI::setEnabled);

    // Axes.
    QGroupBox* axesBox = new QGroupBox(tr("Plot axes"), rollout);
    QVBoxLayout* axesSublayout = new QVBoxLayout(axesBox);
    axesSublayout->setContentsMargins(4,4,4,4);
    layout->addWidget(axesBox);
    // x-axis.
    {
        BooleanParameterUI* rangeUI = createParamUI<BooleanParameterUI>(PROPERTY_FIELD(HistogramModifier::fixXAxisRange));
        axesSublayout->addWidget(rangeUI->checkBox());

        QHBoxLayout* hlayout = new QHBoxLayout();
        axesSublayout->addLayout(hlayout);
        FloatParameterUI* startPUI = createParamUI<FloatParameterUI>(PROPERTY_FIELD(HistogramModifier::xAxisRangeStart));
        FloatParameterUI* endPUI = createParamUI<FloatParameterUI>(PROPERTY_FIELD(HistogramModifier::xAxisRangeEnd));
        hlayout->addWidget(new QLabel(tr("From:")));
        hlayout->addLayout(startPUI->createFieldLayout());
        hlayout->addSpacing(12);
        hlayout->addWidget(new QLabel(tr("To:")));
        hlayout->addLayout(endPUI->createFieldLayout());
        startPUI->setEnabled(false);
        endPUI->setEnabled(false);
        connect(rangeUI->checkBox(), &QCheckBox::toggled, startPUI, &FloatParameterUI::setEnabled);
        connect(rangeUI->checkBox(), &QCheckBox::toggled, endPUI, &FloatParameterUI::setEnabled);
    }
    // y-axis.
    {
        BooleanParameterUI* rangeUI = createParamUI<BooleanParameterUI>(PROPERTY_FIELD(HistogramModifier::fixYAxisRange));
        axesSublayout->addWidget(rangeUI->checkBox());

        QHBoxLayout* hlayout = new QHBoxLayout();
        axesSublayout->addLayout(hlayout);
        FloatParameterUI* startPUI = createParamUI<FloatParameterUI>(PROPERTY_FIELD(HistogramModifier::yAxisRangeStart));
        FloatParameterUI* endPUI = createParamUI<FloatParameterUI>(PROPERTY_FIELD(HistogramModifier::yAxisRangeEnd));
        hlayout->addWidget(new QLabel(tr("From:")));
        hlayout->addLayout(startPUI->createFieldLayout());
        hlayout->addSpacing(12);
        hlayout->addWidget(new QLabel(tr("To:")));
        hlayout->addLayout(endPUI->createFieldLayout());
        startPUI->setEnabled(false);
        endPUI->setEnabled(false);
        connect(rangeUI->checkBox(), &QCheckBox::toggled, startPUI, &FloatParameterUI::setEnabled);
        connect(rangeUI->checkBox(), &QCheckBox::toggled, endPUI, &FloatParameterUI::setEnabled);
    }

    // Status label.
    layout->addSpacing(6);
    layout->addWidget(createParamUI<ObjectStatusDisplay>()->statusWidget());

    // Update data plot whenever the modifier has calculated new results.
    connect(this, &PropertiesEditor::pipelineOutputChanged, this, &HistogramModifierEditor::plotHistogram);
}

/******************************************************************************
* Replots the histogram computed by the modifier.
******************************************************************************/
void HistogramModifierEditor::plotHistogram()
{
    handleExceptions([&]() {
        HistogramModifier* modifier = static_object_cast<HistogramModifier>(editObject());

        if(modifier && modifier->fixYAxisRange()) {
            _plotWidget->setAxisScale(QwtPlot::yLeft, modifier->yAxisRangeStart(), modifier->yAxisRangeEnd());
        }
        else {
            _plotWidget->setAxisAutoScale(QwtPlot::yLeft);
        }

        if(modifier && modifier->selectInRange()) {
            auto minmax = std::minmax(modifier->selectionRangeStart(), modifier->selectionRangeEnd());
            _selectionRangeIndicator->setInterval(minmax.first, minmax.second);
            _selectionRangeIndicator->show();
        }
        else {
            _selectionRangeIndicator->hide();
        }

        if(modifier && modificationNode()) {
            // Request the modifier's pipeline output.
            const PipelineFlowState& state = getPipelineOutput();

            // Look up the generated data table in the modifier's pipeline output.
            QString tableName = QStringLiteral("histogram[%1]").arg(modifier->sourceProperty().nameWithComponent());
            _plotWidget->setTable(state.getObjectBy<DataTable>(modificationNode(), tableName), true);
        }
        else {
            _plotWidget->setTable({});
        }
    });
}

}   // End of namespace
