// 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 VECTOR2_H
#define VECTOR2_H

#include "Angle.h"

extern "C" {
/// <summary>
/// 2-dimensional Vector representation (C-style)
/// </summary>
/// This is a C-style implementation
/// This uses the right-handed coordinate system.
typedef struct Vec2 {
  /// <summary>
  /// The right axis of the vector
  /// </summary>
  float x;
  /// <summary>
  /// The upward/forward axis of the vector
  /// </summary>
  float y;

} Vec2;
}

namespace Passer {
namespace LinearAlgebra {

struct Vector3;
template <typename T> class PolarOf;

/// @brief A 2-dimensional vector
/// @remark This uses the right=handed carthesian coordinate system.
/// @note This implementation intentionally avoids the use of x and y
struct Vector2 : Vec2 {
  friend struct Vec2;

public:
  /// @brief A new 2-dimensional zero vector
  Vector2();
  /// @brief A new 2-dimensional vector
  /// @param right The distance in the right direction in meters
  /// @param forward The distance in the forward direction in meters
  Vector2(float right, float forward);
  /// @brief Convert a Vector3 to a Vector2
  /// @param v The 3D vector
  /// @note This will project the vector to the horizontal plane
  Vector2(Vector3 v);
  /// @brief Convert a Polar vector to a 2-dimensional vector
  /// @param v The vector in polar coordinates
  Vector2(PolarOf<float> v);

  /// @brief Vector2 destructor
  ~Vector2();

  /// @brief A vector with zero for all axis
  const static Vector2 zero;
  /// @brief A vector with one for all axis
  const static Vector2 one;
  /// @brief A normalized forward-oriented vector
  const static Vector2 forward;
  /// @brief A normalized back-oriented vector
  const static Vector2 back;
  /// @brief A normalized right-oriented vector
  const static Vector2 right;
  /// @brief A normalized left-oriented vector
  const static Vector2 left;
  /// @brief A normalized up-oriented vector
  /// @note This is a convenience function which is equal to Vector2::forward
  const static Vector2 up;
  /// @brief A normalized down-oriented vector
  /// @note This is a convenience function which is equal to Vector2::down
  const static Vector2 down;

  /// @brief Check if this vector to the given vector
  /// @param v The vector to check against
  /// @return true if it is identical to the given vector
  /// @note This uses float comparison to check equality which may have strange
  /// effects. Equality on floats should be avoided.
  bool operator==(const Vector2 &v);

  /// @brief The vector length
  /// @param v The vector for which you need the length
  /// @return The vector length
  static float Magnitude(const Vector2 &v);
  /// @brief The vector length
  /// @return The vector length
  float magnitude() const;
  /// @brief The squared vector length
  /// @param v The vector for which you need the squared length
  /// @return The squared vector length
  /// @remark The squared length is computationally simpler than the real
  /// length. Think of Pythagoras A^2 + B^2 = C^2. This prevents the calculation
  /// of the squared root of C.
  static float SqrMagnitude(const Vector2 &v);
  /// @brief The squared vector length
  /// @return The squared vector length
  /// @remark The squared length is computationally simpler than the real
  /// length. Think of Pythagoras A^2 + B^2 = C^2. This prevents the calculation
  /// of the squared root of C.
  float sqrMagnitude() const;

  /// @brief Convert the vector to a length of 1
  /// @param v The vector to convert
  /// @return The vector normalized to a length of 1
  static Vector2 Normalize(const Vector2 &v);
  /// @brief Convert the vector to a length 1
  /// @return The vector normalized to a length of 1
  Vector2 normalized() const;

  /// @brief Negate the vector such that it points in the opposite direction
  /// @return The negated vector
  Vector2 operator-();

  /// @brief Subtract a vector from this vector
  /// @param v The vector to subtract from this vector
  /// @return The result of the subtraction
  Vector2 operator-(const Vector2 &v) const;
  Vector2 operator-=(const Vector2 &v);
  /// @brief Add a vector to this vector
  /// @param v The vector to add to this vector
  /// @return The result of the addition
  Vector2 operator+(const Vector2 &v) const;
  Vector2 operator+=(const Vector2 &v);

  /// @brief Scale the vector using another vector
  /// @param v1 The vector to scale
  /// @param v2 A vector with the scaling factors
  /// @return The scaled vector
  /// @remark Each component of the vector v1 will be multiplied with the
  /// matching component from the scaling vector v2.
  static Vector2 Scale(const Vector2 &v1, const Vector2 &v2);
  /// @brief Scale the vector uniformly up
  /// @param f The scaling factor
  /// @return The scaled vector
  /// @remark Each component of the vector will be multipled with the same
  /// factor f.
  friend Vector2 operator*(const Vector2 &v, float f) {
    return Vector2(v.x * f, v.y * f);
  }
  friend Vector2 operator*(float f, const Vector2 &v) {
    return Vector2(v.x * f, v.y * f);
    // return Vector2(f * v.x, f * v.y);
  }
  Vector2 operator*=(float f);
  /// @brief Scale the vector uniformly down
  /// @param f The scaling factor
  /// @return The scaled vector
  /// @remark Each componet of the vector will be divided by the same factor.
  friend Vector2 operator/(const Vector2 &v, float f) {
    return Vector2(v.x / f, v.y / f);
  }
  friend Vector2 operator/(float f, const Vector2 &v) {
    return Vector2(f / v.x, f / v.y);
  }
  Vector2 operator/=(float f);

  /// @brief The dot product of two vectors
  /// @param v1 The first vector
  /// @param v2 The second vector
  /// @return The dot product of the two vectors
  static float Dot(const Vector2 &v1, const Vector2 &v2);

  /// @brief The distance between two vectors
  /// @param v1 The first vector
  /// @param v2 The second vector
  /// @return The distance between the two vectors
  static float Distance(const Vector2 &v1, const Vector2 &v2);

  /// @brief The angle between two vectors
  /// @param v1 The first vector
  /// @param v2 The second vector
  /// @return The angle between the two vectors
  /// @remark This reterns an unsigned angle which is the shortest distance
  /// between the two vectors. Use Vector2::SignedAngle if a signed angle is
  /// needed.
  static float Angle(const Vector2 &v1, const Vector2 &v2);
  /// @brief The signed angle between two vectors
  /// @param v1 The starting vector
  /// @param v2 The ending vector
  /// @return The signed angle between the two vectors
  static float SignedAngle(const Vector2 &v1, const Vector2 &v2);

  /// @brief Rotate the vector
  /// @param v The vector to rotate
  /// @param a The angle in degrees to rotate
  /// @return The rotated vector
  static Vector2 Rotate(const Vector2 &v, Passer::LinearAlgebra::AngleSingle a);

  /// @brief Lerp (linear interpolation) between two vectors
  /// @param v1 The starting vector
  /// @param v2 The end vector
  /// @param f The interpolation distance
  /// @return The lerped vector
  /// @remark The factor f is unclamped. Value 0 matches the vector *v1*, Value
  /// 1 matches vector *v2*. Value -1 is vector *v1* minus the difference
  /// between *v1* and *v2* etc.
  static Vector2 Lerp(const Vector2 &v1, const Vector2 &v2, float f);
};

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

#include "Polar.h"

#endif