#pragma once

#include "FrameworkMath.h"

namespace Framework
{
    template<typename T, int N>
    //! An N-dimensional vector
    class VecN
    {
    private:
        T v[N];

    public:
        //! Constructor
        inline VecN() {}

        //! Constructor
        //! \param v The values of the vector
        inline VecN(const T v[N])
        {
            memcpy(this->v, v, sizeof(T) * N);
        }

        inline VecN(std::initializer_list<T> l)
        {
            assert(l.size() == N);
            int i = 0;
            for (auto v : l)
            {
                this->v[i] = v;
                i++;
            }
        }

        //! Constructor
        //! \param vect A vector whose values should be copied
        inline VecN(const VecN& vect)
            : VecN(vect.v)
        {}

        //! Scales the vector so that it has length 1
        inline VecN& normalize()
        {
            const T length = getLength();
            for (int i = 0; i < N; i++)
                v[i] /= length;
            return *this;
        }

        //! Swaps the values of the vector with those of another vector
        //! \param vect The other vector
        inline VecN& swap(VecN& vect)
        {
            const VecN tmp = vect;
            vect = *this;
            *this = tmp;
            return *this;
        }

        //! Calculates an angle between this and another vector
        inline float angle(VecN vect)
        {
            return lowPrecisionACos(
                (float)(*this * vect)
                / ((float)getLength() * (float)vect.getLength()));
        }

        //! Copies the values of another vector
        //! \param r The other vector
        inline VecN operator=(const VecN& r)
        {
            memcpy(v, r.v, sizeof(T) * N);
            return *this;
        }

        //! Adds another vector to this one
        //! \param r The other vector
        inline VecN operator+=(const VecN& r)
        {
            for (int i = 0; i < N; i++)
                v[i] += r.v[i];
            return *this;
        }

        //! Subtracts another vector from this one
        //! \param r The other vector
        inline VecN operator-=(const VecN& r)
        {
            for (int i = 0; i < N; i++)
                v[i] -= r.v[i];
            return *this;
        }

        //! Scales this vector
        //! \param r The factor
        inline VecN operator*=(const T& r)
        {
            for (int i = 0; i < N; i++)
                v[i] *= r;
            return *this;
        }

        //! Scales this vector by 1/factor
        //! \param r The factor
        inline VecN operator/=(const T& r)
        {
            for (int i = 0; i < N; i++)
                v[i] /= r;
            return *this;
        }

        //! Calculates the square of the distance between two vectors
        //! \param p The other vector
        inline T distanceSq(const VecN& p) const
        {
            T sum = (T)0;
            for (int i = 0; i < N; i++)
                sum += (v[i] - p.v[i]) * (v[i] - p.v[i]);
            return sum;
        }

        //! Calculates the distance between two vectors
        //! \param p The other vector
        inline T distance(const VecN& p) const
        {
            return sqrt(distanceSq(p));
        }

        //! Returns a new vector that is the negation of this one
        inline VecN operator-() const
        {
            T r[N];
            for (int i = 0; i < N; i++)
                r[i] = -v[i];
            return {r};
        }

        template<typename T2>
        //! Converts the vector type to another
        inline operator VecN<T2, N>() const
        {
            T2 r[N];
            for (int i = 0; i < N; i++)
                r[i] = (T2)v[i];
            return {r};
        }

        //! Calculates the square of the vector's length
        inline T getLengthSq() const
        {
            return *this * *this;
        }

        //! Calculates the length of the vector
        inline T getLength() const
        {
            return (T)sqrt(getLengthSq());
        }

        //! Calculates the dot product between two vectors
        //! \param r The other vector
        inline T operator*(const VecN& r) const
        {
            T sum = (T)0;
            for (int i = 0; i < N; i++)
                sum += v[i] * r.v[i];
            return sum;
        }

        //! Calculates the sum of two vectors
        //! \param r The other vector
        inline VecN operator+(const VecN& r) const
        {
            return VecN(*this) += r;
        }

        //! Subtracts two vectors from each other
        //! \param r The other vector
        inline VecN operator-(const VecN& r) const
        {
            return VecN(*this) -= r;
        }

        //! Scales the vector without modifying it
        //! \param r The factor
        inline VecN operator*(const T& r) const
        {
            return VecN(*this) *= r;
        }

        //! Scales the vector by 1/factor without modifying it
        //! \param r The factor
        inline VecN operator/(const T& r) const
        {
            return VecN(*this) /= r;
        }

        //! Checks two vectors for equality
        //! \param r The other vector
        inline bool operator==(const VecN& r) const
        {
            for (int i = 0; i < N; i++)
            {
                if (v[i] != r.v[i]) return 0;
            }
            return 1;
        }

        //! Checks two vectors for inequality
        //! \param r The other vector
        inline bool operator!=(const VecN& r) const
        {
            return !(*this == r);
        }

        inline T operator[](int i) const
        {
            return v[i];
        }
    };
} // namespace Framework