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

//! The mathematics implemented here is described in full detail in a paper
//! by Joachim Wuttke, entitled
//! "Form factor (Fourier shape transform) of polygon and polyhedron."

#include "Sample/HardParticle/FormfactorPolyhedron.h"
#include "Base/Util/Assert.h"
#include "Sample/Particle/PolyhedralUtil.h"
#include "Sample/Particle/Rotations.h"
#include <ff/Prism.h>

FormfactorPolyhedron::FormfactorPolyhedron()
    : HardFormfactor()
{
}

FormfactorPolyhedron::~FormfactorPolyhedron() = default;

//! Called by child classes to set faces and other internal variables.

complex_t FormfactorPolyhedron::formfactorAtBottom(C3 q) const
{
    ASSERT(m_validated);
    // ff library v0.4+ places origin at center of mass; shift to bottom for BornAgain convention
    const double shift = -pimpl->z_bottom();
    return pimpl->formfactor(q) * exp_I(q.z() * shift);
}

double FormfactorPolyhedron::volume() const
{
    ASSERT(m_validated);
    return pimpl->volume();
}

std::shared_ptr<ff::Polyhedron> FormfactorPolyhedron::getPolyhedron() const
{
    if (!pimpl)
        return nullptr;
    if (auto casted = std::dynamic_pointer_cast<ff::Prism>(pimpl))
        return std::shared_ptr<ff::Polyhedron>(casted->asPolyhedron());
    if (auto casted = std::dynamic_pointer_cast<ff::Polyhedron>(pimpl))
        return casted;

    return nullptr;
}
double FormfactorPolyhedron::radialExtension() const
{
    ASSERT(m_validated);
    return pimpl->radius();
}

double FormfactorPolyhedron::zBottom(const Rotation* rotation) const
{
    ASSERT(m_validated);
    // ff library v0.4+ places origin at center of mass; shift to bottom for BornAgain convention
    const double shift = -pimpl->z_bottom();
    // For unrotated particles, simply add the shift. For rotated particles, the shift
    // is applied before rotation, so the z-component contribution depends on rotation.
    if (!rotation) {
        ASSERT(PolyhedralUtil::zMin(pimpl->vertices(), nullptr) + shift == 0);
        return 0;
    }
    // Apply shift as R3(0,0,shift), then rotate, then take z-component
    const double rotated_shift = rotation->transformed(R3(0, 0, shift)).z();
    return PolyhedralUtil::zMin(pimpl->vertices(), rotation) + rotated_shift;
}

double FormfactorPolyhedron::zTop(const Rotation* rotation) const
{
    ASSERT(m_validated);
    // ff library v0.4+ places origin at center of mass; shift to bottom for BornAgain convention
    const double shift = -pimpl->z_bottom();
    if (!rotation)
        return PolyhedralUtil::zMax(pimpl->vertices(), nullptr) + shift;
    const double rotated_shift = rotation->transformed(R3(0, 0, shift)).z();
    return PolyhedralUtil::zMax(pimpl->vertices(), rotation) + rotated_shift;
}

void FormfactorPolyhedron::setPolyhedron(const ff::Topology& topology, double z_bottom,
                                         const std::vector<R3>& vertices) const
{
    pimpl = std::make_unique<ff::Polyhedron>(topology, vertices, R3(0, 0, -z_bottom));
}
