#pragma once

#include "colla/colla.h"
#include <math.h>

#include "libs/handmademath.h"

typedef union int2 {
    int data[2];
    struct { int x, y; };
} int2;

typedef union int3 {
    int data[3];
    struct { int x, y, z; };
} int3;

typedef union float2 {
    HMM_Vec2 hm;
    float data[2];
    struct { float x, y; };
} vec2;

typedef union float3 {
    HMM_Vec3 hm;
    float data[3];
    struct { float x, y, z; };
} vec3;

typedef union float4 {
    HMM_Vec4 hm;
    float data[4];
    struct { float x, y, z, w; };
    struct { float r, g, b, a; };
} vec4;

typedef union mat4 {
    HMM_Mat4 hm;
    float data[16];
    struct {
        float m00, m01, m02, m03;
        float m10, m11, m12, m13;
        float m20, m21, m22, m23;
        float m30, m31, m32, m33;
    };
} mat4;

#define clamp(val, vmin, vmax) \
    _Generic((val),            \
        float:  fclamp,        \
        float2: f2clamp,       \
        float3: f3clamp,       \
        float4: f4clamp        \
    )(val, vmin, vmax)

#define saturate(val)       \
    _Generic((val),         \
        float:   fsaturate, \
        float2: f2saturate, \
        float3: f3saturate, \
        float4: f4saturate \
    )(val)
        
#define add(a, b) \
    _Generic((a), \
        float2: f2add, \
        float3: f3add, \
        float4: f4add \
    )(a, b)

#define adds(v, s) \
    _Generic((v), \
        float2: f2adds, \
        float3: f3adds, \
        float4: f4adds \
    )(v, s)

#define cross HMM_Cross
#define norm HMM_Norm
#define mul HMM_Mul
#define add HMM_Add
#define m4_lookat HMM_LookAt_LH
#define v3 HMM_V3
#define m4_proj HMM_Perspective_LH_ZO

#if 0
force_inline float fclamp(float val, float vmin, float vmax) {
    return MIN(MAX(val, vmin), vmax);
}

force_inline float2 f2clamp(float2 val, float2 vmin, float2 vmax) {
    return (float2) {
        fclamp(val.x, vmin.x, vmax.x),
        fclamp(val.y, vmin.y, vmax.y),
    };
}

force_inline float3 f3clamp(float3 val, float3 vmin, float3 vmax) {
    return (float3) {
        fclamp(val.x, vmin.x, vmax.x),
        fclamp(val.y, vmin.y, vmax.y),
        fclamp(val.z, vmin.z, vmax.z),
    };
}

force_inline float4 f4clamp(float4 val, float4 vmin, float4 vmax) {
    return (float4) {
        fclamp(val.x, vmin.x, vmax.x),
        fclamp(val.y, vmin.y, vmax.y),
        fclamp(val.z, vmin.z, vmax.z),
        fclamp(val.w, vmin.w, vmax.w),
    };
}

force_inline float2 f2add(float2 a, float2 b) {
    return (float2) { a.x + b.x, a.y + b.y };
}

force_inline float3 f3add(float3 a, float3 b) {
    return (float3) { a.x + b.x, a.y + b.y, a.z + b.z };
}

force_inline float4 f4add(float4 a, float4 b) {
    return (float4) { a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w };
}

force_inline float2 f2adds(float2 v, float s) {
    return (float2) { v.x + s, v.y + s };
}

force_inline float3 f3adds(float3 v, float s) {
    return (float3) { v.x + s, v.y + s, v.z + s };
}

force_inline float4 f4adds(float4 v, float s) {
    return (float4) { v.x + s, v.y + s, v.z + s, v.w + s };
}

force_inline float fsaturate(float v) {
    return fclamp(v, 0, 1);
}

force_inline float2 f2saturate(float2 v) {
    return f2clamp(v, (float2){0}, (float2){1,1});
}

force_inline float3 f3saturate(float3 v) {
    return f3clamp(v, (float3){0}, (float3){1,1,1});
}

force_inline float4 f4saturate(float4 v) {
    return f4clamp(v, (float4){0}, (float4){1,1,1,1});
}

force_inline float f3mag2(float3 v) {
    return v.x * v.x + v.y * v.y + v.z * v.z;
}

force_inline float f3mag(float3 v) {
    return sqrtf(f3mag2(v));
}

force_inline float3 f3muls(float3 v, float s) {
    return (float3) {
        .x = v.x * s,
        .y = v.y * s,
        .z = v.z * s,
    };
}

force_inline float3 f3divs(float3 v, float s) {
    s = 1.f / s;
    return f3muls(v, s);
}

force_inline float3 f3norm(float3 v) {
    float mag = f3mag(v);
    if (mag == 0) return v;
    return f3divs(v, mag);
}
#endif
