//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Project/LinkInstrumentManager.cpp
//! @brief     Implements class LinkInstrumentManager
//!
//! @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/Model/Project/LinkInstrumentManager.h"
#include "GUI/Model/Device/InstrumentItems.h"
#include "GUI/Model/Device/RealItem.h"
#include "GUI/Model/Model/RealModel.h"
#include "GUI/Model/Project/ProjectDocument.h"
#include <QMessageBox>
#include <QPushButton>

namespace {

bool QuestionOnInstrumentReshaping(const QString& message)
{
    QMessageBox msgBox;
    msgBox.setText("Instrument description conflicts with the experimental data.");

    QString informative;
    informative.append(message);
    informative.append("\n\nDo you want to adjust the instrument to the experimental data?\n\n");
    msgBox.setInformativeText(informative);

    QPushButton* modifyInstrumentButton =
        msgBox.addButton("Yes, please modify instrument", QMessageBox::YesRole);
    msgBox.addButton("No, leave as it is", QMessageBox::NoRole);

    msgBox.exec();

    return msgBox.clickedButton() == modifyInstrumentButton;
}


QString printShapeMessage(const std::vector<int>& instrument_shape,
                          const std::vector<int>& data_shape)
{
    auto to_str = [](const std::vector<int>& shape) {
        std::string result;
        for (size_t i = 0, size = shape.size(); i < size; ++i) {
            result += std::to_string(shape[i]);
            if (i + 1 != size)
                result += ", ";
        }
        return result;
    };

    std::string message_string = "instrument [";
    message_string += to_str(instrument_shape);
    message_string += "], data [";
    message_string += to_str(data_shape);
    message_string += "]";
    return QString::fromStdString(message_string);
}

} // namespace

LinkInstrumentManager::LinkInstrumentManager(ProjectDocument* document)
    : m_document(document)
{
    connect(m_document->multiNotifier(), &MultiInstrumentNotifier::instrumentAddedOrRemoved, this,
            &LinkInstrumentManager::onInstrumentAddedOrRemoved);

    connect(m_document->multiNotifier(), &MultiInstrumentNotifier::instrumentChanged, this,
            &LinkInstrumentManager::onInstrumentChanged);
}

bool LinkInstrumentManager::canLinkDataToInstrument(const RealItem* realItem,
                                                    const QString& identifier, QWidget* parent)
{
    auto* instrumentItem = m_document->instrumentModel()->findInstrumentItemById(identifier);

    // linking to null instrument is possible, it means unlinking from currently linked
    if (!instrumentItem)
        return true;

    if (instrumentItem->shape().size() != realItem->shape().size()) {
        if (parent)
            QMessageBox::warning(parent, "Cannot link to instrument",
                                 "Cannot link, data is incompatible with the instrument.");
        return false;
    }

    if (realItem->isSpecularData() && !realItem->hasNativeData()) {
        if (parent)
            QMessageBox::warning(parent, "Cannot link to instrument",
                                 "Cannot link, data is empty.");
        return false;
    }

    if (instrumentItem->alignedWith(realItem))
        return true;

    QString message =
        realItem->holdsDimensionalData()
            ? "Experimental data carries information on the range/points of measurement."
            : printShapeMessage(instrumentItem->shape(), realItem->shape());
    if (!QuestionOnInstrumentReshaping(message))
        return false;

    m_document->multiNotifier()->updateInstrumentToRealDataItem(instrumentItem, realItem);
    return true;
}

void LinkInstrumentManager::onInstrumentChanged(const InstrumentItem* instrument)
{
    // Run through all RealItem and refresh linking to match possible change in detector
    // axes definition.
    for (auto* realItem : m_document->realModel()->realItems())
        if (realItem->instrumentId() == instrument->id()) {
            if (!instrument->alignedWith(realItem)) {
                realItem->unlinkFromInstrument();
                emit linkToInstrumentChanged(realItem);
            } else
                realItem->linkToInstrument(instrument); // link stays same, only data is updated
        }
}

void LinkInstrumentManager::onInstrumentAddedOrRemoved()
{
    // remove links in realItems (in case of a linked instrument was removed)
    for (auto* realItem : m_document->realModel()->realItems())
        if (!m_document->instrumentModel()->instrumentExists(realItem->instrumentId()))
            realItem->unlinkFromInstrument();
}
