//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/ParEdit/CustomEditors.cpp
//! @brief     Implements classes CustomEditor, ComboPropertyEditor, ParSpinBoxEditor.
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2018
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "GUI/View/ParEdit/CustomEditors.h"
#include "Base/Util/Assert.h"
#include "Fit/Param/RealLimits.h"
#include "GUI/Model/Descriptor/ComboProperty.h"
#include "GUI/View/Base/CustomEventFilters.h"
#include "GUI/View/ParEdit/ParSpinBox.h"
#include <QBoxLayout>
#include <cmath>

namespace {

//! Single step for ParSpinBoxEditor.

double singleStep(int decimals)
{
    // For item with decimals=3 (i.e. 0.001) single step will be 0.1
    return 1. / std::pow(10., decimals - 1);
}

} // namespace

//  ************************************************************************************************
//  class CustomEditor
//  ************************************************************************************************

//! Sets the data from the model to editor.

void CustomEditor::setData(const QVariant& data)
{
    m_data = data;
    initEditor();
}

//! Inits editor widgets from m_data.

void CustomEditor::initEditor() {}

//! Saves the data from the editor and informs external delegates.

void CustomEditor::setDataIntern(const QVariant& data)
{
    m_data = data;
    dataChanged(m_data);
}

//  ************************************************************************************************
//  class ComboPropertyEditor
//  ************************************************************************************************

ComboPropertyEditor::ComboPropertyEditor(QWidget* parent)
    : CustomEditor(parent)
    , m_box(new QComboBox)
{
    setAutoFillBackground(true);
    setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);

    auto* layout = new QVBoxLayout(this);
    layout->setContentsMargins(0, 0, 0, 0);
    layout->setSpacing(0);
    layout->addWidget(m_box);

    setConnected(true);
}

QSize ComboPropertyEditor::sizeHint() const
{
    return m_box->sizeHint();
}

QSize ComboPropertyEditor::minimumSizeHint() const
{
    return m_box->minimumSizeHint();
}

void ComboPropertyEditor::onIndexChanged(int index)
{
    auto comboProperty = m_data.value<ComboProperty>();

    if (comboProperty.currentIndex() != index) {
        comboProperty.setCurrentIndex(index);
        setDataIntern(QVariant::fromValue<ComboProperty>(comboProperty));
    }
}

void ComboPropertyEditor::initEditor()
{
    setConnected(false);

    m_box->clear();
    m_box->insertItems(0, internLabels());
    m_box->setCurrentIndex(internIndex());

    setConnected(true);
}

//! Returns list of labels for QComboBox

QStringList ComboPropertyEditor::internLabels()
{
    if (!m_data.canConvert<ComboProperty>())
        return {};
    auto comboProperty = m_data.value<ComboProperty>();
    return comboProperty.values();
}

//! Returns index for QComboBox.

int ComboPropertyEditor::internIndex()
{
    if (!m_data.canConvert<ComboProperty>())
        return 0;
    auto comboProperty = m_data.value<ComboProperty>();
    return comboProperty.currentIndex();
}

void ComboPropertyEditor::setConnected(bool isConnected)
{
    if (isConnected)
        connect(m_box, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
                &ComboPropertyEditor::onIndexChanged, Qt::UniqueConnection);
    else
        disconnect(m_box, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
                   this, &ComboPropertyEditor::onIndexChanged);
}

//  ************************************************************************************************
//  class ParSpinBoxEditor
//  ************************************************************************************************

ParSpinBoxEditor::ParSpinBoxEditor(QWidget* parent)
    : CustomEditor(parent)
    , m_double_editor(new ParSpinBox)
{
    setAutoFillBackground(true);
    setFocusPolicy(Qt::StrongFocus);
    m_double_editor->setFocusPolicy(Qt::StrongFocus);
    m_double_editor->setKeyboardTracking(false);

    auto* layout = new QVBoxLayout(this);
    layout->setContentsMargins(0, 0, 0, 0);
    layout->setSpacing(0);
    layout->addWidget(m_double_editor);

    connect(m_double_editor, &ParSpinBox::valueChanged, [this] { onEditingFinished(); });

    setFocusProxy(m_double_editor);
}

void ParSpinBoxEditor::setLimits(const RealLimits& limits)
{
    m_double_editor->setMinimum(limits.hasLowerLimit() ? limits.min()
                                                       : std::numeric_limits<double>::lowest());
    m_double_editor->setMaximum(limits.hasUpperLimit() ? limits.max()
                                                       : std::numeric_limits<double>::max());
}

void ParSpinBoxEditor::setDecimals(int decimals)
{
    m_double_editor->setDecimals(decimals);
    m_double_editor->setSingleStep(singleStep(decimals));
}

void ParSpinBoxEditor::setSingleStep(double step)
{
    m_double_editor->setSingleStep(step);
}

void ParSpinBoxEditor::onEditingFinished()
{
    double new_value = m_double_editor->value();

    if (new_value != m_data.toDouble())
        setDataIntern(QVariant::fromValue(new_value));
}

void ParSpinBoxEditor::initEditor()
{
    ASSERT(m_data.type() == QVariant::Double);
    QSignalBlocker b(m_double_editor); // just show value, not signal about its change
    m_double_editor->setValue(m_data.toDouble());
}
