//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Device/IO/ZipUtil.cpp
//! @brief     Implements functions in namespace ZipUtil.
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2023
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "Device/IO/ZipUtil.h"
#include "Base/Util/PathUtil.h"
#include "Base/Util/StringUtil.h"
#include <fstream>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-copy"
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/filter/bzip2.hpp>
#include <boost/iostreams/filter/gzip.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#pragma GCC diagnostic pop

namespace {

const std::string GzipExtension = ".gz";
const std::string BzipExtension = ".bz2";

bool isGZipped(const std::string& fname)
{
    return Base::Path::hasExtension(fname, GzipExtension);
}

bool isBZipped(const std::string& fname)
{
    return Base::Path::hasExtension(fname, BzipExtension);
}

bool isCompressed(const std::string& fname)
{
    return isGZipped(fname) || isBZipped(fname);
}

bool isNativeBinary(const std::string& fname)
{
    const std::string ext = ZipUtil::uncompressedExtension(fname);
    return ext == ".tif" || ext == ".tiff";
}

bool isBinary(const std::string& fname)
{
    return isCompressed(fname) || isNativeBinary(fname);
}

} // namespace


std::string ZipUtil::uncompressedExtension(std::string fname)
{
    if (isGZipped(fname))
        fname = fname.substr(0, fname.size() - GzipExtension.size());
    else if (isBZipped(fname))
        fname = fname.substr(0, fname.size() - BzipExtension.size());

    return Base::String::to_lower(Base::Path::extension(fname));
}

std::stringstream ZipUtil::file2stream(const std::string& fname)
{
    if (!Base::Path::IsFileExists(fname))
        throw std::runtime_error("File does not exist: " + fname);

    std::ifstream input_stream;
    std::ios_base::openmode openmode = std::ios::in;
    if (isBinary(fname))
        openmode |= std::ios_base::binary;

    input_stream.open(Base::Path::osPath(fname), openmode);

    if (!input_stream.is_open())
        throw std::runtime_error("Cannot open file for reading: " + fname);
    if (!input_stream.good())
        throw std::runtime_error("File is not good, probably it is a directory:" + fname);

    boost::iostreams::filtering_streambuf<boost::iostreams::input> input_filtered;
    if (::isGZipped(fname))
        input_filtered.push(boost::iostreams::gzip_decompressor());
    else if (::isBZipped(fname))
        input_filtered.push(boost::iostreams::bzip2_decompressor());
    input_filtered.push(input_stream);
    // we use stringstream since it provides random access which is important for tiff files
    std::stringstream ss;
    boost::iostreams::copy(input_filtered, ss);

    return ss;
}

void ZipUtil::stream2file(const std::string& fname, std::stringstream& s)
{
    std::ofstream fout;
    std::ios_base::openmode openmode = std::ios::out;
    if (isBinary(fname))
        openmode |= std::ios_base::binary;

    fout.open(Base::Path::osPath(fname), openmode);

    if (!fout.is_open())
        throw std::runtime_error("Cannot open file for writing: " + fname);
    if (!fout.good())
        throw std::runtime_error("File is not good, probably it is a directory: " + fname);

    boost::iostreams::filtering_streambuf<boost::iostreams::input> input_filtered;
    if (::isGZipped(fname))
        input_filtered.push(boost::iostreams::gzip_compressor());
    else if (::isBZipped(fname))
        input_filtered.push(boost::iostreams::bzip2_compressor());
    input_filtered.push(s);

    boost::iostreams::copy(input_filtered, fout);

    fout.close();
}
