// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0.If a copy of the MPL was not distributed with this
// file, You can obtain one at https ://mozilla.org/MPL/2.0/.

#ifndef QUATERNION_H
#define QUATERNION_H

#include "Vector3.h"

extern "C" {
/// <summary>
/// A quaternion (C-style)
/// </summary>
/// This is a C-style implementation
typedef struct Quat {
  /// <summary>
  /// The x component
  /// </summary>
  float x;
  /// <summary>
  /// The y component
  /// </summary>
  float y;
  /// <summary>
  /// The z component
  /// </summary>
  float z;
  /// <summary>
  /// The w component
  /// </summary>
  float w;
} Quat;
}

namespace Passer {
namespace LinearAlgebra {

/// <summary>
/// A quaternion
/// </summary>
struct Quaternion : Quat {
public:
  /// <summary>
  /// Create a new identity quaternion
  /// </summary>
  Quaternion();
  /// <summary>
  /// create a new quaternion with the given values
  /// </summary>
  /// <param name="_x">x component</param>
  /// <param name="_y">y component</param>
  /// <param name="_z">z component</param>
  /// <param name="_w">w component</param>
  Quaternion(float _x, float _y, float _z, float _w);
  /// <summary>
  /// Create a quaternion from C-style Quat
  /// </summary>
  /// <param name="q"></param>
  Quaternion(Quat q);
  /// <summary>
  /// Quaternion destructor
  /// </summary>
  ~Quaternion();

  /// <summary>
  /// An identity quaternion
  /// </summary>
  const static Quaternion identity;

  /// <summary>
  /// Convert to unit quaternion
  /// </summary>
  /// This will preserve the orientation,
  /// but ensures that it is a unit quaternion.
  void Normalize();
  /// <summary>
  /// Convert to unity quaternion
  /// </summary>
  /// <param name="q">The quaternion to convert</param>
  /// <returns>A unit quaternion</returns>
  /// This will preserve the orientation,
  /// but ensures that it is a unit quaternion.
  static Quaternion Normalize(const Quaternion &q);

  /// <summary>
  /// Convert to euler angles
  /// </summary>
  /// <param name="q">The quaternion to convert</param>
  /// <returns>A vector containing euler angles</returns>
  /// The euler angles performed in the order: Z, X, Y
  static Vector3 ToAngles(const Quaternion &q);

  /// <summary>
  /// Rotate a vector using this quaterion
  /// </summary>
  /// <param name="vector">The vector to rotate</param>
  /// <returns>The rotated vector</returns>
  Vector3 operator*(const Vector3 &vector) const;
  /// <summary>
  /// Multiply this quaternion with another quaternion
  /// </summary>
  /// <param name="rotation">The quaternion to multiply with</param>
  /// <returns>The resulting rotation</returns>
  /// The result will be this quaternion rotated according to
  /// the give rotation.
  Quaternion operator*(const Quaternion &rotation) const;

  /// <summary>
  /// Check the equality of two quaternions
  /// </summary>
  /// <param name="quaternion">The quaternion to compare to</param>
  /// <returns>True when the components of the quaternions are
  /// identical</returns> Note that this does not compare the rotations
  /// themselves. Two quaternions with the same rotational effect may have
  /// different components. Use Quaternion::Angle to check if the rotations are
  /// the same.
  bool operator==(const Quaternion &quaternion) const;

  /// <summary>
  /// The inverse of quaterion
  /// </summary>
  /// <param name="quaternion">The quaternion for which the inverse is
  /// needed</param> <returns>The inverted quaternion</returns>
  static Quaternion Inverse(Quaternion quaternion);

  /// <summary>
  /// A rotation which looks in the given direction
  /// </summary>
  /// <param name="forward">The look direction</param>
  /// <param name="upwards">The up direction</param>
  /// <returns>The look rotation</returns>
  static Quaternion LookRotation(const Vector3 &forward,
                                 const Vector3 &upwards);
  /// <summary>
  /// Creates a quaternion with the given forward direction with up =
  /// Vector3::up
  /// </summary>
  /// <param name="forward">The look direction</param>
  /// <returns>The rotation for this direction</returns>
  /// For the rotation, Vector::up is used for the up direction.
  /// Note: if the forward direction == Vector3::up, the result is
  /// Quaternion::identity
  static Quaternion LookRotation(const Vector3 &forward);

  /// <summary>
  /// Calculat the rotation from on vector to another
  /// </summary>
  /// <param name="fromDirection">The from direction</param>
  /// <param name="toDirection">The to direction</param>
  /// <returns>The rotation from the first to the second vector</returns>
  static Quaternion FromToRotation(Vector3 fromDirection, Vector3 toDirection);

  /// <summary>
  /// Rotate form one orientation to anther with a maximum amount of degrees
  /// </summary>
  /// <param name="from">The from rotation</param>
  /// <param name="to">The destination rotation</param>
  /// <param name="maxDegreesDelta">The maximum amount of degrees to
  /// rotate</param> <returns>The possibly limited rotation</returns>
  static Quaternion RotateTowards(const Quaternion &from, const Quaternion &to,
                                  float maxDegreesDelta);

  /// <summary>
  /// Convert an angle/axis representation to a quaternion
  /// </summary>
  /// <param name="angle">The angle</param>
  /// <param name="axis">The axis</param>
  /// <returns>The resulting quaternion</returns>
  static Quaternion AngleAxis(float angle, const Vector3 &axis);
  /// <summary>
  /// Convert this quaternion to angle/axis representation
  /// </summary>
  /// <param name="angle">A pointer to the angle for the result</param>
  /// <param name="axis">A pointer to the axis for the result</param>
  void ToAngleAxis(float *angle, Vector3 *axis);

  /// <summary>
  /// Get the angle between two orientations
  /// </summary>
  /// <param name="orientation1">The first orientation</param>
  /// <param name="orientation2">The second orientation</param>
  /// <returns>The smallest angle in degrees between the two
  /// orientations</returns>
  static float Angle(Quaternion orientation1, Quaternion orientation2);
  /// <summary>
  /// Sherical lerp between two rotations
  /// </summary>
  /// <param name="rotation1">The first rotation</param>
  /// <param name="rotation2">The second rotation</param>
  /// <param name="factor">The factor between 0 and 1.</param>
  /// <returns>The resulting rotation</returns>
  /// A factor 0 returns rotation1, factor1 returns rotation2.
  static Quaternion Slerp(const Quaternion &rotation1,
                          const Quaternion &rotation2, float factor);
  /// <summary>
  /// Unclamped sherical lerp between two rotations
  /// </summary>
  /// <param name="rotation1">The first rotation</param>
  /// <param name="rotation2">The second rotation</param>
  /// <param name="factor">The factor</param>
  /// <returns>The resulting rotation</returns>
  /// A factor 0 returns rotation1, factor1 returns rotation2.
  /// Values outside the 0..1 range will result in extrapolated rotations
  static Quaternion SlerpUnclamped(const Quaternion &rotation1,
                                   const Quaternion &rotation2, float factor);

  /// <summary>
  /// Create a rotation from euler angles
  /// </summary>
  /// <param name="x">The angle around the right axis</param>
  /// <param name="y">The angle around the upward axis</param>
  /// <param name="z">The angle around the forward axis</param>
  /// <returns>The resulting quaternion</returns>
  /// Rotation are appied in the order Z, X, Y.
  static Quaternion Euler(float x, float y, float z);
  /// <summary>
  /// Create a rotation from a vector containing euler angles
  /// </summary>
  /// <param name="eulerAngles">Vector with the euler angles</param>
  /// <returns>The resulting quaternion</returns>
  /// Rotation are appied in the order Z, X, Y.
  static Quaternion Euler(Vector3 eulerAngles);

  /// <summary>
  /// Create a rotation from euler angles
  /// </summary>
  /// <param name="x">The angle around the right axis</param>
  /// <param name="y">The angle around the upward axis</param>
  /// <param name="z">The angle around the forward axis</param>
  /// <returns>The resulting quaternion</returns>
  /// Rotation are appied in the order X, Y, Z.
  static Quaternion EulerXYZ(float x, float y, float z);
  /// <summary>
  /// Create a rotation from a vector containing euler angles
  /// </summary>
  /// <param name="eulerAngles">Vector with the euler angles</param>
  /// <returns>The resulting quaternion</returns>
  /// Rotation are appied in the order X, Y, Z.
  static Quaternion EulerXYZ(Vector3 eulerAngles);

  /// <summary>
  /// Returns the angle of around the give axis for a rotation
  /// </summary>
  /// <param name="axis">The axis around which the angle should be
  /// computed</param> <param name="rotation">The source rotation</param>
  /// <returns>The signed angle around the axis</returns>
  static float GetAngleAround(Vector3 axis, Quaternion rotation);
  /// <summary>
  /// Returns the rotation limited around the given axis
  /// </summary>
  /// <param name="axis">The axis which which the rotation should be
  /// limited</param> <param name="rotation">The source rotation</param>
  /// <returns>The rotation around the given axis</returns>
  static Quaternion GetRotationAround(Vector3 axis, Quaternion rotation);
  /// <summary>
  /// Swing-twist decomposition of a rotation
  /// </summary>
  /// <param name="axis">The base direction for the decomposition</param>
  /// <param name="rotation">The source rotation</param>
  /// <param name="swing">A pointer to the quaternion for the swing
  /// result</param> <param name="twist">A pointer to the quaternion for the
  /// twist result</param>
  static void GetSwingTwist(Vector3 axis, Quaternion rotation,
                            Quaternion *swing, Quaternion *twist);

  /// <summary>
  /// Calculate the dot product of two quaternions
  /// </summary>
  /// <param name="rotation1">The first rotation</param>
  /// <param name="rotation2">The second rotation</param>
  /// <returns></returns>
  static float Dot(Quaternion rotation1, Quaternion rotation2);

private:
  float GetLength() const;
  float GetLengthSquared() const;
  static float GetLengthSquared(const Quaternion &q);

  void ToAxisAngleRad(const Quaternion &q, Vector3 *const axis, float *angle);
  static Quaternion FromEulerRad(Vector3 euler);
  static Quaternion FromEulerRadXYZ(Vector3 euler);

  Vector3 xyz() const;
};

} // namespace LinearAlgebra
} // namespace Passer
using namespace Passer::LinearAlgebra;

#endif