#include "Spherical.h"

#include "Angle.h"
#include "Quaternion.h"

#include <math.h>

template <typename T> SphericalOf<T>::SphericalOf() {
  this->distance = 0.0f;
  this->direction = DirectionOf<T>();
}

template <typename T>
SphericalOf<T>::SphericalOf(float distance, AngleOf<T> horizontal,
                            AngleOf<T> vertical) {
  if (distance < 0) {
    this->distance = -distance;
    this->direction = -DirectionOf<T>(horizontal, vertical);
  } else {
    this->distance = distance;
    this->direction = DirectionOf<T>(horizontal, vertical);
  }
}

template <typename T>
SphericalOf<T>::SphericalOf(float distance, DirectionOf<T> direction) {
  if (distance < 0) {
    this->distance = -distance;
    this->direction = -direction;
  } else {
    this->distance = distance;
    this->direction = direction;
  }
}

template <typename T>
SphericalOf<T> SphericalOf<T>::Degrees(float distance, float horizontal,
                                       float vertical) {
  AngleOf<T> horizontalAngle = AngleOf<T>::Degrees(horizontal);
  AngleOf<T> verticalAngle = AngleOf<T>::Degrees(vertical);
  SphericalOf<T> r = SphericalOf<T>(distance, horizontalAngle, verticalAngle);
  return r;
}

template <typename T>
SphericalOf<T> SphericalOf<T>::Radians(float distance, float horizontal,
                                       float vertical) {
  return SphericalOf<T>(distance, AngleOf<T>::Radians(horizontal),
                        AngleOf<T>::Radians(vertical));
}

template <typename T>
SphericalOf<T> SphericalOf<T>::FromPolar(PolarOf<T> polar) {
  AngleOf<T> horizontal = polar.angle;
  AngleOf<T> vertical = AngleOf<T>();
  SphericalOf<T> r = SphericalOf(polar.distance, horizontal, vertical);
  return r;
}

template <typename T> SphericalOf<T> SphericalOf<T>::FromVector3(Vector3 v) {
  float distance = v.magnitude();
  if (distance == 0.0f) {
    return SphericalOf(distance, AngleOf<T>(), AngleOf<T>());
  } else {
    AngleOf<T> verticalAngle =
        AngleOf<T>::Radians((pi / 2 - acosf(v.Up() / distance)));
    AngleOf<T> horizontalAngle =
        AngleOf<T>::Radians(atan2f(v.Right(), v.Forward()));
    return SphericalOf(distance, horizontalAngle, verticalAngle);
  }
}

/**
 * @brief Converts spherical coordinates to a 3D vector.
 *
 * This function converts the spherical coordinates represented by the
 * SphericalOf object to a 3D vector (Vector3). The conversion is based
 * on the distance and direction (vertical and horizontal angles) of the
 * spherical coordinates.
 *
 * @tparam T The type of the distance and direction values.
 * @return Vector3 The 3D vector representation of the spherical coordinates.
 */
template <typename T> Vector3 SphericalOf<T>::ToVector3() const {
  float verticalRad = (pi / 2) - this->direction.vertical.InRadians();
  float horizontalRad = this->direction.horizontal.InRadians();

  float cosVertical = cosf(verticalRad);
  float sinVertical = sinf(verticalRad);
  float cosHorizontal = cosf(horizontalRad);
  float sinHorizontal = sinf(horizontalRad);

  float x = this->distance * sinVertical * sinHorizontal;
  float y = this->distance * cosVertical;
  float z = this->distance * sinVertical * cosHorizontal;

  Vector3 v = Vector3(x, y, z);
  return v;
}

template <typename T>
const SphericalOf<T> SphericalOf<T>::zero =
    SphericalOf<T>(0.0f, AngleOf<T>(), AngleOf<T>());
template <typename T>
const SphericalOf<T> SphericalOf<T>::forward =
    SphericalOf<T>(1.0f, AngleOf<T>(), AngleOf<T>());
template <typename T>
const SphericalOf<T> SphericalOf<T>::back =
    SphericalOf<T>(1.0f, AngleOf<T>::Degrees(180), AngleOf<T>());
template <typename T>
const SphericalOf<T> SphericalOf<T>::right =
    SphericalOf<T>(1.0f, AngleOf<T>::Degrees(90), AngleOf<T>());
template <typename T>
const SphericalOf<T> SphericalOf<T>::left =
    SphericalOf<T>(1.0f, AngleOf<T>::Degrees(-90), AngleOf<T>());
template <typename T>
const SphericalOf<T> SphericalOf<T>::up =
    SphericalOf<T>(1.0f, AngleOf<T>(), AngleOf<T>::Degrees(90));
template <typename T>
const SphericalOf<T> SphericalOf<T>::down =
    SphericalOf<T>(1.0f, AngleOf<T>(), AngleOf<T>::Degrees(-90));

template <typename T>
SphericalOf<T> SphericalOf<T>::WithDistance(float distance) {
  SphericalOf<T> v = SphericalOf<T>(distance, this->direction);
  return v;
}

template <typename T> SphericalOf<T> SphericalOf<T>::operator-() const {
  SphericalOf<T> v = SphericalOf<T>(
      this->distance, this->direction.horizontal + AngleOf<T>::Degrees(180),
      this->direction.vertical + AngleOf<T>::Degrees(180));
  return v;
}

template <typename T>
SphericalOf<T> SphericalOf<T>::operator-(const SphericalOf<T> &s2) const {
  // let's do it the easy way...
  Vector3 v1 = this->ToVector3();
  Vector3 v2 = s2.ToVector3();
  Vector3 v = v1 - v2;
  SphericalOf<T> r = SphericalOf<T>::FromVector3(v);
  return r;
}
template <typename T>
SphericalOf<T> SphericalOf<T>::operator-=(const SphericalOf<T> &v) {
  *this = *this - v;
  return *this;
}

template <typename T>
SphericalOf<T> SphericalOf<T>::operator+(const SphericalOf<T> &s2) const {
  // let's do it the easy way...
  Vector3 v1 = this->ToVector3();
  Vector3 v2 = s2.ToVector3();
  Vector3 v = v1 + v2;
  SphericalOf<T> r = SphericalOf<T>::FromVector3(v);
  return r;
  /*
  // This is the hard way...
  if (v2.distance <= 0)
    return Spherical(this->distance, this->horizontalAngle,
                     this->verticalAngle);
  if (this->distance <= 0)
    return v2;

  float deltaHorizontalAngle =
      (float)Angle::Normalize(v2.horizontalAngle - this->horizontalAngle);
  float horizontalRotation = deltaHorizontalAngle < 0
                                 ? 180 + deltaHorizontalAngle
                                 : 180 - deltaHorizontalAngle;
  float deltaVerticalAngle =
      Angle::Normalize(v2.verticalAngle - this->verticalAngle);
  float verticalRotation = deltaVerticalAngle < 0 ? 180 + deltaVerticalAngle
                                                  : 180 - deltaVerticalAngle;

  if (horizontalRotation == 180 && verticalRotation == 180)
    // angle is too small, take this angle and add the distances
    return Spherical(this->distance + v2.distance, this->horizontalAngle,
                     this->verticalAngle);

  Angle rotation = AngleBetween(*this, v2);
  float newDistance =
      Angle::CosineRuleSide(v2.distance, this->distance, rotation);
  float angle =
      Angle::CosineRuleAngle(newDistance, this->distance, v2.distance);

  // Now we have to project the angle to the horizontal and vertical planes...
  // The axis for the angle is the cross product of the two spherical vectors
  // (which function we do not have either...)
  float horizontalAngle = 0;
  float verticalAngle = 0;

  float newHorizontalAngle =
      deltaHorizontalAngle < 0
          ? Angle::Normalize(this->horizontalAngle - horizontalAngle)
          : Angle::Normalize(this->horizontalAngle + horizontalAngle);
  float newVerticalAngle =
      deltaVerticalAngle < 0
          ? Angle::Normalize(this->verticalAngle - verticalAngle)
          : Angle::Normalize(this->verticalAngle + verticalAngle);

  Spherical v = Spherical(newDistance, newHorizontalAngle, newVerticalAngle);
  */
}
template <typename T>
SphericalOf<T> SphericalOf<T>::operator+=(const SphericalOf<T> &v) {
  *this = *this + v;
  return *this;
}

template <typename T> SphericalOf<T> SphericalOf<T>::operator*=(float f) {
  this->distance *= f;
  return *this;
}

template <typename T> SphericalOf<T> SphericalOf<T>::operator/=(float f) {
  this->distance /= f;
  return *this;
}

#include "FloatSingle.h"
#include "Vector3.h"

const float epsilon = 1E-05f;

template <typename T>
float SphericalOf<T>::DistanceBetween(const SphericalOf<T> &v1,
                                      const SphericalOf<T> &v2) {
  // SphericalOf<T> difference = v1 - v2;
  // return difference.distance;
  Vector3 vec1 = v1.ToVector3();
  Vector3 vec2 = v2.ToVector3();
  float distance = Vector3::Distance(vec1, vec2);
  return distance;
}

template <typename T>
AngleOf<T> SphericalOf<T>::AngleBetween(const SphericalOf &v1,
                                        const SphericalOf &v2) {
  // float denominator = v1.distance * v2.distance;
  // if (denominator < epsilon)
  //   return 0.0f;

  Vector3 v1_3 = v1.ToVector3();
  Vector3 v2_3 = v2.ToVector3();
  // float dot = Vector3::Dot(v1_3, v2_3);
  // float fraction = dot / denominator;
  // if (isnan(fraction))
  //   return fraction;  // short cut to returning NaN universally

  // float cdot = Float::Clamp(fraction, -1.0, 1.0);
  // float r = ((float)acos(cdot)) * Rad2Deg;
  AngleSingle r = Vector3::Angle(v1_3, v2_3);
  return AngleOf<T>::Degrees(r.InDegrees());
}

template <typename T>
AngleOf<T> Passer::LinearAlgebra::SphericalOf<T>::SignedAngleBetween(
    const SphericalOf<T> &v1, const SphericalOf<T> &v2,
    const SphericalOf<T> &axis) {
  Vector3 v1_vector = v1.ToVector3();
  Vector3 v2_vector = v2.ToVector3();
  Vector3 axis_vector = axis.ToVector3();
  AngleSingle r = Vector3::SignedAngle(v1_vector, v2_vector, axis_vector);
  return AngleOf<T>::Degrees(r.InDegrees());
}

template <typename T>
SphericalOf<T> SphericalOf<T>::Rotate(const SphericalOf<T> &v,
                                      AngleOf<T> horizontalAngle,
                                      AngleOf<T> verticalAngle) {
  SphericalOf<T> r =
      SphericalOf(v.distance, v.direction.horizontal + horizontalAngle,
                  v.direction.vertical + verticalAngle);
  return r;
}
template <typename T>
SphericalOf<T> SphericalOf<T>::RotateHorizontal(const SphericalOf<T> &v,
                                                AngleOf<T> a) {
  SphericalOf<T> r =
      SphericalOf(v.distance, v.direction.horizontal + a, v.direction.vertical);
  return r;
}
template <typename T>
SphericalOf<T> SphericalOf<T>::RotateVertical(const SphericalOf<T> &v,
                                              AngleOf<T> a) {
  SphericalOf<T> r =
      SphericalOf(v.distance, v.direction.horizontal, v.direction.vertical + a);
  return r;
}

template class Passer::LinearAlgebra::SphericalOf<float>;
template class Passer::LinearAlgebra::SphericalOf<signed short>;