//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Loader/DataLoader.cpp
//! @brief     Implements functions in namespace RW.
//!
//! @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/Loader/DataLoader.h"
#include "Base/Util/Assert.h"
#include "Device/Data/Datafield.h"
#include "Device/IO/IOFactory.h"
#include "Device/IO/ZipUtil.h"
#include "GUI/Model/Data/DataItem.h"
#include "GUI/Model/File/DatafileItem.h"
#include "GUI/Model/Util/Path.h"
#include "GUI/View/Info/MessageBox.h"
#include "GUI/View/Loader/ImportDialogs.h"
#include "GUI/View/Widget/AppConfig.h"
#include <QFileDialog>
#include <QFileInfo>

namespace {

// The following two functions are templated on T in {IO::Filetype1D, IO::Filetype2D}.

template <class T>
QString join_filterkeys(std::vector<std::pair<const QString, T>> _nomap, const QString& _separator)
{
    QString result;
    for (const auto& it : _nomap) {
        if (!result.isEmpty())
            result += _separator;
        result += it.first;
    }
    return result;
}

template <class T>
T filterkey2type(std::vector<std::pair<const QString, T>> _nomap, const QString& _key)
{
    for (const auto& it : _nomap)
        if (it.first == _key)
            return it.second;
    ASSERT_NEVER;
}

} // namespace


std::vector<DatafileItem*> RW::importData1D()
{
    static const std::vector<std::pair<const QString, IO::Filetype1D>> filters1D{
        {"Legacy table / interactive configuration (*.csv *.dat *.tab *.txt *.dat.gz)", IO::csv1D},
        {"Motofit (*.mft)", IO::mft},
        {"BornAgain (*.int *.int.gz)", IO::bornagain1D},
        {"all (*.*)", IO::unknown1D}};

    static const QString filters = ::join_filterkeys(filters1D, ";;");

    const QStringList fnames = QFileDialog::getOpenFileNames(
        Q_NULLPTR, "Open Intensity Files", gApp->data_import_dir, filters, &gApp->import_filter_1D,
        gApp->useNativeFileDialog() ? QFileDialog::Options() : QFileDialog::DontUseNativeDialog);
    if (fnames.isEmpty())
        return {};

    gApp->data_import_dir = GUI::Path::fileDir(fnames[0]); // all in list have same dir
    const IO::Filetype1D global_ftype = ::filterkey2type(filters1D, gApp->import_filter_1D);

    std::vector<DatafileItem*> result;
    for (const QString& fname : fnames) {
        try {
            const std::string ext = ZipUtil::uncompressedExtension(fname.toStdString());
            if (ext == ".001" || ext == ".tif" || ext == ".tiff")
                throw std::runtime_error("File \"" + ext + "\" seems to be 2D");

            const ImportSettings1D* settings = nullptr;

            IO::Filetype1D ftype = global_ftype;
            if (ftype == IO::unknown1D)
                ftype = IO::filename2type1D(fname.toStdString());
            if (ftype == IO::csv1D) {
                Import1dDialog dialog(nullptr, fname);
                const auto result = dialog.exec();
                if (result != QDialog::Accepted)
                    return {};
                settings = &Import1dDialog::Msettings;
            }

            Datafield df = IO::readData1D(fname.toStdString(), ftype, settings);
            if (df.rank() != 1)
                throw std::runtime_error("File does not contain a 1d data set");
            result.push_back(new DatafileItem(QFileInfo(fname).baseName(), df));
        } catch (std::exception& ex) {
            const QString message = QString("Cannot read file %1:\n\n%2")
                                        .arg(fname)
                                        .arg(QString::fromStdString(std::string(ex.what())));
            GUI::Message::warning(nullptr, "File import", message);
            return {};
        }
    }
    return result;
}

std::vector<DatafileItem*> RW::importData2D()
{
    static const std::vector<std::pair<const QString, IO::Filetype2D>> filters2D{
        {"CSV (*.csv *.dat *.tab *.txt *.dat.gz)", IO::csv2D},
        {"all (*.*)", IO::unknown2D},
        {"TIFF (*.tif *.tiff *.tif.gz)", IO::tiff},
        {"Nicos/SANSDRaw (*.001)", IO::nicos2D},
        {"BornAgain (*.int *.int.gz)", IO::bornagain2D}};

    static const QString filters = ::join_filterkeys(filters2D, ";;");

    const QStringList fnames = QFileDialog::getOpenFileNames(
        Q_NULLPTR, "Open Intensity Files", gApp->data_import_dir, filters, &gApp->import_filter_2D,
        gApp->useNativeFileDialog() ? QFileDialog::Options() : QFileDialog::DontUseNativeDialog);
    if (fnames.isEmpty())
        return {};

    gApp->data_import_dir = GUI::Path::fileDir(fnames[0]); // all in list have same dir
    const IO::Filetype2D global_ftype = ::filterkey2type(filters2D, gApp->import_filter_2D);

    std::vector<DatafileItem*> result;
    for (const QString& fname : fnames) {
        try {
            const std::string ext = ZipUtil::uncompressedExtension(fname.toStdString());
            if (ext == ".mft")
                throw std::runtime_error("File \"" + ext + "\" seems to be 1D");

            const ImportSettings2D* settings = nullptr;

            IO::Filetype2D ftype = global_ftype;
            if (ftype == IO::unknown2D)
                ftype = IO::filename2type2D(fname.toStdString());
            if (ftype == IO::csv2D) {
                Import2dDialog dialog(nullptr, fname);
                const auto result = dialog.exec();
                if (result != QDialog::Accepted)
                    return {};
                settings = &Import2dDialog::Msettings;
            }

            Datafield df = IO::readData2D(fname.toStdString(), ftype, settings);
            if (df.rank() != 2)
                throw std::runtime_error("File does not contain a 2d data set");
            result.push_back(new DatafileItem(QFileInfo(fname).baseName(), df));
        } catch (std::exception& ex) {
            const QString message = QString("Cannot read file %1\n\n%2")
                                        .arg(fname)
                                        .arg(QString::fromStdString(std::string(ex.what())));
            GUI::Message::warning(nullptr, "File import", message);
            return {};
        }
    }
    return result;
}
