//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Sample/Scattering/Rotations.cpp
//! @brief     Implements class IRotationes.
//!
//! @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/Scattering/Rotations.h"
#include "Base/Util/Assert.h"

//  ************************************************************************************************
//  interface IRotation
//  ************************************************************************************************

IRotation::IRotation(const std::vector<double>& PValues)
    : INode(PValues)
{
}

IRotation* IRotation::createRotation(const RotMatrix& matrix)
{
    if (matrix.isIdentity())
        return new IdentityRotation;
    if (std::optional<double> angle = matrix.angleAroundCoordAxis(0))
        return new RotationX(angle.value());
    if (std::optional<double> angle = matrix.angleAroundCoordAxis(1))
        return new RotationY(angle.value());
    if (std::optional<double> angle = matrix.angleAroundCoordAxis(2))
        return new RotationZ(angle.value());
    auto angles = matrix.zxzEulerAngles();
    return new RotationEuler(angles[0], angles[1], angles[2]);
}

R3 IRotation::transformed(const R3& v) const
{
    return rotMatrix().transformed(v);
}

bool IRotation::isIdentity() const
{
    return rotMatrix().isIdentity();
}

bool IRotation::zInvariant() const
{
    return rotMatrix().isZRotation();
}

//! Returns concatenated rotation (first right, then left).

IRotation* createProduct(const IRotation& left, const IRotation& right)
{
    RotMatrix tr_left = left.rotMatrix();
    RotMatrix tr_right = right.rotMatrix();
    IRotation* result = IRotation::createRotation(tr_left * tr_right);
    return result;
}

//  ************************************************************************************************
//  class IdentityRotation
//  ************************************************************************************************

IdentityRotation::IdentityRotation()
    : IRotation({})
{
}

RotMatrix IdentityRotation::rotMatrix() const
{
    return {};
}

//  ************************************************************************************************
//  class RotationX
//  ************************************************************************************************

//! Constructor of rotation around x-axis
RotationX::RotationX(const std::vector<double> P)
    : IRotation(P)
    , m_angle(m_P[0])
{
}

RotationX::RotationX(double angle)
    : RotationX(std::vector<double>{angle})
{
}

RotMatrix RotationX::rotMatrix() const
{
    return RotMatrix::AroundX(m_angle);
}

//  ************************************************************************************************
//  class RotationY
//  ************************************************************************************************

//! Constructor of rotation around y-axis
RotationY::RotationY(const std::vector<double> P)
    : IRotation(P)
    , m_angle(m_P[0])
{
}

RotationY::RotationY(double angle)
    : RotationY(std::vector<double>{angle})
{
}

RotMatrix RotationY::rotMatrix() const
{
    return RotMatrix::AroundY(m_angle);
}

//  ************************************************************************************************
//  class RotationZ
//  ************************************************************************************************

// --- RotationZ --------------------------------------------------------------

//! Constructor of rotation around z-axis
RotationZ::RotationZ(const std::vector<double> P)
    : IRotation(P)
    , m_angle(m_P[0])
{
}

RotationZ::RotationZ(double angle)
    : RotationZ(std::vector<double>{angle})
{
}

RotMatrix RotationZ::rotMatrix() const
{
    return RotMatrix::AroundZ(m_angle);
}

//  ************************************************************************************************
//  class RotationEuler
//  ************************************************************************************************

RotationEuler::RotationEuler(const std::vector<double> P)
    : IRotation(P)
    , m_alpha(m_P[0])
    , m_beta(m_P[1])
    , m_gamma(m_P[2])
{
}

RotationEuler::RotationEuler(double alpha, double beta, double gamma)
    : RotationEuler(std::vector<double>{alpha, beta, gamma})
{
}

IRotation* RotationEuler::createInverse() const
{
    RotMatrix inverse_transform(rotMatrix().Inverse());
    return createRotation(inverse_transform);
}

RotMatrix RotationEuler::rotMatrix() const
{
    return RotMatrix::EulerZXZ(m_alpha, m_beta, m_gamma);
}
