//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Beam/GrazingScanItem.cpp
//! @brief     Implements class GrazingScanItem
//!
//! @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/Beam/GrazingScanItem.h"
#include "Base/Axis/Scale.h"
#include "Base/Const/Units.h"
#include "Base/Util/Assert.h"
#include "Device/Coord/ICoordSystem.h"
#include "GUI/Model/Axis/PointwiseAxisItem.h"
#include "GUI/Model/Descriptor/DistributionItems.h"

namespace {

namespace Tag {

const QString UniformAxis("UniformAxis");
const QString ListScan("ListScan");
const QString PointwiseAxis("PointwiseAxis"); // used in pre-21
const QString BeamInclinationDistribution("BeamInclinationDistribution");
const QString IsUniformAxis("IsUniformAxis");
const QString BaseData("BaseData");

} // namespace Tag

void setAxisPresentationDefaults(BasicAxisItem* axisItem)
{
    ASSERT(axisItem);
    axisItem->setTitle("alpha_i");

    if (!dynamic_cast<PointwiseAxisItem*>(axisItem)) {
        axisItem->setMin(0.0);
        axisItem->setMax(3.0);
        axisItem->setBinCount(500);
    }
}

void initDistribution(DistributionItem* newDistribution,
                      const DistributionItem* currentDistribution)
{
    newDistribution->setUnit(Unit::degree);

    double meanVal = 0.0;
    if (auto* cd = dynamic_cast<const SymmetricResolutionItem*>(currentDistribution))
        meanVal = cd->mean();

    if (auto* nd = dynamic_cast<SymmetricResolutionItem*>(newDistribution)) {
        nd->setMean(meanVal);
        nd->setMeanDecimals(3);
    }

    newDistribution->initDistribution(meanVal);
}

} // namespace


GrazingScanItem::GrazingScanItem()
    : BeamDistributionItem()
{
    m_distribution.initWithInitializer("Distribution", "",
                                       DistributionItemCatalog::symmetricTypes(), initDistribution);

    m_uniformAlphaAxis.reset(new BasicAxisItem());
    setAxisPresentationDefaults(m_uniformAlphaAxis.get());
    m_currentAxisIsUniformAxis = true;
}

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

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

    // is uniform axis?
    w->writeStartElement(Tag::IsUniformAxis);
    XML::writeAttribute(w, XML::Attrib::value, m_currentAxisIsUniformAxis);
    w->writeEndElement();

    // uniform axis
    if (m_uniformAlphaAxis) {
        w->writeStartElement(Tag::UniformAxis);
        m_uniformAlphaAxis->writeTo(w);
        w->writeEndElement();
    }

    // pointwise axis
    if (m_pointwiseAlphaAxis) {
        w->writeStartElement(Tag::ListScan);
        m_pointwiseAlphaAxis->writeTo(w);
        w->writeEndElement();
    }
}

void GrazingScanItem::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) {
            BeamDistributionItem::readFrom(r);
            m_distribution->setUnit(Unit::degree);
            XML::gotoEndElementOfTag(r, tag);

            // is uniform axis?
        } else if (tag == Tag::IsUniformAxis) {
            XML::readAttribute(r, XML::Attrib::value, &m_currentAxisIsUniformAxis);
            XML::gotoEndElementOfTag(r, tag);

            // uniform axis
        } else if (tag == Tag::UniformAxis) {
            m_uniformAlphaAxis = std::make_unique<BasicAxisItem>();
            setAxisPresentationDefaults(m_uniformAlphaAxis.get());
            m_uniformAlphaAxis->readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // pointwise axis
        } else if (tag == Tag::ListScan || tag == Tag::PointwiseAxis) { // compatibility with pre-21
            m_pointwiseAlphaAxis = std::make_unique<PointwiseAxisItem>();
            setAxisPresentationDefaults(m_pointwiseAlphaAxis.get());
            m_pointwiseAlphaAxis->readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}

double GrazingScanItem::scaleFactor() const
{
    return Units::deg;
}

int GrazingScanItem::nBins() const
{
    return alphaAxisItem()->binCount();
}

BasicAxisItem* GrazingScanItem::alphaAxisItem() const
{
    return m_currentAxisIsUniformAxis ? m_uniformAlphaAxis.get() : m_pointwiseAlphaAxis.get();
}

bool GrazingScanItem::pointwiseAlphaAxisDefined() const
{
    return m_pointwiseAlphaAxis.get() != nullptr;
}

bool GrazingScanItem::pointwiseAlphaAxisSelected() const
{
    return !m_currentAxisIsUniformAxis;
}

bool GrazingScanItem::uniformAlphaAxisSelected() const
{
    return m_currentAxisIsUniformAxis;
}

void GrazingScanItem::selectUniformAxis()
{
    m_currentAxisIsUniformAxis = true;
}

void GrazingScanItem::selectListScan()
{
    ASSERT(pointwiseAlphaAxisDefined());
    m_currentAxisIsUniformAxis = false;
}

void GrazingScanItem::initUniformAxis(const Scale& axis)
{
    m_uniformAlphaAxis->setBinCount(static_cast<int>(axis.size()));
}

void GrazingScanItem::initListScan(const Scale& axis, QString units, const ICoordSystem& converter)
{
    if (!m_pointwiseAlphaAxis) {
        m_pointwiseAlphaAxis.reset(new PointwiseAxisItem());
        setAxisPresentationDefaults(m_pointwiseAlphaAxis.get());
    }

    m_pointwiseAlphaAxis->setAxisAndUnit(axis, units);
    m_pointwiseAlphaAxis->updateAxIndicators(converter);
}
