//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Sample/Particle/Mesocrystal.h
//! @brief     Defines class Mesocrystal.
//!
//! @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_MESOCRYSTAL_H
#define BORNAGAIN_SAMPLE_PARTICLE_MESOCRYSTAL_H

#include "Sample/Particle/IParticle.h"

class Crystal;
class HardFormfactor;
class Lattice3D;
class Particle;
struct MesoOptions;

#ifndef SWIG
#include <optional>
#endif // SWIG

//! A particle with a crystalline inner structure, made of smaller particles,
//! and an outer shape described by a particle form factor.

class Mesocrystal : public IParticle {
public:
    Mesocrystal(const Crystal& crystal, const HardFormfactor& formfactor);
    ~Mesocrystal() override;

#ifndef SWIG
    Mesocrystal* clone() const override;

    const HardFormfactor* outerShape() const { return m_outer_formfactor.get(); }
    const Crystal& particleStructure() const;

    void setSliceNumber(size_t n) override;

    double bottomZ() const override;
    double topZ() const override;
    double volumeToAdmix() const override;

    //! A workaround to set custom volumeToAdmix() for sliced shapes
    void setVolumeToAdmix(double volume);
    double slicedVolumeToAdmix(const Particle& slicedOuterShape) const;

    std::unique_ptr<const Material> materialToAdmix() const override;
    bool hasRadius() const override;
    double radius() const override;
    double radialExtension() const override;
    bool containsSoftParticle() const override;

    //! Returns mesocrystal-local lattice positions of occupied nodes.
    std::vector<R3> calcBasisPositions() const;
    size_t numNodes() const { return basisIndexes().nodes.size(); }

    //! Prepares the mesocrystal for scattering computation.
    void prepareForComputation(const Material& ambientMat, const MesoOptions& meso_options);

    complex_t theFF(const WavevectorInfo& wavevectors) const override;
    SpinMatrix thePolFF(const WavevectorInfo& wavevectors) const override;

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

private:
    //! Occupied mesocrystal nodes in absolute lattice coordinates.
    struct ShapeIndexes {
        struct KRange {
            int begin; //!< First k index (inclusive)
            int end;   //!< Last k index (inclusive)
        };

        struct KColumn {
            int i, j;                    //!< Absolute (i,j) of this column
            std::vector<KRange> kRanges; //!< Contiguous occupied k-ranges
        };

        std::vector<I3> nodes;         //!< All occupied lattice indices
        std::vector<KColumn> kColumns; //!< Occupied columns with k-ranges
        I3 min;                        //!< Per-axis minimum index
        I3 max;                        //!< Per-axis maximum index
        int maxKCount;                 //!< Longest contiguous k-run
    };

    R3 latticePosition(const I3& index) const;
    //! True if node at index (shifted by basis position) lies inside the outer shape.
    bool indexInShape(const I3& index) const;
    const ShapeIndexes& basisIndexes() const;
    //! Enumerates occupied lattice nodes in mesocrystal-local coordinates.
    ShapeIndexes calcBasisIndexes() const;
    static complex_t summedPhaseFactors(const C3& q, const Lattice3D& lattice,
                                        const ShapeIndexes& shape);
    complex_t structureAmplitude(const C3& q) const;
    complex_t outerShapeFF(const WavevectorInfo& wavevectors) const;
    complex_t reciprocalSpaceSum(const WavevectorInfo& wavevectors) const;
    SpinMatrix reciprocalSpaceSumPol(const WavevectorInfo& wavevectors) const;
    void calculateLargestReciprocalDistance();
    complex_t debyeWallerFactor(const C3& q) const;

    // Model data
    const std::unique_ptr<Crystal> m_crystal;                 //!< Crystalline inner structure
    const std::unique_ptr<HardFormfactor> m_outer_formfactor; //!< Outer shape of the mesocrystal

    std::optional<double> m_admix_volume; // set by setVolumeToAdmix()

    // Computation data (set by prepareForComputation)
    std::unique_ptr<const Lattice3D> m_lab_lattice;
    std::unique_ptr<IParticle> m_lab_basis;
    bool m_use_reciprocal_sum{false};
    double m_radius_factor{0};
    double m_max_rec_length{0};
    mutable std::optional<ShapeIndexes> m_cached_scattering_indexes;

#endif // SWIG
};

#endif // BORNAGAIN_SAMPLE_PARTICLE_MESOCRYSTAL_H
