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

#include "Device/IO/ReadWriteNicos.h"
#include "Base/Axis/Frame.h"
#include "Base/Axis/MakeScale.h"
#include "Base/Util/StringUtil.h"
#include "Device/Data/Datafield.h"
#include <memory>

namespace {

std::string lineRelatedError(const std::string& errorText, int lineNumber)
{
    return "Line " + std::to_string(lineNumber) + ": " + errorText;
}


unsigned int readAssignedPositiveIntValue(const std::string& line, int lineNumber)
{
    const auto parts = Base::String::split(line, "=");
    if (parts.size() != 2)
        throw std::runtime_error(lineRelatedError("Missing assigned value", lineNumber));

    int value = 0;
    if (!Base::String::to_int(parts[1], &value))
        throw std::runtime_error(
            lineRelatedError("Cannot parse assigned value '" + parts[1] + "'", lineNumber));

    if (value <= 0)
        throw std::runtime_error(
            lineRelatedError("Value of '" + parts[1] + "' is nonpositive", lineNumber));

    return value;
}

} // namespace


Datafield Util::RW::readNicos(std::istream& input_stream)
{
    std::string line;
    int lineNumber = 0;

    unsigned int width = 0;
    unsigned int height = 0;

    // -- read dimensions
    bool inFileSection = false;
    bool fileSectionFound = false;
    bool typeFound = false;
    while (std::getline(input_stream, line)) {
        lineNumber++;
        line = Base::String::trimFront(line, " ");

        if (!inFileSection) {
            if (Base::String::startsWith(line, "%File")) {
                inFileSection = true;
                fileSectionFound = true;
                continue;
            }
            continue;
        }

        if (Base::String::startsWith(line, "%"))
            break; // next section

        if (Base::String::startsWith(line, "Type=")) {
            const auto parts = Base::String::split(line, "=");
            if (parts[1] != "SANSDRaw")
                throw std::runtime_error(
                    lineRelatedError("Unsupported file type '" + parts[1] + "'", lineNumber));
            typeFound = true;
        }

        if (Base::String::startsWith(line, "DataSizeX"))
            width = readAssignedPositiveIntValue(line, lineNumber);
        else if (Base::String::startsWith(line, "DataSizeY"))
            height = readAssignedPositiveIntValue(line, lineNumber);
        if (width != 0 && height != 0)
            break;
    }

    if (!fileSectionFound)
        throw std::runtime_error("Could not find 'File' section");
    if (!typeFound)
        throw std::runtime_error("File type not found");
    if (width == 0)
        throw std::runtime_error("Could not find 'DataSizeX' value");
    if (height == 0)
        throw std::runtime_error("Could not find 'DataSizeY' value");

    std::vector<const Scale*> axes{newEquiDivision("u (bin)", width, 0.0, width),
                                   newEquiDivision("v (bin)", height, 0.0, height)};
    auto result = std::make_unique<Datafield>(std::move(axes));

    // -- read data
    bool inCountSection = false;
    bool countSectionFound = false;
    unsigned int dataRow = 0;

    while (std::getline(input_stream, line)) {
        lineNumber++;
        line = Base::String::trimFront(line, " ");

        if (!inCountSection) {
            if (Base::String::startsWith(line, "%Counts")) {
                inCountSection = true;
                countSectionFound = true;
                continue;
            }
            continue;
        }

        if (Base::String::startsWith(line, "%"))
            break; // next section

        // line is a data line
        line = Base::String::trim(line, " ");
        if (line.empty())
            continue;

        const auto valuesAsString = Base::String::split(line, ",");
        if (valuesAsString.size() != width)
            throw std::runtime_error(
                lineRelatedError("Number of found values (" + std::to_string(valuesAsString.size())
                                     + ") does not match DataSizeX (" + std::to_string(width) + ")",
                                 lineNumber));

        for (unsigned col = 0; col < width; ++col) {

            int value = 0;
            if (!Base::String::to_int(valuesAsString[col], &value))
                throw std::runtime_error(lineRelatedError(
                    "Value '" + valuesAsString[col] + "' could not be converted to integer",
                    lineNumber));

            // y-axis "0" is at bottom => invert y
            // to show first line at top of image
            (*result)[(height - 1 - dataRow) * width + col] = value;
        }
        dataRow++;

        if (dataRow == height)
            break;
    }

    if (!countSectionFound)
        throw std::runtime_error("Could not find 'Counts' section");
    if (dataRow != height)
        throw std::runtime_error("Number of found data rows (" + std::to_string(dataRow)
                                 + ") does not match DataSizeY (" + std::to_string(height) + ")");

    return *result;
}
