//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Sample/Particle/Formfactor.cpp
//! @brief     Implements class interface Formfactor.
//!
//! @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/Formfactor.h"
#include "Base/Spin/SpinMatrix.h"
#include "Base/Util/Assert.h"
#include "Base/Vector/WavevectorInfo.h"
#include "Sample/Particle/PolyhedralUtil.h"
#include "Sample/Particle/Rotations.h"
#include <sstream>
#include <stdexcept>

namespace {

void throwInfiniteFF(const std::string& result_str, const WavevectorInfo& wavevectors)
{
    std::ostringstream msg;
    msg << "Infinite form factor\n"
        << result_str << "at ki = " << wavevectors.getKi() << "\n"
        << "   kf = " << wavevectors.getKf() << "\n"
        << "    q = " << wavevectors.getQ() << "\n"
        << "This can occur due to evanescence or strong absorption, causing the high-kz wave "
           "to decay to zero within the particle. Increasing the number of slices may help.\n";
    throw std::runtime_error(msg.str());
}

} // namespace


Formfactor::~Formfactor() = default;

double Formfactor::volume() const
{
    return std::abs(formfactorAtBottom(C3()));
}

double Formfactor::zBottom(const Rotation* rotation) const
{
    ASSERT(m_shape3D);
    return PolyhedralUtil::zMin(m_shape3D->vertices(), rotation);
}

double Formfactor::zTop(const Rotation* rotation) const
{
    ASSERT(m_shape3D);
    return PolyhedralUtil::zMax(m_shape3D->vertices(), rotation);
}

bool Formfactor::canSliceAnalytically(const Rotation* rotation) const
{
    return !rotation || rotation->zInvariant();
}

complex_t Formfactor::theFF(const WavevectorInfo& wavevectors) const
{
    const complex_t result = formfactorAtBottom(wavevectors.getQ());
    if (!std::isfinite(result.real()) || !std::isfinite(result.imag())) {
        std::ostringstream os;
        os << result << "\n";
        ::throwInfiniteFF(os.str(), wavevectors);
    }
    return result;
}

SpinMatrix Formfactor::thePolFF(const WavevectorInfo& wavevectors) const
{
    return theFF(wavevectors) * SpinMatrix::One();
}
