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

//  ************************************************************************************************
//  interface Rotation
//  ************************************************************************************************

Rotation::Rotation() = default;

Rotation* Rotation::newRotation(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 Rotation::transformed(const R3& v) const
{
    return rotMatrix().transformed(v);
}

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

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

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

Rotation* newProduct(const Rotation& left, const Rotation& right)
{
    RotMatrix tr_left = left.rotMatrix();
    RotMatrix tr_right = right.rotMatrix();
    Rotation* result = Rotation::newRotation(tr_left * tr_right);
    return result;
}

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

IdentityRotation::IdentityRotation()
    : Rotation()
{
}

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

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

//! Constructor of rotation around x-axis
RotationX::RotationX(double angle)
    : Rotation()
    , m_angle(angle)
{
}

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

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

//! Constructor of rotation around y-axis
RotationY::RotationY(double angle)
    : Rotation()
    , m_angle(angle)
{
}

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

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

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

//! Constructor of rotation around z-axis
RotationZ::RotationZ(double angle)
    : Rotation()
    , m_angle(angle)
{
}

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

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

RotationEuler::RotationEuler(double alpha, double beta, double gamma)
    : Rotation()
    , m_alpha(alpha)
    , m_beta(beta)
    , m_gamma(gamma)
{
}

Rotation* RotationEuler::newInverse() const
{
    RotMatrix inverse_transform(rotMatrix().Inverse());
    return newRotation(inverse_transform);
}

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