#pragma once
#include <cmath>
#include <algorithm>
#include <ostream>
#include <glm/glm.hpp>

namespace core
{
    namespace types
    {

        struct Vec2i;

        struct Vec2
        {
            float x{0}, y{0};

            Vec2() = default;
            Vec2(float x, float y) : x(x), y(y) {}
            Vec2(float v) : x(v), y(v) {}
            Vec2(const glm::vec2 &v) : x(v.x), y(v.y) {}
            Vec2(const Vec2i &v);

            operator glm::vec2() const { return {x, y}; }

            Vec2 operator+(const Vec2 &rhs) const { return {x + rhs.x, y + rhs.y}; }
            Vec2 operator-(const Vec2 &rhs) const { return {x - rhs.x, y - rhs.y}; }
            Vec2 operator*(float scalar) const { return {x * scalar, y * scalar}; }
            Vec2 operator/(float scalar) const { return {x / scalar, y / scalar}; }

            Vec2 operator*(const Vec2 &rhs) const { return {x * rhs.x, y * rhs.y}; }

            Vec2 &operator+=(const Vec2 &rhs) { x += rhs.x; y += rhs.y; return *this; }
            Vec2 &operator-=(const Vec2 &rhs) { x -= rhs.x; y -= rhs.y; return *this; }
            Vec2 &operator*=(float scalar) { x *= scalar; y *= scalar; return *this; }
            Vec2 &operator/=(float scalar) { x /= scalar; y /= scalar; return *this; }

            Vec2 operator-() const { return {-x, -y}; }

            bool operator==(const Vec2 &rhs) const { return x == rhs.x && y == rhs.y; }
            bool operator!=(const Vec2 &rhs) const { return !(*this == rhs); }

            float Length() const { return std::sqrt(x * x + y * y); }
            float LengthSquared() const { return x * x + y * y; }

            Vec2 Normalized() const
            {
                float len = Length();
                return len != 0 ? *this / len : Vec2(0.0f);
            }

            void Normalize()
            {
                float len = Length();
                if (len != 0)
                {
                    x /= len;
                    y /= len;
                }
            }

            float Dot(const Vec2 &rhs) const { return x * rhs.x + y * rhs.y; }

            void Clamp(const Vec2 &min, const Vec2 &max)
            {
                x = std::max(min.x, std::min(x, max.x));
                y = std::max(min.y, std::min(y, max.y));
            }

            static Vec2 Lerp(const Vec2 &a, const Vec2 &b, float t)
            {
                return a + (b - a) * t;
            }

            static float Distance(const Vec2 &a, const Vec2 &b)
            {
                return (a - b).Length();
            }

            friend std::ostream &operator<<(std::ostream &os, const Vec2 &v)
            {
                return os << "(" << v.x << ", " << v.y << ")";
            }
        };

        struct Vec2i
        {
            int x{0}, y{0};

            Vec2i() = default;
            Vec2i(int x, int y) : x(x), y(y) {}
            Vec2i(int v) : x(v), y(v) {}
            Vec2i(const Vec2 &v) : x(static_cast<int>(v.x)), y(static_cast<int>(v.y)) {}

            Vec2i operator+(const Vec2i &rhs) const { return {x + rhs.x, y + rhs.y}; }
            Vec2i operator-(const Vec2i &rhs) const { return {x - rhs.x, y - rhs.y}; }
            Vec2i operator*(int scalar) const { return {x * scalar, y * scalar}; }
            Vec2i operator/(int scalar) const { return {x / scalar, y / scalar}; }

            Vec2 operator*(const Vec2 &rhs) const { return Vec2(x * rhs.x, y * rhs.y); }

            Vec2i &operator+=(const Vec2i &rhs) { x += rhs.x; y += rhs.y; return *this; }
            Vec2i &operator-=(const Vec2i &rhs) { x -= rhs.x; y -= rhs.y; return *this; }
            Vec2i &operator*=(int scalar) { x *= scalar; y *= scalar; return *this; }
            Vec2i &operator/=(int scalar) { x /= scalar; y /= scalar; return *this; }

            bool operator==(const Vec2i &rhs) const { return x == rhs.x && y == rhs.y; }
            bool operator!=(const Vec2i &rhs) const { return !(*this == rhs); }

            friend std::ostream &operator<<(std::ostream &os, const Vec2i &v)
            {
                return os << "(" << v.x << ", " << v.y << ")";
            }
        };

        inline Vec2 operator*(float scalar, const Vec2 &v) { return v * scalar; }

    } // namespace types
} // namespace core