//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Sim/Export/PyFmt2.cpp
//! @brief     Implements functions from namespace pyfmt2.
//!
//! @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 "Sim/Export/PyFmt2.h"
#include "Base/Axis/MakeScale.h"
#include "Base/Axis/Scale.h"
#include "Base/Const/Units.h"
#include "Base/Py/PyFmt.h"
#include "Base/Util/Assert.h"
#include "Base/Util/StringUtil.h"
#include "Device/Mask/Ellipse.h"
#include "Device/Mask/InfinitePlane.h"
#include "Device/Mask/Line.h"
#include "Device/Mask/Polygon.h"
#include "Device/Mask/Rectangle.h"
#include "Param/Distrib/Distributions.h"
#include "Param/Distrib/ParameterDistribution.h"
#include <iomanip>

//! Returns fixed Python code snippet that defines the function "simulate".

std::string Py::Fmt2::representShape2D(const std::string& indent, const IShape2D* ishape,
                                       bool mask_value,
                                       std::function<std::string(double)> printValueFunc)
{
    std::ostringstream result;
    result << std::setprecision(12);

    if (const auto* shape = dynamic_cast<const Polygon*>(ishape)) {
        std::vector<double> xpos, ypos;
        shape->getPoints(xpos, ypos);
        result << indent << "points = [";
        for (size_t i = 0; i < xpos.size(); ++i) {
            result << "[" << printValueFunc(xpos[i]) << ", " << printValueFunc(ypos[i]) << "]";
            if (i != xpos.size() - 1)
                result << ", ";
        }
        result << "]\n";
        result << indent << "detector.addMask("
               << "ba.Polygon(points), " << Py::Fmt::printBool(mask_value) << ")\n";
    } else if (dynamic_cast<const InfinitePlane*>(ishape))
        result << indent << "detector.maskAll()\n";
    else if (const auto* shape = dynamic_cast<const Ellipse*>(ishape)) {
        result << indent << "detector.addMask(";
        result << "ba.Ellipse(" << printValueFunc(shape->getCenterX()) << ", "
               << printValueFunc(shape->getCenterY()) << ", " << printValueFunc(shape->radiusX())
               << ", " << printValueFunc(shape->radiusY());
        if (shape->getTheta() != 0.0)
            result << ", " << Py::Fmt::printDegrees(shape->getTheta());
        result << "), " << Py::Fmt::printBool(mask_value) << ")\n";
    }

    else if (const auto* shape = dynamic_cast<const Rectangle*>(ishape)) {
        result << indent << "detector.addMask(";
        result << "ba.Rectangle(" << printValueFunc(shape->getXlow()) << ", "
               << printValueFunc(shape->getYlow()) << ", " << printValueFunc(shape->getXup())
               << ", " << printValueFunc(shape->getYup()) << "), " << Py::Fmt::printBool(mask_value)
               << ")\n";
    }

    else if (const auto* shape = dynamic_cast<const VerticalLine*>(ishape)) {
        result << indent << "detector.addMask(";
        result << "ba.VerticalLine(" << printValueFunc(shape->getXpos()) << "), "
               << Py::Fmt::printBool(mask_value) << ")\n";
    }

    else if (const auto* shape = dynamic_cast<const HorizontalLine*>(ishape)) {
        result << indent << "detector.addMask(";
        result << "ba.HorizontalLine(" << printValueFunc(shape->getYpos()) << "), "
               << Py::Fmt::printBool(mask_value) << ")\n";
    } else
        ASSERT_NEVER;

    return result.str();
}

//! Prints an axis.
std::string Py::Fmt2::printAxis(const Scale* a, const std::string& unit)
{
    std::ostringstream result;
    if (a->isEquiDivision())
        result << "ba.EquiDivision(" << Py::Fmt::printString(a->axisLabel()) << ", " << a->size()
               << ", " << Py::Fmt::printValue(a->min(), unit) << ", "
               << Py::Fmt::printValue(a->max(), unit) << ")";
    else if (a->isEquiScan())
        result << "ba.EquiScan(" << Py::Fmt::printString(a->axisLabel()) << ", " << a->size()
               << ", " << Py::Fmt::printValue(a->min(), unit) << ", "
               << Py::Fmt::printValue(a->max(), unit) << ")";
    else if (a->isScan()) {
        result << "ba.ListScan(" << Py::Fmt::printString(a->axisLabel()) << ", [";
        const std::vector<double>& points = a->binCenters();
        for (auto iter = points.begin(); iter != points.end() - 1; ++iter)
            result << Py::Fmt::printValue(*iter, unit) << ",";
        result << Py::Fmt::printValue(points.back(), unit) << "])\n";
    } else
        ASSERT_NEVER; // not implemented for current axis type
    return result.str();
}

std::string Py::Fmt2::printParameterDistribution(const ParameterDistribution& par_distr,
                                                 const std::string& distVarName)
{
    std::ostringstream result;

    result << "ba.ParameterDistribution(ba." << par_distr.whichParameterAsPyEnum() << ", "
           << distVarName << ")";

    return result.str();
}

std::string Py::Fmt2::printDistribution(const IDistribution1D& distr)
{
    std::ostringstream result;
    result << std::setprecision(16); // TODO revert to precision(12) after improving DistrGate
    result << "ba." << distr.className() << "(";
    for (size_t i = 0;;) {
        result << distr.parVal(i);
        if (++i == distr.nPars())
            break;
        result << ", ";
    }
    result << ", " << distr.nSamples();
    if (distr.relSamplingWidth() != 1.)
        result << ", " << distr.relSamplingWidth();
    result << ")\n";
    return result.str();
}
