//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Data/Data1DItem.cpp
//! @brief     Implements class Data1DItem.
//!
//! @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 "GUI/Model/Data/Data1DItem.h"
#include "Base/Axis/Frame.h"
#include "Base/Axis/Scale.h"
#include "Base/Util/Assert.h"
#include "Device/Data/Datafield.h"
#include "GUI/Model/Axis/AmplitudeAxisItem.h"
#include "GUI/Model/Axis/BasicAxisItem.h"
#include "GUI/Model/Util/UtilXML.h"

namespace {
namespace Tag {

const QString BaseData("BaseData");
const QString LineType("LineType");
const QString Color("Color");
const QString Thickness("Thickness");
const QString ScatterType("ScatterType");
const QString ScatterSize("ScatterSize");

} // namespace Tag

// scatters for representation of 1D graphs
const QMap<QString, QCPScatterStyle::ScatterShape> scatter_map = {
    {"None", QCPScatterStyle::ScatterShape::ssNone},
    {"Disc", QCPScatterStyle::ScatterShape::ssDisc},
    {"Circle", QCPScatterStyle::ScatterShape::ssCircle},
    {"Cross", QCPScatterStyle::ScatterShape::ssCross},
    {"Diamond", QCPScatterStyle::ScatterShape::ssDiamond},
    {"Star", QCPScatterStyle::ScatterShape::ssStar}};

// connection lines for representation of 1D graphs
const QMap<QString, QCPGraph::LineStyle> line_map = {
    {"None", QCPGraph::LineStyle::lsNone},
    {"Line", QCPGraph::LineStyle::lsLine},
    {"StepLeft", QCPGraph::LineStyle::lsStepLeft},
    {"StepRight", QCPGraph::LineStyle::lsStepRight},
    {"StepCenter", QCPGraph::LineStyle::lsStepCenter},
    {"Impulse", QCPGraph::LineStyle::lsImpulse}};

} // namespace

Data1DItem::Data1DItem()
    : DataItem(M_TYPE)
    , m_line_type(line_map.key(QCPGraph::LineStyle::lsLine))
    , m_color(Qt::blue)
    , m_thickness(1.5)
    , m_scatter_type(scatter_map.key(QCPScatterStyle::ScatterShape::ssNone))
    , m_scatter_size(5.0)
{
    setSimuPlotStyle();
}

void Data1DItem::setDatafield(const Datafield& data)
{
    ASSERT(data.rank() == 1);
    DataItem::setDatafield(data);
    updateAxesZoomLevel();
}

double Data1DItem::xMin() const
{
    const double defaultXmin(0.0);
    return m_datafield ? m_datafield->axis(0).min() : defaultXmin;
}

double Data1DItem::xMax() const
{
    const double defaultXmax(1.0);
    return m_datafield ? m_datafield->axis(0).max() : defaultXmax;
}

double Data1DItem::yMin() const
{
    return dataRange().first;
}

double Data1DItem::yMax() const
{
    return dataRange().second;
}

bool Data1DItem::isLog() const
{
    return axItemY()->isLogScale();
}

void Data1DItem::setLog(bool islog)
{
    axItemY()->setLogScale(islog);
}

std::vector<int> Data1DItem::axdims() const
{
    return {xSize()};
}

QCPGraph::LineStyle Data1DItem::lineStyle()
{
    return line_map.value(m_line_type);
}

void Data1DItem::setLineStyle(QCPGraph::LineStyle lineStyle)
{
    ASSERT(line_map.values().contains(lineStyle));
    m_line_type = line_map.key(lineStyle);
}
void Data1DItem::setColor(Qt::GlobalColor color)
{
    m_color = color;
}
void Data1DItem::setThickness(double thickness)
{
    m_thickness = thickness;
}

QCPScatterStyle::ScatterShape Data1DItem::scatter()
{
    return scatter_map.value(m_scatter_type);
}

void Data1DItem::setScatter(QCPScatterStyle::ScatterShape scatter)
{
    ASSERT(scatter_map.values().contains(scatter));
    m_scatter_type = scatter_map.key(scatter);
}
void Data1DItem::setScatterSize(double scatterSize)
{
    m_scatter_size = scatterSize;
}

void Data1DItem::setSimuPlotStyle()
{
    setScatter(QCPScatterStyle::ScatterShape::ssNone);
    setColor(Qt::GlobalColor::blue);
    setLineStyle(QCPGraph::LineStyle::lsLine);
}

void Data1DItem::setDiffPlotStyle()
{
    setScatter(QCPScatterStyle::ScatterShape::ssNone);
    setColor(Qt::GlobalColor::black);
    setLineStyle(QCPGraph::LineStyle::lsLine);
}

void Data1DItem::setRealPlotStyle()
{
    setScatter(QCPScatterStyle::ScatterShape::ssDisc);
    setColor(Qt::GlobalColor::black);
    setLineStyle(QCPGraph::LineStyle::lsNone);
}

void Data1DItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeAttribute(w, XML::Attrib::version, uint(1));

    // parameters from base class
    w->writeStartElement(Tag::BaseData);
    DataItem::writeBaseTo(w);
    w->writeEndElement();

    // line type
    w->writeStartElement(Tag::LineType);
    XML::writeAttribute(w, XML::Attrib::value, m_line_type);
    w->writeEndElement();

    // color
    w->writeStartElement(Tag::Color);
    XML::writeAttribute(w, XML::Attrib::value, m_color.name(QColor::HexArgb));
    w->writeEndElement();

    // thickness
    w->writeStartElement(Tag::Thickness);
    XML::writeAttribute(w, XML::Attrib::value, m_thickness);
    w->writeEndElement();

    // scatter type
    w->writeStartElement(Tag::ScatterType);
    XML::writeAttribute(w, XML::Attrib::value, m_scatter_type);
    w->writeEndElement();

    // scatter size
    w->writeStartElement(Tag::ScatterSize);
    XML::writeAttribute(w, XML::Attrib::value, m_scatter_size);
    w->writeEndElement();
}

void Data1DItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

    while (r->readNextStartElement()) {
        QString tag = r->name().toString();

        // parameters from base class
        if (tag == Tag::BaseData) {
            DataItem::readBaseFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // line type
        } else if (tag == Tag::LineType) {
            XML::readAttribute(r, XML::Attrib::value, &m_line_type);
            XML::gotoEndElementOfTag(r, tag);

            // color
        } else if (tag == Tag::Color) {
            QString col;
            XML::readAttribute(r, XML::Attrib::value, &col);
            m_color = QColor(col);
            XML::gotoEndElementOfTag(r, tag);

            // thickness
        } else if (tag == Tag::Thickness) {
            XML::readAttribute(r, XML::Attrib::value, &m_thickness);
            XML::gotoEndElementOfTag(r, tag);

            // scatter type
        } else if (tag == Tag::ScatterType) {
            XML::readAttribute(r, XML::Attrib::value, &m_scatter_type);
            XML::gotoEndElementOfTag(r, tag);

            // scatter size
        } else if (tag == Tag::ScatterSize) {
            XML::readAttribute(r, XML::Attrib::value, &m_scatter_size);
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}

//! Sets zoom range of X,Y axes, if it was not yet defined.

void Data1DItem::updateAxesZoomLevel()
{
    // set zoom range of x-axis to min, max values if it was not set already
    if (upperX() < lowerX())
        setXrange(xMin(), xMax());

    // set zoom range of y-axis to min, max values if it was not set already
    if (upperY() < lowerY())
        setYrange(yMin(), yMax());

    const int nx = static_cast<int>(m_datafield->axis(0).size());
    axItemX()->resize(nx);
}

std::pair<double, double> Data1DItem::dataRange() const
{
    const double default_min = 0.0;
    const double default_max = 1.0;
    const Datafield* data = c_field();
    if (!data)
        return {default_min, default_max};

    const auto vec = data->flatVector();
    double min(*std::min_element(vec.cbegin(), vec.cend()));
    min = std::numeric_limits<double>::epsilon() < min ? min : default_min;

    double max(*std::max_element(vec.cbegin(), vec.cend()));
    max *= 1.1;

    double logRange = pow(10, axItemY()->logRangeOrders());
    if (isLog())
        min = std::max(min, max / logRange);
    else
        min /= 2.0;

    return {min, max};
}

void Data1DItem::resetView()
{
    setXrange(xMin(), xMax());
    if (m_datafield)
        setYrange(yMin(), yMax());
}
