#ifndef TINY_VECTOR_HPP
#define TINY_VECTOR_HPP

#include <utils/InvalidData.hpp>
#include <utils/NaNHelper.hpp>
#include <utils/PugsAssert.hpp>
#include <utils/PugsMacros.hpp>
#include <utils/Types.hpp>

#include <cmath>
#include <iostream>

template <size_t N, typename T = double>
class [[nodiscard]] TinyVector
{
 public:
  inline static constexpr size_t Dimension = N;
  using data_type                          = T;

 private:
  T m_values[N];
  static_assert((N > 0), "TinyVector size must be strictly positive");

  template <typename... Args>
  PUGS_FORCEINLINE constexpr void _unpackVariadicInput(const T& t, Args&&... args) noexcept
  {
    m_values[N - 1 - sizeof...(args)] = t;
    if constexpr (sizeof...(args) > 0) {
      this->_unpackVariadicInput(std::forward<Args>(args)...);
    }
  }

 public:
  PUGS_INLINE
  constexpr TinyVector operator-() const
  {
    TinyVector opposite;
    for (size_t i = 0; i < N; ++i) {
      opposite.m_values[i] = -m_values[i];
    }
    return opposite;
  }

  PUGS_INLINE
  constexpr size_t dimension() const
  {
    return N;
  }

  PUGS_INLINE
  constexpr bool operator==(const TinyVector& v) const
  {
    for (size_t i = 0; i < N; ++i) {
      if (m_values[i] != v.m_values[i])
        return false;
    }
    return true;
  }

  PUGS_INLINE
  constexpr bool operator!=(const TinyVector& v) const
  {
    return not this->operator==(v);
  }

  PUGS_INLINE
  constexpr friend T dot(const TinyVector& u, const TinyVector& v)
  {
    T t = u.m_values[0] * v.m_values[0];
    for (size_t i = 1; i < N; ++i) {
      t += u.m_values[i] * v.m_values[i];
    }
    return t;
  }

  PUGS_INLINE
  constexpr TinyVector& operator*=(const T& t)
  {
    for (size_t i = 0; i < N; ++i) {
      m_values[i] *= t;
    }
    return *this;
  }

  PUGS_INLINE
  constexpr friend TinyVector operator*(const T& t, const TinyVector& v)
  {
    TinyVector w = v;
    return w *= t;
  }

  PUGS_INLINE
  constexpr friend TinyVector operator*(const T& t, TinyVector&& v)
  {
    v *= t;
    return std::move(v);
  }

  PUGS_INLINE
  constexpr friend std::ostream& operator<<(std::ostream& os, const TinyVector& v)
  {
    os << '(' << NaNHelper(v.m_values[0]);
    for (size_t i = 1; i < N; ++i) {
      os << ',' << NaNHelper(v.m_values[i]);
    }
    os << ')';
    return os;
  }

  PUGS_INLINE
  constexpr TinyVector operator+(const TinyVector& v) const
  {
    TinyVector sum;
    for (size_t i = 0; i < N; ++i) {
      sum.m_values[i] = m_values[i] + v.m_values[i];
    }
    return sum;
  }

  PUGS_INLINE
  constexpr TinyVector operator+(TinyVector&& v) const
  {
    for (size_t i = 0; i < N; ++i) {
      v.m_values[i] += m_values[i];
    }
    return std::move(v);
  }

  PUGS_INLINE
  constexpr TinyVector operator-(const TinyVector& v) const
  {
    TinyVector difference;
    for (size_t i = 0; i < N; ++i) {
      difference.m_values[i] = m_values[i] - v.m_values[i];
    }
    return difference;
  }

  PUGS_INLINE
  constexpr TinyVector operator-(TinyVector&& v) const
  {
    for (size_t i = 0; i < N; ++i) {
      v.m_values[i] = m_values[i] - v.m_values[i];
    }
    return std::move(v);
  }

  PUGS_INLINE
  constexpr TinyVector& operator+=(const TinyVector& v)
  {
    for (size_t i = 0; i < N; ++i) {
      m_values[i] += v.m_values[i];
    }
    return *this;
  }

  PUGS_INLINE
  constexpr void operator+=(const volatile TinyVector& v) volatile
  {
    for (size_t i = 0; i < N; ++i) {
      m_values[i] += v.m_values[i];
    }
  }

  PUGS_INLINE
  constexpr TinyVector& operator-=(const TinyVector& v)
  {
    for (size_t i = 0; i < N; ++i) {
      m_values[i] -= v.m_values[i];
    }
    return *this;
  }

  PUGS_INLINE
  constexpr T& operator[](size_t i) noexcept(NO_ASSERT)
  {
    Assert(i < N);
    return m_values[i];
  }

  PUGS_INLINE
  constexpr const T& operator[](size_t i) const noexcept(NO_ASSERT)
  {
    Assert(i < N);
    return m_values[i];
  }

  PUGS_INLINE
  constexpr TinyVector& operator=(ZeroType) noexcept
  {
    static_assert(std::is_arithmetic<T>(), "Cannot assign 'zero' value for non-arithmetic types");
    for (size_t i = 0; i < N; ++i) {
      m_values[i] = 0;
    }
    return *this;
  }

  PUGS_INLINE
  TinyVector& operator=(const TinyVector&) noexcept = default;

  PUGS_INLINE
  constexpr TinyVector& operator=(TinyVector&& v) noexcept = default;

  template <typename... Args>
  PUGS_INLINE constexpr TinyVector(const T& t, Args&&... args) noexcept
  {
    static_assert(sizeof...(args) == N - 1, "wrong number of parameters");
    this->_unpackVariadicInput(t, std::forward<Args>(args)...);
  }

  // One does not use the '=default' constructor to avoid
  // (zero-initialization) performances issues
  PUGS_INLINE
  constexpr TinyVector() noexcept
  {
#ifndef NDEBUG
    for (size_t i = 0; i < N; ++i) {
      m_values[i] = invalid_data_v<T>;
    }
#endif   // NDEBUG
  }

  PUGS_INLINE
  constexpr TinyVector(ZeroType) noexcept
  {
    static_assert(std::is_arithmetic<T>(), "Cannot construct from 'zero' value "
                                           "for non-arithmetic types");
    for (size_t i = 0; i < N; ++i) {
      m_values[i] = 0;
    }
  }

  PUGS_INLINE
  constexpr TinyVector(const TinyVector&) noexcept = default;

  PUGS_INLINE
  constexpr TinyVector(TinyVector && v) noexcept = default;

  PUGS_INLINE
  ~TinyVector() noexcept = default;
};

template <size_t N, typename T>
PUGS_INLINE constexpr T
l2Norm(const TinyVector<N, T>& x)
{
  static_assert(std::is_arithmetic<T>(), "Cannot compute L2 norm for non-arithmetic types");
  static_assert(std::is_floating_point<T>::value, "L2 norm is defined for floating point types only");
  return std::sqrt(dot(x, x));
}

// Cross product is only defined for K^3 vectors
template <typename T>
PUGS_INLINE constexpr TinyVector<3, T>
crossProduct(const TinyVector<3, T>& u, const TinyVector<3, T>& v)
{
  TinyVector<3, T> cross_product(u[1] * v[2] - u[2] * v[1], u[2] * v[0] - u[0] * v[2], u[0] * v[1] - u[1] * v[0]);
  return cross_product;
}

#endif   // TINYVECTOR_HPP