//  ************************************************************************************************
//
//  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/Frame.h"
#include "Base/Axis/Scale.h"
#include "Base/Const/Units.h"
#include "Base/Util/Assert.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);
    if (dynamic_cast<PointwiseAxisItem*>(axisItem))
        return;
    axisItem->setMin(0.0);
    axisItem->setMax(3.0);
    axisItem->resize(500);
}

void initDistribution(DistributionItem* newDistribution,
                      const DistributionItem* currentDistribution)
{
    ASSERT(newDistribution);
    double meanVal = 0.0;
    if (const 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_current_axis_is_uniform_axis(true)
{
    m_distribution.initWithInitializer("Distribution", "", DistributionsCatalog::symmetricTypes(),
                                       initDistribution);

    m_uniform_alpha_axis = std::make_unique<BasicAxisItem>();
    setAxisPresentationDefaults(m_uniform_alpha_axis.get());
}

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_current_axis_is_uniform_axis);
    w->writeEndElement();

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

    // pointwise axis
    if (m_pointwise_alpha_axis) {
        w->writeStartElement(Tag::ListScan);
        m_pointwise_alpha_axis->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);
            XML::gotoEndElementOfTag(r, tag);

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

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

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

        } else
            r->skipCurrentElement();
    }
}

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

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

BasicAxisItem* GrazingScanItem::alphaAxisItem() const
{
    return m_current_axis_is_uniform_axis ? m_uniform_alpha_axis.get()
                                          : m_pointwise_alpha_axis.get();
}

bool GrazingScanItem::pointwiseAlphaAxisDefined() const
{
    return (bool)m_pointwise_alpha_axis;
}

bool GrazingScanItem::pointwiseAlphaAxisSelected() const
{
    return !m_current_axis_is_uniform_axis;
}
void GrazingScanItem::selectUniformAxis()
{
    m_current_axis_is_uniform_axis = true;
}

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

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

void GrazingScanItem::initListScan(const Scale& axis)
{
    if (!m_pointwise_alpha_axis) {
        m_pointwise_alpha_axis = std::make_unique<PointwiseAxisItem>();
        setAxisPresentationDefaults(m_pointwise_alpha_axis.get());
    }
    m_pointwise_alpha_axis->setAxis(axis);
}

void GrazingScanItem::updateAxIndicators(const Frame& frame)
{
    if (!m_pointwise_alpha_axis)
        return;
    m_pointwise_alpha_axis->updateAxIndicators(frame);
}
