#ifndef VEC_H
#define VEC_H

#include <stdint.h>
#include <math.h>


/* a point in some space (integer) */
typedef struct vec2 {
_Alignas(8)
    int32_t x;
    int32_t y;
} t_vec2;


/* a point in some space (floating point) */
typedef struct fvec2 {
_Alignas(8)
    float x;
    float y;
} t_fvec2;


/* a point in some three dimension space (floating point) */
/* y goes up, x goes to the right */
typedef struct fvec3 {
_Alignas(16)
    float x;
    float y;
    float z;
} t_fvec3;


/* a point in some three dimension space (floating point) */
/* y goes up, x goes to the right */
typedef struct fvec4 {
_Alignas(16)
    float x;
    float y;
    float z;
    float w;
} t_fvec4;


/* a point in some space (short) */
typedef struct shvec2 {
_Alignas(4)
    int16_t x;
    int16_t y;
} t_shvec2;


/* aren't macros to prevent double evaluation with side effects */
/* maybe could be inlined? i hope LTO will resolve this */
static inline t_fvec2 fvec2_from_vec2(t_vec2 vec) {
    return (t_fvec2) {
        .x = (float)vec.x,
        .y = (float)vec.y,
    };
}

static inline t_fvec2 fvec2_from_shvec2(t_shvec2 vec) {
    return (t_fvec2) {
        .x = (float)vec.x,
        .y = (float)vec.y,
    };
}

static inline t_fvec3 fvec3_add(t_fvec3 a, t_fvec3 b) {
    return (t_fvec3) { a.x + b.x, a.y + b.y, a.z + b.z };
}

static inline t_fvec3 fvec3_sub(t_fvec3 a, t_fvec3 b) {
    return (t_fvec3) { a.x - b.x, a.y - b.y, a.z - b.z };
}

static inline t_fvec2 fvec2_div(t_fvec2 a, t_fvec2 b) {
    return (t_fvec2) { a.x / b.x, a.y / b.y };
}

static inline t_fvec2 fvec2_scale(t_fvec2 a, float s) {
    return (t_fvec2) { a.x * s, a.y * s };
}

static inline t_fvec3 fvec3_scale(t_fvec3 a, float s) {
    return (t_fvec3) { a.x * s, a.y * s, a.z * s };
}

static inline float fvec3_dot(t_fvec3 a, t_fvec3 b) {
    return a.x * b.x + a.y * b.y + a.z * b.z;
}

static inline t_fvec3 fvec3_cross(t_fvec3 a, t_fvec3 b) {
    return (t_fvec3) {
        a.y * b.z - a.z * b.y,
        a.z * b.x - a.x * b.z,
        a.x * b.y - a.y * b.x,
    };
}

/* TODO: fast_sqrt version? */
static inline t_fvec3 fvec3_norm(t_fvec3 a) {
    const float n = sqrtf(fvec3_dot(a, a));
    /* TODO: do we need truncating over epsilon as cglm does? */
    return fvec3_scale(a, 1.0f / n);
}

static inline t_fvec3 fvec3_rotate(t_fvec3 v, float angle, t_fvec3 axis) {
    /* from cglm */
    t_fvec3 v1, v2, k;
    float c, s;

    c = cosf(angle);
    s = sinf(angle);

    k = fvec3_norm(axis);

    /* Right Hand, Rodrigues' rotation formula:
        v = v*cos(t) + (kxv)sin(t) + k*(k.v)(1 - cos(t))
    */
    v1 = fvec3_scale(v, c);

    v2 = fvec3_cross(k, v);
    v2 = fvec3_scale(v2, s);

    v1 = fvec3_add(v1, v2);

    v2 = fvec3_scale(k, fvec3_dot(k, v) * (1.0f - c));
    v = fvec3_add(v1, v2);

    return v;
}

#define m_to_fvec2(p_any_vec2) (_Generic((p_any_vec2),          \
                                    t_vec2:   fvec2_from_vec2,  \
                                    t_shvec2: fvec2_from_shvec2 \
                                )(p_any_vec2))

#define m_vec_sub(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0),   \
                                    t_fvec3:   fvec3_sub            \
                                )(p_any_vec0, p_any_vec1))

#define m_vec_div(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0),   \
                                    t_fvec2:   fvec2_div            \
                                )(p_any_vec0, p_any_vec1))

#define m_vec_scale(p_any_vec, p_any_scalar) (_Generic((p_any_vec),     \
                                    t_fvec2:   fvec2_scale,             \
                                    t_fvec3:   fvec3_scale              \
                                )(p_any_vec, p_any_scalar))

#define m_vec_dot(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0),   \
                                    t_fvec3:   fvec3_dot            \
                                )(p_any_vec0, p_any_vec1))

#define m_vec_cross(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0),     \
                                    t_fvec3:   fvec3_cross              \
                                )(p_any_vec0, p_any_vec1))

#define m_vec_norm(p_any_vec) (_Generic((p_any_vec),            \
                                    t_fvec3:   fvec3_norm       \
                                )(p_any_vec))

#endif
