//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Sample/Material/IMaterialImpl.cpp
//! @brief     Implements magnetic material base implementation.
//!
//! @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/Material/IMaterialImpl.h"
#include "Base/Const/PhysicalConstants.h"
#include "Base/Spin/SpinMatrix.h"
#include "Base/Vector/WavevectorInfo.h"
#include "Sample/Material/MaterialUtil.h"
#include <memory>

namespace {

using PhysConsts::gamma_n;
using PhysConsts::mu_B;
using PhysConsts::r_e;

// The factor 1e-18 is here to have unit: m/A*nm^-2
constexpr double magnetization_prefactor = (gamma_n * r_e / 2.0 / mu_B) * 1e-18;

C3 OrthogonalToBaseVector(C3 base, const R3 vector)
{
    if (base.mag2() == 0.0)
        return {};
    C3 projection = (base.dot(vector) / base.mag2()) * base;
    return vector.complex() - projection;
}

} // namespace


IMaterialImpl::IMaterialImpl(std::string name, R3 magnetization)
    : m_name(std::move(name))
    , m_magnetization(magnetization)
{
}

IMaterialImpl* IMaterialImpl::inverted() const
{
    std::string name = isScalarMaterial() ? matName() : matName() + "_inv";
    IMaterialImpl* result = this->clone();
    result->setMagnetization(-magnetization());
    return result;
}

bool IMaterialImpl::isScalarMaterial() const
{
    return m_magnetization == R3();
}

bool IMaterialImpl::isMagneticMaterial() const
{
    return !isScalarMaterial();
}
SpinMatrix IMaterialImpl::polarizedSubtrSLD(const WavevectorInfo& wavevectors) const
{
    C3 mag_ortho = OrthogonalToBaseVector(wavevectors.getQ(), m_magnetization);
    complex_t unit_factor = scalarSubtrSLD(wavevectors.vacuumLambda());
    return MaterialUtil::MagnetizationCorrection(unit_factor, magnetization_prefactor, mag_ortho);
}

IMaterialImpl* IMaterialImpl::rotatedMaterial(const RotMatrix& transform) const
{
    R3 transformed_field = transform.transformed(m_magnetization);
    IMaterialImpl* result = this->clone();
    result->setMagnetization(transformed_field);
    return result;
}
