#include "aliases.h"
#include "img.h"
#include "mem_arena.h"
#include "mem_utils.h"
#include "obj.h"
#include "vec.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define SIZE 1200
#define RESOURCE(NAME) "resources/" NAME

V3f g_eye = {0.2f, 0.1f, 0.75f};
V3f g_target = {0};
V3f g_up = {0.0f, 1.0f, 0.0f};
M4x4f g_cam_matrix = mat4x4_identity;

enum {
  TINY_EXIT_SUCCESS,
  TINY_EXIT_ARENA_INIT_FAILED,
  TINY_EXIT_RENDER_INIT_FAILED,
  TINY_EXIT_MODEL_LOAD_FAILED,
};

internal M4x4f get_projection_matrix(ProjectionType projection_type);
internal V3f main_shader_vertex(const V3f *vertex, M4x4f *model_view,
                                M4x4f *projection, const Image *img);

int main(void) {
  Arena *arena = NULL;
  if (!wapp_mem_arena_init(&arena, 10ul * 1024ul * 1024ul * 1024ul,
                           WAPP_MEM_ALLOC_RESERVE, false)) {
    return TINY_EXIT_ARENA_INIT_FAILED;
  }

  srand(time(NULL));

  Colour bg = {.r = 42, .g = 45, .b = 52, .a = 255};
  Colour teal = {.r = 14, .g = 156, .b = 208, .a = 255};
  Render render;
  if (!init_render(arena, &render, SIZE, SIZE)) {
    return TINY_EXIT_RENDER_INIT_FAILED;
  }

  Model obj = load_obj_file(arena, RESOURCE("head.obj"), RESOURCE("head.pnm"));
  if (IS_INVALID_MODEL(obj)) {
    return TINY_EXIT_MODEL_LOAD_FAILED;
  }

  M4x4f model_view = lookat(g_eye, g_target, g_up);
  M4x4f perspective_projection =
      get_projection_matrix(PROJECTION_TYPE_PERSPECTIVE);

  Shader main_shader = {
      .vertex = main_shader_vertex,
      .model_view = &model_view,
      .projection = &perspective_projection,
  };

  clear_buffer(&(render.img), &bg);
  render_model(&obj, &render, &main_shader, RENDER_TYPE_SHADED, teal);
  save_image(&(render.img), "result.pam");

  wapp_mem_arena_destroy(&arena);

  return TINY_EXIT_SUCCESS;
}

internal M4x4f get_projection_matrix(ProjectionType projection_type) {
  if (projection_type == PROJECTION_TYPE_PERSPECTIVE) {
    // Calculate projection matrix
    V3f cam = V3(V3f, f32, g_target.x, g_target.y, g_target.z, g_eye.x, g_eye.y,
                 g_eye.z);
    normalise_v3(cam);
    f32 coeff = -1.0f / magnitude_v3(cam) * 0.5f;
    return projection(coeff);
  }

  return mat4x4_identity;
}

internal V3f main_shader_vertex(const V3f *vertex, M4x4f *model_view,
                                M4x4f *projection, const Image *img) {
  V4f vh = {.x = vertex->x, .y = vertex->y, .z = vertex->z, .w = 1.0f};
  vh = mat4x4_mul_vec4((*projection), mat4x4_mul_vec4((*model_view), vh));
  vh.y = 0.0 - vh.y;
  vh = mat4x4_mul_vec4(viewport(vh.x, vh.y, img->width, img->height), vh);

  return project_vec4(vh);
}
