#pragma once

#include "FrameworkMath.h"

namespace Framework
{
    template<typename T, int N>
    //! Ein 3D Vektor
    class VecN
    {
    private:
        T v[N];

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

        //! Konstruktor
        //! \param v die werte des Vectors
        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++;
            }
        }

        //! Konstruktor
        //! \param vect Ein Vektor, dessen Werte kopiert werden sollen
        inline VecN(const VecN& vect)
            : VecN(vect.v)
        {}

        //! Skalliert den Vektor, so dass er die Länge 1 hat
        inline VecN& normalize()
        {
            const T length = getLength();
            for (int i = 0; i < N; i++)
                v[i] /= length;
            return *this;
        }

        //! Vertaucht die Werte des Vektors mit denen eines anderen Vektor
        //! \param vect Der andere Vektor
        inline VecN& swap(VecN& vect)
        {
            const VecN tmp = vect;
            vect = *this;
            *this = tmp;
            return *this;
        }

        //! Berechnet einen winkel zwischen diesem und einem anderen Vektor
        inline float angle(VecN vect)
        {
            return lowPrecisionACos(
                (float)(*this * vect)
                / ((float)getLength() * (float)vect.getLength()));
        }

        //! Kopiert die Werte eines anderen Vektors
        //! \param r Der andere Vektor
        inline VecN operator=(const VecN& r)
        {
            memcpy(v, r.v, sizeof(T) * N);
            return *this;
        }

        //! Addiert einen anderen Vektor zu diesem Hinzu
        //! \param r Der andere Vektor
        inline VecN operator+=(const VecN& r)
        {
            for (int i = 0; i < N; i++)
                v[i] += r.v[i];
            return *this;
        }

        //! Zieht einen anderen Vektor von diesem ab
        //! \param r Der andere Vektor
        inline VecN operator-=(const VecN& r)
        {
            for (int i = 0; i < N; i++)
                v[i] -= r.v[i];
            return *this;
        }

        //! Skalliert diesen Vektor
        //! \param r Der Faktor
        inline VecN operator*=(const T& r)
        {
            for (int i = 0; i < N; i++)
                v[i] *= r;
            return *this;
        }

        //! Skalliert diesen Vektor mit 1/Faktor
        //! \param r Der Faktor
        inline VecN operator/=(const T& r)
        {
            for (int i = 0; i < N; i++)
                v[i] /= r;
            return *this;
        }

        //! Errechnet das Quadrat des Abstands zwischen zewi Vektoren
        //! \param p Der andere Vektor
        inline T abstandSq(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;
        }

        //! Errechnet den Abstand zwischen zwei Vektoren
        //! \param p Der andere Vektor
        inline T abstand(const VecN& p) const
        {
            return sqrt(abstandSq(p));
        }

        //! Gibt einen neuen Vektor zurück, der die negation von diesem ist
        inline VecN operator-() const
        {
            T r[N];
            for (int i = 0; i < N; i++)
                r[i] = -v[i];
            return {r};
        }

        template<typename T2>
        //! Konvertiert den Typ des Vektors in einen anderen
        inline operator VecN<T2, N>() const
        {
            T2 r[N];
            for (int i = 0; i < N; i++)
                r[i] = (T2)v[i];
            return {r};
        }

        //! Errechnet das Quadrat der Länge des Vektors
        inline T getLengthSq() const
        {
            return *this * *this;
        }

        //! Errechnet due Länge des Vektors
        inline T getLength() const
        {
            return (T)sqrt(getLengthSq());
        }

        //! Bildet das Skalarprodukt zwischen zwei Vektoren
        //! \param r Der andere Vektor
        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;
        }

        //! Errechnet die Summe zweier Vektoren
        //! \param r Der andere Vektor
        inline VecN operator+(const VecN& r) const
        {
            return VecN(*this) += r;
        }

        //! Zieht zwei Vektoren von einander ab
        //! \param r Der andere Vektor
        inline VecN operator-(const VecN& r) const
        {
            return VecN(*this) -= r;
        }

        //! Skalliert den Vektor ohne ihn zu verändern
        //! \param r Der Faktor
        inline VecN operator*(const T& r) const
        {
            return VecN(*this) *= r;
        }

        //! Skalliert den Vektor mit 1/Faktor ohne ihn zu Verändern
        //! \param r Der Faktor
        inline VecN operator/(const T& r) const
        {
            return VecN(*this) /= r;
        }

        //! Überprüft zwei Vektoren auf Gleichheit
        //! \param r Der andere Vektor
        inline bool operator==(const VecN& r) const
        {
            for (int i = 0; i < N; i++)
            {
                if (v[i] != r.v[i]) return 0;
            }
            return 1;
        }

        //! Überprüft zwei Vektoren auf Ungleichheit
        //! \param r Der andere Vektor
        inline bool operator!=(const VecN& r) const
        {
            return !(*this == r);
        }

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