//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Resample/Particle/ReParticle.cpp
//! @brief     Implements class interface ReParticle.
//!
//! @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 "Resample/Particle/ReParticle.h"
#include "Base/Type/Span.h"
#include "Base/Util/Assert.h"
#include "Base/Vector/WavevectorInfo.h" // debug
#include "Sample/Material/Material.h"
#include "Sample/Material/MaterialFactoryFuncs.h"
#include "Sample/Particle/IFormFactor.h"
#include "Sample/Scattering/Rotations.h"

ReParticle::ReParticle(const std::optional<size_t>& i_layer, const IFormFactor* ff,
                       const Material* material, const Material* ambient_material,
                       const R3* position, const RotMatrix* rotMatrix)
    : IReParticle(i_layer)
    , m_ff(ff)
    , m_material(material)
    , m_ambient_material(ambient_material)
    , m_position(position)
    , m_rot_matrix(rotMatrix)
{
}

ReParticle::ReParticle(const IFormFactor* ff, const R3* position, const RotMatrix* rot)
    : ReParticle({}, ff, nullptr, nullptr, position, rot)
{
}

ReParticle::~ReParticle() = default;

ReParticle* ReParticle::clone() const
{
    return new ReParticle(i_layer(), m_ff->clone(),
                          m_material ? new Material(*m_material) : nullptr,
                          m_ambient_material ? new Material(*m_ambient_material) : nullptr,
                          m_position ? new R3(*m_position) : nullptr,
                          m_rot_matrix ? new RotMatrix(*m_rot_matrix) : nullptr);
}

void ReParticle::setMaterial(const Material& material)
{
    m_material = std::make_unique<Material>(material);
}

void ReParticle::setAmbientMaterial(const Material& ambient_material)
{
    m_ambient_material = std::make_unique<Material>(ambient_material);
}

double ReParticle::volume() const
{
    return m_ff->volume();
}

double ReParticle::radialExtension() const
{
    return m_ff->radialExtension();
}

complex_t ReParticle::theFF(const WavevectorInfo& wavevectors) const
{
    WavevectorInfo wavevectors2 =
        m_rot_matrix ? wavevectors.transformed(m_rot_matrix->Inverse()) : wavevectors;
    complex_t result = m_ff->theFF(wavevectors2);
    if (m_material && m_ambient_material)
        result = (m_material->scalarSubtrSLD(wavevectors2)
                  - m_ambient_material->scalarSubtrSLD(wavevectors2))
                 * result;
    return result * phaseFactor(wavevectors, m_position.get());
}

SpinMatrix ReParticle::thePolFF(const WavevectorInfo& wavevectors) const
{
    WavevectorInfo wavevectors2 =
        m_rot_matrix ? wavevectors.transformed(m_rot_matrix->Inverse()) : wavevectors;
    SpinMatrix result = m_ff->thePolFF(wavevectors2);
    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)
        SpinMatrix time_reverse_conj(0, 1, -1, 0);
        // the interaction and time reversal taken together:
        SpinMatrix V_eff = time_reverse_conj
                           * (m_material->polarizedSubtrSLD(wavevectors2)
                              - m_ambient_material->polarizedSubtrSLD(wavevectors2));
        result *= V_eff;
    }
    return result * phaseFactor(wavevectors, m_position.get());
}

Span ReParticle::zSpan() const
{
    RotMatrix transform = m_rot_matrix ? *m_rot_matrix : RotMatrix();
    std::unique_ptr<const IRotation> total_rotation(IRotation::createRotation(transform));
    Span span = m_ff->spanZ(total_rotation.get());
    if (m_position)
        return span + m_position->z();
    return span;
}

bool ReParticle::contains(const R3& position) const
{
    return m_ff->contains(position);
}

bool ReParticle::consideredEqualTo(const IReParticle& ire) const
{
    if (const auto* re = dynamic_cast<const ReParticle*>(&ire)) {
        bool same_material = (!m_material && !re->material())
                             || (m_material && re->material() && (*m_material == *re->material()));
        bool same_ambient = (!m_ambient_material && !re->ambientMaterial())
                            || (m_ambient_material && re->ambientMaterial()
                                && (*m_ambient_material == *re->ambientMaterial()));
        bool same_rotation =
            (!m_rot_matrix && !re->rotMatrix())
            || (m_rot_matrix && re->rotMatrix() && (*m_rot_matrix == *re->rotMatrix()));
        bool same_ff = (m_ff && re->ff() && m_ff->isEqualTo(re->ff()));

        // translated position is not compared here, this is intentional
        return IReParticle::consideredEqualTo(ire) && same_ff && same_material && same_ambient
               && same_rotation;
    }
    return false;
}
