#define STB_IMAGE_IMPLEMENTATION

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


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

  Window window;
  window.width = 1920;
  window.height = 1080;
  bool success = setup_window(&window);

  if (!success) {
    printf("Failed to setup window");
    return 1;
  }

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

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

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

    Mesh *floor = new Mesh();
    create_mesh(floor, vertices, indices, 32, 6);
    meshes[0] = floor;
  }

  {
    // 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[1] = pyramid1;

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

  {
    // Create a shader
    Shader *shader = new Shader();
    create_shader(shader, (char*)"shaders/shader.vert", (char*)"shaders/shader.frag");
    shaders[0] = *shader;
  }

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

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

  Material shiny_material;
  shiny_material.shininess = 256.0f;
  shiny_material.specular_intensity = 4.0f;

  Material dull_material;
  dull_material.shininess = 4.0f;
  dull_material.specular_intensity = 0.3f;

  Directional_Light sun_light;
  sun_light.color = Color::WHITE;
  sun_light.ambient_intensity = 0.3f;
  sun_light.diffuse_intensity = 0.3f;
  sun_light.direction = Vector3(5.0f, -1.0, -5.0f);

  Point_Light point_lights[MAX_POINT_LIGHTS];
  u32 point_light_count = 0;

  {
    Point_Light point_light;
    point_light.color = Color::MAGENTA;
    point_light.ambient_intensity = 0.0f;
    point_light.diffuse_intensity = 0.3f;
    point_light.position = Vector3(10.0f, 2.0f, 0.0f);
    point_light.constant = 0.3f;
    point_light.linear = 0.1f;
    point_light.exponent = 0.1f;

    point_lights[0] = point_light;
    point_light_count++;
  }

  {
    Point_Light point_light;
    point_light.color = Color::GREEN;
    point_light.ambient_intensity = 0.0f;
    point_light.diffuse_intensity = 0.3f;
    point_light.position = Vector3(-10.0f, 2.0f, 0.0f);
    point_light.constant = 0.3f;
    point_light.linear = 0.1f;
    point_light.exponent = 0.1f;

    point_lights[1] = point_light;
    point_light_count++;
  }

  Spot_Light spot_lights[MAX_SPOT_LIGHTS];
  u32 spot_light_count = 0;

  {
    Spot_Light spot_light;
    spot_light.color = Color::BLUE;
    spot_light.direction = glm::normalize(Vector3(0.0f, -1.0f, 0.0f));
    spot_light.ambient_intensity = 5.0f;
    spot_light.diffuse_intensity = 2.0f;
    spot_light.position = Vector3(0.0f, 10.0f, 0.0f);
    spot_light.constant = 1.0f;
    spot_light.linear = 0.0f;
    spot_light.exponent = 0.0f;
    spot_light.edge = 20.0f;
    spot_light.processed_edge = cosf(glm::radians(20.0f));

    spot_lights[0] = spot_light;
    spot_light_count++;
  }

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

  Camera camera;
  camera.position.y = 2.0f;
  update_camera(&camera);


  // 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(&shaders[0], &sun_light);
    use_light(&shaders[0], point_lights, point_light_count);
    use_light(&shaders[0], spot_lights, spot_light_count);

    glUniformMatrix4fv(shaders[0].projection, 1, GL_FALSE, glm::value_ptr(projection));
    Matrix4 view = calculate_view_matrix(&camera);
    glUniformMatrix4fv(shaders[0].view, 1, GL_FALSE, glm::value_ptr(view));
    glUniform3f(shaders[0].eye_position, camera.position.x, camera.position.y, camera.position.z);


    { // Floor
      Matrix4 model(1.0f);
      model = glm::translate(model, Vector3(0.0f, 0.0f, 0.0f));

      glUniformMatrix4fv(shaders[0].model, 1, GL_FALSE, glm::value_ptr(model));
      use_texture(&plaster_texture);
      use_material(&dull_material, shaders[0].shininess, shaders[0].specular_intensity);
      render_mesh(meshes[0]);
    }

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

      glUniformMatrix4fv(shaders[0].model, 1, GL_FALSE, glm::value_ptr(model));
      use_texture(&clay_texture);
      use_material(&shiny_material, shaders[0].shininess, shaders[0].specular_intensity);
      render_mesh(meshes[1]);
    }

    { // Prism
      Matrix4 model(1.0f);
      model = glm::translate(model, Vector3(2.0f, 1.0f, -4.0f));

      glUniformMatrix4fv(shaders[0].model, 1, GL_FALSE, glm::value_ptr(model));
      use_texture(&clay_texture);
      use_material(&dull_material, shaders[0].shininess, shaders[0].specular_intensity);
      render_mesh(meshes[2]);
    }

    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;
}
