#pragma once

#include "Vec3.h"

namespace Framework
{
    template<typename T>
    //! Stores a 3D plane in coordinate form (x*?+y*?+z*?-w=0)
    class Plane3D
    {
    public:
        T x; //! The X component of the normal vector of the plane
        T y; //! The Y component of the normal vector of the plane
        T z; //! The Z component of the normal vector of the plane
        T w; //! The value the dot product of a vector with the normal
             //! vector must have to lie on the plane

        //! Constructor
        inline Plane3D()
            : x((T)0),
              y((T)0),
              z((T)0),
              w((T)0)
        {}

        //! Constructor
        //! \param x The X component of the normal vector of the plane
        //! \param y The Y component of the normal vector of the plane
        //! \param z The Z component of the normal vector of the plane
        //! \param w The value the dot product of a vector with the
        //! normal vector must have to lie on the plane
        inline Plane3D(T x, T y, T z, T w)
            : x(x),
              y(y),
              z(z),
              w(w)
        {}

        //! Constructor
        //! \param vect The plane whose values should be copied
        inline Plane3D(const Plane3D& vect)
            : Plane3D(vect.x, vect.y, vect.z, vect.w)
        {}

        //! Normalizes the normal vector of the plane to length 1
        inline Plane3D& normalize()
        {
            const T length = len();
            x /= length;
            y /= length;
            z /= length;
            w /= length;
            return *this;
        }

        //! Swaps the values of two planes
        //! \param vect The other plane
        inline Plane3D& Swap(Plane3D& vect)
        {
            const Plane3D tmp = vect;
            vect = *this;
            *this = tmp;
            return *this;
        }

        //! Copies the values of another plane
        //! \param r The other plane
        inline Plane3D operator=(const Plane3D& r)
        {
            x = r.x;
            y = r.y;
            z = r.z;
            w = r.w;
            return *this;
        }

        //! Adds another plane
        //! \param r The other plane
        inline Plane3D operator+=(const Plane3D& r)
        {
            x += r.x;
            y += r.y;
            z += r.z;
            w += r.w;
            return *this;
        }

        //! Subtracts another plane
        //! \param r The other plane
        inline Plane3D operator-=(const Plane3D& r)
        {
            x -= r.x;
            y -= r.y;
            z -= r.z;
            w -= r.w;
            return *this;
        }

        //! Multiplies the plane by a factor
        //! \param r The factor
        inline Plane3D operator*=(const T& r)
        {
            x *= r;
            y *= r;
            z *= r;
            w *= r;
            return *this;
        }

        //! Divides the plane by a factor
        //! \param r The factor
        inline Plane3D operator/=(const T& r)
        {
            x /= r;
            y /= r;
            z /= r;
            w /= r;
            return *this;
        }

        template<typename T2>
        //! Converts the plane to another type
        inline operator Plane3D<T2>() const
        {
            return {(T2)x, (T2)y, (T2)z, (T2)w};
        }

        //! Calculates the square of the length of the normal vector of the plane
        inline T lenSq() const
        {
            return x * x + y * y + z * z;
        }

        //! Calculates the length of the normal vector
        inline T len() const
        {
            return sqrt(lenSq());
        }

        //! Calculates the distance from the plane to a vector
        //! \param r The vector
        inline T operator*(const Vec3<T>& r) const
        {
            return x * r.x + y * r.y + z * r.z + w;
        }

        //! Adds two planes
        //! \param r The other plane
        inline Plane3D operator+(const Plane3D& r) const
        {
            return Vec4(*this) += r;
        }

        //! Subtracts two planes
        //! \param r The other plane
        inline Plane3D operator-(const Plane3D& r) const
        {
            return Plane3D(*this) -= r;
        }

        //! Multiplies the plane by a factor
        //! r: The factor
        inline Plane3D operator*(const T& r) const
        {
            return Plane3D(*this) *= r;
        }

        //! Divides the plane by a factor
        //! \param r The factor
        inline Plane3D operator/(const T& r) const
        {
            return Plane3D(*this) /= r;
        }

        //! Checks two planes for equality
        //! \param r The other plane
        inline bool operator==(const Plane3D& r) const
        {
            return x == r.x && y == r.y && z == r.z && w == r.w;
        }

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

        inline Vec3<T> normal() const
        {
            return {x, y, z};
        }
    };
} // namespace Framework