//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Sample/Particle/Particle.cpp
//! @brief     Implements class Particle.
//!
//! @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)
//
//  ************************************************************************************************

#include "Sample/Particle/Particle.h"
#include "Base/Spin/SpinMatrix.h"
#include "Base/Util/Assert.h"
#include "Base/Vector/RotMatrix.h"
#include "Base/Vector/WavevectorInfo.h"
#include "Sample/Element/DiffuseElement.h"
#include "Sample/Material/Material.h"
#include "Sample/Particle/DwbaChannels.h"
#include "Sample/Particle/Formfactor.h"
#include "Sample/Particle/Rotations.h"
#include "Sample/Particle/SoftFormfactor.h"

Particle::Particle(const Material& material, const Formfactor& formfactor)
    : m_material(material.clone())
    , m_formfactor(formfactor.clone())
{
}

Particle::~Particle() = default;

Particle* Particle::clone() const
{
    ASSERT(m_formfactor);
    auto* result = new Particle(*m_material, *m_formfactor);
    instructClone(result);
    if (m_ambient_material)
        result->setAmbientMaterial(*m_ambient_material);
    if (m_admix_volume)
        result->setVolumeToAdmix(*m_admix_volume);
    return result;
}

void Particle::validate() const
{
    m_formfactor->validate();
}

double Particle::volumeToAdmix() const
{
    if (m_admix_volume.has_value())
        return *m_admix_volume;

    return m_formfactor->volume();
}

void Particle::setVolumeToAdmix(double volume)
{
    m_admix_volume = volume;
}

std::unique_ptr<const Material> Particle::materialToAdmix() const
{
    return std::unique_ptr<const Material>(m_material->clone());
}

void Particle::setMaterial(const Material& material)
{
    m_material.reset(material.clone());
}

void Particle::setFormfactor(const Formfactor* ff)
{
    m_formfactor.reset(ff);
}

void Particle::setAmbientMaterial(const Material& material)
{
    m_ambient_material.reset(material.clone());
}

double Particle::bottomZ() const
{
    return m_formfactor->zBottom(particleRotation()) + particlePosition().z();
}

double Particle::topZ() const
{
    return m_formfactor->zTop(particleRotation()) + particlePosition().z();
}

double Particle::radius() const
{
    return m_formfactor->radialExtension();
}

double Particle::radialExtension() const
{
    return m_formfactor->radialExtension();
}

bool Particle::containsSoftParticle() const
{
    return dynamic_cast<const SoftFormfactor*>(m_formfactor.get()) != nullptr;
}

complex_t Particle::theFF(const WavevectorInfo& wavevectors) const
{
    WavevectorInfo wv2 = wavevectors.backTransformed(rotMatrix());
    complex_t result = m_formfactor->theFF(wv2);
    if (m_material && m_ambient_material)
        result *= m_material->scalarSubtrSLD(wv2) - m_ambient_material->scalarSubtrSLD(wv2);
    const R3& pos = particlePosition();
    if (pos != R3())
        result *= exp_I(pos.dot(wavevectors.getQ()));
    return result;
}

SpinMatrix Particle::thePolFF(const WavevectorInfo& wavevectors) const
{
    WavevectorInfo wv2 = wavevectors.backTransformed(rotMatrix());
    SpinMatrix result = m_formfactor->thePolFF(wv2);
    if (m_material && m_ambient_material) {
        // the conjugated linear part of time reversal operator T
        // (T=UK with K complex conjugate operator and U is linear)
        // the interaction and time reversal taken together:
        SpinMatrix V_eff =
            SpinMatrix(0, 1, -1, 0)
            * (m_material->polarizedSubtrSLD(wv2) - m_ambient_material->polarizedSubtrSLD(wv2));
        result *= V_eff;
    }
    const R3& pos = particlePosition();
    if (pos != R3())
        result *= exp_I(pos.dot(wavevectors.getQ()));
    return result;
}

ScatteringMoments Particle::scatteringMoments(const DiffuseElement& ele) const
{
    const DwbaChannels dwba = extractDwbaChannels(ele, sliceNumber());
    const ChannelMoments moments = channelMoments(dwba);

    complex_t mean = 0;
    for (size_t m = 0; m < dwba.n_active; ++m)
        mean += dwba.w[m] * moments.mu[m];
    return {mean, 0};
}

PolScatteringMoments Particle::polScatteringMoments(const DiffuseElement& ele) const
{
    const PolDwbaChannels dwba = extractPolDwbaChannels(ele, sliceNumber());
    const PolChannelMoments moments = polChannelMoments(dwba);

    SpinMatrix mean;
    for (size_t m = 0; m < dwba.n_active; ++m)
        mean += applyWeight(moments.mu[m], dwba.flux[m]);
    return {mean, SpinMatrix()};
}

ChannelMoments Particle::channelMoments(const DwbaChannels& channels) const
{
    ChannelMoments result; // result.C stays zero: deterministic particle has no covariance
    for (size_t m = 0; m < channels.n_active; ++m)
        result.mu[m] = theFF(channels.q[m]);
    return result;
}

PolChannelMoments Particle::polChannelMoments(const PolDwbaChannels& channels) const
{
    PolChannelMoments result; // result.C stays zero: deterministic particle has no covariance
    for (size_t m = 0; m < channels.n_active; ++m)
        result.mu[m] = thePolFF(channels.q[m]);
    return result;
}
