//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Sample/Particle/IParticle.cpp
//! @brief     Implements interface IParticle.
//!
//! @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/IParticle.h"
#include "Base/Util/Assert.h"
#include "Sample/Element/DiffuseElement.h"
#include "Sample/Particle/Rotations.h"

IParticle::IParticle() = default;

IParticle::~IParticle() = default;

IParticle* IParticle::translate(const R3& translation)
{
    m_position += translation;
    return this;
}

IParticle* IParticle::rotate(const Rotation& rotation)
{
    if (m_rotation)
        m_rotation.reset(newProduct(rotation, *m_rotation));
    else
        m_rotation.reset(rotation.clone());
    m_position = rotation.transformed(m_position);
    // Recompute cached rotation matrix immediately
    if (m_rotation && !m_rotation->isIdentity())
        m_rot_matrix = std::make_unique<RotMatrix>(m_rotation->rotMatrix());
    else
        m_rot_matrix.reset();
    return this;
}

double IParticle::height() const
{
    return intrinsicTopZ() - intrinsicBottomZ();
}

double IParticle::height_above_center() const
{
    return intrinsicTopZ() - intrinsicCenterZ();
}

double IParticle::height_below_center() const
{
    return intrinsicCenterZ() - intrinsicBottomZ();
}

IParticle* IParticle::rotate(const Rotation* rotation)
{
    if (rotation)
        return rotate(*rotation);
    return this;
}

const Rotation* IParticle::particleRotation() const
{
    return m_rotation.get();
}

std::vector<std::unique_ptr<IParticle>> IParticle::decompose() const
{
    std::vector<std::unique_ptr<IParticle>> result;
    result.emplace_back(std::unique_ptr<IParticle>{this->clone()});
    return result;
}

size_t IParticle::sliceNumber() const
{
    ASSERT(m_slice_number.has_value());
    return *m_slice_number;
}

const RotMatrix* IParticle::rotMatrix() const
{
    if (!m_rot_matrix && m_rotation && !m_rotation->isIdentity())
        m_rot_matrix = std::make_unique<RotMatrix>(m_rotation->rotMatrix());
    return m_rot_matrix.get();
}

SscaMoments IParticle::sscaMoments(const DiffuseElement& ele, double kappa_qp,
                                   double mean_extension) const
{
    const auto [m, v] = scatteringMoments(ele);
    const complex_t phase = exp_I(kappa_qp * (radialExtension() - mean_extension));
    return {v + std::norm(m), phase * m, std::conj(phase) * m, phase * phase};
}

PolSscaMoments IParticle::polSscaMoments(const DiffuseElement& ele, double kappa_qp,
                                         double mean_extension) const
{
    const auto [m, v] = polScatteringMoments(ele);
    const complex_t phase = exp_I(kappa_qp * (radialExtension() - mean_extension));
    return {v + m * ele.polarizer() * m.adjoint(), phase * m, std::conj(phase) * m, phase * phase};
}

void IParticle::instructClone(IParticle* dest) const
{
    dest->rotate(particleRotation());
    dest->translate(particlePosition());
    if (m_slice_number)
        dest->setSliceNumber(*m_slice_number);
}
