#pragma once

#include "bx/math.h"
#include <cfloat>

struct Vec2
{
    float x = 0.0f;
    float y = 0.0f;

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

    friend Vec2 operator+(Vec2 lhs, const Vec2& rhs)
    {
        lhs += rhs;
        return lhs;
    }

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

    friend Vec2 operator-(Vec2 lhs, const Vec2& rhs)
    {
        lhs -= rhs;
        return lhs;
    }

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

    friend Vec2 operator*(Vec2 lhs, const float rhs)
    {
        lhs *= rhs;
        return lhs;
    }

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

    friend Vec2 operator/(Vec2 lhs, const float rhs)
    {
        lhs /= rhs;
        return lhs;
    }

    float Magnitude()
    {
        return bx::sqrt(x * x + y * y);
    }

    Vec2 Normalize()
    {
        float mag = Magnitude();
        if (mag < FLT_EPSILON) return {};
        return {x / mag, y / mag};
    }
};

struct Vec3
{
    float x = 0.0f;
    float y = 0.0f;
    float z = 0.0f;

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

    friend Vec3 operator+(Vec3 lhs, const Vec3& rhs)
    {
        lhs += rhs;
        return lhs;
    }

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

    friend Vec3 operator-(Vec3 lhs, const Vec3& rhs)
    {
        lhs -= rhs;
        return lhs;
    }

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

    friend Vec3 operator*(Vec3 lhs, const float rhs)
    {
        lhs *= rhs;
        return lhs;
    }

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

    friend Vec3 operator/(Vec3 lhs, const float rhs)
    {
        lhs /= rhs;
        return lhs;
    }

    float Magnitude()
    {
        return bx::sqrt(x * x + y * y + z * z);
    }

    Vec3 Normalize()
    {
        float mag = Magnitude();
        if (mag < FLT_EPSILON) return {};
        return {x / mag, y / mag, z / mag};
    }
};

struct Vec4
{
    float x = 0.0f;
    float y = 0.0f;
    float z = 0.0f;
    float w = 0.0f;
};

struct Mat3
{
    // clang-format off
    float M[9]{
        1.0f, 0.0f, 0.0f,
        0.0f, 1.0f, 0.0f,
        0.0f, 0.0f, 1.0f,
    };
    // clang-format on
};

struct Mat4
{
    // clang-format off
    float M[16]{
        1.0, 0.0, 0.0, 0.0,
        0.0, 1.0, 0.0, 0.0,
        0.0, 0.0, 1.0, 0.0,
        0.0, 0.0, 0.0, 1.0,
    };
    // clang-format on

    Mat4 Inverse();
    Mat4 Transpose();
};

inline int32_t SetFlags(int32_t in, int32_t flags)
{
    return in | flags;
}

inline int32_t ClearFlags(int32_t in, int32_t flags)
{
    return in & ~flags;
}

inline int32_t FlagBool(int32_t in, int32_t flags, bool IsSet)
{
    if (IsSet) return SetFlags(in, flags);
    return ClearFlags(in, flags);
}

inline bool GetFlag(int32_t in, int32_t flags)
{
    return (in & flags) > 0;
}

struct Transform
{
    Mat4 M;
    Mat4 MI;
    bx::Vec3 Position{0.0f, 0.0f, 0.0f};
    Mat4 Rotation;
    bx::Vec3 Scale{1.0f, 1.0f, 1.0f};

    static void CreateTransform(float* out, bx::Vec3 pos, bx::Quaternion rot, bx::Vec3 scale);
    void Translate(Vec3 offset);
    void TranslateLocal(Vec3 offset);
    void Rotate(Vec3 rotation);
    void RotateLocal(Vec3 rotation);
    bx::Vec3 LocalToGlobalPoint(Vec3 local);
    Vec3 LocalToGlobalDirection(Vec3 local);
    bx::Vec3 GlobalToLocalPoint(Vec3 global);
    Vec3 GlobalToLocalDirection(Vec3 global);
    Vec3 Right() const;
    Vec3 Up() const;
    Vec3 Forward() const;
    void SetPosition(Vec3 pos);
    Vec3 GetPosition();
    const float* GetPtr();
    void UpdateMatrix();
    void UpdateMatrixForCam();
};

struct SharedData;

namespace Game
{
    struct GameInstance;

    SharedData& GetShared();
    void SetShared(SharedData& instance);
    GameInstance& GetInstance();
    void SetInstance(GameInstance& instance);
    void* AllocateScratch(size_t byteCount, size_t align = 16);
} // namespace Game
