//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Beam/DistributionItems.h
//! @brief     Defines 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)
//
//  ************************************************************************************************

#ifndef BORNAGAIN_GUI_MODEL_BEAM_DISTRIBUTIONITEMS_H
#define BORNAGAIN_GUI_MODEL_BEAM_DISTRIBUTIONITEMS_H

#include "GUI/Model/Descriptor/DoubleProperty.h"
#include <QVector>
#include <memory>

class IDistribution1D;

class DistributionItem {
public:
    virtual ~DistributionItem() = default;
    DistributionItem();

    template <typename T> bool is() const { return dynamic_cast<const T*>(this) != nullptr; }

    virtual std::unique_ptr<IDistribution1D> createDistribution(double scale = 1.0) const = 0;

    uint numberOfSamples() const { return m_number_of_samples; }
    void setNumberOfSamples(uint v) { m_number_of_samples = v; }

    DoubleProperty& relSamplingWidth() { return m_rel_sampling_width; }
    const DoubleProperty& relSamplingWidth() const { return m_rel_sampling_width; }

    virtual DoubleProperty& center() = 0;
    virtual const DoubleProperty& center() const = 0;
    virtual void setCenter(double v) = 0;

    //! Serialization of contents.
    //!
    //! Important: limits will not be serialized here. They have
    //! to be set again by the owner of DistributionItem after reading it
    virtual void writeTo(QXmlStreamWriter* w) const;
    virtual void readFrom(QXmlStreamReader* r);

    virtual DoubleProperties distributionValues(bool withMean = true) = 0;

protected:
    uint m_number_of_samples = 5;

    DoubleProperty m_rel_sampling_width;
};

class SymmetricDistributionItem : public DistributionItem {
public:
    SymmetricDistributionItem(double mean, int decimals = 3, const QString& meanLabel = "Mean");

    DoubleProperty& mean() { return m_mean; }
    const DoubleProperty& mean() const { return m_mean; }

    DoubleProperty& center() override { return mean(); }
    const DoubleProperty& center() const override { return mean(); }
    void setCenter(double v) override { m_mean.setDVal(v); }

    void writeTo(QXmlStreamWriter* w) const override;
    void readFrom(QXmlStreamReader* r) override;

protected:
    DoubleProperty m_mean;
};

class DistributionNoneItem : public SymmetricDistributionItem {
public:
    DistributionNoneItem();

    std::unique_ptr<IDistribution1D> createDistribution(double scale = 1.0) const override;

    DoubleProperties distributionValues(bool withMean = true) override;
};

class DistributionGateItem : public DistributionItem {
public:
    DistributionGateItem();

    std::unique_ptr<IDistribution1D> createDistribution(double scale = 1.0) const override;

    DoubleProperty& center() override { return m_center; }
    const DoubleProperty& center() const override { return m_center; }
    void setCenter(double v) override { m_center.setDVal(v); }

    DoubleProperty& halfwidth() { return m_halfwidth; }
    const DoubleProperty& halfwidth() const { return m_halfwidth; }

    void writeTo(QXmlStreamWriter* w) const override;
    void readFrom(QXmlStreamReader* r) override;

    DoubleProperties distributionValues(bool withMean = true) override;

private:
    DoubleProperty m_center;
    DoubleProperty m_halfwidth;
};

class DistributionLorentzItem : public SymmetricDistributionItem {
public:
    DistributionLorentzItem();

    std::unique_ptr<IDistribution1D> createDistribution(double scale = 1.0) const override;

    DoubleProperty& hwhm() { return m_hwhm; }
    const DoubleProperty& hwhm() const { return m_hwhm; }

    void writeTo(QXmlStreamWriter* w) const override;
    void readFrom(QXmlStreamReader* r) override;

    DoubleProperties distributionValues(bool withMean = true) override;

private:
    DoubleProperty m_hwhm;
};

class DistributionGaussianItem : public SymmetricDistributionItem {
public:
    DistributionGaussianItem();

    std::unique_ptr<IDistribution1D> createDistribution(double scale = 1.0) const override;

    DoubleProperty& standardDeviation() { return m_standard_deviation; }
    const DoubleProperty& standardDeviation() const { return m_standard_deviation; }

    void writeTo(QXmlStreamWriter* w) const override;
    void readFrom(QXmlStreamReader* r) override;

    DoubleProperties distributionValues(bool withMean = true) override;

private:
    DoubleProperty m_standard_deviation;
};

class DistributionLogNormalItem : public DistributionItem {
public:
    DistributionLogNormalItem();

    std::unique_ptr<IDistribution1D> createDistribution(double scale = 1.0) const override;

    DoubleProperty& median() { return m_median; }
    const DoubleProperty& median() const { return m_median; }

    DoubleProperty& center() override { return median(); }
    const DoubleProperty& center() const override { return median(); }
    void setCenter(double v) override { m_median.setDVal(v); }

    DoubleProperty& scaleParameter() { return m_scale_parameter; }
    const DoubleProperty& scaleParameter() const { return m_scale_parameter; }

    void writeTo(QXmlStreamWriter* w) const override;
    void readFrom(QXmlStreamReader* r) override;

    DoubleProperties distributionValues(bool withMean = true) override;

private:
    DoubleProperty m_median;
    DoubleProperty m_scale_parameter;
};

class DistributionCosineItem : public SymmetricDistributionItem {
public:
    DistributionCosineItem();

    std::unique_ptr<IDistribution1D> createDistribution(double scale = 1.0) const override;

    DoubleProperty& hwhm() { return m_hwhm; }
    const DoubleProperty& hwhm() const { return m_hwhm; }

    void writeTo(QXmlStreamWriter* w) const override;
    void readFrom(QXmlStreamReader* r) override;

    DoubleProperties distributionValues(bool withMean = true) override;

private:
    DoubleProperty m_hwhm;
};

#endif // BORNAGAIN_GUI_MODEL_BEAM_DISTRIBUTIONITEMS_H
