//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Device/IO/ParseUtil.cpp
//! @brief     Implements class DataFormatUtils.
//!
//! @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/ParseUtil.h"
#include "Base/Axis/MakeScale.h"
#include "Base/Util/PathUtil.h"
#include "Base/Util/StringUtil.h"
#include "Device/Data/Datafield.h"
#include <cmath> // ignoreDenormalized
#include <functional>
#include <iostream>
#include <iterator>

namespace {

std::istringstream getAxisStringRepresentation(std::istream& input_stream)
{
    std::string line;
    std::getline(input_stream, line);
    const std::vector<std::string> to_replace = {",", "\"", "(", ")", "[", "]"};
    Base::String::replaceItemsFromString(line, to_replace, " ");
    return std::istringstream(line);
}

void readLineOfDoubles(std::vector<double>& buffer, std::istringstream& iss)
{
    iss.imbue(std::locale::classic());
    std::copy(std::istream_iterator<double>(iss), std::istream_iterator<double>(),
              back_inserter(buffer));
}

} // namespace

//! Creates axis of certain type from input stream
Scale* Util::Parse::parseScale(std::istream& input_stream)
{
    auto iss = ::getAxisStringRepresentation(input_stream);
    std::string type;
    if (!(iss >> type))
        throw std::runtime_error("Cannot read axis type from input");

    if (type == "EquiDivision" || type == "FixedBinAxis" /* for compatibility with pre-21 */) {
        std::string name;
        size_t nbins;
        if (!(iss >> name >> nbins))
            throw std::runtime_error("Reading EquiDivision: cannot read name or size");
        std::vector<double> boundaries;
        ::readLineOfDoubles(boundaries, iss);
        if (boundaries.size() != 2)
            throw std::runtime_error("Reading EquiDivision: cannot read start or stop");
        return newEquiDivision(name, nbins, boundaries[0], boundaries[1]);

    } else if (type == "ListScan" || type == "DiscreteAxis"
               || type == "PointwiseAxis" /* for compatibility with pre-21 */) {
        std::string name;
        if (!(iss >> name))
            throw std::runtime_error("Reading ListScan: cannot read name");
        std::vector<double> coordinates;
        ::readLineOfDoubles(coordinates, iss);
        return newListScan(name, coordinates);

    } else if (type == "GenericScale") {
        std::string name;
        if (!(iss >> name))
            throw std::runtime_error("Reading GenericScale: cannot read name");
        std::vector<double> coordinates;
        ::readLineOfDoubles(coordinates, iss);
        return newGenericScale(name, coordinates);

    } else
        throw std::runtime_error("Unknown axis type '" + type + "'");
}

//! Fills output data raw buffer from input stream
void Util::Parse::fillDatafield(Datafield* data, std::istream& input_stream)
{
    std::string line;
    size_t iout = 0;
    // parse values
    while (std::getline(input_stream, line)) {
        if (line.empty() || line[0] == '#')
            break;
        std::istringstream iss(line);
        std::vector<double> buffer;
        ::readLineOfDoubles(buffer, iss);
        for (auto value : buffer)
            (*data)[iout++] = value;
    }
    if (iout != data->size())
        throw std::runtime_error("Error while parsing data, did not reach expected end");

    // skip lines before errorbars
    while (std::getline(input_stream, line))
        if (line[0] == '#')
            break;

    // parse errorbars
    data->errorSigmas().clear();
    while (std::getline(input_stream, line)) {
        if (line.empty() || line[0] == '#')
            break;
        std::istringstream iss(line);
        std::vector<double> buffer;
        ::readLineOfDoubles(buffer, iss);
        for (auto value : buffer)
            data->errorSigmas().push_back(value);
    }
    if (!data->errorSigmas().empty() && data->errorSigmas().size() != data->size())
        throw std::runtime_error("Error while parsing data, num errorbars != num values");
}

//! Parse double values from string to vector of double

std::vector<double> Util::Parse::parse_doubles(const std::string& str)
{
    std::vector<double> result;
    std::istringstream iss(str);
    ::readLineOfDoubles(result, iss);
    if (result.empty()) {
        std::string out = str;
        if (out.size() > 10) {
            out.resize(10, ' ');
            out += " ...";
        }
        throw std::runtime_error("Found '" + out + "' while expecting a floating-point number");
    }
    return result;
}

double Util::Parse::ignoreDenormalized(double value)
{
    return (std::fpclassify(value) == FP_SUBNORMAL) ? 0.0 : value;
}
