//  ************************************************************************************************
//
//  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/Scale.h"
#include "Base/Util/Assert.h"
#include "Device/Data/Datafield.h"
#include "GUI/Model/Axis/AmplitudeAxisItem.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);
    setTheDatafield(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);
}

size_t Data1DItem::axdim(int i) const
{
    ASSERT(i == 0);
    return axItemX()->size();
}

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::writeBaseElement<DataItem>(w, XML::Tag::BaseData, this);
    XML::writeTaggedValue(w, Tag::LineType, m_line_type);
    XML::writeTaggedValue(w, Tag::Color, m_color.name(QColor::HexArgb));
    XML::writeTaggedValue(w, Tag::Thickness, m_thickness);
    XML::writeTaggedValue(w, Tag::ScatterType, m_scatter_type);
    XML::writeTaggedValue(w, Tag::ScatterSize, m_scatter_size);
}

void Data1DItem::readFrom(QXmlStreamReader* r)
{
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();
        if (tag == Tag::BaseData)
            XML::readBaseElement<DataItem>(r, tag, this);
        else if (tag == Tag::LineType)
            m_line_type = XML::readTaggedString(r, tag);
        else if (tag == Tag::Color)
            m_color = XML::readTaggedColor(r, tag);
        else if (tag == Tag::Thickness)
            m_thickness = XML::readTaggedDouble(r, tag);
        else if (tag == Tag::ScatterType)
            m_scatter_type = XML::readTaggedString(r, tag);
        else if (tag == Tag::ScatterSize)
            m_scatter_size = XML::readTaggedDouble(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().dVal());
    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());
        axItemY()->adjustLogRangeOrders();
    }
}
