//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Base/Distrib/Distributions.h
//! @brief     Defines classes representing one-dimensional distributions.
//!
//! @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)
//
//  ************************************************************************************************

#ifndef BORNAGAIN_BASE_DISTRIB_DISTRIBUTIONS_H
#define BORNAGAIN_BASE_DISTRIB_DISTRIBUTIONS_H

#include "Base/Type/Cloneable.h"
#include <cstddef>
#include <vector>

struct ParameterSample;

//  ************************************************************************************************
//  interface Distribution1D
//  ************************************************************************************************

//! Interface for one-dimensional distributions.

class Distribution1D : public Cloneable {
public:
    Distribution1D(size_t n_samples = 25, double rel_sampling_width = 1.);

    void setNSamples(size_t n) { m_n_samples = n; }

    void setRelSamplingWidth(double w) { m_relative_sampling_width = w; }

    virtual std::vector<ParameterSample> distributionSamples() const = 0;

#ifndef SWIG
    Distribution1D* clone() const override = 0;

    virtual void validate() const {}

    //! Returns the distribution-specific probability density for value x.
    virtual double probabilityDensity(double x) const = 0;

    //! Returns the distribution-specific mean.
    virtual double mean() const = 0;

    //! Sets the distribution-specific mean.
    virtual void setMean(double val) = 0;

    //! Returns true if the distribution is in the limit case of a Dirac delta distribution.
    virtual bool isDelta() const = 0;

    virtual std::vector<std::pair<double, double>> plotGraph() const;

    size_t nSamples() const;

    double relSamplingWidth() const { return m_relative_sampling_width; }

protected:
    mutable bool m_validated{false};
    std::vector<ParameterSample> samplesInRange(double xmin, double xmax) const;
    size_t m_n_samples;

private:
    //! Returns a range that is suitable for plotting the pdf.
    virtual std::pair<double, double> plotRange() const;

    double m_relative_sampling_width;
#endif // SWIG
};


//  ************************************************************************************************
//  class DistributionGate
//  ************************************************************************************************

//! Uniform distribution function with half width hwhm.

class DistributionGate : public Distribution1D {
public:
    DistributionGate(double min, double max, size_t n_samples = 25);

    std::vector<ParameterSample> distributionSamples() const override;

#ifndef SWIG
    void setMean(double val) override;

    DistributionGate* clone() const override;
    void validate() const override;

    double probabilityDensity(double x) const override;

    double mean() const override { return (m_min + m_max) / 2.0; }
    double min() const { return m_min; }
    double max() const { return m_max; }
    bool isDelta() const override;

private:
    std::pair<double, double> plotRange() const override;

    double m_min;
    double m_max;
#endif // SWIG
};

//  ************************************************************************************************
//  class DistributionLorentz
//  ************************************************************************************************

//! Lorentz distribution with half width hwhm.

class DistributionLorentz : public Distribution1D {
public:
    DistributionLorentz(double mean, double hwhm, size_t n_samples = 25,
                        double rel_sampling_width = 2.);

    std::vector<ParameterSample> distributionSamples() const override;

#ifndef SWIG
    void setMean(double val) override;

    DistributionLorentz* clone() const override;
    void validate() const override;

    double probabilityDensity(double x) const override;
    double mean() const override { return m_mean; }
    double hwhm() const { return m_hwhm; }
    bool isDelta() const override;

private:
    std::pair<double, double> plotRange() const override;

    double m_mean;
    double m_hwhm;
#endif // SWIG
};

//  ************************************************************************************************
//  class DistributionGaussian
//  ************************************************************************************************

//! Gaussian distribution with standard deviation std_dev.

class DistributionGaussian : public Distribution1D {
public:
    DistributionGaussian(double mean, double std_dev, size_t n_samples = 25,
                         double rel_sampling_width = 2.);

    std::vector<ParameterSample> distributionSamples() const override;

#ifndef SWIG
    void setMean(double val) override;

    DistributionGaussian* clone() const override;
    void validate() const override;

    double probabilityDensity(double x) const override;
    double mean() const override { return m_mean; }
    double stdDev() const { return m_std_dev; }
    bool isDelta() const override;

private:
    std::pair<double, double> plotRange() const override;

    double m_mean;
    double m_std_dev;
#endif // SWIG
};

//  ************************************************************************************************
//  class DistributionLogNormal
//  ************************************************************************************************

//! Log-normal distribution.

class DistributionLogNormal : public Distribution1D {
public:
    DistributionLogNormal(double median, double scale_param, size_t n_samples = 25,
                          double rel_sampling_width = 2.);

    std::vector<ParameterSample> distributionSamples() const override;

#ifndef SWIG
    void setMean(double val) override;

    DistributionLogNormal* clone() const override;
    void validate() const override;

    double probabilityDensity(double x) const override;
    double mean() const override;
    double median() const { return m_median; }
    double scalePar() const { return m_scale_param; }
    bool isDelta() const override;

private:
    std::pair<double, double> plotRange() const override;

    double m_median;
    double m_scale_param;
#endif // SWIG
};

//  ************************************************************************************************
//  class DistributionCosine
//  ************************************************************************************************

//! Cosine distribution.

class DistributionCosine : public Distribution1D {
public:
    DistributionCosine(double mean, double sigma, size_t n_samples = 25);

    std::vector<ParameterSample> distributionSamples() const override;

#ifndef SWIG
    void setMean(double val) override;

    DistributionCosine* clone() const override;
    void validate() const override;

    double probabilityDensity(double x) const override;
    double mean() const override { return m_mean; }
    double hwhm() const { return m_hwhm; }
    bool isDelta() const override;

private:
    std::pair<double, double> plotRange() const override;

    double m_mean;
    double m_hwhm;
#endif // SWIG
};

#endif // BORNAGAIN_BASE_DISTRIB_DISTRIBUTIONS_H
