//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Sample/Material/Material.cpp
//! @brief     Implements class Material.
//!
//! @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/Material.h"
#include "Base/Const/PhysicalConstants.h"
#include "Base/Spin/SpinMatrix.h"
#include "Base/Util/Assert.h"
#include "Base/Vector/WavevectorInfo.h"
#include <cmath>
#include <stdexcept>
#include <typeinfo>

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(const C3& base, const R3& vector)
{
    if (base.mag2() == 0.0)
        return {};
    C3 projection = (base.dot(vector) / base.mag2()) * base;
    return vector.complex() - projection;
}

} // namespace


namespace {

void validateColorComponent(double value)
{
    if (!std::isfinite(value) || value < 0.0 || value > 1.0)
        throw std::runtime_error("Material color components must be finite values in [0, 1]");
}

} // namespace


Material::Material(std::string name, const MaterialColor& color, const R3& magnetization)
    : m_name(std::move(name))
    , m_color(color)
    , m_magnetization(magnetization)
{
    validateColorComponent(m_color.r);
    validateColorComponent(m_color.g);
    validateColorComponent(m_color.b);
}

complex_t Material::scalarSubtrSLD(const WavevectorInfo& wavevectors) const
{
    return plainSubtrSLD(wavevectors.vacuumLambda());
}

SpinMatrix Material::polarizedSubtrSLD(const WavevectorInfo& wavevectors) const
{
    C3 mag_ortho = ::orthogonalToBaseVector(wavevectors.getQ(), m_magnetization);
    complex_t unit_factor = scalarSubtrSLD(wavevectors);

    return unit_factor * SpinMatrix::One()
           + ::magnetization_prefactor * SpinMatrix::PauliMapping(mag_ortho);
}

std::unique_ptr<Material>
Material::rotatedMaterial(const RotMatrix& transform) const // TODO param:=rotation
{
    R3 transformed_field = transform.transformed(m_magnetization);
    auto result = this->cloned_unique();
    result->setMagnetization(transformed_field);
    return result;
}

bool operator==(const Material& left, const Material& right)
{
    if (left.materialName() != right.materialName())
        return false;
    if (left.magnetization() != right.magnetization())
        return false;
    if (left.rawData() != right.rawData())
        return false;
    if (left.hasNativeRefractiveIndex() != right.hasNativeRefractiveIndex())
        return false;
    if (left.hasNativeSLD() != right.hasNativeSLD())
        return false;
    return true;
}

bool operator!=(const Material& left, const Material& right)
{
    return !(left == right);
}
