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

#include "GUI/View/ParEdit/FitParameterDelegate.h"
#include "Base/Util/Assert.h"
#include "GUI/Model/Descriptor/ComboProperty.h"
#include "GUI/Model/Project/ProjectDocument.h"
#include "GUI/Model/Tune/FitParameterItem.h"
#include "GUI/View/Base/CustomEventFilters.h"
#include "GUI/View/Numeric/ScientificSpinBox.h"
#include "GUI/View/ParEdit/CustomEditors.h"
#include <QApplication>
#include <QModelIndex>
#include <QSpinBox>

namespace {

double getStep(double val)
{
    return val == 0.0 ? 1.0 : val / 100.;
}

bool isDoubleProperty(const QVariant& variant)
{
    return variant.type() == QVariant::Double;
}

bool isComboProperty(const QVariant& variant)
{
    return variant.canConvert<ComboProperty>();
}

bool hasStringRepresentation(const QModelIndex& index)
{
    auto variant = index.data();
    if (isComboProperty(variant))
        return true;
    if (isDoubleProperty(variant) && index.internalPointer())
        return true;

    return false;
}

QString toString(const QModelIndex& index)
{
    auto variant = index.data();
    if (isComboProperty(variant))
        return variant.value<ComboProperty>().label();

    if (isDoubleProperty(variant) && index.internalPointer()) {
        auto* item = static_cast<QObject*>(index.internalPointer());
        auto* doubleItem = dynamic_cast<FitDoubleItem*>(item);
        // only "Scientific SpinBoxes" in Fit-Window
        return ScientificSpinBox::toString(doubleItem->value(), doubleItem->decimals());
    }
    return "";
}

QWidget* createEditor(QObject* item, QWidget* parent)
{
    CustomEditor* result(nullptr);

    if (auto* doubleItem = dynamic_cast<FitDoubleItem*>(item)) {
        // only Scientific SpinBoxes in Fit-Window
        auto* editor = new ScientificSpinBoxEditor;
        editor->setLimits(doubleItem->limits());
        editor->setDecimals(doubleItem->decimals());
        editor->setSingleStep(getStep(doubleItem->value()));
        result = editor;
    } else if (dynamic_cast<FitTypeItem*>(item))
        result = new ComboPropertyEditor;

    if (parent && result) {
        result->setParent(parent);

        QObject::connect(result, &CustomEditor::dataChanged, [=] { gDoc->setModified(); });
    }

    return result;
}

} // namespace


FitParameterDelegate::FitParameterDelegate(QObject* parent)
    : QStyledItemDelegate(parent)
{
}

void FitParameterDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,
                                 const QModelIndex& index) const
{
    if (::hasStringRepresentation(index)) {
        QString text = ::toString(index);
        paintCustomLabel(painter, option, index, text);
    } else
        QStyledItemDelegate::paint(painter, option, index);
}

QWidget* FitParameterDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option,
                                            const QModelIndex& index) const
{
    auto* result = createEditorFromIndex(index, parent);

    if (auto* customEditor = dynamic_cast<CustomEditor*>(result)) {
        new TabFromFocusProxy(customEditor);
        connect(customEditor, &CustomEditor::dataChanged, this,
                &FitParameterDelegate::onCustomEditorDataChanged);
    }

    if (!result) // falling back to default behaviour
        result = QStyledItemDelegate::createEditor(parent, option, index);

    return result;
}

//! Propagates changed data from the editor to the model.

void FitParameterDelegate::setModelData(QWidget* editor, QAbstractItemModel* model,
                                        const QModelIndex& index) const
{
    if (!index.isValid())
        return;

    if (auto* customEditor = dynamic_cast<CustomEditor*>(editor))
        model->setData(index, customEditor->editorData());
    else
        QStyledItemDelegate::setModelData(editor, model, index);
}

//! Propagates the data change from the model to the editor (if it is still opened).

void FitParameterDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
{
    if (!index.isValid())
        return;

    if (auto* customEditor = dynamic_cast<CustomEditor*>(editor))
        customEditor->setData(index.data());
    else
        QStyledItemDelegate::setEditorData(editor, index);
}

//! Increases height of the row by 20% wrt the default.

QSize FitParameterDelegate::sizeHint(const QStyleOptionViewItem& option,
                                     const QModelIndex& index) const
{
    QSize result = QStyledItemDelegate::sizeHint(option, index);
    result.setHeight(static_cast<int>(result.height() * 1.2));
    return result;
}

//! Makes an editor occupying whole available space in a cell. If cell contains an icon
//! as a decoration (i.e. icon of material property), it will be hidden as soon as editor
//! up and running.

void FitParameterDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option,
                                                const QModelIndex& index) const
{
    QStyledItemDelegate::updateEditorGeometry(editor, option, index);
    editor->setGeometry(option.rect);
}


QWidget* FitParameterDelegate::createEditorFromIndex(const QModelIndex& index,
                                                     QWidget* parent) const
{
    if (index.internalPointer()) {
        auto* item = static_cast<QObject*>(index.internalPointer());
        return ::createEditor(item, parent);
    }
    return nullptr;
}

//! Notifies everyone that the editor has completed editing the data.

void FitParameterDelegate::onCustomEditorDataChanged(const QVariant&)
{
    auto* editor = qobject_cast<CustomEditor*>(sender());
    ASSERT(editor);
    emit commitData(editor);
}

//! Paints custom text in a a place corresponding given index.

void FitParameterDelegate::paintCustomLabel(QPainter* painter, const QStyleOptionViewItem& option,
                                            const QModelIndex& index, const QString& text) const
{
    QStyleOptionViewItem opt = option;
    initStyleOption(&opt, index); // calling original method to take into accounts colors etc
    opt.text = displayText(text, option.locale); // by overriding text with ours
    const QWidget* widget = opt.widget;
    QStyle* style = widget ? widget->style() : QApplication::style();
    style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget);
}
