//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Mask/MaskEditorPropertyPanel.cpp
//! @brief     Implements class MaskEditorPropertyPanel
//!
//! @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/Mask/MaskEditorPropertyPanel.h"
#include "GUI/Model/Data/IntensityDataItem.h"
#include "GUI/Model/Data/MaskItems.h"
#include "GUI/Model/Project/ProjectDocument.h"
#include "GUI/View/Common/IntensityDataPropertyWidget.h"
#include "GUI/View/Numeric/DoubleSpinBox.h"
#include "GUI/View/Numeric/NumberUtil.h"
#include "GUI/View/Tool/GroupBoxCollapser.h"
#include "GUI/View/Tool/LayoutUtil.h"
#include <QCheckBox>
#include <QGroupBox>
#include <QLineEdit>

using std::function;

MaskEditorPropertyPanel::MaskEditorPropertyPanel(QWidget* parent)
    : DataAccessWidget(parent)
    , m_listView(new QListView)
    , m_propertyPanel(new IntensityDataPropertyWidget)
    , m_maskContainerModel(nullptr)
    , m_intensityDataItem(nullptr)
    , m_currentMaskItem(nullptr)
    , m_inhibitSelectionChange(false)
{
    setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding);
    setObjectName(QLatin1String("MaskEditorToolPanel"));

    m_listView->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(m_listView, &QListView::customContextMenuRequested, this,
            &MaskEditorPropertyPanel::onCustomContextMenuRequested);

    auto* mainLayout = new QVBoxLayout;
    mainLayout->setContentsMargins(0, 0, 0, 0);
    mainLayout->setSpacing(8);

    // -- plot properties
    auto* plotPropertiesGroup = new QGroupBox("Plot properties", this);
    auto* plotPropertiesLayout = new QVBoxLayout(plotPropertiesGroup);
    plotPropertiesLayout->setContentsMargins(0, 0, 0, 0);
    m_propertyPanel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);
    plotPropertiesLayout->addWidget(m_propertyPanel);
    GroupBoxCollapser::installIntoGroupBox(plotPropertiesGroup);

    // -- mask stack
    auto* maskStackGroup = new QGroupBox("Mask stack", this);
    auto* maskStackLayout = new QVBoxLayout(maskStackGroup);
    maskStackLayout->setContentsMargins(0, 0, 0, 0);
    m_listView->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);
    maskStackLayout->addWidget(m_listView);
    GroupBoxCollapser::installIntoGroupBox(maskStackGroup);

    // -- mask properties
    auto* maskPropertiesGroup = new QGroupBox("Mask properties", this);
    maskPropertiesGroup->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);
    m_maskPropertiesLayout = new QFormLayout(maskPropertiesGroup);
    m_maskPropertiesLayout->setContentsMargins(8, 8, 8, 8);
    GroupBoxCollapser::installIntoGroupBox(maskPropertiesGroup);

    mainLayout->addWidget(plotPropertiesGroup);
    mainLayout->addWidget(maskStackGroup);
    mainLayout->addWidget(maskPropertiesGroup);
    mainLayout->addSpacerItem(
        new QSpacerItem(0, 10, QSizePolicy::Fixed, QSizePolicy::MinimumExpanding));

    setLayout(mainLayout);
}

void MaskEditorPropertyPanel::setJobOrRealItem(QObject* job_or_real_item)
{
    DataAccessWidget::setJobOrRealItem(job_or_real_item);
    m_propertyPanel->setJobOrRealItem(job_or_real_item);
}

QSize MaskEditorPropertyPanel::sizeHint() const
{
    return QSize(128, 128);
}

QSize MaskEditorPropertyPanel::minimumSizeHint() const
{
    return QSize(128, 128);
}

void MaskEditorPropertyPanel::setMaskContext(MaskContainerModel* containerModel)
{
    m_maskContainerModel = containerModel;

    m_listView->setModel(m_maskContainerModel);
    m_listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
    m_listView->setRootIndex(m_maskContainerModel->maskContainer->rootIndex);

    connect(m_listView->selectionModel(), &QItemSelectionModel::selectionChanged, this,
            &MaskEditorPropertyPanel::onSelectionChanged, Qt::UniqueConnection);
}

void MaskEditorPropertyPanel::resetContext()
{
    m_maskContainerModel = nullptr;
    m_intensityDataItem = nullptr;
    m_listView->setModel(nullptr);
    setCurrentMaskItem(nullptr);
}

QItemSelectionModel* MaskEditorPropertyPanel::selectionModel()
{
    ASSERT(m_listView);
    return m_listView->selectionModel();
}

//! Show/Hide panel. When panel is hidden, all property editors are disabled.
void MaskEditorPropertyPanel::setPanelHidden(bool hidden)
{
    setHidden(hidden);

    if (hidden) {
        setCurrentMaskItem(nullptr);
    } else {
        QModelIndexList indexes = selectionModel()->selectedIndexes();
        if (!indexes.empty())
            setCurrentMaskItem(maskItemForIndex(indexes.front()));
    }
}

void MaskEditorPropertyPanel::onSelectionChanged(const QItemSelection& selected,
                                                 const QItemSelection&)
{
    if (m_inhibitSelectionChange)
        return;

    if (!selected.empty())
        setCurrentMaskItem(maskItemForIndex(selected.indexes().front()));
    else
        setCurrentMaskItem(nullptr);
}

void MaskEditorPropertyPanel::onCustomContextMenuRequested(const QPoint& point)
{
    emit itemContextMenuRequest(m_listView->mapToGlobal(point));
}

void MaskEditorPropertyPanel::setCurrentMaskItem(MaskItem* maskItem)
{
    if (m_currentMaskItem)
        disconnect(m_currentMaskItem, nullptr, this, nullptr);

    GUI::Util::Layout::clearLayout(m_maskPropertiesLayout);

    m_currentMaskItem = maskItem;

    createMaskEditorUI();
}

void MaskEditorPropertyPanel::createMaskEditorUI()
{
    if (!m_currentMaskItem)
        return;

    auto* maskItem = m_currentMaskItem; // shorthand
    // -- mask value (only if not RoI)
    if (!dynamic_cast<RegionOfInterestItem*>(maskItem)) {
        const auto maskValueGetter = [maskItem] { return maskItem->maskValue(); };
        const auto maskValueSetter = [maskItem](bool b) { maskItem->setMaskValue(b); };
        addMaskCheckBox("Mask value", maskValueGetter, maskValueSetter);
    }
    // -- mask visibility
    const auto visibilityValueGetter = [maskItem] { return maskItem->isVisible(); };
    const auto visibilityValueSetter = [this, maskItem](bool b) {
        m_inhibitSelectionChange = true;
        maskItem->setIsVisible(b);
        maskItem->setWasVisible(b);
        m_inhibitSelectionChange = false;
    };
    addMaskCheckBox("Show", visibilityValueGetter, visibilityValueSetter);

    // -- name (only if not RoI)
    if (!dynamic_cast<RegionOfInterestItem*>(maskItem)) {
        auto* edit = new QLineEdit(maskItem->maskName(), m_maskPropertiesLayout->parentWidget());
        connect(edit, &QLineEdit::textEdited,
                [maskItem](const QString& t) { maskItem->setMaskName(t); });
        connect(edit, &QLineEdit::editingFinished, [] { gProjectDocument.value()->setModified(); });
        m_maskPropertiesLayout->addRow("Name:", edit);
    }

    if (auto* c = dynamic_cast<RectangleItem*>(maskItem)) {
        addMaskSpinBox(
            "xlow", [c] { return c->xLow(); }, [c](double v) { c->setXLow(v); },
            RealLimits::limitless());
        addMaskSpinBox(
            "ylow", [c] { return c->yLow(); }, [c](double v) { c->setYLow(v); },
            RealLimits::limitless());
        addMaskSpinBox(
            "xup", [c] { return c->xUp(); }, [c](double v) { c->setXUp(v); },
            RealLimits::limitless());
        addMaskSpinBox(
            "yup", [c] { return c->yUp(); }, [c](double v) { c->setYUp(v); },
            RealLimits::limitless());
    } else if (auto* c = dynamic_cast<EllipseItem*>(maskItem)) {
        addMaskSpinBox(
            "X center", [c] { return c->xCenter(); }, [c](double v) { c->setXCenter(v); },
            RealLimits::limitless());
        addMaskSpinBox(
            "Y center", [c] { return c->yCenter(); }, [c](double v) { c->setYCenter(v); },
            RealLimits::limitless());
        addMaskSpinBox(
            "X radius", [c] { return c->xRadius(); }, [c](double v) { c->setXRadius(v); },
            RealLimits::nonnegative());
        addMaskSpinBox(
            "Y radius", [c] { return c->yRadius(); }, [c](double v) { c->setYRadius(v); },
            RealLimits::nonnegative());
        addMaskSpinBox(
            "Angle", [c] { return c->angle(); }, [c](double v) { c->setAngle(v); },
            RealLimits::limitless());
    } else if (auto* c = dynamic_cast<VerticalLineItem*>(maskItem))
        addMaskSpinBox(
            "X position", [c] { return c->posX(); }, [c](double v) { c->setPosX(v); },
            RealLimits::limitless());
    else if (auto* c = dynamic_cast<HorizontalLineItem*>(maskItem))
        addMaskSpinBox(
            "Y position", [c] { return c->posY(); }, [c](double v) { c->setPosY(v); },
            RealLimits::limitless());
}

void MaskEditorPropertyPanel::addMaskSpinBox(const QString& label, function<double()> getter,
                                             function<void(double)> setter,
                                             const RealLimits& limits)
{
    QDoubleSpinBox* spinBox = new QDoubleSpinBox;
    GUI::View::NumberUtil::configSpinbox(spinBox, 3, limits);
    spinBox->setValue(getter());

    connect(spinBox, &QDoubleSpinBox::valueChanged, this, [setter](double newVal) {
        setter(newVal);
        gProjectDocument.value()->setModified();
    });
    connect(m_currentMaskItem, &MaskItem::maskGeometryChanged, spinBox, [=] {
        QSignalBlocker b(spinBox);
        spinBox->setValue(getter());
    });

    m_maskPropertiesLayout->addRow(label + ":", spinBox);
}

void MaskEditorPropertyPanel::addMaskCheckBox(const QString& title, function<bool()> getter,
                                              function<void(bool)> setter)
{
    auto* checkBox = new QCheckBox(title, m_maskPropertiesLayout->parentWidget());
    checkBox->setChecked(getter());
    connect(checkBox, &QCheckBox::stateChanged, this, [setter, checkBox]() {
        setter(checkBox->isChecked());
        gProjectDocument.value()->setModified();
    });

    connect(m_currentMaskItem, &MaskItem::maskVisibilityChanged, this, [checkBox, getter]() {
        QSignalBlocker b(checkBox);
        checkBox->setChecked(getter());
    });

    m_maskPropertiesLayout->addRow(checkBox);
}

MaskItem* MaskEditorPropertyPanel::maskItemForIndex(const QModelIndex& index)
{
    return m_maskContainerModel->itemForIndex(index);
}
