#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;
            this->x = (T)x;
            this->z = (T)z;
            return *this;
        }
    };
}

#endif