//  ************************************************************************************************
//
//  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) {}

    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; }
    void setRelSamplingWidth(double v) { m_rel_sampling_width.setValue(v); }

    bool hasRelSamplingWidth() const;

    //! 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:
    void initRelSamplingWidth();

    uint m_number_of_samples = 5;

    DoubleProperty m_rel_sampling_width;
};

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

    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();

    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();

    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();

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

    DoubleProperty& standardDeviation() { return m_standard_deviation; }
    const DoubleProperty& standardDeviation() const { return m_standard_deviation; }
    void setStandardDeviation(double v) { m_standard_deviation.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_standard_deviation;
};

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

    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_scale_parameter; }
    const DoubleProperty& scaleParameter() const { return m_scale_parameter; }
    void setScaleParameter(double v) { m_scale_parameter.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_scale_parameter;
};

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

    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();

    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_left_width; }
    const DoubleProperty& leftWidth() const { return m_left_width; }
    void setLeftWidth(double v) { m_left_width.setValue(v); }

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

    DoubleProperty& rightWidth() { return m_right_width; }
    const DoubleProperty& rightWidth() const { return m_right_width; }
    void setRightWidth(double v) { m_right_width.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_left_width;
    DoubleProperty m_middle_width;
    DoubleProperty m_right_width;
};

#endif // BORNAGAIN_GUI_MODEL_DESCRIPTOR_DISTRIBUTIONITEMS_H
