//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Device/IO/SaveProjections.cpp
//! @brief     Implements function saveProjections.
//!
//! @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/SaveProjections.h"
#include "Base/Axis/Scale.h"
#include "Base/Py/PyFmt.h"
#include "Device/Data/Datafield.h"
#include <algorithm>
#include <fstream>
#include <iomanip>
#include <sstream>
#include <stdexcept>

namespace {

const int bin_centers_colwidth = 12;
const int bin_values_colwidth = 20;

std::string to_scientific_str(double value)
{
    std::string str = Py::Fmt::printScientificDouble(value);
    std::ostringstream oss;
    oss << std::left << std::setw(bin_values_colwidth) << str;
    return oss.str();
}

std::string to_double_str(double value)
{
    std::string str = Py::Fmt::printDouble(value);
    std::ostringstream oss;
    oss << std::left << std::setw(bin_centers_colwidth) << str;
    return oss.str();
}

struct Projection {
    double axis_value;
    std::vector<double> bin_values;
};

struct ProjectionsData {
    bool is_horizontal;
    std::vector<double> bin_centers;
    std::vector<Projection> projections;
};

std::string projectionFileHeader(const ProjectionsData& projs)
{
    const char label1 = projs.is_horizontal ? 'x' : 'y';
    const char label2 = projs.is_horizontal ? 'y' : 'x';

    std::ostringstream oss;
    oss << "# " << std::left << std::setw(bin_centers_colwidth - 1) << label1;

    for (const auto& data : projs.projections) {
        std::ostringstream col;
        col << "z @ " << label2 << "=" << std::fixed << std::setprecision(4) << data.axis_value;
        oss << std::left << std::setw(bin_values_colwidth) << col.str();
    }
    oss << "\n";

    return oss.str();
}

ProjectionsData computeProjectionsData(const Datafield& field, bool is_horizontal,
                                       std::vector<double> positions)
{
    ProjectionsData result;
    result.is_horizontal = is_horizontal;

    std::sort(positions.begin(), positions.end());

    for (double pos : positions) {
        Projection data;
        data.axis_value = pos;

        Datafield proj = is_horizontal ? field.xProjection(pos) : field.yProjection(pos);

        data.bin_values = proj.flatVector();
        if (result.bin_centers.empty())
            result.bin_centers = proj.axis(0).binCenters();

        result.projections.push_back(std::move(data));
    }
    return result;
}

std::string projectionsToString(const Datafield& field, bool is_horizontal,
                                const std::vector<double>& positions)
{
    if (positions.empty())
        return {};

    ProjectionsData projData = computeProjectionsData(field, is_horizontal, positions);

    if (projData.projections.empty())
        return {};

    std::ostringstream oss;
    oss << projectionFileHeader(projData);

    for (size_t i_point = 0; i_point < projData.bin_centers.size(); ++i_point) {
        oss << to_double_str(projData.bin_centers[i_point]) << " ";
        for (const auto& data : projData.projections)
            oss << to_scientific_str(data.bin_values[i_point]);
        oss << "\n";
    }
    return oss.str();
}

} // namespace


void IO::saveProjections(const Datafield& field, const std::vector<double>& horizontal_positions,
                         const std::vector<double>& vertical_positions, const std::string& filename)
{
    std::ofstream file(filename);
    if (!file.is_open())
        throw std::runtime_error("Cannot create file for saving projections: " + filename);

    file << "# Projections along x-axis (horizontal projections) \n";
    file << projectionsToString(field, true, horizontal_positions);
    file << "\n";

    file << "# Projections along y-axis (vertical projections) \n";
    file << projectionsToString(field, false, vertical_positions);
    file << "\n";

    file.close();
}
