//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/coregui/Views/ImportDataWidgets/ImportDataUtils.cpp
//! @brief     Implements ImportDataUtils namespace
//!
//! @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/ImportDataUtils.h"
#include "Base/Axis/IAxis.h"
#include "Base/Axis/PointwiseAxis.h"
#include "Device/Histo/IntensityDataIOFactory.h"
#include "Device/InputOutput/DataFormatUtils.h"
#include "GUI/coregui/DataLoaders/AbstractDataLoader1D.h"
#include "GUI/coregui/DataLoaders/DataLoaders1D.h"
#include "GUI/coregui/DataLoaders/QREDataLoader.h"
#include "GUI/coregui/Models/AxesItems.h"
#include "GUI/coregui/Models/InstrumentItems.h"
#include "GUI/coregui/Models/IntensityDataItem.h"
#include "GUI/coregui/Models/RealDataItem.h"
#include "GUI/coregui/mainwindow/AppSvc.h"
#include "GUI/coregui/mainwindow/projectmanager.h"
#include "GUI/coregui/utils/GUIHelpers.h"
#include <QApplication>
#include <QFileDialog>
#include <QFileInfo>
#include <QMessageBox>

namespace {
const QString filter_string_ba = "Intensity File (*.int *.gz *.tif *.tiff *.txt *.csv);;"
                                 "Other (*.*)";
const QString filter_string_ascii = "Intensity File (*.int *.int.gz *.txt *.csv *.dat *.ascii);;"
                                    "Ascii column-wise data (*.*)";

int rank(const RealDataItem& item)
{
    return static_cast<int>(item.shape().size());
}

int rank(const InstrumentItem& item)
{
    return static_cast<int>(item.shape().size());
}

} // namespace

std::unique_ptr<OutputData<double>> ImportDataUtils::ImportKnownData(const QString& fileName)
{
    // Try to use the canonical tools for importing data
    std::unique_ptr<OutputData<double>> result;
    try {
        std::unique_ptr<OutputData<double>> data(
            IntensityDataIOFactory::readOutputData(fileName.toStdString()));
        result = CreateSimplifiedOutputData(*data);
    } catch (std::exception& ex) {
        QString message = QString("Error while trying to read file\n\n'%1'\n\n%2")
                              .arg(fileName)
                              .arg(QString::fromStdString(std::string(ex.what())));
        QMessageBox::warning(nullptr, "IO Problem", message);
    }
    return result;
}

std::unique_ptr<OutputData<double>>
ImportDataUtils::ImportReflectometryData(const QString& fileName)
{
    std::unique_ptr<OutputData<double>> result;
    try {
        std::unique_ptr<OutputData<double>> data(
            IntensityDataIOFactory::readReflectometryData(fileName.toStdString()));
        result.swap(data);
    } catch (std::exception& ex) {
        QString message = QString("Error while trying to read file\n\n'%1'\n\n%2")
                              .arg(fileName)
                              .arg(QString::fromStdString(std::string(ex.what())));
        QMessageBox::warning(nullptr, "IO Problem", message);
    }
    return result;
}

std::unique_ptr<OutputData<double>> ImportDataUtils::Import2dData(const QString& fileName)
{
    return ImportKnownData(fileName);
}

QString ImportDataUtils::Import1dData(RealDataItem* realDataItem,
                                      const AbstractDataLoader* selectedLoader)
{
    const QString fileName = realDataItem->nativeFileName();
    const std::string fileNameStdString = fileName.toStdString();

    if (selectedLoader == nullptr) {
        if (DataFormatUtils::isCompressed(fileNameStdString)
            || DataFormatUtils::isIntFile(fileNameStdString)
            || DataFormatUtils::isTiffFile(fileNameStdString)) {
            try {
                ImportDataInfo data(ImportKnownData(fileName), Axes::Units::QSPACE);
                if (data) {
                    realDataItem->setImportData(std::move(data));
                    return QString();
                }
            } catch (std::exception& ex) {
                // If it is not tiff but e.g. dat.gz, it could be tried with CSV import
                const bool tryWithLoaders =
                    (DataFormatUtils::isIntFile(fileNameStdString)
                     && !DataFormatUtils::isCompressed(
                         fileNameStdString)); // #baimport support compressed

                if (!tryWithLoaders)
                    // import is not possible
                    return QString::fromLatin1(ex.what());
            }
        }
    }

    // -- try with selected CSV loader. If none selected, try with QRE loader

    QFile file(fileName);
    const bool fileCouldBeOpened = file.open(QFile::ReadOnly | QIODevice::Text);

    if (!fileCouldBeOpened)
        return "File could not be opened.";

    QByteArray fileContent = file.readAll();
    file.close();

    if (fileContent.isEmpty())
        return "The imported file is empty.";

    if (DataFormatUtils::isCompressed(fileNameStdString)) {
        // #baimport implement decompress
    }

    AbstractDataLoader* loader = nullptr;
    if (selectedLoader == nullptr)
        loader = new QREDataLoader();
    else
        loader = selectedLoader->clone();
    loader->initWithDefaultImportSettings();
    loader->setRealDataItem(realDataItem);
    realDataItem->setDataLoader(loader);
    QApplication::setOverrideCursor(Qt::WaitCursor);
    loader->setFileContents(fileContent);
    loader->guessSettings();
    loader->processContents();
    QApplication::restoreOverrideCursor();

    return QString();
}

bool ImportDataUtils::Compatible(const InstrumentItem& instrumentItem,
                                 const RealDataItem& realDataItem)
{
    return rank(instrumentItem) == rank(realDataItem);
}

std::unique_ptr<OutputData<double>>
ImportDataUtils::CreateSimplifiedOutputData(const OutputData<double>& data)
{
    const size_t data_rank = data.rank();
    if (data_rank > 2 || data_rank < 1)
        throw std::runtime_error("Error in ImportDataUtils::CreateSimplifiedOutputData: passed "
                                 "array is neither 1D nor 2D");

    std::unique_ptr<OutputData<double>> result(new OutputData<double>);
    for (size_t i = 0; i < data_rank; ++i) {
        const IAxis& axis = data.axis(i);
        const size_t axis_size = axis.size();
        const double min = 0.0;
        const double max = axis_size;
        result->addAxis(FixedBinAxis(axis.getName(), axis_size, min, max));
    }
    result->setRawDataVector(data.getRawDataVector());

    return result;
}

QString ImportDataUtils::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(std::move(message_string));
}
