/////////////////////////////////////////////////////////////////////
// A few choice functions and types from the cglm library
// https://github.com/recp/cglm
//
#pragma once

#include <math.h>
#include <string.h>

typedef float vec2[2];
typedef float vec3[3];
typedef float vec4[4];
typedef float mat4[4][4];

static void glm_mat4_zero(mat4 m)
{
    memset(m, 0, sizeof(mat4));
}

static void glm_mat4_identity(mat4 m)
{
    glm_mat4_zero(m);
    m[0][0] = 1.0f;
    m[1][1] = 1.0f;
    m[2][2] = 1.0f;
    m[3][3] = 1.0f;
}

static void glm_vec4_scale(vec4 v, float s, vec4 dest)
{
    dest[0] = v[0] * s;
    dest[1] = v[1] * s;
    dest[2] = v[2] * s;
    dest[3] = v[3] * s;
}

static void glm_vec4_add(vec4 a, vec4 b, vec4 dest)
{
    dest[0] = a[0] + b[0];
    dest[1] = a[1] + b[1];
    dest[2] = a[2] + b[2];
    dest[3] = a[3] + b[3];
}

static void glm_vec3_scale(vec3 v, float s, vec3 dest)
{
    dest[0] = v[0] * s;
    dest[1] = v[1] * s;
    dest[2] = v[2] * s;
}

static void glm_vec3_sub(vec3 a, vec3 b, vec3 dest)
{
    dest[0] = a[0] - b[0];
    dest[1] = a[1] - b[1];
    dest[2] = a[2] - b[2];
}

static float glm_vec3_dot(vec3 a, vec3 b)
{
    return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
}

static float glm_vec3_norm2(vec3 v)
{
    return glm_vec3_dot(v, v);
}

static float glm_vec3_norm(vec3 v)
{
    return sqrtf(glm_vec3_norm2(v));
}

static void glm_vec3_normalize(vec3 v)
{
    float norm = glm_vec3_norm(v);

    if (norm == 0.0f)
    {
        v[0] = v[1] = v[2] = 0.0f;
        return;
    }

    glm_vec3_scale(v, 1.0f / norm, v);
}

static void glm_vec3_cross(vec3 a, vec3 b, vec3 dest)
{
    /* (u2.v3 - u3.v2, u3.v1 - u1.v3, u1.v2 - u2.v1) */
    dest[0] = a[1] * b[2] - a[2] * b[1];
    dest[1] = a[2] * b[0] - a[0] * b[2];
    dest[2] = a[0] * b[1] - a[1] * b[0];
}

static void glm_vec3_crossn(vec3 a, vec3 b, vec3 dest)
{
    glm_vec3_cross(a, b, dest);
    glm_vec3_normalize(dest);
}

static void glm_perspective(
    float fovy,
    float aspect,
    float nearVal,
    float farVal,
    mat4 dest)
{
    glm_mat4_zero(dest);

    float f = 1.0f / tanf(fovy * 0.5f);
    float fn = 1.0f / (nearVal - farVal);

    dest[0][0] = f / aspect;
    dest[1][1] = f;
    dest[2][2] = (nearVal + farVal) * fn;
    dest[2][3] = -1.0f;
    dest[3][2] = 2.0f * nearVal * farVal * fn;
}

static void glm_translate(mat4 m, vec3 v)
{
    vec4 v1, v2, v3;

    glm_vec4_scale(m[0], v[0], v1);
    glm_vec4_scale(m[1], v[1], v2);
    glm_vec4_scale(m[2], v[2], v3);

    glm_vec4_add(v1, m[3], m[3]);
    glm_vec4_add(v2, m[3], m[3]);
    glm_vec4_add(v3, m[3], m[3]);
}

static void glm_lookat(vec3 eye, vec3 center, vec3 up, mat4 dest)
{
    vec3 f, u, s;

    glm_vec3_sub(center, eye, f);
    glm_vec3_normalize(f);

    glm_vec3_crossn(f, up, s);
    glm_vec3_cross(s, f, u);

    dest[0][0] = s[0];
    dest[0][1] = u[0];
    dest[0][2] = -f[0];
    dest[1][0] = s[1];
    dest[1][1] = u[1];
    dest[1][2] = -f[1];
    dest[2][0] = s[2];
    dest[2][1] = u[2];
    dest[2][2] = -f[2];
    dest[3][0] = -glm_vec3_dot(s, eye);
    dest[3][1] = -glm_vec3_dot(u, eye);
    dest[3][2] = glm_vec3_dot(f, eye);
    dest[0][3] = dest[1][3] = dest[2][3] = 0.0f;
    dest[3][3] = 1.0f;
}