#ifndef Vec3_H
#define Vec3_H

#include "FrameworkMath.h"

namespace Framework
{
    template<typename T>
    //! Ein 3D Vektor
    class Vec3
    {
    public:
        T x; //! X Komponente des Vektors
        T y; //! y Komponente des Vektors
        T z; //! z Komponente des Vektors

        //! Konstruktor
        inline Vec3() {}

        //! Konstruktor
        //! \param x Die X Komponente des neuen Vektors
        //! \param y Die Y Komponente des neuen Vektors
        //! \param z Die Z Komponente des neuen Vektors
        inline Vec3(T x, T y, T z)
            : x(x),
              y(y),
              z(z)
        {}

        //! Konstruktor
        //! \param vect Ein Vektor, dessen Werte kopiert werden sollen
        inline Vec3(const Vec3& vect)
            : Vec3(vect.x, vect.y, vect.z)
        {}

        //! Skalliert den Vektor, so dass er die L�nge 1 hat
        inline Vec3& normalize()
        {
            const T length = getLength();
            x /= length;
            y /= length;
            z /= length;
            return *this;
        }

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

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

        //! Kopiert die Werte eines anderen Vektors
        //! \param r Der andere Vektor
        inline Vec3 operator=(const Vec3& r)
        {
            x = r.x;
            y = r.y;
            z = r.z;
            return *this;
        }

        //! Addiert einen anderen Vektor zu diesem Hinzu
        //! \param r Der andere Vektor
        inline Vec3 operator+=(const Vec3& r)
        {
            x += r.x;
            y += r.y;
            z += r.z;
            return *this;
        }

        //! Zieht einen anderen Vektor von diesem ab
        //! \param r Der andere Vektor
        inline Vec3 operator-=(const Vec3& r)
        {
            x -= r.x;
            y -= r.y;
            z -= r.z;
            return *this;
        }

        //! Skalliert diesen Vektor
        //! \param r Der Faktor
        inline Vec3 operator*=(const T& r)
        {
            x *= r;
            y *= r;
            z *= r;
            return *this;
        }

        //! Skalliert diesen Vektor mit 1/Faktor
        //! \param r Der Faktor
        inline Vec3 operator/=(const T& r)
        {
            x /= r;
            y /= r;
            z /= r;
            return *this;
        }

        //! Errechnet das Quadrat des Abstands zwischen zewi Vektoren
        //! \param p Der andere Vektor
        inline T abstandSq(const Vec3& p) const
        {
            return (x - p.x) * (x - p.x) + (y - p.y) * (y - p.y)
                 + (z - p.z) * (z - p.z);
        }

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

        //! Gibt einen neuen Vektor zur�ck, der die negation von diesem ist
        inline Vec3 operator-() const
        {
            return {-x, -y, -z};
        }

        template<typename T2>
        //! Konvertiert den Typ des Vektors in einen anderen
        inline operator Vec3<T2>() const
        {
            return {(T2)x, (T2)y, (T2)z};
        }

        //! 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 Vec3& r) const
        {
            return x * r.x + y * r.y + z * r.z;
        }

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

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

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

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

        //! �berpr�ft zwei Vektoren auf Gleichheit
        //! \param r Der andere Vektor
        inline bool operator==(const Vec3& r) const
        {
            return x == r.x && y == r.y && z == r.z;
        }

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

        //! Gibt das Kreutzprodukt zwischen diesem und einem anderen Vector
        //! zur�ck \param b der andere Vector
        inline Vec3 crossProduct(const Vec3& b) const
        {
            return Vec3(
                y * b.z - z * b.y, z * b.x - x * b.z, x * b.y - y * b.x);
        }

        inline Vec3& rotateY(float radian)
        {
            const float cosTheta = lowPrecisionCos(radian);
            const float sinTheta = lowPrecisionSin(radian);
            float x = cosTheta * this->x + sinTheta * this->z;
            float z = -sinTheta * this->x + cosTheta * this->z;
            this->x = (T)x;
            this->z = (T)z;
            return *this;
        }

        inline Vec3& rotateX(float radian)
        {
            const float cosTheta = lowPrecisionCos(radian);
            const float sinTheta = lowPrecisionSin(radian);
            float y = cosTheta * this->y + -sinTheta * this->z;
            float z = sinTheta * this->y + cosTheta * this->z;
            this->y = (T)y;
            this->z = (T)z;
            return *this;
        }

        inline Vec3& rotateZ(float radian)
        {
            const float cosTheta = lowPrecisionCos(radian);
            const float sinTheta = lowPrecisionSin(radian);
            float x = cosTheta * this->x + -sinTheta * this->y;
            float y = sinTheta * this->x + cosTheta * this->y;
            this->x = (T)x;
            this->y = (T)y;
            return *this;
        }
    };
} // namespace Framework

#endif