//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Tests/Unit/Sim/ParticleSimulationTest.cpp
//! @brief     Tests class ParticleSimulation.
//!
//! @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 "Sim/Simulation/ParticleSimulation.h"
#include "Base/Axis/Pixel.h"
#include "Base/Vector/GisasDirection.h"
#include "Base/Vector/WavevectorInfo.h"
#include "Device/Beam/Beam.h"
#include "Device/Data/Datafield.h"
#include "Device/Detector/SphericalDetector.h"
#include "Sample/HardParticle/Sphere.h"
#include "Sample/Material/SLDMaterial.h"
#include "Sample/Material/Vacuum.h"
#include "Sample/Particle/Mixture.h"
#include "Sample/Particle/Particle.h"
#include "Tests/GTestWrapper/google_test.h"
#include <numbers>

// Regression test for #1377: Mixture intensity must be the incoherent average <|F|^2>,
// not |<F>|^2.
TEST(ParticleSimulation, MixtureUsesIncoherentIntensity)
{
    const Beam beam(1.0, 1.54, 0.17, 0.0);
    const SphericalDetector detector(1, 0.16, 0.20, 1, 0.23, 0.27);
    auto pixel = detector.createPixel(0);
    const R3 k_i = vecOfLambdaAlphaPhi(beam.wavelength(), -beam.alpha_i(), -beam.phi_i());
    const R3 k_f = pixel->getK(0.5, 0.5, beam.wavelength());
    const WavevectorInfo wavevectors(k_i, k_f, beam.wavelength());
    const double qx = std::real(wavevectors.getQ().x());

    ASSERT_GT(std::abs(qx), 1e-6);

    const Vacuum ambient;
    const SLDMaterial material("particle", MaterialColor(0.5, 0.5, 0.5), 1.0, 0.0);

    Particle left(material, Sphere(1.3));
    left.setAmbientMaterial(ambient);

    Particle right(material, Sphere(1.3));
    right.setAmbientMaterial(ambient);
    right.translate(std::numbers::pi / qx, 0.0, 0.0);

    const complex_t ff_left = left.theFF(wavevectors);
    const complex_t ff_right = right.theFF(wavevectors);
    const double expected_intensity = 0.5 * std::norm(ff_left) + 0.5 * std::norm(ff_right);
    const double coherent_intensity = std::norm(0.5 * (ff_left + ff_right));

    ASSERT_GT(expected_intensity, 1e-12);
    EXPECT_NEAR(std::abs(ff_left + ff_right), 0.0, std::abs(ff_left) * 1e-12 + 1e-14);
    EXPECT_LT(coherent_intensity, expected_intensity * 1e-10);

    Mixture mixture;
    mixture.addParticle(left, 1.0);
    mixture.addParticle(right, 1.0);
    mixture.validate();

    const Datafield result = ParticleSimulation(beam, mixture, detector).simulate();
    const double tol = expected_intensity * 1e-12 + 1e-14;

    ASSERT_EQ(result.size(), 1u);
    EXPECT_NEAR(result.valAt(0), expected_intensity, tol);
}
