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

namespace OX {

    struct Vec3
    {
        float x{0}, y{0}, z{0};

        Vec3() = default;
        Vec3(const float x, const float y, const float z) : x(x), y(y), z(z) {}
        Vec3(const float v) : x(v), y(v), z(v) {}
        Vec3(const glm::vec3 &v) : x(v.x), y(v.y), z(v.z) {}

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





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

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



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

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

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

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

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

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

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

        Vec3 Cross(const Vec3 &rhs) const
        {
            return Vec3(
                y * rhs.z - z * rhs.y,
                z * rhs.x - x * rhs.z,
                x * rhs.y - y * rhs.x
            );
        }

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

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

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

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

        float& operator[](const int i) {
            switch (i) {
                case 0: return x;
                case 1: return y;
                case 2: return z;
                default: throw std::out_of_range("Vec3 index out of range");
            }
        }

        const float& operator[](const int i) const {
            switch (i) {
                case 0: return x;
                case 1: return y;
                case 2: return z;
                default: throw std::out_of_range("Vec3 index out of range");
            }
        }


    };

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



}
