//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file     Sample/Flux/MatrixFlux.cpp
//! @brief    Implements class MatrixFlux.
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2020
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "Sample/Flux/MatrixFlux.h"
#include "Base/Util/Assert.h"

namespace {

const auto eps = std::numeric_limits<double>::epsilon() * 10.;

} // namespace


MatrixFlux::MatrixFlux(double kz_sign, const Spinor& k_eigen, const R3& b, double magnetic_SLD)
    : m_k_eigen(k_eigen)
    , m_T(+1, 0, 0, +1)
    , m_R(-1, 0, 0, -1)
    , m_b(b)
    , m_kz_sign(kz_sign)
    , m_magnetic_SLD(magnetic_SLD)
{
    ASSERT(std::abs(m_b.mag() - 1) < eps || (m_b.mag() < eps && magnetic_SLD < eps));
}


//! See PhysRef, chapter "Polarized", section "Wavenumber operator".
Spinor MatrixFlux::T1plus() const
{
    return T1Matrix() * m_T.col0();
}

Spinor MatrixFlux::R1plus() const
{
    return T1Matrix() * m_R.col0();
}

Spinor MatrixFlux::T2plus() const
{
    return T2Matrix() * m_T.col0();
}

Spinor MatrixFlux::R2plus() const
{
    return T2Matrix() * m_R.col0();
}

Spinor MatrixFlux::T1min() const
{
    return T1Matrix() * m_T.col1();
}

Spinor MatrixFlux::R1min() const
{
    return T1Matrix() * m_R.col1();
}

Spinor MatrixFlux::T2min() const
{
    return T2Matrix() * m_T.col1();
}

Spinor MatrixFlux::R2min() const
{
    return T2Matrix() * m_R.col1();
}

Spinor MatrixFlux::getKz() const
{
    return m_kz_sign * m_k_eigen;
}
SpinMatrix MatrixFlux::computeKappa() const
{
    const complex_t alpha = m_k_eigen.u + m_k_eigen.v;
    const complex_t beta = m_k_eigen.u - m_k_eigen.v;
    return SpinMatrix(alpha + beta * m_b.z(), beta * (m_b.x() - I * m_b.y()),
                      beta * (m_b.x() + I * m_b.y()), alpha - beta * m_b.z())
           / 2.;
}

SpinMatrix MatrixFlux::computeInverseKappa() const
{
    const complex_t alpha = m_k_eigen.u + m_k_eigen.v;
    const complex_t beta = m_k_eigen.u - m_k_eigen.v;
    if (std::abs(alpha * alpha - beta * beta) == 0.)
        throw std::runtime_error("Inverse wavenumber operator is singular for given B field."
                                 " Add some absorption to the nuclear SLD.");

    return 2. / (alpha * alpha - beta * beta)
           * SpinMatrix(alpha - beta * m_b.z(), beta * (-m_b.x() + I * m_b.y()),
                        -beta * (m_b.x() + I * m_b.y()), alpha + beta * m_b.z());
}

SpinMatrix MatrixFlux::eigenToMatrix(const Spinor& diagonal) const
{
    if (std::abs(m_b.mag() - 1.) < eps) {
        const complex_t half_sum = (diagonal.u + diagonal.v) / 2.;
        const complex_t half_diff = (diagonal.u - diagonal.v) / 2.;
        return SpinMatrix(half_sum + half_diff * m_b.z(), half_diff * (m_b.x() - I * m_b.y()),
                          half_diff * (m_b.x() + I * m_b.y()), half_sum - half_diff * m_b.z());
    }
    ASSERT(m_b.mag() < eps); // remaining case: no field
    return SpinMatrix(diagonal.u, 0, 0, diagonal.v);
}

SpinMatrix MatrixFlux::computeDeltaMatrix(double thickness) const
{
    if (!std::isfinite(thickness))
        return eigenToMatrix({1., 1.});
    return eigenToMatrix(
        {exp_I(m_kz_sign * thickness * m_k_eigen.u), exp_I(m_kz_sign * thickness * m_k_eigen.v)});
}

SpinMatrix MatrixFlux::T1Matrix() const
{
    return eigenToMatrix({1., 0.});
}

SpinMatrix MatrixFlux::T2Matrix() const
{
    return eigenToMatrix({0., 1.});
}
