#pragma once

#include <math.h>

#include "pxl8_types.h"

#if defined(__x86_64__) || defined(_M_X64)
    #include <xmmintrin.h>
#elif defined(__aarch64__) || defined(_M_ARM64)
    #include <arm_neon.h>
#endif

#define PXL8_PI  3.14159265358979323846f
#define PXL8_TAU (PXL8_PI * 2.0f)

static inline f32 pxl8_fast_inv_sqrt(f32 x) {
#if defined(__x86_64__) || defined(_M_X64)
    __m128 v = _mm_set_ss(x);
    v = _mm_rsqrt_ss(v);
    return _mm_cvtss_f32(v);
#elif defined(__aarch64__) || defined(_M_ARM64)
    float32x2_t v = vdup_n_f32(x);
    float32x2_t est = vrsqrte_f32(v);
    est = vmul_f32(est, vrsqrts_f32(vmul_f32(v, est), est));
    return vget_lane_f32(est, 0);
#else
    f32 half = 0.5f * x;
    i32 i = *(i32*)&x;
    i = 0x5f3759df - (i >> 1);
    x = *(f32*)&i;
    x = x * (1.5f - half * x * x);
    return x;
#endif
}

static inline f32 pxl8_fast_rcp(f32 x) {
#if defined(__x86_64__) || defined(_M_X64)
    __m128 v = _mm_set_ss(x);
    __m128 rcp = _mm_rcp_ss(v);
    rcp = _mm_add_ss(rcp, _mm_mul_ss(rcp, _mm_sub_ss(_mm_set_ss(1.0f), _mm_mul_ss(v, rcp))));
    return _mm_cvtss_f32(rcp);
#elif defined(__aarch64__) || defined(_M_ARM64)
    float32x2_t v = vdup_n_f32(x);
    float32x2_t est = vrecpe_f32(v);
    est = vmul_f32(est, vrecps_f32(v, est));
    return vget_lane_f32(est, 0);
#else
    return 1.0f / x;
#endif
}

typedef struct pxl8_vec2 {
    f32 x, y;
} pxl8_vec2;

typedef struct pxl8_vec3 {
    f32 x, y, z;
} pxl8_vec3;

typedef struct pxl8_vec4 {
    f32 x, y, z, w;
} pxl8_vec4;

typedef struct pxl8_mat4 {
    f32 m[16];
} pxl8_mat4;

typedef struct pxl8_plane {
    f32 distance;
    pxl8_vec3 normal;
} pxl8_plane;

typedef struct pxl8_frustum {
    pxl8_plane planes[6];
} pxl8_frustum;

typedef struct pxl8_projected_point {
    f32 depth;
    i32 x;
    i32 y;
    bool visible;
} pxl8_projected_point;

#ifdef __cplusplus
extern "C" {
#endif

u32 pxl8_hash32(u32 x);

pxl8_vec2 pxl8_vec2_add(pxl8_vec2 a, pxl8_vec2 b);
f32 pxl8_vec2_dot(pxl8_vec2 a, pxl8_vec2 b);
f32 pxl8_vec2_length(pxl8_vec2 v);
pxl8_vec2 pxl8_vec2_normalize(pxl8_vec2 v);
pxl8_vec2 pxl8_vec2_scale(pxl8_vec2 v, f32 s);
pxl8_vec2 pxl8_vec2_sub(pxl8_vec2 a, pxl8_vec2 b);

pxl8_vec3 pxl8_vec3_add(pxl8_vec3 a, pxl8_vec3 b);
pxl8_vec3 pxl8_vec3_cross(pxl8_vec3 a, pxl8_vec3 b);
f32 pxl8_vec3_dot(pxl8_vec3 a, pxl8_vec3 b);
f32 pxl8_vec3_length(pxl8_vec3 v);
pxl8_vec3 pxl8_vec3_lerp(pxl8_vec3 a, pxl8_vec3 b, f32 t);
pxl8_vec3 pxl8_vec3_normalize(pxl8_vec3 v);
pxl8_vec3 pxl8_vec3_scale(pxl8_vec3 v, f32 s);
pxl8_vec3 pxl8_vec3_sub(pxl8_vec3 a, pxl8_vec3 b);

pxl8_mat4 pxl8_mat4_identity(void);
pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up);
pxl8_mat4 pxl8_mat4_mul(pxl8_mat4 a, pxl8_mat4 b);
pxl8_vec3 pxl8_mat4_mul_vec3(pxl8_mat4 m, pxl8_vec3 v);
pxl8_vec4 pxl8_mat4_mul_vec4(pxl8_mat4 m, pxl8_vec4 v);
pxl8_mat4 pxl8_mat4_ortho(f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far);
pxl8_mat4 pxl8_mat4_perspective(f32 fov, f32 aspect, f32 near, f32 far);
pxl8_mat4 pxl8_mat4_rotate_x(f32 angle);
pxl8_mat4 pxl8_mat4_rotate_y(f32 angle);
pxl8_mat4 pxl8_mat4_rotate_z(f32 angle);
pxl8_mat4 pxl8_mat4_scale(f32 x, f32 y, f32 z);
pxl8_mat4 pxl8_mat4_translate(f32 x, f32 y, f32 z);

pxl8_frustum pxl8_frustum_from_matrix(pxl8_mat4 vp);
bool pxl8_frustum_test_aabb(const pxl8_frustum* frustum, pxl8_vec3 min, pxl8_vec3 max);

#ifdef __cplusplus
}
#endif
