//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Device/IO/IOFactory.cpp
//! @brief     Implements data import/export functions.
//!
//! @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 "Device/IO/IOFactory.h"
#include "Base/Util/Assert.h"
#include "Device/Data/Datafield.h"
#include "Device/IO/DiffUtil.h"
#include "Device/IO/ImportSettings.h"
#include "Device/IO/ReadReflectometry.h"
#include "Device/IO/ReadWrite2DTable.h"
#include "Device/IO/ReadWriteINT.h"
#include "Device/IO/ReadWriteNicos.h"
#include "Device/IO/ReadWriteTiff.h"
#include "Device/IO/ZipUtil.h"
#include <sstream>
#include <stdexcept>

IO::Filetype1D IO::filename2type1D(const std::string& fname)
{
    const std::string ext = ZipUtil::uncompressedExtension(fname);

    if (ext == ".int")
        return bornagain1D;
    if (ext == ".mft")
        return mft;
    return csv1D;
}

IO::Filetype2D IO::filename2type2D(const std::string& fname)
{
    const std::string ext = ZipUtil::uncompressedExtension(fname);

    if (ext == ".int")
        return bornagain2D;
    if (ext == ".001")
        return nicos2D;
    if (ext == ".tif" || ext == ".tiff") // as in function isNativeBinary
        return tiff;
    return csv2D;
}

Datafield IO::readData1D(const std::string& fname, Filetype1D ftype,
                         const ImportSettings1D* import_settings)
{
    ASSERT(ftype != unknown1D);

    std::stringstream s = ZipUtil::file2stream(fname);

    if (ftype == csv1D) {
        if (!import_settings)
            throw std::runtime_error("CSV/ASCII data require ImportSettings1D");
        return RW::readReflectometryTable(s, *import_settings);
    }

    if (import_settings)
        throw std::runtime_error("ImportSettings1D must only be given for CSV data");

    if (ftype == mft)
        return RW::readMotofit(s);

    if (ftype == bornagain1D)
        return RW::readBAInt(s);

    ASSERT_NEVER;
}

Datafield IO::readData1D(const std::string& fname)
{
    return readData1D(fname, filename2type1D(fname));
}

Datafield IO::readData2D(const std::string& fname, Filetype2D ftype,
                         const ImportSettings2D* import_settings)
{
    ASSERT(ftype != unknown2D);

    std::stringstream s = ZipUtil::file2stream(fname);

    if (ftype == csv2D) {
        if (!import_settings)
            throw std::runtime_error("No import settings given for 'csv' data");
        return RW::read2DTable(s, import_settings);
    }

    if (ftype == bornagain2D)
        return RW::readBAInt(s);

    if (ftype == nicos2D)
        return RW::readNicos(s);

#ifdef BA_TIFF_SUPPORT
    if (ftype == tiff)
        return RW::readTiff(s);
#endif

    ASSERT_NEVER;
}

Datafield IO::readData2D(const std::string& fname)
{
    Filetype2D ftype = filename2type2D(fname);
    if (ftype == csv2D) {
        // Default settings for auto-detected CSV: no axes, no swap
        ImportSettings2D defaultSettings("x", "y");
        return readData2D(fname, ftype, &defaultSettings);
    }
    return readData2D(fname, ftype);
}

void IO::writeDatafield(const Datafield& data, const std::string& fname)
{
    const std::string ext = ZipUtil::uncompressedExtension(fname);
    try {
        std::stringstream s;

        if (ext == ".int")
            RW::writeBAInt(data, s);

#ifdef BA_TIFF_SUPPORT
        else if (ext == ".tiff" || ext == ".tif")
            RW::writeTiff(data, s);
#endif
        else
            RW::write2DTable(data, s);

        ZipUtil::stream2file(fname, s);

    } catch (const std::exception& ex) {
        throw std::runtime_error("Failed writing to " + fname + ": " + ex.what());
    }
}

bool IO::Test::dataMatchesFile(const Datafield& data, const std::string& refFileName, double tol)
{
    std::unique_ptr<Datafield> refDat;
    try {
        refDat =
            std::make_unique<Datafield>(IO::readData2D(refFileName, IO::Filetype2D::bornagain2D));
    } catch (const std::exception& ex) {
        std::cerr << "File comparison: Could not read reference data from file " << refFileName
                  << std::endl;
        std::cerr << "Error message: " << ex.what() << std::endl;
        return false;
    }
    return DiffUtil::checkRelativeDifference(data, *refDat, tol);
}
