#ifndef TINY_VECTOR_HPP
#define TINY_VECTOR_HPP

#include <iostream>

#include <PastisAssert.hpp>
#include <Kokkos_Core.hpp>
#include <Types.hpp>

template <size_t N, typename T=double>
class TinyVector
{
private:
  T m_values[N];
  static_assert((N>0),"TinyVector size must be strictly positive");

  void _unpackVariadicInput(const T& t)
  {
    m_values[N-1] = t;
  }

  template <typename... Args>
  void _unpackVariadicInput(const T& t, Args&&... args)
  {
    m_values[N-1-sizeof...(args)] = t;
    this->_unpackVariadicInput(args...);
  }

public:
  KOKKOS_INLINE_FUNCTION
  TinyVector operator-() const
  {
    TinyVector opposed;
    for (size_t i=0; i<N; ++i) {
      opposed.m_values[i] =-m_values[i];
    }
    return std::move(opposed);
  }

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

  KOKKOS_INLINE_FUNCTION
  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;
  }

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

  KOKKOS_INLINE_FUNCTION
  T operator,(const TinyVector& v) const
  {
    T t = m_values[0]*v.m_values[0];
    for (size_t i=1; i<N; ++i) {
      t += m_values[i]*v.m_values[i];
    }
    return std::move(t);
  }

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

  KOKKOS_INLINE_FUNCTION
  friend TinyVector operator*(const T& t, const TinyVector& v)
  {
    TinyVector tv;
    for (size_t i=0; i<N; ++i) {
      tv.m_values[i] = t * v.m_values[i];
    }
    return std::move(tv);
  }

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

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

  KOKKOS_INLINE_FUNCTION
  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 std::move(sum);
  }

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

  KOKKOS_INLINE_FUNCTION
  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 std::move(difference);
  }

  KOKKOS_INLINE_FUNCTION
  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);
  }

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

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

  KOKKOS_INLINE_FUNCTION
  T& operator[](const size_t& i)
  {
    Assert(i<N);
    return m_values[i];
  }

  KOKKOS_INLINE_FUNCTION
  const T& operator[](const size_t& i) const
  {
    Assert(i<N);
    return m_values[i];
  }

  KOKKOS_INLINE_FUNCTION
  TinyVector& operator=(const ZeroType& z)
  {
    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;
  }

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

  KOKKOS_INLINE_FUNCTION
  TinyVector& operator=(TinyVector&& v) = default;

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

  KOKKOS_INLINE_FUNCTION
  TinyVector()
  {
    ;
  }

  KOKKOS_INLINE_FUNCTION
  TinyVector(const ZeroType& z)
  {
    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;
    }
  }

  KOKKOS_INLINE_FUNCTION
  TinyVector(const TinyVector& v)
  {
    for (size_t i=0; i<N; ++i) {
      m_values[i] = v.m_values[i];
    }
  }

  KOKKOS_INLINE_FUNCTION
  TinyVector(TinyVector&& v) = default;

  KOKKOS_INLINE_FUNCTION
  ~TinyVector()
  {
    ;
  }
};

#endif // TINYVECTOR_HPP