//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Beam/DistributionItems.cpp
//! @brief     Implements class DistributionItem and several subclasses.
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2022
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "GUI/Model/Beam/DistributionItems.h"
#include "GUI/Model/Util/UtilXML.h"
#include "Param/Distrib/Distributions.h"

namespace {
namespace Tag {

const QString BaseData("BaseData");
const QString Center("Center");
const QString Halfwidth("Halfwidth");
const QString HWHM("HWHM");
const QString LeftWidth("LeftWidth");
const QString Maximum("Maximum");
const QString Mean("Mean");
const QString Median("Median");
const QString MiddleWidth("MiddleWidth");
const QString Minimum("Minimum");
const QString NumberOfSamples("NumberOfSamples");
const QString RelSamplingWidth("RelSamplingWidth");
const QString RightWidth("RightWidth");
const QString ScaleParameter("ScaleParameter");
const QString Sigma("Sigma");
const QString StandardDeviation("StandardDeviation");

} // namespace Tag
} // namespace


DistributionItem::DistributionItem()
{
    m_rel_sampling_width.init("Rel. sampling width", "", "", 2.0, 3, RealLimits::nonnegative(),
                              "relSamplingWidth");
}

void DistributionItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeTaggedValue(w, Tag::NumberOfSamples, m_number_of_samples);

    // relative sampling width
    m_rel_sampling_width.writeTo2(w, Tag::RelSamplingWidth);
}

void DistributionItem::readFrom(QXmlStreamReader* r)
{
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();
        if (tag == Tag::NumberOfSamples)
            m_number_of_samples = XML::readTaggedUInt(r, tag);
        else if (tag == Tag::RelSamplingWidth)
            m_rel_sampling_width.readFrom2(r, tag);
        else
            r->skipCurrentElement();
    }
}

// --------------------------------------------------------------------------------------------- //

SymmetricDistributionItem::SymmetricDistributionItem(double mean, int decimals,
                                                     const QString& meanLabel)
{
    m_mean.init(meanLabel, "", "", mean, decimals, RealLimits::limitless(), "mean");
}

const QString& SymmetricDistributionItem::units()
{
    return m_mean.units();
}

void SymmetricDistributionItem::setUnits(const QString& units)
{
    m_mean.setUnits(units);
}

void SymmetricDistributionItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeBaseElement<DistributionItem>(w, XML::Tag::BaseData, this);

    // mean
    m_mean.writeTo2(w, Tag::Mean);
}

void SymmetricDistributionItem::readFrom(QXmlStreamReader* r)
{
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();
        if (tag == Tag::BaseData)
            XML::readBaseElement<DistributionItem>(r, tag, this);
        else if (tag == Tag::Mean)
            m_mean.readFrom2(r, tag);
        else
            r->skipCurrentElement();
    }
}

// --------------------------------------------------------------------------------------------- //

DistributionDeltaItem::DistributionDeltaItem()
    : SymmetricDistributionItem(0., 3, "Value")
{
}

std::unique_ptr<IDistribution1D> DistributionDeltaItem::createDistribution(double) const
{
    return {};
}

DoubleProperties DistributionDeltaItem::distributionValues(bool withMean)
{
    return withMean ? DoubleProperties{&m_mean} : DoubleProperties{};
}

// --------------------------------------------------------------------------------------------- //

DistributionGateItem::DistributionGateItem()
{
    m_center.init("Center", "", "", 0.0, 3, RealLimits::limitless(), "");
    m_halfwidth.init("Half width", "", "", 1.0, 3, RealLimits::nonnegative(), "");
}

std::unique_ptr<IDistribution1D> DistributionGateItem::createDistribution(double scale) const
{
    return std::make_unique<DistributionGate>(scale * (m_center.dVal() - m_halfwidth.dVal()),
                                              scale * (m_center.dVal() + m_halfwidth.dVal()),
                                              m_number_of_samples);
}

const QString& DistributionGateItem::units()
{
    return m_center.units();
}

void DistributionGateItem::setUnits(const QString& units)
{
    m_center.setUnits(units);
    m_halfwidth.setUnits(units);
}

void DistributionGateItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeBaseElement<DistributionItem>(w, XML::Tag::BaseData, this);
    m_center.writeTo2(w, Tag::Center);
    m_halfwidth.writeTo2(w, Tag::Halfwidth);
}

void DistributionGateItem::readFrom(QXmlStreamReader* r)
{
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();
        if (tag == Tag::BaseData)
            XML::readBaseElement<DistributionItem>(r, tag, this);
        else if (tag == Tag::Center)
            m_center.readFrom2(r, tag);
        else if (tag == Tag::Halfwidth)
            m_halfwidth.readFrom2(r, tag);
        else
            r->skipCurrentElement();
    }
}

DoubleProperties DistributionGateItem::distributionValues(bool withMean)
{
    return withMean ? DoubleProperties{&m_center, &m_halfwidth} : DoubleProperties{&m_halfwidth};
}

// --------------------------------------------------------------------------------------------- //

DistributionLorentzItem::DistributionLorentzItem()
    : SymmetricDistributionItem(0)
{
    m_hwhm.init("HWHM", "", "", 1.0, 3, RealLimits::nonnegative(), "hwhm");
}

std::unique_ptr<IDistribution1D> DistributionLorentzItem::createDistribution(double scale) const
{
    return std::make_unique<DistributionLorentz>(scale * m_mean.dVal(), scale * m_hwhm.dVal(),
                                                 m_number_of_samples, m_rel_sampling_width.dVal());
}

void DistributionLorentzItem::setUnits(const QString& units)
{
    SymmetricDistributionItem::setUnits(units);
    m_hwhm.setUnits(units);
}

void DistributionLorentzItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeBaseElement<SymmetricDistributionItem>(w, XML::Tag::BaseData, this);

    // half width at half maximum
    m_hwhm.writeTo2(w, Tag::HWHM);
}

void DistributionLorentzItem::readFrom(QXmlStreamReader* r)
{
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();
        if (tag == Tag::BaseData)
            XML::readBaseElement<SymmetricDistributionItem>(r, tag, this);
        else if (tag == Tag::HWHM)
            m_hwhm.readFrom2(r, tag);
        else
            r->skipCurrentElement();
    }
}

DoubleProperties DistributionLorentzItem::distributionValues(bool withMean)
{
    return withMean ? DoubleProperties{&m_mean, &m_hwhm, &m_rel_sampling_width}
                    : DoubleProperties{&m_hwhm, &m_rel_sampling_width};
}

// --------------------------------------------------------------------------------------------- //

DistributionGaussianItem::DistributionGaussianItem()
    : SymmetricDistributionItem(0)
{
    m_standard_deviation.init("StdDev", "", "", 1.0, 3, RealLimits::nonnegative(), "stdDev");
}

std::unique_ptr<IDistribution1D> DistributionGaussianItem::createDistribution(double scale) const
{
    return std::make_unique<DistributionGaussian>(scale * m_mean.dVal(),
                                                  scale * m_standard_deviation.dVal(),
                                                  m_number_of_samples, m_rel_sampling_width.dVal());
}

void DistributionGaussianItem::setUnits(const QString& units)
{
    SymmetricDistributionItem::setUnits(units);
    m_standard_deviation.setUnits(units);
}

void DistributionGaussianItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeBaseElement<SymmetricDistributionItem>(w, XML::Tag::BaseData, this);

    // standard deviation
    m_standard_deviation.writeTo2(w, Tag::StandardDeviation);
}

void DistributionGaussianItem::readFrom(QXmlStreamReader* r)
{
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();
        if (tag == Tag::BaseData)
            XML::readBaseElement<SymmetricDistributionItem>(r, tag, this);
        else if (tag == Tag::StandardDeviation)
            m_standard_deviation.readFrom2(r, tag);
        else
            r->skipCurrentElement();
    }
}

DoubleProperties DistributionGaussianItem::distributionValues(bool withMean)
{
    return withMean ? DoubleProperties{&m_mean, &m_standard_deviation, &m_rel_sampling_width}
                    : DoubleProperties{&m_standard_deviation, &m_rel_sampling_width};
}

// --------------------------------------------------------------------------------------------- //

DistributionLogNormalItem::DistributionLogNormalItem()
{
    m_median.init("Median", "", "", 1.0, "median");
    m_scale_parameter.init("ScaleParameter", "", "", 0.5, 3, RealLimits::positive(), "scalePar");
}

std::unique_ptr<IDistribution1D> DistributionLogNormalItem::createDistribution(double scale) const
{
    return std::make_unique<DistributionLogNormal>(scale * m_median.dVal(),
                                                   m_scale_parameter.dVal(), m_number_of_samples,
                                                   m_rel_sampling_width.dVal());
}

const QString& DistributionLogNormalItem::units()
{
    return m_median.units();
}

void DistributionLogNormalItem::setUnits(const QString& units)
{
    m_median.setUnits(units);
}

void DistributionLogNormalItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeBaseElement<DistributionItem>(w, XML::Tag::BaseData, this);

    // median
    m_median.writeTo2(w, Tag::Median);

    // scale parameter
    m_scale_parameter.writeTo2(w, Tag::ScaleParameter);
}

void DistributionLogNormalItem::readFrom(QXmlStreamReader* r)
{
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();
        if (tag == Tag::BaseData)
            XML::readBaseElement<DistributionItem>(r, tag, this);
        else if (tag == Tag::Median)
            m_median.readFrom2(r, tag);
        else if (tag == Tag::ScaleParameter)
            m_scale_parameter.readFrom2(r, tag);
        else
            r->skipCurrentElement();
    }
}

DoubleProperties DistributionLogNormalItem::distributionValues(bool withMean)
{
    return withMean ? DoubleProperties{&m_median, &m_scale_parameter, &m_rel_sampling_width}
                    : DoubleProperties{&m_scale_parameter, &m_rel_sampling_width};
}

// --------------------------------------------------------------------------------------------- //

DistributionCosineItem::DistributionCosineItem()
    : SymmetricDistributionItem(0)
{
    m_hwhm.init("HWHM", "", "", 1.0, 3, RealLimits::nonnegative(), "hwhm");
}

std::unique_ptr<IDistribution1D> DistributionCosineItem::createDistribution(double scale) const
{
    return std::make_unique<DistributionCosine>(scale * m_mean.dVal(), scale * m_hwhm.dVal(),
                                                m_number_of_samples);
}

void DistributionCosineItem::setUnits(const QString& units)
{
    SymmetricDistributionItem::setUnits(units);
    m_hwhm.setUnits(units);
}

void DistributionCosineItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeBaseElement<SymmetricDistributionItem>(w, XML::Tag::BaseData, this);

    // sigma
    m_hwhm.writeTo2(w, Tag::Sigma);
}

void DistributionCosineItem::readFrom(QXmlStreamReader* r)
{
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();
        if (tag == Tag::BaseData)
            XML::readBaseElement<SymmetricDistributionItem>(r, tag, this);
        else if (tag == Tag::Sigma)
            m_hwhm.readFrom2(r, tag);
        else
            r->skipCurrentElement();
    }
}

DoubleProperties DistributionCosineItem::distributionValues(bool withMean)
{
    return withMean ? DoubleProperties{&m_mean, &m_hwhm} : DoubleProperties{&m_hwhm};
}
