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

SpecularDataItem::SpecularDataItem()
    : DataItem(M_TYPE)
    , m_lineType(line_map.key(QCPGraph::LineStyle::lsLine))
    , m_color(Qt::blue)
    , m_thickness(1.5)
    , m_scatterType(scatter_map.key(QCPScatterStyle::ScatterShape::ssNone))
    , m_scatterSize(5.0)
{
    setYaxisTitle("Signal [a.u]");
    setSimuPlotStyle();
}

void SpecularDataItem::setDatafield(Datafield* data)
{
    if (data != nullptr) {
        ASSERT(data->rank() == 1);
        DataItem::setDatafield(data);
        updateAxesZoomLevel();
    } else
        DataItem::setDatafield(data);
}

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

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

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

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

bool SpecularDataItem::isLog() const
{
    return yAxisItem()->isLogScale();
}

void SpecularDataItem::setLog(bool islog)
{
    yAxisItem()->setLogScale(islog);
}

void SpecularDataItem::updateCoords(const ICoordSystem& converter)
{
    GUI::Model::DataItemUtil::updateDataAxes(this, converter);
}

std::vector<int> SpecularDataItem::shape() const
{
    return {xSize()};
}

QCPGraph::LineStyle SpecularDataItem::lineStyle()
{
    return line_map.value(m_lineType);
}

void SpecularDataItem::setLineStyle(QCPGraph::LineStyle lineStyle)
{
    ASSERT(line_map.values().contains(lineStyle));
    m_lineType = line_map.key(lineStyle);
}

QColor SpecularDataItem::color()
{
    return m_color;
}

void SpecularDataItem::setColor(Qt::GlobalColor color)
{
    m_color = color;
}

double SpecularDataItem::thickness()
{
    return m_thickness;
}

void SpecularDataItem::setThickness(double thickness)
{
    m_thickness = thickness;
}

QCPScatterStyle::ScatterShape SpecularDataItem::scatter()
{
    return scatter_map.value(m_scatterType);
}

void SpecularDataItem::setScatter(QCPScatterStyle::ScatterShape scatter)
{
    ASSERT(scatter_map.values().contains(scatter));
    m_scatterType = scatter_map.key(scatter);
}

double SpecularDataItem::scatterSize()
{
    return m_scatterSize;
}

void SpecularDataItem::setScatterSize(double scatterSize)
{
    m_scatterSize = scatterSize;
}

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

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

    setYaxisTitle("Relative difference");
}

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

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

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

    // line type
    w->writeStartElement(Tag::LineType);
    XML::writeAttribute(w, XML::Attrib::value, m_lineType);
    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_scatterType);
    w->writeEndElement();

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

void SpecularDataItem::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::readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // line type
        } else if (tag == Tag::LineType) {
            XML::readAttribute(r, XML::Attrib::value, &m_lineType);
            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_scatterType);
            XML::gotoEndElementOfTag(r, tag);

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

        } else
            r->skipCurrentElement();
    }
}

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

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

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

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

QPair<double, double> SpecularDataItem::dataRange() const
{
    const double default_min = 0.0;
    const double default_max = 1.0;
    const Datafield* data = c_field();
    if (!data)
        return QPair<double, double>(default_min, default_max);

    const auto vec = data->flatVector();
    double min(*std::min_element(vec.cbegin(), vec.cend()));
    double max(*std::max_element(vec.cbegin(), vec.cend()));

    min /= 2.0;
    min = std::numeric_limits<double>::epsilon() < min ? min : default_min;
    max *= 2.0;
    max = max > min ? max : default_max;

    return QPair<double, double>(min, max);
}

void SpecularDataItem::resetView()
{
    if (m_datafield)
        setAxesRangeToData();
    else {
        setLowerX(xMin());
        setUpperX(xMax());
    }
}
