//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Sample/Correlation/Profiles1D.h
//! @brief     Defines class interface IProfile1D, and children thereof.
//!
//! @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_SAMPLE_CORRELATION_PROFILES1D_H
#define BORNAGAIN_SAMPLE_CORRELATION_PROFILES1D_H

#include "Base/Type/ICloneable.h"
#include "Param/Node/INode.h"

#ifndef SWIG
#include "Sample/Correlation/IDistribution1DSampler.h"
#endif // SWIG


//! Interface for a one-dimensional distribution, with normalization adjusted so that
//! the Fourier transform standardizedFT(q) is a decay function that starts at standardizedFT(0)=1.

class IProfile1D : public ICloneable, public INode {
public:
    IProfile1D(const std::vector<double>& PValues);

    std::vector<ParaMeta> parDefs() const override { return {{"Omega", "nm"}}; }

    //! Returns Fourier transform of the normalized distribution;
    //! is a decay function starting at standardizedFT(0)=1.
    virtual double standardizedFT(double q) const = 0;
    //! Returns Fourier transform of the distribution scaled as decay function f(x)/f(0).
    virtual double decayFT(double q) const = 0;

    double omega() const { return m_omega; }
    double decayLength() const { return m_omega; }

    //! Returns the negative of the second order derivative in q space around q=0
    virtual double qSecondDerivative() const = 0;
#ifndef SWIG
    IProfile1D* clone() const override = 0;

    virtual std::unique_ptr<IDistribution1DSampler> createSampler() const = 0;

    //! Creates the Python constructor of this class (or derived classes)
    virtual std::string pythonConstructor() const;
#endif

    std::string validate() const override;

protected:
    const double& m_omega; //!< half-width
};


//! Exponential IProfile1D exp(-|omega*x|);
//! its Fourier transform standardizedFT(q) is a Cauchy-Lorentzian starting at standardizedFT(0)=1.

class Profile1DCauchy : public IProfile1D {
public:
    Profile1DCauchy(std::vector<double> P);
    Profile1DCauchy(double omega);

    std::string className() const final { return "Profile1DCauchy"; }
    double standardizedFT(double q) const override;
    double decayFT(double q) const override;
    double qSecondDerivative() const override;

#ifndef SWIG
    Profile1DCauchy* clone() const override;

    std::unique_ptr<IDistribution1DSampler> createSampler() const override;
#endif
};

//! Gaussian IProfile1D;
//! its Fourier transform standardizedFT(q) is a Gaussian starting at standardizedFT(0)=1.

class Profile1DGauss : public IProfile1D {
public:
    Profile1DGauss(std::vector<double> P);
    Profile1DGauss(double omega);

    std::string className() const final { return "Profile1DGauss"; }
    double standardizedFT(double q) const override;
    double decayFT(double q) const override;
    double qSecondDerivative() const override;

#ifndef SWIG
    Profile1DGauss* clone() const override;

    std::unique_ptr<IDistribution1DSampler> createSampler() const override;
#endif
};

//! Square gate IProfile1D;
//! its Fourier transform standardizedFT(q) is a sinc function starting at standardizedFT(0)=1.

class Profile1DGate : public IProfile1D {
public:
    Profile1DGate(std::vector<double> P);
    Profile1DGate(double omega);

    std::string className() const final { return "Profile1DGate"; }
    double standardizedFT(double q) const override;
    double decayFT(double q) const override;
    double qSecondDerivative() const override;

#ifndef SWIG
    Profile1DGate* clone() const override;

    std::unique_ptr<IDistribution1DSampler> createSampler() const override;
#endif
};

//! Triangle IProfile1D [1-|x|/omega if |x|<omega, and 0 otherwise];
//! its Fourier transform standardizedFT(q) is a squared sinc function starting at
//! standardizedFT(0)=1.

class Profile1DTriangle : public IProfile1D {
public:
    Profile1DTriangle(std::vector<double> P);
    Profile1DTriangle(double omega);

    std::string className() const final { return "Profile1DTriangle"; }
    double standardizedFT(double q) const override;
    double decayFT(double q) const override;
    double qSecondDerivative() const override;

#ifndef SWIG
    Profile1DTriangle* clone() const override;

    std::unique_ptr<IDistribution1DSampler> createSampler() const override;
#endif
};

//! IProfile1D consisting of one cosine wave
//! [1+cos(pi*x/omega) if |x|<omega, and 0 otherwise];
//! its Fourier transform standardizedFT(q) starts at standardizedFT(0)=1.

class Profile1DCosine : public IProfile1D {
public:
    Profile1DCosine(std::vector<double> P);
    Profile1DCosine(double omega);

    std::string className() const final { return "Profile1DCosine"; }
    double standardizedFT(double q) const override;
    double decayFT(double q) const override;
    double qSecondDerivative() const override;

#ifndef SWIG
    Profile1DCosine* clone() const override;

    std::unique_ptr<IDistribution1DSampler> createSampler() const override;
#endif
};

//! IProfile1D that provides a Fourier transform standardizedFT(q) in form
//! of a pseudo-Voigt decay function eta*Gauss + (1-eta)*Cauchy, with both components
//! starting at 1 for q=0.

class Profile1DVoigt : public IProfile1D {
public:
    Profile1DVoigt(std::vector<double> P);
    Profile1DVoigt(double omega, double eta);

    std::string className() const final { return "Profile1DVoigt"; }
    std::vector<ParaMeta> parDefs() const final { return {{"Omega", "nm"}, {"Eta", ""}}; }
    double standardizedFT(double q) const override;
    double decayFT(double q) const override;
    double eta() const { return m_eta; }
    double qSecondDerivative() const override;

#ifndef SWIG
    Profile1DVoigt* clone() const override;

    std::unique_ptr<IDistribution1DSampler> createSampler() const override;
    std::string pythonConstructor() const override;
#endif

    std::string validate() const override;

protected:
    const double& m_eta; //!< balances between Gauss (eta=0) and Lorentz (eta=1)
};

#endif // BORNAGAIN_SAMPLE_CORRELATION_PROFILES1D_H
