//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Sample/Scattering/Rotations.h
//! @brief     Defines 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)
//
//  ************************************************************************************************

#ifndef BORNAGAIN_SAMPLE_SCATTERING_ROTATIONS_H
#define BORNAGAIN_SAMPLE_SCATTERING_ROTATIONS_H

#include "Base/Type/ICloneable.h"
#include "Base/Vector/RotMatrix.h"
#include "Param/Node/INode.h"
#include <heinz/Vectors3D.h>

//! Abstract base class for rotations.

class IRotation : public ICloneable, public INode {
public:
    static IRotation* createRotation(const RotMatrix& matrix);

    IRotation(const std::vector<double>& PValues);

    IRotation* clone() const override = 0;

    //! Returns a new IRotation object that is the current object's inverse
    virtual IRotation* createInverse() const = 0;

    //! Returns transformation.
    virtual RotMatrix rotMatrix() const = 0;

    R3 transformed(const R3& v) const;

    //! Returns true if rotation matrix is identity matrix (no rotations)
    virtual bool isIdentity() const;

    bool zInvariant() const;
};

IRotation* createProduct(const IRotation& left, const IRotation& right);


//! The identity rotation, which leaves everything in place.

class IdentityRotation : public IRotation // TODO RECONSIDER: merge with class IRotation
{
public:
    IdentityRotation();

    IdentityRotation* clone() const override { return new IdentityRotation(); }
    std::string className() const final { return "IdentityRotation"; }
    IdentityRotation* createInverse() const override { return new IdentityRotation(); }

    RotMatrix rotMatrix() const override;

    bool isIdentity() const override { return true; }
};

//! A rotation about the x axis.

class RotationX : public IRotation {
public:
    RotationX(std::vector<double> P);
    RotationX(double angle);

    RotationX* clone() const override { return new RotationX(m_angle); }
    std::string className() const final { return "RotationX"; }
    std::vector<ParaMeta> parDefs() const final { return {{"Angle", "rad"}}; }
    RotationX* createInverse() const override { return new RotationX(-m_angle); }

    double angle() const { return m_angle; }

    RotMatrix rotMatrix() const override;

protected:
    const double& m_angle;
};

//! A rotation about the y axis.

class RotationY : public IRotation {
public:
    RotationY(std::vector<double> P);
    RotationY(double angle);

    RotationY* clone() const override { return new RotationY(m_angle); }
    std::string className() const final { return "RotationY"; }
    std::vector<ParaMeta> parDefs() const final { return {{"Angle", "rad"}}; }
    RotationY* createInverse() const override { return new RotationY(-m_angle); }

    double angle() const { return m_angle; }

    RotMatrix rotMatrix() const override;

protected:
    const double& m_angle;
};

//! A rotation about the z axis.

class RotationZ : public IRotation {
public:
    RotationZ(std::vector<double> P);
    RotationZ(double angle);

    RotationZ* clone() const override { return new RotationZ(m_angle); }
    std::string className() const final { return "RotationZ"; }
    std::vector<ParaMeta> parDefs() const final { return {{"Angle", "rad"}}; }
    RotationZ* createInverse() const override { return new RotationZ(-m_angle); }

    double angle() const { return m_angle; }

    RotMatrix rotMatrix() const override;

protected:
    const double& m_angle;
};

//! A sequence of rotations about the z-x'-z'' axes.

class RotationEuler : public IRotation {
public:
    RotationEuler(std::vector<double> P);
    RotationEuler(double alpha, double beta, double gamma);

    RotationEuler* clone() const override { return new RotationEuler(m_alpha, m_beta, m_gamma); }
    std::string className() const final { return "RotationEuler"; }
    std::vector<ParaMeta> parDefs() const final
    {
        return {{"Alpha", "rad"}, {"Beta", "rad"}, {"Gamma", "rad"}};
    }
    IRotation* createInverse() const override;

    double alpha() const { return m_alpha; } //!< First Euler angle, rotation around z axis
    double beta() const { return m_beta; }   //!< Second Euler angle, rotation around x' axis
    double gamma() const { return m_gamma; } //!< Third Euler angle, rotation around z'' axis

    RotMatrix rotMatrix() const override;

protected:
    double m_alpha, m_beta, m_gamma;
};

#endif // BORNAGAIN_SAMPLE_SCATTERING_ROTATIONS_H
