//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Sample/Aggregate/Struct3D.cpp
//! @brief     Implements class Struct3D.
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2026
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "Sample/Aggregate/Struct3D.h"
#include "Base/Spin/SpinMatrix.h"
#include "Sample/Element/DiffuseElement.h"
#include "Sample/Particle/DwbaChannels.h"
#include "Sample/Particle/IParticle.h"

double Struct3D::scalarScattering(const DiffuseElement& ele) const
{
    const DwbaChannels dwba = extractDwbaChannels(ele, sliceNumber());
    const ChannelMoments moments = particle()->channelMoments(dwba);

    double result = 0;

    // Diagonal terms (m == n): cross-factors are real
    for (size_t m = 0; m < dwba.n_active; ++m) {
        const complex_t cross = structureCrossFactor(dwba.q[m], dwba.q[m]);
        const complex_t self = selfPositionFactor(dwba.q[m], dwba.q[m]);
        result += std::norm(dwba.w[m])
                  * std::real(cross * std::norm(moments.mu[m]) + self * moments.C[m][m]);
    }

    // Off-diagonal terms (m < n): exploit Hermitian symmetry, (m,n) + (n,m) = 2 Re
    for (size_t m = 0; m < dwba.n_active; ++m)
        for (size_t n = m + 1; n < dwba.n_active; ++n) {
            const complex_t cross = structureCrossFactor(dwba.q[m], dwba.q[n]);
            const complex_t self = selfPositionFactor(dwba.q[m], dwba.q[n]);
            result += 2
                      * std::real(dwba.w[m] * std::conj(dwba.w[n])
                                  * (cross * moments.mu[m] * std::conj(moments.mu[n])
                                     + self * moments.C[m][n]));
        }

    return result;
}

double Struct3D::polarizedScattering(const DiffuseElement& ele) const
{
    const PolDwbaChannels dwba = extractPolDwbaChannels(ele, sliceNumber());
    const PolChannelMoments moments = particle()->polChannelMoments(dwba);

    // Pre-compute weighted means: W_m(mu_m) for each channel
    std::array<SpinMatrix, N_POL_DWBA> w_mu;
    for (size_t m = 0; m < dwba.n_active; ++m)
        w_mu[m] = applyWeight(moments.mu[m], dwba.flux[m]);

    SpinMatrix result;

    // Diagonal terms (m == n)
    for (size_t m = 0; m < dwba.n_active; ++m) {
        const complex_t cross = structureCrossFactor(dwba.q[m], dwba.q[m]);
        const complex_t self = selfPositionFactor(dwba.q[m], dwba.q[m]);
        const SpinMatrix coherent = w_mu[m] * dwba.polarizer * w_mu[m].adjoint();
        result += cross * coherent + self * moments.C[m][m];
    }

    // Off-diagonal terms (m < n): exploit Hermitian symmetry, (m,n) + (n,m) = term + term†
    for (size_t m = 0; m < dwba.n_active; ++m)
        for (size_t n = m + 1; n < dwba.n_active; ++n) {
            const complex_t cross = structureCrossFactor(dwba.q[m], dwba.q[n]);
            const complex_t self = selfPositionFactor(dwba.q[m], dwba.q[n]);
            const SpinMatrix coherent = w_mu[m] * dwba.polarizer * w_mu[n].adjoint();
            const SpinMatrix term = cross * coherent + self * moments.C[m][n];
            result += term + term.adjoint();
        }

    return SpinMatrix::intensityTrace(ele.analyzer(), result);
}
