#ifndef VEC_H
#define VEC_H

#include "aliases.h"
#include "typed_list.h"
#include <math.h>

typedef struct i64x2 V2i;
struct i64x2 {
  i64 x;
  i64 y;
};

typedef struct u64x2 V2u;
struct u64x2 {
  u64 x;
  u64 y;
};

typedef union f32x2 V2f;
union f32x2 {
  f32 elements[2];
  struct {
    union {
      f32 x;
      f32 u;
    };
    union {
      f32 y;
      f32 v;
    };
  };
};

typedef union f32x3 V3f;
union f32x3 {
  f32 elements[3];
  struct {
    union {
      f32 x;
      f32 r;
    };
    union {
      f32 y;
      f32 g;
    };
    union {
      f32 z;
      f32 b;
    };
  };
};

typedef union f32x4 V4f;
union f32x4 {
  f32 elements[4];
  struct {
    f32 x;
    f32 y;
    f32 z;
    f32 w;
  };
};

typedef struct u64x3 V3u;
struct u64x3 {
  u64 x;
  u64 y;
  u64 z;
};

typedef union f32_3x2 M3x2f;
union f32_3x2 {
  V2f rows[3];
  struct {
    V2f row0;
    V2f row1;
    V2f row2;
  };
};

typedef union f32_2x3 M2x3f;
union f32_2x3 {
  V3f rows[2];
  struct {
    V3f row0;
    V3f row1;
  };
};

typedef union f32_3x3 M3x3f;
union f32_3x3 {
  V3f rows[3];
  struct {
    V3f row0;
    V3f row1;
    V3f row2;
  };
};

typedef union f32_4x4 M4x4f;
union f32_4x4 {
  V4f rows[4];
  struct {
    V4f row0;
    V4f row1;
    V4f row2;
    V4f row3;
  };
};

MAKE_LIST_TYPE(V3f);
MAKE_LIST_TYPE(V2f);

#define V2(T, ELEM_T, X0, Y0, X1, Y1)                                          \
  ((T){(ELEM_T)X1 - (ELEM_T)X0, (ELEM_T)Y1 - (ELEM_T)Y0})

#define V3(T, ELEM_T, X0, Y0, Z0, X1, Y1, Z1)                                  \
  ((T){(ELEM_T)X1 - (ELEM_T)X0, (ELEM_T)Y1 - (ELEM_T)Y0,                       \
       (ELEM_T)Z1 - (ELEM_T)Z0})

#define V3_to_V4(V3) ((V4f){.x = V3.x, .y = V3.y, .z = V3.z, .w = 1.0f})

#define dot_v2(V1, V2) ((f32)V1.x * (f32)V2.x + (f32)V1.y * (f32)V2.y)

#define dot_v3(V1, V2)                                                         \
  ((f32)V1.x * (f32)V2.x + (f32)V1.y * (f32)V2.y + (f32)V1.z * (f32)V2.z)

#define add_v3(V1, V2)                                                         \
  ((V3f){                                                                      \
      .x = V1.x + V2.x,                                                        \
      .y = V1.y + V2.y,                                                        \
      .z = V1.z + V2.z,                                                        \
  })

#define sub_v3(V1, V2)                                                         \
  ((V3f){                                                                      \
      .x = V1.x - V2.x,                                                        \
      .y = V1.y - V2.y,                                                        \
      .z = V1.z - V2.z,                                                        \
  })

#define mul_v3(V1, V2)                                                         \
  ((V3f){                                                                      \
      .x = V1.x * V2.x,                                                        \
      .y = V1.y * V2.y,                                                        \
      .z = V1.z * V2.z,                                                        \
  })

#define num_mul_v3(V, N) ((V3f){.x = (N) * V.x, .y = (N) * V.y, .z = (N) * V.z})

#define num_div_v3(V, N) ((V3f){.x = V.x / (N), .y = V.y / (N), .z = V.z / (N)})

#define magnitude_v3(V) (sqrtf(dot_v3(V, V)))

#define normalise_v3(V)                                                        \
  do {                                                                         \
    f32 magnitude = magnitude_v3(V);                                           \
    V.x /= magnitude;                                                          \
    V.y /= magnitude;                                                          \
    V.z /= magnitude;                                                          \
  } while (0)

#define cross_product(V1, V2)                                                  \
  ((V3f){                                                                      \
      .x = V1.y * V2.z - V1.z * V2.y,                                          \
      .y = V1.z * V2.x - V1.x * V2.z,                                          \
      .z = V1.x * V2.y - V1.y * V2.x,                                          \
  })

#define mat3x2_transpose(MAT)                                                  \
  ((M2x3f){                                                                    \
      .row0 = {.x = MAT.row0.x, .y = MAT.row1.x, .z = MAT.row2.x},             \
      .row1 = {.x = MAT.row0.y, .y = MAT.row1.y, .z = MAT.row2.y},             \
  })

#define mat2x3_mul_vec3(MAT, V)                                                \
  ((V2f){                                                                      \
      .elements[0] = dot_v3(MAT.rows[0], V),                                   \
      .elements[1] = dot_v3(MAT.rows[1], V),                                   \
  })

#define mat3x3_transpose(MAT)                                                  \
  ((M3x3f){                                                                    \
      .row0 = {.x = MAT.row0.x, .y = MAT.row1.x, .z = MAT.row2.x},             \
      .row1 = {.x = MAT.row0.y, .y = MAT.row1.y, .z = MAT.row2.y},             \
      .row2 = {.x = MAT.row0.z, .y = MAT.row1.z, .z = MAT.row2.z},             \
  })

#define mat3x3_mul_vec3(MAT, V)                                                \
  ((V3f){                                                                      \
      .elements[0] = dot_v3(MAT.rows[0], V),                                   \
      .elements[1] = dot_v3(MAT.rows[1], V),                                   \
      .elements[2] = dot_v3(MAT.rows[2], V),                                   \
  })

#define mat4x4_identity                                                        \
  ((M4x4f){                                                                    \
      .row0 = {1.0f, 0.0f, 0.0f, 0.0f},                                        \
      .row1 = {0.0f, 1.0f, 0.0f, 0.0f},                                        \
      .row2 = {0.0f, 0.0f, 1.0f, 0.0f},                                        \
      .row3 = {0.0f, 0.0f, 0.0f, 1.0f},                                        \
  })

#define mat4x4_to_array(MAT)                                                   \
  ((f32[4][4]){                                                                \
      {MAT.row0.x, MAT.row0.y, MAT.row0.z, MAT.row0.w},                        \
      {MAT.row1.x, MAT.row1.y, MAT.row1.z, MAT.row1.w},                        \
      {MAT.row2.x, MAT.row2.y, MAT.row2.z, MAT.row2.w},                        \
      {MAT.row3.x, MAT.row3.y, MAT.row3.z, MAT.row3.w},                        \
  })

#define array_to_mat4x4(arr)                                                   \
  ((M4x4f){                                                                    \
      .row0 = {arr[0][0], arr[0][1], arr[0][2], arr[0][3]},                    \
      .row1 = {arr[1][0], arr[1][1], arr[1][2], arr[1][3]},                    \
      .row2 = {arr[2][0], arr[2][1], arr[2][2], arr[2][3]},                    \
      .row3 = {arr[3][0], arr[3][1], arr[3][2], arr[3][3]},                    \
  })

#define mat4x4_transpose(MAT)                                                  \
  ((M4x4f){                                                                    \
      .row0 = {.x = MAT.row0.x,                                                \
               .y = MAT.row1.x,                                                \
               .z = MAT.row2.x,                                                \
               .w = MAT.row3.x},                                               \
      .row1 = {.x = MAT.row0.y,                                                \
               .y = MAT.row1.y,                                                \
               .z = MAT.row2.y,                                                \
               .w = MAT.row3.y},                                               \
      .row2 = {.x = MAT.row0.z,                                                \
               .y = MAT.row1.z,                                                \
               .z = MAT.row2.z,                                                \
               .w = MAT.row3.z},                                               \
      .row3 = {.x = MAT.row0.w,                                                \
               .y = MAT.row1.w,                                                \
               .z = MAT.row2.w,                                                \
               .w = MAT.row3.w},                                               \
  })

#define mat4x4_mul(MAT1, MAT2)                                                 \
  ((M4x4f){                                                                    \
      .row0.x = MAT1.row0.x * MAT2.row0.x + MAT1.row0.y * MAT2.row1.x +        \
                MAT1.row0.z * MAT2.row2.x + MAT1.row0.w * MAT2.row3.x,         \
      .row0.y = MAT1.row0.x * MAT2.row0.y + MAT1.row0.y * MAT2.row1.y +        \
                MAT1.row0.z * MAT2.row2.y + MAT1.row0.w * MAT2.row3.y,         \
      .row0.z = MAT1.row0.x * MAT2.row0.z + MAT1.row0.y * MAT2.row1.z +        \
                MAT1.row0.z * MAT2.row2.z + MAT1.row0.w * MAT2.row3.z,         \
      .row0.w = MAT1.row0.x * MAT2.row0.w + MAT1.row0.y * MAT2.row1.w +        \
                MAT1.row0.z * MAT2.row2.w + MAT1.row0.w * MAT2.row3.w,         \
      .row1.x = MAT1.row1.x * MAT2.row0.x + MAT1.row1.y * MAT2.row1.x +        \
                MAT1.row1.z * MAT2.row2.x + MAT1.row1.w * MAT2.row3.x,         \
      .row1.y = MAT1.row1.x * MAT2.row0.y + MAT1.row1.y * MAT2.row1.y +        \
                MAT1.row1.z * MAT2.row2.y + MAT1.row1.w * MAT2.row3.y,         \
      .row1.z = MAT1.row1.x * MAT2.row0.z + MAT1.row1.y * MAT2.row1.z +        \
                MAT1.row1.z * MAT2.row2.z + MAT1.row1.w * MAT2.row3.z,         \
      .row1.w = MAT1.row1.x * MAT2.row0.w + MAT1.row1.y * MAT2.row1.w +        \
                MAT1.row1.z * MAT2.row2.w + MAT1.row1.w * MAT2.row3.w,         \
      .row2.x = MAT1.row2.x * MAT2.row0.x + MAT1.row2.y * MAT2.row1.x +        \
                MAT1.row2.z * MAT2.row2.x + MAT1.row2.w * MAT2.row3.x,         \
      .row2.y = MAT1.row2.x * MAT2.row0.y + MAT1.row2.y * MAT2.row1.y +        \
                MAT1.row2.z * MAT2.row2.y + MAT1.row2.w * MAT2.row3.y,         \
      .row2.z = MAT1.row2.x * MAT2.row0.z + MAT1.row2.y * MAT2.row1.z +        \
                MAT1.row2.z * MAT2.row2.z + MAT1.row2.w * MAT2.row3.z,         \
      .row2.w = MAT1.row2.x * MAT2.row0.w + MAT1.row2.y * MAT2.row1.w +        \
                MAT1.row2.z * MAT2.row2.w + MAT1.row2.w * MAT2.row3.w,         \
      .row3.x = MAT1.row3.x * MAT2.row0.x + MAT1.row3.y * MAT2.row1.x +        \
                MAT1.row3.z * MAT2.row2.x + MAT1.row3.w * MAT2.row3.x,         \
      .row3.y = MAT1.row3.x * MAT2.row0.y + MAT1.row3.y * MAT2.row1.y +        \
                MAT1.row3.z * MAT2.row2.y + MAT1.row3.w * MAT2.row3.y,         \
      .row3.z = MAT1.row3.x * MAT2.row0.z + MAT1.row3.y * MAT2.row1.z +        \
                MAT1.row3.z * MAT2.row2.z + MAT1.row3.w * MAT2.row3.z,         \
      .row3.w = MAT1.row3.x * MAT2.row0.w + MAT1.row3.y * MAT2.row1.w +        \
                MAT1.row3.z * MAT2.row2.w + MAT1.row3.w * MAT2.row3.w,         \
  })

#define mat4x4_mul_vec4(MAT, V)                                                \
  ((V4f){                                                                      \
      .x = MAT.row0.x * V.x + MAT.row0.y * V.y + MAT.row0.z * V.z +            \
           MAT.row0.w * V.w,                                                   \
      .y = MAT.row1.x * V.x + MAT.row1.y * V.y + MAT.row1.z * V.z +            \
           MAT.row1.w * V.w,                                                   \
      .z = MAT.row2.x * V.x + MAT.row2.y * V.y + MAT.row2.z * V.z +            \
           MAT.row2.w * V.w,                                                   \
      .w = MAT.row3.x * V.x + MAT.row3.y * V.y + MAT.row3.z * V.z +            \
           MAT.row3.w * V.w,                                                   \
  })

#define project_vec4(V) ((V3f){.x = V.x / V.w, .y = V.y / V.w, .z = V.z / V.w})

#define mat_access(MAT, i, j) (MAT.rows[i].elements[j])

M4x4f lookat(V3f eye, V3f target, V3f up);
M4x4f projection(f32 coeff);
M4x4f viewport(f32 x, f32 y, u64 w, u64 h);
M3x3f mat3x3_inv(M3x3f matrix);
M4x4f mat4x4_inv(M4x4f matrix);

#endif // VEC_H
