//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/coregui/Views/ImportDataWidgets/RealDataSelectorWidget.cpp
//! @brief     Implements class RealDataSelectorWidget
//!
//! @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/coregui/Views/ImportDataWidgets/RealDataSelectorWidget.h"
#include "Device/Data/DataUtils.h"
#include "GUI/coregui/DataLoaders/DataLoaders1D.h"
#include "GUI/coregui/DataLoaders/QREDataLoader.h"
#include "GUI/coregui/Models/RealDataItem.h"
#include "GUI/coregui/Models/RealDataModel.h"
#include "GUI/coregui/Views/ImportDataWidgets/ImportDataUtils.h"
#include "GUI/coregui/Views/ImportDataWidgets/RealDataPropertiesWidget.h"
#include "GUI/coregui/Views/ImportDataWidgets/RealDataTreeModel.h"
#include "GUI/coregui/mainwindow/AppSvc.h"
#include "GUI/coregui/mainwindow/StyledToolBar.h"
#include "GUI/coregui/mainwindow/mainwindow.h"
#include "GUI/coregui/mainwindow/projectmanager.h"
#include "GUI/coregui/utils/GUIHelpers.h"
#include <QApplication>
#include <QFileDialog>
#include <QItemSelectionModel>
#include <QLineEdit>
#include <QListView>
#include <QMenu>
#include <QSplitter>
#include <QTreeView>
#include <QVBoxLayout>

RealDataSelectorWidget::RealDataSelectorWidget(QWidget* parent)
    : QWidget(parent)
    , m_itemTree(new QTreeView)
    , m_itemTreeModel(new RealDataTreeModel)
    , m_propertiesWidget(new RealDataPropertiesWidget)
    , m_import2dDataAction(new QAction(this))
    , m_import1dDataAction(new QAction(this))
    , m_renameDataAction(new QAction(this))
    , m_removeDataAction(new QAction(this))
    , m_rotateDataAction(new QAction(this))
{
    setMinimumSize(128, 600);
    setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
    setWindowTitle("RealDataSelectorWidget");

    // #baimport ++ necessary to tree somehow?
    /*
    QSize RealDataItemSelectorWidget::sizeHint() const
    {
        return QSize(Constants::ITEM_SELECTOR_WIDGET_WIDTH, Constants::ITEM_SELECTOR_WIDGET_HEIGHT);
    }

    QSize RealDataItemSelectorWidget::minimumSizeHint() const { return QSize(25, 25); }
    */

    m_import2dDataAction->setText("Import 2D data");
    m_import2dDataAction->setIcon(QIcon(":/images/import.svg"));
    m_import2dDataAction->setIconText("2D");
    m_import2dDataAction->setToolTip("Import 2D data");
    connect(m_import2dDataAction, &QAction::triggered, [this]() { importData(2); });

    m_import1dDataAction->setText("Import 1D data");
    m_import1dDataAction->setIcon(QIcon(":/images/import.svg"));
    m_import1dDataAction->setIconText("1D");
    m_import1dDataAction->setToolTip("Import 1D data");
    connect(m_import1dDataAction, &QAction::triggered, [this]() { importData(1); });

    m_renameDataAction->setText("Rename");
    m_renameDataAction->setIcon(QIcon()); // #baTODO: Icon needed?
    m_renameDataAction->setIconText("Rename");
    m_renameDataAction->setToolTip("Rename data");
    connect(m_renameDataAction, &QAction::triggered, this,
            &RealDataSelectorWidget::renameCurrentItem);

    m_removeDataAction->setText("Remove");
    m_removeDataAction->setIcon(QIcon(":/images/delete.svg"));
    m_removeDataAction->setIconText("Remove");
    m_removeDataAction->setToolTip("Remove selected data");
    connect(m_removeDataAction, &QAction::triggered, this,
            &RealDataSelectorWidget::removeCurrentItem);

    m_rotateDataAction->setText("Rotate");
    m_rotateDataAction->setIcon(QIcon(":/images/rotate-left.svg"));
    m_rotateDataAction->setIconText("Rotate");
    m_rotateDataAction->setToolTip("Rotate intensity data by 90 deg counterclockwise");
    connect(m_rotateDataAction, &QAction::triggered, this,
            &RealDataSelectorWidget::rotateCurrentItem);

    QToolBar* toolBar = new StyledToolBar(this);
    toolBar->setMinimumSize(toolBar->minimumHeight(), toolBar->minimumHeight());
    toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
    toolBar->addAction(m_import2dDataAction);
    toolBar->addAction(m_import1dDataAction);
    toolBar->addAction(m_removeDataAction);
    toolBar->addAction(m_rotateDataAction);

    // #baimport ++ following line necessary for tree as well? relevant for RealItems at all?
    // m_itemTree->setAttribute(Qt::WA_MacShowFocusRect, false);
    m_itemTree->setItemsExpandable(false);
    m_itemTree->setRootIsDecorated(false);
    m_itemTree->setHeaderHidden(true);
    m_itemTree->setContextMenuPolicy(Qt::CustomContextMenu);
    m_itemTree->setModel(m_itemTreeModel);

    auto splitter = new QSplitter;
    splitter->setOrientation(Qt::Vertical);
    splitter->addWidget(m_itemTree);
    splitter->addWidget(m_propertiesWidget);
    splitter->setChildrenCollapsible(true);

    auto mainLayout = new QVBoxLayout;
    mainLayout->setMargin(0);
    mainLayout->setSpacing(0);
    mainLayout->setContentsMargins(0, 0, 0, 0);
    mainLayout->addWidget(toolBar);
    mainLayout->addWidget(splitter);
    setLayout(mainLayout);

    connect(m_itemTree, &QTreeView::customContextMenuRequested, this,
            &RealDataSelectorWidget::onContextMenuRequest);

    updateActionEnabling();
}

QSize RealDataSelectorWidget::sizeHint() const
{
    return QSize(200, 400);
}

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

void RealDataSelectorWidget::setModel(RealDataModel* realDataModel)
{
    m_itemTreeModel->setRealDataModel(realDataModel);

    connect(m_itemTreeModel, &QAbstractItemModel::modelReset,
            [this]() { m_itemTree->expandAll(); });

    connect(m_itemTreeModel, &QAbstractItemModel::rowsInserted,
            [this]() { m_itemTree->expandAll(); });

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

RealDataItem* RealDataSelectorWidget::currentItem()
{
    return m_itemTreeModel->itemForIndex(currentIndex());
}

void RealDataSelectorWidget::setCurrentItem(RealDataItem* item)
{
    m_itemTree->selectionModel()->clearSelection();
    QModelIndex index = m_itemTreeModel->indexForItem(item);
    if (index.isValid())
        m_itemTree->selectionModel()->setCurrentIndex(index, QItemSelectionModel::SelectCurrent);
}

QModelIndex RealDataSelectorWidget::currentIndex()
{
    return m_itemTree->selectionModel()->currentIndex();
}

void RealDataSelectorWidget::onSelectionChanged()
{
    updateActionEnabling();
    m_propertiesWidget->setItem(currentItem());
    emit selectionChanged(currentItem());
}

void RealDataSelectorWidget::onContextMenuRequest(const QPoint& point)
{
    auto realDataItemAtPoint = m_itemTreeModel->itemForIndex(m_itemTree->indexAt(point));
    updateActionEnabling(realDataItemAtPoint);

    QMenu menu;
    menu.setToolTipsVisible(true);

    if (realDataItemAtPoint != nullptr) {
        menu.addAction(m_renameDataAction);
        menu.addAction(m_removeDataAction);
        if (realDataItemAtPoint->isIntensityData())
            menu.addAction(m_rotateDataAction);
        menu.addSeparator();
    }

    menu.addAction(m_import2dDataAction);
    menu.addAction(m_import1dDataAction);
    menu.exec(m_itemTree->mapToGlobal(point));
}

void RealDataSelectorWidget::importData(int ndim)
{
    QMap<QString, AbstractDataLoader*> loaderOfFilter;
    QString filters;
    QString selectedFilter;
    if (ndim == 2) {
        filters = "Intensity File (*.int *.gz *.tif *.tiff *.txt *.csv);;"
                  "Other (*.*)";
        selectedFilter = AppSvc::projectManager()->recentlyUsedImportFilter2D();
    } else {
        for (auto loader : DataLoaders1D::instance().loaders()) {
            const QString filter =
                loader->name()
                + " (*.txt *.csv *.dat)"; // #baimport - take file filters from loader
            loaderOfFilter[filter] = loader;

            if (!filters.isEmpty())
                filters += ";;";
            filters += filter;
        }
        filters += ";;Other (*.*)";

        selectedFilter = AppSvc::projectManager()->recentlyUsedImportFilter1D();
    }
    QString dirname = AppSvc::projectManager()->userImportDir();
    QStringList fileNames = QFileDialog::getOpenFileNames(Q_NULLPTR, "Open Intensity Files",
                                                          dirname, filters, &selectedFilter);

    if (fileNames.isEmpty())
        return;

    QString newImportDir = GUIHelpers::fileDir(fileNames[0]);
    if (newImportDir != dirname)
        AppSvc::projectManager()->setImportDir(newImportDir);

    if (ndim == 1)
        AppSvc::projectManager()->setRecentlyUsedImportFilter1D(selectedFilter);
    else
        AppSvc::projectManager()->setRecentlyUsedImportFilter2D(selectedFilter);

    for (const auto& fileName : fileNames) {
        QFileInfo info(fileName);
        auto baseNameOfLoadedFile = info.baseName();

        if (ndim == 2) {
            std::unique_ptr<OutputData<double>> data = ImportDataUtils::Import2dData(fileName);
            if (data) {
                auto realDataItem = m_itemTreeModel->insertIntensityDataItem();
                realDataItem->setName(baseNameOfLoadedFile);
                realDataItem->setOutputData(data.release());
                setCurrentItem(realDataItem);
            }
        } else if (ndim == 1) {
            auto realDataItem = m_itemTreeModel->insertSpecularDataItem();
            realDataItem->setName(baseNameOfLoadedFile);
            realDataItem->setNativeFileName(fileName);

            const AbstractDataLoader* selectedLoader =
                loaderOfFilter.value(selectedFilter, nullptr);
            const QString errorText = ImportDataUtils::Import1dData(realDataItem, selectedLoader);
            if (errorText.isEmpty())
                setCurrentItem(realDataItem);
            else {
                m_itemTreeModel->removeItem(realDataItem);

                GUIHelpers::warning("File import",
                                    QString("The file '%1' could not be imported.")
                                        .arg(QDir::toNativeSeparators(fileName)),
                                    errorText);
            }
        }
    }
}

void RealDataSelectorWidget::renameCurrentItem()
{
    if (currentIndex().isValid())
        m_itemTree->edit(currentIndex());
}

void RealDataSelectorWidget::removeCurrentItem()
{
    m_itemTreeModel->removeItem(currentItem());
}

void RealDataSelectorWidget::rotateCurrentItem()
{
    if (!currentItem() || !currentItem()->isIntensityData())
        // should never happen because of action disabling => no
        // dialog necessary
        return;

    if (currentItem()->rotationAffectsSetup()) {
        const QString title("Rotate data");
        const QString message("Rotation will break the link between the data and the instrument. "
                              "Detector masks or projections, if they exist, will be removed.");
        if (!GUIHelpers::question(MainWindow::instance(), title, message,
                                  "Do you wish to rotate the data?", "Yes, please rotate",
                                  "No, cancel data rotation"))
            return;
    }

    QApplication::setOverrideCursor(Qt::WaitCursor);
    currentItem()->rotateData();
    QApplication::restoreOverrideCursor();
}

void RealDataSelectorWidget::updateActionEnabling()
{
    updateActionEnabling(currentItem());
}

void RealDataSelectorWidget::updateActionEnabling(const RealDataItem* item)
{
    m_import2dDataAction->setEnabled(true);
    m_import1dDataAction->setEnabled(true);

    m_rotateDataAction->setEnabled(item != nullptr ? item->isIntensityData() : false);
    m_removeDataAction->setEnabled(item != nullptr);
    m_renameDataAction->setEnabled(item != nullptr);
}

void RealDataSelectorWidget::showEvent(QShowEvent*)
{
    // ensure a current item when widget is shown
    if (!currentItem())
        setCurrentItem(m_itemTreeModel->topMostItem());
}
