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

Material::Material(std::unique_ptr<IMaterialImpl>&& material_impl)
    : m_material_impl(std::move(material_impl))
{
}

Material::Material(const Material& material)
{
    ASSERT(!material.isEmpty());
    m_material_impl.reset(material.m_material_impl->clone());
}

Material& Material::operator=(const Material& other)
{
    if (this == &other)
        return *this;
    ASSERT(!other.isEmpty());
    m_material_impl.reset(other.m_material_impl->clone());
    return *this;
}

Material Material::inverted() const
{
    std::unique_ptr<IMaterialImpl> material_impl(m_material_impl->inverted());
    return {std::move(material_impl)};
}

complex_t Material::refractiveIndex(double wavelength) const
{
    return m_material_impl->refractiveIndex(wavelength);
}

complex_t Material::refractiveIndex2(double wavelength) const
{
    return m_material_impl->refractiveIndex2(wavelength);
}

bool Material::isScalarMaterial() const
{
    return m_material_impl->isScalarMaterial();
}

bool Material::isMagneticMaterial() const
{
    return m_material_impl->isMagneticMaterial();
}

std::string Material::materialName() const
{
    return m_material_impl->matName();
}

MATERIAL_TYPES Material::typeID() const
{
    return m_material_impl->typeID();
}

void Material::checkRefractiveIndex(double wavelength) const
{
    const complex_t n = refractiveIndex(wavelength);
    if (n.real() < 0.9 || n.real() > 1.1) {
        std::stringstream msg;
        msg << "Refractive index " << n << " at wavelength " << wavelength
            << " is too far from 1. Invalid material data?";
        throw std::runtime_error(msg.str());
    }
}

R3 Material::magnetization() const
{
    return m_material_impl->magnetization();
}

complex_t Material::refractiveIndex_or_SLD() const
{
    return m_material_impl->refractiveIndex_or_SLD();
}

bool Material::isDefaultMaterial() const
{
    return refractiveIndex_or_SLD() == complex_t() && isScalarMaterial();
}

bool Material::isEmpty() const
{
    return !m_material_impl;
}

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

SpinMatrix Material::polarizedSubtrSLD(const WavevectorInfo& wavevectors) const
{
    return m_material_impl->polarizedSubtrSLD(wavevectors);
}

Material Material::rotatedMaterial(const RotMatrix& transform) const // TODO param:=rotation
{
    std::unique_ptr<IMaterialImpl> material_impl(m_material_impl->rotatedMaterial(transform));
    return {std::move(material_impl)};
}

std::ostream& operator<<(std::ostream& ostr, const Material& m)
{
    ostr << m.m_material_impl->print();
    return ostr;
}

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

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