//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Param/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_PARAM_DISTRIB_DISTRIBUTIONS_H
#define BORNAGAIN_PARAM_DISTRIB_DISTRIBUTIONS_H

#include "Base/Types/ICloneable.h"
#include "Param/Node/INode.h"
#include <vector>

struct ParameterSample;

//  ************************************************************************************************
//  interface IDistribution1D
//  ************************************************************************************************

//! Interface for one-dimensional distributions.

class IDistribution1D : public ICloneable, public INode {
public:
    IDistribution1D(const std::vector<double>& PValues, size_t n_samples,
                    double rel_sampling_width = 1.);

    IDistribution1D* clone() const override = 0;

    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

    //! 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;

    //! 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:
    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;

    //! Returns weighted samples from given interpolation points and probabilityDensity().
    std::vector<ParameterSample>
    generateSamplesFromValues(const std::vector<double>& sample_values) const;

    double m_relative_sampling_width;
#endif // SWIG
};


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

//! Uniform distribution function with half width hwhm.

class DistributionGate : public IDistribution1D {
public:
    DistributionGate(std::vector<double> P, size_t n_samples);

    DistributionGate(double min, double max, size_t n_samples = 25);

    std::string className() const final { return "DistributionGate"; }

    DistributionGate* clone() const override;

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

#ifndef SWIG

    std::vector<ParaMeta> parDefs() const final
    {
        return {{"Min", ""}, {"Max", ""}};
    }

    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;

    std::string validate() const override;

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

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

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

//! Lorentz distribution with half width hwhm.

class DistributionLorentz : public IDistribution1D {
public:
    DistributionLorentz(std::vector<double> P, size_t n_samples, double rel_sampling_width);

    DistributionLorentz(double mean, double hwhm, size_t n_samples = 25,
                        double rel_sampling_width = 2.);

    std::string className() const final { return "DistributionLorentz"; }

    DistributionLorentz* clone() const override;

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

#ifndef SWIG

    std::vector<ParaMeta> parDefs() const final
    {
        return {{"Mean", ""}, {"HWHM", ""}};
    }

    double probabilityDensity(double x) const override;

    double mean() const override
    {
        return m_mean;
    }

    double hwhm() const
    {
        return m_hwhm;
    }

    bool isDelta() const override;

    std::string validate() const override;

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

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

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

//! Gaussian distribution with standard deviation std_dev.

class DistributionGaussian : public IDistribution1D {
public:
    DistributionGaussian(std::vector<double> P, size_t n_samples, double rel_sampling_width);

    DistributionGaussian(double mean, double std_dev, size_t n_samples = 25,
                         double rel_sampling_width = 2.);

    DistributionGaussian* clone() const override;

    std::string className() const final { return "DistributionGaussian"; }

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

#ifndef SWIG

    std::vector<ParaMeta> parDefs() const final
    {
        return {{"Mean", ""}, {"StdDev", ""}};
    }

    double probabilityDensity(double x) const override;

    double mean() const override
    {
        return m_mean;
    }

    double getStdDev() const
    {
        return m_std_dev;
    }

    bool isDelta() const override;

    std::string validate() const override;

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

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

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

//! Log-normal distribution.

class DistributionLogNormal : public IDistribution1D {
public:
    DistributionLogNormal(std::vector<double> P, size_t n_samples, double rel_sampling_width);

    DistributionLogNormal(double median, double scale_param, size_t n_samples = 25,
                          double rel_sampling_width = 2.);

    std::string className() const final { return "DistributionLogNormal"; }

    DistributionLogNormal* clone() const override;

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

#ifndef SWIG

    std::vector<ParaMeta> parDefs() const final
    {
        return {{"Median", ""}, {"ScaleParameter", ""}};
    }

    double probabilityDensity(double x) const override;

    double mean() const override;

    double getMedian() const
    {
        return m_median;
    }

    double getScalePar() const
    {
        return m_scale_param;
    }

    bool isDelta() const override;

    std::string validate() const override;

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

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

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

//! Cosine distribution.

class DistributionCosine : public IDistribution1D {
public:
    DistributionCosine(std::vector<double> P, size_t n_samples);

    DistributionCosine(double mean, double sigma, size_t n_samples = 25);

    std::string className() const final { return "DistributionCosine"; }

    DistributionCosine* clone() const override;

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

#ifndef SWIG

    std::vector<ParaMeta> parDefs() const final
    {
        return {{"Mean", ""}, {"HWHM", ""}};
    }

    double probabilityDensity(double x) const override;

    double mean() const override
    {
        return m_mean;
    }

    double hwhm() const
    {
        return m_hwhm;
    }

    bool isDelta() const override;

    std::string validate() const override;

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

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

//  ************************************************************************************************
//  class DistributionTrapezoid
//  ************************************************************************************************

//! Trapezoidal distribution.

class DistributionTrapezoid : public IDistribution1D {
public:
    DistributionTrapezoid(std::vector<double> P, size_t n_samples);

    DistributionTrapezoid(double center, double left, double middle, double right,
                          size_t n_samples = 25);

    std::string className() const final { return "DistributionTrapezoid"; }

    DistributionTrapezoid* clone() const override;

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

#ifndef SWIG

    std::vector<ParaMeta> parDefs() const final
    {
        return {{"Center", ""}, {"LeftWidth", ""}, {"MiddleWidth", ""}, {"RightWidth", ""}};
    }

    double probabilityDensity(double x) const override;

    double mean() const override
    {
        return m_center;
    }

    double getLeftWidth() const
    {
        return m_left;
    }

    double getMiddleWidth() const
    {
        return m_middle;
    }

    double getRightWidth() const
    {
        return m_right;
    }

    bool isDelta() const override;

    std::string validate() const override;

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

    const double& m_center;
    const double& m_left;
    const double& m_middle;
    const double& m_right;
#endif // SWIG
};

#endif // BORNAGAIN_PARAM_DISTRIB_DISTRIBUTIONS_H
