#pragma once

#include "Types.hpp"
#include <functional>
#include <cstddef>
#include "MathF.hpp"

namespace TSE
{
    class Vector2;
    class Vector4;

    class Vector3
    {
        public:

        #pragma region member
        float x = 0, y = 0, z = 0;
        #pragma endregion member

        #pragma region consts
        static const Vector3 left;
        static const Vector3 right;
        static const Vector3 up;
        static const Vector3 down;
        static const Vector3 forward;
        static const Vector3 back;
        static const Vector3 one;
        static const Vector3 zero;

        #pragma endregion consts

        #pragma region ctor
        /// @brief default constructer constructs a Vector3 with (0,0,0)
        Vector3();
        /// @brief constructor where you only need to set x and y. z stays 0.
        /// @param _x x component
        /// @param _y y component
        Vector3(float _x, float _y);
        /// @brief constructor with custom values
        /// @param _x x component
        /// @param _y y component
        /// @param _z z component
        Vector3(float _x, float _y, float _z);
        /// @brief copy constructor
        /// @param other the vector3 to be copied
        Vector3(const Vector3& other);
        /// @brief converter constructer. it converts a Vector2 to a vector3 where z gets value 0
        /// @param other the Vector2 to be converted
        Vector3(const Vector2& other);
        /// @brief converter constructer. it converts a Vector4 to a vector3 where w gets disregarded
        /// @param other the Vector3 to be converted
        Vector3(const Vector4& other);
        #pragma endregion ctor
        
        #pragma region methods
        /// @brief the length of the vector
        /// @return the length
        float Length() const;
        /// @brief the length² of the vector
        /// @return the length²
        float LengthSqr() const;
        /// @brief checks if the individual components have valid values aka are not nan
        /// @return are the values valid
        bool IsValid() const;
        /// @brief normalizes the Vector3 to have a length of 1
        void Normalize();
        /// @brief normalizes the Vector3 to have a length of 1. If the current length of the vector is smaller then epsilon, it gives you the fallback value.
        /// @param fallback the value to set the vector to if the current length is smaller then epsilon
        void NormalizeSafe(const Vector3& fallback);
        /// @brief gives you the vector3 as a string representation. mostly for debugging
        /// @return the vector3 in a format like (x|y|z)
        string ToString() const;
        /// @brief creates a Vector2 with the same values as the Vector3, the y component gets disregarded
        /// @return the Vector3 as a Vector2
        Vector2 ToVector2() const;
        /// @brief creates a Vector4 with the same values as the Vector3, the w component get the value 0
        /// @return the Vector3 as a Vector4
        Vector4 ToVector4() const;

        #pragma region static
        /// @brief gives you the distance between two vectors.
        /// @param a point 1
        /// @param b point 2
        /// @return the distance between point 1 and point 2
        static float Distance(const Vector3& a, const Vector3& b);
        /// @brief gives you the distance² between two vectors.
        /// @param a point 1
        /// @param b point 2
        /// @return the distance² between point 1 and point 2
        static float DistanceSqr(const Vector3& a, const Vector3& b);
        /// @brief normalizes a copy of the Vector3 to have a length of 1
        /// @param v the vector to be normaliezed
        /// @return a normalies copy of the vector
        static Vector3 Normalize(const Vector3& v);
        /// @brief normalizes a copy of the Vector3 to have a length of 1. If the current length of the vector is smaller then epsilon, it gives you the fallback value.
        /// @param fallback the value to set the vector to if the current length is smaller then epsilon
        /// @param v the vector to be normaliezed
        /// @return a normalies copy of the vector
        static Vector3 NormalizeSafe(const Vector3& v, const Vector3& fallback);
        /// @brief linearly interpolates between two points using t
        /// @param a point 1
        /// @param b point 2
        /// @param t the amoint to interpolate by
        /// @return the interpolated point
        static Vector3 Lerp(const Vector3& a, const Vector3& b, float t);
        /// @brief bezier interpolation using three points using t
        /// @param a point 1
        /// @param b controll point
        /// @param c point 2
        /// @param t the amoint to interpolate by
        /// @return the interpolated point
        static Vector3 Berp(const Vector3& a, const Vector3& b, const Vector3& c, float t);
        /// @brief gives you the midpoint between two points
        /// @param a point 1
        /// @param b point 2
        /// @return the midpoint
        static Vector3 Midpoint(const Vector3& a, const Vector3& b);
        /// @brief gives you the dot product between to vectors
        /// @param a vector 1
        /// @param b vector 2
        /// @return the dot product
        static float Dot(const Vector3& a, const Vector3& b);
        /// @brief it gives you the Cross Product
        /// @param a vector 1
        /// @param b vector 2
        /// @return cross product
        static Vector3 Cross(const Vector3& a, const Vector3& b);
        #pragma endregion static

        #pragma region operators
        Vector3 operator+(const Vector3& other) const;
        Vector3 operator+=(const Vector3& other);
        Vector3 operator-(const Vector3& other) const;
        Vector3 operator-=(const Vector3& other);
        Vector3 operator*(const Vector3& other) const;
        Vector3 operator*=(const Vector3& other);
        Vector3 operator/(const Vector3& other) const;
        Vector3 operator/=(const Vector3& other);
        Vector3 operator*(const float other) const;
        Vector3 operator*=(const float other);
        Vector3 operator/(const float other) const;
        Vector3 operator/=(const float other);
        bool operator==(const Vector3& other) const;
        bool operator!=(const Vector3& other) const;
        #pragma endregion operators
        #pragma endregion methods

    };
} // namespace TSE


namespace std
{
    template<>
    struct hash<TSE::Vector3>
    {
        size_t operator()(const TSE::Vector3& v) const noexcept
        {
            size_t h1 = std::hash<float>{}(v.x);
            size_t h2 = std::hash<float>{}(v.y);
            size_t h3 = std::hash<float>{}(v.z);
            size_t hash = h1;
            hash ^= h2 + TSE::TSE_HASH_GOLDEN_RATIO_32 + (hash << 6) + (hash >> 2);
            hash ^= h3 + TSE::TSE_HASH_GOLDEN_RATIO_32 + (hash << 6) + (hash >> 2);
            return hash;
        }
    };
}