//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Descriptor/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_DESCRIPTOR_DISTRIBUTIONITEMS_H
#define BORNAGAIN_GUI_MODEL_DESCRIPTOR_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;

    virtual void initDistribution(double) {}

    //! Set the unit of the distributed value
    virtual void setUnit(const std::variant<QString, Unit>& unit) = 0;

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

    DoubleProperty& relSamplingWidth() { return m_relSamplingWidth; }
    const DoubleProperty& relSamplingWidth() const { return m_relSamplingWidth; }
    void setRelSamplingWidth(double v) { m_relSamplingWidth.setValue(v); }

    bool hasRelSamplingWidth() const;

    //! Serialization of contents.
    //!
    //! Important: limits and a changed unit (with setUnit) 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:
    void initRelSamplingWidth();

    uint m_numberOfSamples = 5;

    DoubleProperty m_relSamplingWidth;
};

class SymmetricResolutionItem : public DistributionItem {
public:
    SymmetricResolutionItem(double mean, int decimals = 3, const QString& meanLabel = "Mean");
    void setUnit(const std::variant<QString, Unit>& unit) override;

    DoubleProperty& mean() { return m_mean; }
    const DoubleProperty& mean() const { return m_mean; }
    void setMean(double v) { m_mean.setValue(v); }

    void setMeanDecimals(uint d);

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

    virtual double deviation(double scale) const = 0;

protected:
    DoubleProperty m_mean;
};

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

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

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

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

    void setUnit(const std::variant<QString, Unit>& unit) override;

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

    DoubleProperty& minimum() { return m_minimum; }
    const DoubleProperty& minimum() const { return m_minimum; }
    void setMinimum(double v) { m_minimum.setValue(v); }

    DoubleProperty& maximum() { return m_maximum; }
    const DoubleProperty& maximum() const { return m_maximum; }
    void setMaximum(double v) { m_maximum.setValue(v); }

    void setRange(double min, double max);

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

    DoubleProperties distributionValues(bool withMean = true) override;

protected:
    DoubleProperty m_minimum;
    DoubleProperty m_maximum;
};

class DistributionLorentzItem : public SymmetricResolutionItem {
public:
    DistributionLorentzItem();
    void setUnit(const std::variant<QString, Unit>& unit) override;

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

    DoubleProperty& hwhm() { return m_hwhm; }
    const DoubleProperty& hwhm() const { return m_hwhm; }
    void setHwhm(double v) { m_hwhm.setValue(v); }

    void initDistribution(double value) override;

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

    DoubleProperties distributionValues(bool withMean = true) override;

protected:
    DoubleProperty m_hwhm;
};

class DistributionGaussianItem : public SymmetricResolutionItem {
public:
    DistributionGaussianItem();
    void setUnit(const std::variant<QString, Unit>& unit) override;

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

    DoubleProperty& standardDeviation() { return m_standardDeviation; }
    const DoubleProperty& standardDeviation() const { return m_standardDeviation; }
    void setStandardDeviation(double v) { m_standardDeviation.setValue(v); }

    void initDistribution(double value) override;

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

    DoubleProperties distributionValues(bool withMean = true) override;

protected:
    DoubleProperty m_standardDeviation;
};

class DistributionLogNormalItem : public DistributionItem {
public:
    DistributionLogNormalItem();
    void setUnit(const std::variant<QString, Unit>& unit) override;

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

    DoubleProperty& median() { return m_median; }
    const DoubleProperty& median() const { return m_median; }
    void setMedian(double v) { m_median.setValue(v); }

    DoubleProperty& scaleParameter() { return m_scaleParameter; }
    const DoubleProperty& scaleParameter() const { return m_scaleParameter; }
    void setScaleParameter(double v) { m_scaleParameter.setValue(v); }

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

    DoubleProperties distributionValues(bool withMean = true) override;

protected:
    DoubleProperty m_median;
    DoubleProperty m_scaleParameter;
};

class DistributionCosineItem : public SymmetricResolutionItem {
public:
    DistributionCosineItem();
    void setUnit(const std::variant<QString, Unit>& unit) override;

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

    DoubleProperty& hwhm() { return m_hwhm; }
    const DoubleProperty& hwhm() const { return m_hwhm; }
    void setHwhm(double v) { m_hwhm.setValue(v); }

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

    DoubleProperties distributionValues(bool withMean = true) override;

protected:
    DoubleProperty m_hwhm;
};

class DistributionTrapezoidItem : public DistributionItem {
public:
    DistributionTrapezoidItem();
    void setUnit(const std::variant<QString, Unit>& unit) override;

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

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

    DoubleProperty& leftWidth() { return m_leftWidth; }
    const DoubleProperty& leftWidth() const { return m_leftWidth; }
    void setLeftWidth(double v) { m_leftWidth.setValue(v); }

    DoubleProperty& middleWidth() { return m_middleWidth; }
    const DoubleProperty& middleWidth() const { return m_middleWidth; }
    void setMiddleWidth(double v) { m_middleWidth.setValue(v); }

    DoubleProperty& rightWidth() { return m_rightWidth; }
    const DoubleProperty& rightWidth() const { return m_rightWidth; }
    void setRightWidth(double v) { m_rightWidth.setValue(v); }

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

    DoubleProperties distributionValues(bool withMean = true) override;

protected:
    DoubleProperty m_center;
    DoubleProperty m_leftWidth;
    DoubleProperty m_middleWidth;
    DoubleProperty m_rightWidth;
};

#endif // BORNAGAIN_GUI_MODEL_DESCRIPTOR_DISTRIBUTIONITEMS_H
