//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Sample/Aggregate/Struct3D.h
//! @brief     Defines class Struct3D.
//!
//! @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_AGGREGATE_STRUCT3D_H
#define BORNAGAIN_SAMPLE_AGGREGATE_STRUCT3D_H

#include "Sample/Aggregate/Struct.h"
#ifndef SWIG
#include "Base/Util/Assert.h"
#include <heinz/Complex.h>
#endif
#include <optional>

class WavevectorInfo;

class Struct3D : public Struct {
#ifndef SWIG
public:
    Struct3D* clone() const override = 0;

    //! Returns the number of particles per unit volume (1/nm³).
    virtual double volumeNumberDensity() const = 0;

    // === Cross-factor interface (implemented by concrete subclasses) ===

    //! Averaged structural cross-term for DWBA channels m, n:
    //! <A(q_m) A*(q_n)> / N
    //! = (1/N) * <sum_{k,j} exp(i q_m·R_k) exp(-i q_n*·R_j)>,
    //! where A(q) = sum_k exp(i q·R_k)
    //! Includes both diagonal self terms (k = j) and off-diagonal pair terms (k != j).
    virtual complex_t structureCrossFactor(const WavevectorInfo& q_m,
                                           const WavevectorInfo& q_n) const = 0;

    //! Averaged diagonal self term for DWBA channels m, n:
    //! <A(q_m - q_n*)> / N
    //! = (1/N) * <sum_k exp(i (q_m - q_n*)·R_k)>
    //! = <exp(i (q_m - q_n*)·R)>
    //! = <exp(i (q_m,z - q_n,z*) z)>.
    virtual complex_t selfPositionFactor(const WavevectorInfo& q_m,
                                         const WavevectorInfo& q_n) const = 0;

    // === Scattering evaluation (generic, uses cross-factors) ===

    double scalarScattering(const DiffuseElement& ele) const override;
    double polarizedScattering(const DiffuseElement& ele) const override;

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

    size_t sliceNumber() const
    {
        ASSERT(m_slice_number.has_value());
        return *m_slice_number;
    }
    void setSliceNumber(size_t n) { m_slice_number = n; }

    double thickness() const { return m_thickness; }
    void setThickness(double thickness) { m_thickness = thickness; }

    //! Allowed z-range for position averaging (set during resampling).
    double zRangeLow() const { return m_z_range_low; }
    double zRangeHigh() const { return m_z_range_high; }
    void setZRange(double low, double high)
    {
        m_z_range_low = low;
        m_z_range_high = high;
    }

protected:
    Struct3D(double coverage, const IParticle& particle)
        : Struct(coverage, particle)
    {
    }

private:
    std::optional<size_t> m_slice_number;
    double m_thickness{0};
    double m_z_range_low{0};
    double m_z_range_high{0};

#endif // SWIG
};

#endif // BORNAGAIN_SAMPLE_AGGREGATE_STRUCT3D_H
