#define STB_IMAGE_IMPLEMENTATION

#include "mesh.h"
#include "shader.h"
#include "window.h"
#include "camera.h"
#include "texture.h"
#include "light.h"


static const char *vertex_shader_code = "shaders/shader.vert";
static const char *fragment_shader_code = "shaders/shader.frag";

int main() {
  Mesh *meshes[10] = {nullptr};
  Shader shaders[10];  

  Window window(1024, 720);
  bool success = setup_window(&window);

  // Delta
  f32 dt = 0.0f;
  f32 last_dt = 0.f;

  {
    // Create a mesh
    f32 vertices[] = {
    // x      y      z      u      v      nx     ny     nz
      -1.0f, -1.0f,  0.0f,  0.0f,  0.0f,  0.0f,  0.0f,  0.0f,
       0.0f, -1.0f,  1.0f,  1.0f,  0.0f,  0.0f,  0.0f,  0.0f,
       1.0f, -1.0f,  0.0f,  1.0f,  0.0f,  0.0f,  0.0f,  0.0f,
       0.0f,  1.0f,  0.0f,  0.0f,  1.0f,  0.0f,  0.0f,  0.0f
    };

    u32 indices[] = {
      0, 3, 1,
      1, 3, 2,
      2, 3, 0,
      0, 1, 2
    };

    calculate_normals_avg(vertices, 32, 8, indices, 12, 5);

    Mesh *pyramid1 = new Mesh();
    create_mesh(pyramid1, vertices, indices, 32, 12);
    meshes[0] = pyramid1;

    Mesh *pyramid2 = new Mesh();
    create_mesh(pyramid2, vertices, indices, 32, 12);
    meshes[1] = pyramid2;
  }

  {
    // Create a shader
    Shader *shader = new Shader();
    create_shader(shader, vertex_shader_code, fragment_shader_code);
    shaders[0] = *shader;
  }

  Texture clay_texture = Texture((char *)"assets/textures/clay_texture_1k.png");
  load_texture(&clay_texture);

  Texture plaster_texture = Texture((char *)"assets/textures/plaster_texture_1k.png");
  load_texture(&plaster_texture);

  Light sun_light(Vector3(1.0f, 1.0f, 1.0f), 0.2f, Vector3(2.0f, -1.0, -2.0f), 1.0f);

  Matrix4 projection = glm::perspective(45.0f, (f32)window.buffer_width / (f32)window.buffer_height, 0.1f, 100.0f);

  Camera camera(Vector3(0.0f, 0.0f, 0.0f), Vector3(0.0f, 1.0f, 0.0f), 90.0f, 0.0f, 5.0f, 1.0f);


  // Loop until window is closed
  while (!glfwWindowShouldClose(window.gl_window)) {
    // Calculate delta
    f32 now = glfwGetTime();
    dt = now - last_dt;
    last_dt = now;

    key_control(&camera, window.ascii_keys, dt);
    mouse_control(&camera, window.x_change_position, window.y_change_position);

    // This sucks
    window.x_change_position = 0.0f;
    window.y_change_position = 0.0f;

    // Clear window
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glUseProgram(shaders[0].id);

    use_light(
      &sun_light,
      shaders[0].uniform_ambient_color, shaders[0].uniform_ambient_intensity,
      shaders[0].uniform_diffuse_direction, shaders[0].uniform_diffuse_intensity
    );

    {
      Matrix4 model(1.0f);
      model = glm::translate(model, Vector3(0.0f, 0.0f, -2.5f));
      model = glm::scale(model, Vector3(0.4f, 0.4f, 1.0f));

      glUniformMatrix4fv(shaders[0].uniform_model, 1, GL_FALSE, glm::value_ptr(model));
      glUniformMatrix4fv(shaders[0].uniform_projection, 1, GL_FALSE, glm::value_ptr(projection));
      use_texture(&clay_texture);
      render_mesh(meshes[0]);
    }

    {
      Matrix4 model(1.0f);
      model = glm::translate(model, Vector3(0.0f, 0.0f, -3.5f));
      model = glm::scale(model, Vector3(0.4f, 0.4f, 1.0f));

      glUniformMatrix4fv(shaders[0].uniform_model, 1, GL_FALSE, glm::value_ptr(model));
      glUniformMatrix4fv(shaders[0].uniform_projection, 1, GL_FALSE, glm::value_ptr(projection));
      use_texture(&plaster_texture);
      render_mesh(meshes[1]);
    }

    Matrix4 view = calculate_view_matrix(&camera);
    glUniformMatrix4fv(shaders[0].uniform_view, 1, GL_FALSE, glm::value_ptr(view));


    glUseProgram(0);

    // We usually have two buffers, the first is hidden and that's
    // the one we draw to. The second is the one we display, so
    // when we're done drawing we call this glfwSwapBuffers() to
    // swap the buffers to show the one we've been drawing to so
    // that it now shows. We then can start drawing on the other
    // buffer.
    glfwSwapBuffers(window.gl_window);

    // Get and handle user input events
    glfwPollEvents();
  }

  return 0;
}
