/*
  This file is part of CDO. CDO is a collection of Operators to manipulate and analyse Climate model Data.

  Author: Uwe Schulzweida

*/
#ifndef VECTOR3D_H
#define VECTOR3D_H

#define _USE_MATH_DEFINES

#include <cstdio>
#include <cmath>

// clang-format off
class  // Vector3d
#ifdef WARN_UNUSED
[[gnu::warn_unused]]
#endif
Vector3d
// clang-format on
{
private:
  double X = 0.0, Y = 0.0, Z = 0.0;

public:
  Vector3d() noexcept {}
  Vector3d(const double &x, const double &y, const double &z) noexcept : X(x), Y(y), Z(z) {}

  explicit Vector3d(const double (&coords)[3]) noexcept : X(coords[0]), Y(coords[1]), Z(coords[2]) {}

  double
  operator[](size_t index) const noexcept
  {
    switch (index)
      {
      case 0: return X;
      case 1: return Y;
      case 2: return Z;
      }

    return double();
  }

  Vector3d
  operator+(const Vector3d &other) const noexcept
  {
    return Vector3d(X + other.X, Y + other.Y, Z + other.Z);
  }

  Vector3d
  operator-(const Vector3d &other) const noexcept
  {
    return Vector3d(X - other.X, Y - other.Y, Z - other.Z);
  }

  Vector3d
  operator-(void) const noexcept
  {
    return Vector3d(-X, -Y, -Z);
  }

  // Calculate the cross/outer/vector product
  Vector3d
  operator%(const Vector3d &other) const noexcept
  {
    return Vector3d(Y * other.Z - Z * other.Y, Z * other.X - X * other.Z, X * other.Y - Y * other.X);
  }

  // Division by scalars
  Vector3d
  operator/(double scalar) const noexcept
  {
    return Vector3d(X / scalar, Y / scalar, Z / scalar);
  }

  Vector3d
  operator/=(double scalar) noexcept
  {
    return *this = *this / scalar;
  }

  // Calculate the dot/inner/scalar  product
  double
  operator*(const Vector3d &other) const noexcept
  {
    return (X * other.X) + (Y * other.Y) + (Z * other.Z);
  }

  double
  magnitude() const noexcept
  {
    return std::sqrt((X * X) + (Y * Y) + (Z * Z));
  }

  Vector3d
  normalised() const noexcept
  {
    return Vector3d(*this) / magnitude();
  }

  void
  d_normalize()
  {
    double dnorm = std::sqrt(*this * *this);
    *this = *this / dnorm;
  }

  double
  longitude() const noexcept
  {
    return std::atan2(Y, X);
  }

  double
  latitude() const noexcept
  {
    return M_PI_2 - std::acos(Z);
  }
};

static inline Vector3d
vector_product(const Vector3d &v0, const Vector3d &v1, const Vector3d &v2)
{
  // e1, e2: edges of the underlying planar triangle: v1-v0 ands v2-v0, respectively
  auto e1 = v1 - v0;
  auto e2 = v2 - v0;
  auto cu = e1 % e2;
  if ((cu * v0) < 0.0) cu = -cu;
  cu.d_normalize();
  return cu;
}

static inline Vector3d
circum_center_mean(const Vector3d &v0, const Vector3d &v1, const Vector3d &v2)
{
  /*
    v0, v1, v2: the coordinates of the three triangle vertices (_dmo,nit vectors) in
    counter clockwise order center: the coordinates of the circumcenter unless co-linear
  */
  // cu0, cu1, cu2: vector product of center:  e1 x e2

  auto cu0 = vector_product(v0, v1, v2);
  auto cu1 = vector_product(v1, v2, v0);
  auto cu2 = vector_product(v2, v0, v1);

  auto center = cu0 + cu1 + cu2;
  center.d_normalize();
  return center;
}

#endif
