/**
 * Playing around with matrices while studying
 * https://github.com/ssloy/tinyrenderer/wiki/Lesson-4:-Perspective-projection
 */

#include <SDL2/SDL.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_rect.h>
#include <SDL2/SDL_render.h>
#include <SDL2/SDL_video.h>
#include <math.h>
#include <stdbool.h>
#include <stdint.h>

#define u64 uint64_t
#define f32 float

#define RADIANS(DEGREES) (DEGREES * M_PI / 180.0f)

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

typedef struct vec3f V3f;
struct vec3f {
  union {
    f32 x;
    f32 col0;
  };
  union {
    f32 y;
    f32 col1;
  };
  union {
    f32 z;
    f32 col2;
  };
};

typedef struct mat3x3 Mat3x3;
struct mat3x3 {
  V3f row0;
  V3f row1;
  V3f row2;
};

#define vec_add(v1, v2) ((V2u){.x = v1.x + v2.x, .y = v1.y + v2.y})
#define vec_sub(v1, v2) ((V2u){.x = v1.x - v2.x, .y = v1.y - v2.y})
#define vec_div_num(v, n) ((V2u){.x = v.x / n, .y = v.y / 2})
#define mat_mul_vec(mat, vec)                                                  \
  ((V3f){.x = mat.row0.col0 * vec.x + mat.row0.col1 * vec.y +                  \
              mat.row0.col2 * vec.z,                                           \
         .y = mat.row1.col0 * vec.x + mat.row1.col1 * vec.y +                  \
              mat.row1.col2 * vec.z,                                           \
         .z = mat.row2.col0 * vec.x + mat.row2.col1 * vec.y +                  \
              mat.row2.col2 * vec.z})
#define project_v3f(v) ((V2u){.x = (u64)(v.x / v.z), .y = (u64)(v.y / v.z)})

// clang-format off
#define mat_mul(mat1, mat2)                                                                                                     \
  ((Mat3x3){                                                                                                                    \
      .row0 = {.col0 = mat1.row0.col0 * mat2.row0.col0 + mat1.row0.col1 * mat2.row1.col0 + mat1.row0.col2 * mat2.row2.col0,     \
               .col1 = mat1.row0.col0 * mat2.row0.col1 + mat1.row0.col1 * mat2.row1.col1 + mat1.row0.col2 * mat2.row2.col1,     \
               .col2 = mat1.row0.col0 * mat2.row0.col2 + mat1.row0.col1 * mat2.row1.col2 + mat1.row0.col2 * mat2.row2.col2},    \
      .row1 = {.col0 = mat1.row1.col0 * mat2.row0.col0 + mat1.row1.col1 * mat2.row1.col0 + mat1.row1.col2 * mat2.row2.col0,     \
               .col1 = mat1.row1.col0 * mat2.row0.col1 + mat1.row1.col1 * mat2.row1.col1 + mat1.row1.col2 * mat2.row2.col1,     \
               .col2 = mat1.row1.col0 * mat2.row0.col2 + mat1.row1.col1 * mat2.row1.col2 + mat1.row1.col2 * mat2.row2.col2},    \
      .row2 = {.col0 = mat1.row2.col0 * mat2.row0.col0 + mat1.row2.col1 * mat2.row1.col0 + mat1.row2.col2 * mat2.row2.col0,     \
               .col1 = mat1.row2.col0 * mat2.row0.col1 + mat1.row2.col1 * mat2.row1.col1 + mat1.row2.col2 * mat2.row2.col1,     \
               .col2 = mat1.row2.col0 * mat2.row0.col2 + mat1.row2.col1 * mat2.row1.col2 + mat1.row2.col2 * mat2.row2.col2},    \
  })
// clang-format on

#define MAT3x3(r0c0, r0c1, r0c2, r1c0, r1c1, r1c2, r2c0, r2c1, r2c2)           \
  ((Mat3x3){{r0c0, r0c1, r0c2}, {r1c0, r1c1, r1c2}, {r2c0, r2c1, r2c2}})

void draw_line(SDL_Renderer *renderer, const V2u *start, const V2u *end);

int main(void) {
  SDL_Init(0);
  SDL_Window *wnd =
      SDL_CreateWindow("Matrices", SDL_WINDOWPOS_CENTERED,
                       SDL_WINDOWPOS_CENTERED, 1200, 800, SDL_WINDOW_SHOWN);
  SDL_Renderer *renderer = SDL_CreateRenderer(wnd, -1, 0);

  f32 rotation = 0.0f;
  f32 radians = RADIANS(rotation);

  // clang-format off
  Mat3x3 pos_rot_mat, neg_rot_mat;

  Mat3x3 mat0 = MAT3x3(2.0f, -0.5f, 0.0f,
                       0.0f,  2.0f, 0.0f,
                       0.0f,  0.0f, 1.0f);
  Mat3x3 mat1 = MAT3x3( 0.5f, 0.0f, 0.0f,
                       0.25f, 0.5f, 0.0f,
                        0.0f, 0.0f, 1.0f);
  // clang-format on

  V3f v0p0 = {400.0f, 150.0f, 1.0f};
  V3f v0p1 = {800.0f, 150.0f, 1.0f};
  V3f v0p2 = {400.0f, 400.0f, 1.0f};
  V3f v0p3 = {800.0f, 400.0f, 1.0f};

  // clang-format off
  Mat3x3 mid_mat = MAT3x3(0.5f, 0.0f, 0.0f,
                          0.0f, 0.5f, 0.0f,
                          0.0f, 0.0f, 1.0f);
  Mat3x3 neg_v0p0_xf = MAT3x3(1.0f, 0.0f, -(v0p0.x),
                              0.0f, 1.0f, -(v0p0.y),
                              0.0f, 0.0f,     1.0f);
  Mat3x3 pos_v0p0_xf = MAT3x3(1.0f, 0.0f,   v0p0.x,
                              0.0f, 1.0f,   v0p0.y,
                              0.0f, 0.0f,     1.0f);
  // clang-format on

  Mat3x3 v0_mid_mat = mat_mul(mat_mul(pos_v0p0_xf, mid_mat), neg_v0p0_xf);
  V3f v0_mid = mat_mul_vec(v0_mid_mat, v0p3);

  // clang-format off
  Mat3x3 neg_v0_mid_xf = MAT3x3(1.0f, 0.0f, -(v0_mid.x),
                                0.0f, 1.0f, -(v0_mid.y),
                                0.0f, 0.0f,       1.0f);
  Mat3x3 pos_v0_mid_xf = MAT3x3(1.0f, 0.0f,   v0_mid.x,
                                0.0f, 1.0f,   v0_mid.y,
                                0.0f, 0.0f,       1.0f);
  // clang-format on

  Mat3x3 v1_mat;
  V3f v1p0, v1p1, v1p2, v1p3;

  Mat3x3 v2_mat;
  V3f v2p0, v2p1, v2p2, v2p3;

  // Projection
  V2u v0_p0 = project_v3f(v0p0);
  V2u v0_p1 = project_v3f(v0p1);
  V2u v0_p2 = project_v3f(v0p2);
  V2u v0_p3 = project_v3f(v0p3);

  V2u v1_p0, v1_p1, v1_p2, v1_p3;
  V2u v2_p0, v2_p1, v2_p2, v2_p3;

  SDL_Event event;
  bool running = true;

  while (running) {
    while (SDL_PollEvent(&event)) {
      switch (event.type) {
      case SDL_QUIT:
        running = false;
        break;
      }
    }

    rotation += 0.001f;
    radians = RADIANS(rotation);

    // clang-format off
    pos_rot_mat = MAT3x3( cosf(radians),  -sinf(radians), 0.0f,
                          sinf(radians),   cosf(radians), 0.0f,
                                   0.0f,            0.0f, 1.0f);
    neg_rot_mat = MAT3x3(cosf(-radians), -sinf(-radians), 0.0f,
                         sinf(-radians),  cosf(-radians), 0.0f,
                                   0.0f,            0.0f, 1.0f);
    // clang-format on

    v1_mat = mat_mul(mat_mul(pos_v0_mid_xf, mat_mul(pos_rot_mat, mat0)),
                     neg_v0_mid_xf);
    v1p0 = mat_mul_vec(v1_mat, v0p0);
    v1p1 = mat_mul_vec(v1_mat, v0p1);
    v1p2 = mat_mul_vec(v1_mat, v0p2);
    v1p3 = mat_mul_vec(v1_mat, v0p3);

    v2_mat = mat_mul(mat_mul(pos_v0_mid_xf, mat_mul(neg_rot_mat, mat1)),
                     neg_v0_mid_xf);
    v2p0 = mat_mul_vec(v2_mat, v0p0);
    v2p1 = mat_mul_vec(v2_mat, v0p1);
    v2p2 = mat_mul_vec(v2_mat, v0p2);
    v2p3 = mat_mul_vec(v2_mat, v0p3);

    v1_p0 = project_v3f(v1p0);
    v1_p1 = project_v3f(v1p1);
    v1_p2 = project_v3f(v1p2);
    v1_p3 = project_v3f(v1p3);

    v2_p0 = project_v3f(v2p0);
    v2_p1 = project_v3f(v2p1);
    v2_p2 = project_v3f(v2p2);
    v2_p3 = project_v3f(v2p3);

    SDL_SetRenderDrawColor(renderer, 128, 128, 128, 255);
    SDL_RenderClear(renderer);

    SDL_SetRenderDrawColor(renderer, 255, 64, 128, 255);
    draw_line(renderer, &v0_p0, &v0_p1);
    draw_line(renderer, &v0_p1, &v0_p3);
    draw_line(renderer, &v0_p3, &v0_p2);
    draw_line(renderer, &v0_p2, &v0_p0);

    SDL_SetRenderDrawColor(renderer, 64, 255, 128, 255);
    draw_line(renderer, &v1_p0, &v1_p1);
    draw_line(renderer, &v1_p1, &v1_p3);
    draw_line(renderer, &v1_p3, &v1_p2);
    draw_line(renderer, &v1_p2, &v1_p0);

    SDL_SetRenderDrawColor(renderer, 64, 128, 255, 255);
    draw_line(renderer, &v2_p0, &v2_p1);
    draw_line(renderer, &v2_p1, &v2_p3);
    draw_line(renderer, &v2_p3, &v2_p2);
    draw_line(renderer, &v2_p2, &v2_p0);

    SDL_RenderPresent(renderer);
  }

  return 0;
}

void draw_line(SDL_Renderer *renderer, const V2u *start, const V2u *end) {
  SDL_RenderDrawLine(renderer, start->x, start->y, end->x, end->y);
}