#pragma once

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

namespace TSE
{
    class Vector3;
    class Vector4;

    class Vector2
    {
        public:
        #pragma region members
        float x = 0, y = 0;
        #pragma endregion members

        #pragma region consts
        static const Vector2 left;
        static const Vector2 right;
        static const Vector2 up;
        static const Vector2 down;
        static const Vector2 one;
        static const Vector2 zero;
        #pragma endregion

        #pragma region ctor
        /// @brief enpty constructor defined as (0,0)
        Vector2();
        /// @brief constructs a vector2 with custom values
        /// @param _x the x component
        /// @param _y the y component
        Vector2(float _x, float _y);
        /// @brief copy constructor
        /// @param other the vector2 to be copied from
        Vector2(const Vector2& other);
        /// @brief converter constructor. it converts a Vector3 to a Vector2 without encointing for the z component
        /// @param other the Vector3 to convert
        Vector2(const Vector3& other);
        /// @brief converter constructor. it converts a Vector4 to a Vector2 without encointing for the z, and w component
        /// @param other the Vector4 to convert
        Vector2(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 Vector2 to have a length of 1
        void Normalize();
        /// @brief normalizes the Vector2 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 Vector2& fallback);
        /// @brief gives you the vector2 as a string representation. mostly for debugging
        /// @return the vector2 in a format like (x|y)
        string ToString() const;
        /// @brief creates a Vector3 with the same values as the Vector2, the y component gets the value 0
        /// @return the Vector2 as a Vector3
        Vector3 ToVector3() const;
        /// @brief creates a Vector4 with the same values as the Vector2, the y and w components get the value 0
        /// @return the Vector2 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 Vector2& a, const Vector2& 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 Vector2& a, const Vector2& b);
        /// @brief normalizes a copy of the Vector2 to have a length of 1
        /// @param v the vector to be normaliezed
        /// @return a normalies copy of the vector
        static Vector2 Normalize(const Vector2& v);
        /// @brief normalizes a copy of the Vector2 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 Vector2 NormalizeSafe(const Vector2& v, const Vector2& 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 Vector2 Lerp(const Vector2& a, const Vector2& 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 Vector2 Berp(const Vector2& a, const Vector2& b, const Vector2& c, float t);
        /// @brief gives you the midpoint between two points
        /// @param a point 1
        /// @param b point 2
        /// @return the midpoint
        static Vector2 Midpoint(const Vector2& a, const Vector2& 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 Vector2& a, const Vector2& b);
        /// @brief it gives you the "2D Cross Product" (there is no real 2D Cross product, but this function is still usefull sometimes)
        /// @param a vector 1
        /// @param b vector 2
        /// @return 2D cross product
        static float Cross(const Vector2& a, const Vector2& b);
        #pragma endregion static

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

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