//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Sample/Particle/IParticle.h
//! @brief     Defines interface IParticle.
//!
//! @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_PARTICLE_IPARTICLE_H
#define BORNAGAIN_SAMPLE_PARTICLE_IPARTICLE_H

#include "Base/Type/Cloneable.h"
#include "Base/Vector/RotMatrix.h"
#include "Sample/Material/Material.h"
#ifndef SWIG
#include "Base/Spin/SpinMatrix.h"
#include "Sample/Particle/DwbaChannels.h"
#endif
#include <heinz/Vectors3D.h>
#include <memory>
#include <optional>

class DiffuseElement;
class Rotation;
class WavevectorInfo;

#ifndef SWIG
//! Mean scattering amplitude and its variance.
struct ScatteringMoments {
    complex_t mean;  //!< <F>
    double variance; //!< <|F|²> - |<F>|²
};

//! Mean scattering matrix and its variance.
struct PolScatteringMoments {
    SpinMatrix mean;     //!< <M>
    SpinMatrix variance; //!< <M · P_in · M†> - <M> · P_in · <M>†
};

//! SSCA (Size-Spacing Coupling Approximation) scattering data.
//!
//! In a polydisperse paracrystal, the SSCA couples particle size
//! to lattice spacing: larger particles sit farther apart. This is
//! controlled by a user parameter κ (kappa, typically 0 or 1).
//! The phase shift for particle i is exp(iκqΔRᵢ), where
//! q is the in-plane scattering vector magnitude and
//! ΔRᵢ = Rᵢ − <R> is the particle's radial extension minus the
//! ensemble mean. Angle brackets <…> denote averaging over the
//! size distribution (e.g. over Mixture components).
struct SscaMoments {
    double intensity = 0.0;         //!< <|F|²>
    complex_t shifted_plus = 0.0;   //!< <F · exp(+iκqΔR)>
    complex_t shifted_minus = 0.0;  //!< <F · exp(-iκqΔR)>
    complex_t phase_coupling = 0.0; //!< <exp(2iκqΔR)>
};

//! Polarized version of SscaMoments.
struct PolSscaMoments {
    SpinMatrix intensity;           //!< <M · P_in · M†>
    SpinMatrix shifted_plus;        //!< <M · exp(+iκqΔR)>
    SpinMatrix shifted_minus;       //!< <M · exp(-iκqΔR)>
    complex_t phase_coupling = 0.0; //!< <exp(2iκqΔR)>
};
#endif

//! Abstract base class for Particle, Compound, CoreAndShell, Mesocrystal, Mixture.
//! Provides position/rotation and form factor.

class IParticle : public Cloneable {
public:
    ~IParticle() override;

    //! Translates the particle, and returns this.
    IParticle* translate(const R3& translation);

    //! Translates the particle, and returns this.
    IParticle* translate(double x, double y, double z) { return translate(R3(x, y, z)); }

    //! Rotates the particle, and returns this.
    IParticle* rotate(const Rotation& rotation);

    double height() const;
    double height_above_center() const;
    double height_below_center() const;

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

    IParticle* rotate(const Rotation* rotation);
    const R3& particlePosition() const { return m_position; }
    const Rotation* particleRotation() const;

    //! Effective volume used to weight the particle in material admixtures.
    virtual double volumeToAdmix() const = 0;

    //! Vertical extent of the particle (includes position)
    virtual double bottomZ() const = 0;
    virtual double topZ() const = 0;
    double centerZ() const { return (bottomZ() + topZ()) / 2; }
    double extensionZ() const { return topZ() - bottomZ(); }
    virtual bool hasRadius() const { return true; }
    virtual double radius() const = 0;

    //! Vertical extent without position (intrinsic to particle shape and rotation)
    double intrinsicBottomZ() const { return bottomZ() - particlePosition().z(); }
    double intrinsicTopZ() const { return topZ() - particlePosition().z(); }
    double intrinsicCenterZ() const { return centerZ() - particlePosition().z(); }

    //! Decompose in constituent IParticle objects
    virtual std::vector<std::unique_ptr<IParticle>> decompose() const;

    //! Returns constituent particles (for Mixture: its components; else {this})
    virtual std::vector<const IParticle*> particles() const { return {this}; }

    // === Calculation context (set during resampling) ===

    size_t sliceNumber() const;
    virtual void setSliceNumber(size_t n) { m_slice_number = n; }

    //! Returns material for averaging, computed from subparticles weighted by volume.
    virtual std::unique_ptr<const Material> materialToAdmix() const = 0;

    //! Returns cached rotation matrix (computed from particleRotation())
    const RotMatrix* rotMatrix() const;

    //! Returns the radial size of the particle. Used for SSCA calculations.
    virtual double radialExtension() const = 0;

    //! Returns true if any constituent particle is soft.
    virtual bool containsSoftParticle() const = 0;

    //! Returns scattering amplitude for complex wavevectors ki, kf.
    virtual complex_t theFF(const WavevectorInfo& wavevectors) const = 0;
    virtual SpinMatrix thePolFF(const WavevectorInfo& wavevectors) const = 0;

    virtual ScatteringMoments scatteringMoments(const DiffuseElement& ele) const = 0;
    virtual PolScatteringMoments polScatteringMoments(const DiffuseElement& ele) const = 0;
    virtual ChannelMoments channelMoments(const DwbaChannels& channels) const = 0;
    virtual PolChannelMoments polChannelMoments(const PolDwbaChannels& channels) const = 0;

    virtual SscaMoments sscaMoments(const DiffuseElement& ele, double kappa_qp,
                                    double mean_extension) const;
    virtual PolSscaMoments polSscaMoments(const DiffuseElement& ele, double kappa_qp,
                                          double mean_extension) const;

    virtual void validate() const {}

protected:
    mutable bool m_validated{false};

    IParticle();

    //! Copies position, rotation, and slice number to destination clone.
    void instructClone(IParticle* dest) const;

    std::optional<size_t> m_slice_number;
    mutable std::unique_ptr<RotMatrix> m_rot_matrix;

private:
    R3 m_position{};
    std::unique_ptr<Rotation> m_rotation;
#endif // SWIG
};

#endif // BORNAGAIN_SAMPLE_PARTICLE_IPARTICLE_H
