//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Sample/Correlation/Profiles2D.h
//! @brief     Defines class interface IProfile2D, 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_PROFILES2D_H
#define BORNAGAIN_SAMPLE_CORRELATION_PROFILES2D_H

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

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

using PhysConsts::pi;

//! Interface for two-dimensional distributions in Fourier space.

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

    std::vector<ParaMeta> parDefs() const override
    {
        return {{"OmegaX", "nm"}, {"OmegaY", "nm"}, {"Gamma", "rad"}};
    }

    double omegaX() const { return m_omega_x; }
    double omegaY() const { return m_omega_y; }
    double decayLengthX() const { return m_omega_x; }
    double decayLengthY() const { return m_omega_y; }
    double gamma() const { return m_gamma; }

    //! Angle in direct space between X- and Y-axis of distribution.
    double delta() const { return (pi / 2); }

    //! Fourier transformed distribution for q in X,Y coordinates
    //! the original distribution (in real space) is assumed to be normalized:
    //! total integral is equal to 1
    virtual double standardizedFT2D(double qx, double qy) const = 0;
    virtual double decayFT2D(double qx, double qy) const = 0;
#ifndef SWIG
    IProfile2D* clone() const override = 0;

    virtual std::unique_ptr<IDistribution2DSampler> 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:
    double sumsq(double qx, double qy) const;

    const double& m_omega_x; //!< Half-width along x axis
    const double& m_omega_y; //!< Half-width along y axis
    const double& m_gamma;   //!< direct-space orientation with respect to the first lattice vector
};


//! Two-dimensional Cauchy distribution in Fourier space;
//! corresponds to a normalized exp(-r) in real space,
//! with \f$r=\sqrt{(\frac{x}{\omega_x})^2 + (\frac{y}{\omega_y})^2}\f$.

class Profile2DCauchy : public IProfile2D {
public:
    Profile2DCauchy(std::vector<double> P);
    Profile2DCauchy(double omega_x, double omega_y, double gamma);

    std::string className() const final { return "Profile2DCauchy"; }
    double standardizedFT2D(double qx, double qy) const override;
    double decayFT2D(double qx, double qy) const override;

#ifndef SWIG
    Profile2DCauchy* clone() const override;

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

//! Two-dimensional Gauss distribution in Fourier space;
//! corresponds to normalized exp(-r^2/2) in real space
//! with \f$r=\sqrt{(\frac{x}{\omega_x})^2 + (\frac{y}{\omega_y})^2}\f$.

class Profile2DGauss : public IProfile2D {
public:
    Profile2DGauss(std::vector<double> P);
    Profile2DGauss(double omega_x, double omega_y, double gamma);

    std::string className() const final { return "Profile2DGauss"; }
    double standardizedFT2D(double qx, double qy) const override;
    double decayFT2D(double qx, double qy) const override;

#ifndef SWIG
    Profile2DGauss* clone() const override;

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

//! Two-dimensional gate distribution in Fourier space;
//! corresponds to normalized constant if r<1 (and 0 otherwise) in real space,
//! with \f$r=\sqrt{(\frac{x}{\omega_x})^2 + (\frac{y}{\omega_y})^2}\f$.

class Profile2DGate : public IProfile2D {
public:
    Profile2DGate(std::vector<double> P);
    Profile2DGate(double omega_x, double omega_y, double gamma);

    std::string className() const final { return "Profile2DGate"; }
    double standardizedFT2D(double qx, double qy) const override;
    double decayFT2D(double qx, double qy) const override;

#ifndef SWIG
    Profile2DGate* clone() const override;

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

//! Two-dimensional cone distribution in Fourier space;
//! corresponds to 1-r if r<1 (and 0 otherwise) in real space
//! with \f$r=\sqrt{(\frac{x}{\omega_x})^2 + (\frac{y}{\omega_y})^2}\f$.

class Profile2DCone : public IProfile2D {
public:
    Profile2DCone(std::vector<double> P);
    Profile2DCone(double omega_x, double omega_y, double gamma);

    std::string className() const final { return "Profile2DCone"; }
    double standardizedFT2D(double qx, double qy) const override;
    double decayFT2D(double qx, double qy) const override;

#ifndef SWIG
    Profile2DCone* clone() const override;

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

//! Two-dimensional Voigt distribution in Fourier space;
//! corresponds to eta*Gauss + (1-eta)*Cauchy

class Profile2DVoigt : public IProfile2D {
public:
    Profile2DVoigt(std::vector<double> P);
    Profile2DVoigt(double omega_x, double omega_y, double gamma, double eta);

    std::string className() const final { return "Profile2DVoigt"; }
    std::vector<ParaMeta> parDefs() const final
    {
        return {{"OmegaX", "nm"}, {"OmegaY", "nm"}, {"Gamma", "rad"}, {"Eta", ""}};
    }
    double standardizedFT2D(double qx, double qy) const override;
    double decayFT2D(double qx, double qy) const override;
    double eta() const { return m_eta; } //!< balances between Gauss (eta=0) and Lorentz (eta=1)

    std::string validate() const override;

#ifndef SWIG
    Profile2DVoigt* clone() const override;

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

protected:
    const double& m_eta;
};

#endif // BORNAGAIN_SAMPLE_CORRELATION_PROFILES2D_H
