#include "renderer.h"
#include "logger.h"
#include <imgui/imgui_impl_glfw.h>


bool init_renderer(Renderer *renderer, u32 width, u32 height) {
  renderer->render_data.width = width;
  renderer->render_data.height = height;
  

  if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
    log(1, "%s error: failed to initialize GLAD\n", __FUNCTION__);
    return false;
  }

  if (!GLAD_GL_VERSION_4_6) {
    log(1, "%s error: failed to get at least OpenGL 4.6\n", __FUNCTION__);
    return false;
  }

  {
    bool success = init_frame_buffer(&renderer->frame_buffer, width, height);
    if (!success) {
      log(1, "%s error: could not init Framebuffer\n", __FUNCTION__);
      return false;
    }
    log(1, "%s: framebuffer succesfully initialized\n", __FUNCTION__);
  }

  {
    bool success = load_texture(&renderer->texture, "assets/textures/crate.png");
    if (!success) {
      log(1, "%s: texture loading failed\n", __FUNCTION__);
      return false;
    }
    log(1, "%s: texture successfully loaded\n", __FUNCTION__);
  }

  {
    bool success = init_vertex_buffer(&renderer->vertex_buffer);
    if (!success) {
      log(1, "%s: vertex buffer successfully created\n", __FUNCTION__);
      return false;
    }
  }

  {
    bool success = init_uniform_buffer(&renderer->uniform_buffer);
    if (!success) {
      log(1, "%s: uniform buffer successfully created\n", __FUNCTION__);
      return false;
    }
  }

  {
    bool success = load_shaders(&renderer->shader, "shaders/basic.vert", "shaders/basic.frag");
    if (!success) {
      log(1, "%s: basic shader loading failed\n", __FUNCTION__);
      return false;
    }
    log(1, "%s: basic shaders succesfully loaded\n", __FUNCTION__);
  }

  {
    bool success = load_shaders(&renderer->alt_shader, "shaders/alt.vert", "shaders/alt.frag");
    if (!success) {
      log(1, "%s: alt shader loading failed\n", __FUNCTION__);
      return false;
    }
    log(1, "%s: alt shaders succesfully loaded\n", __FUNCTION__);
  }

  init_user_interface(&renderer->user_interface, &renderer->render_data);

  glEnable(GL_CULL_FACE);
  glEnable(GL_DEPTH_TEST);

  return true;
}

void set_renderer_size(Renderer *renderer, u32 width, u32 height) {
  if (width == 0 || height == 0) {
    return;
  }

  renderer->render_data.width = width;
  renderer->render_data.height = height;

  resize_frame_buffer(&renderer->frame_buffer, width, height);
  glViewport(0, 0, width, height);
}

void draw_renderer(Renderer *renderer) {
  // @Todo why do we have two glfwGetTime() functions being called and setting to things that are relatively close in purpose?
  static f32 previous_frame_start_time = 0.0;
  f32 frame_start_time = glfwGetTime();

  f64 tick_time = glfwGetTime();
  renderer->render_data.tick_diff = tick_time - renderer->last_tick_time;

  while(renderer->render_data.width == 0 || renderer->render_data.height == 0) {
    glfwGetFramebufferSize(renderer->render_data.glfw_window, &renderer->render_data.width, &renderer->render_data.height);
    glfwWaitEvents();
  }

  bind_frame_buffer(&renderer->frame_buffer);

  glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
  glClearDepth(1.0f);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  renderer->projection_matrix = glm::perspective(glm::radians(static_cast<f32>(renderer->render_data.field_of_view)), static_cast<f32>(renderer->render_data.width) / static_cast<f32>(renderer->render_data.height), 0.1f, 10.0f);

  f32 dt = glfwGetTime();
  Matrix4 model = Matrix4(1.0);

  if (!renderer->render_data.use_alt_shader) {
    use_shader(&renderer->shader);
    model = glm::rotate(Matrix4(1.0f), dt, Vector3(0.0f, 0.0f, 1.0f));
  } else {
    use_shader(&renderer->alt_shader);
    model = glm::rotate(Matrix4(1.0f), -dt, Vector3(0.0f, 0.0f, 1.0f));
  }

  renderer->view_matrix = get_camera_view_matrix(&renderer->camera, &renderer->render_data);
  
  upload_uniform_buffer_data(&renderer->uniform_buffer, renderer->view_matrix, renderer->projection_matrix);

  bind_texture(&renderer->texture);
  bind_vertex_buffer(&renderer->vertex_buffer);

  draw_vertex_buffer(GL_TRIANGLES, 0, renderer->render_data.triangle_count * 3);

  unbind_vertex_buffer();
  unbind_texture();
  unbind_frame_buffer(&renderer->frame_buffer);

  draw_frame_buffer(&renderer->frame_buffer);

  // Timer
  start_timer(&renderer->timer);

  handle_movement_keys(renderer);

  create_user_interface_frame(&renderer->user_interface, &renderer->render_data);
  renderer->render_data.user_interface_generate_time = stop_timer(&renderer->timer);

  render_user_interface(&renderer->user_interface);

  // FPS time
  renderer->render_data.frame_time = frame_start_time - previous_frame_start_time;
  previous_frame_start_time = frame_start_time;
  renderer->last_tick_time = tick_time;
}

void upload_renderer_data(Renderer *renderer, Mesh *vertex_data) {
  renderer->render_data.triangle_count = vertex_data->vertices.size() / 3;
  upload_vertex_buffer_data(&renderer->vertex_buffer, vertex_data);
}

void cleanup_renderer(Renderer *renderer) {
  cleanup_user_interface(&renderer->user_interface);
  cleanup_shader(&renderer->shader);
  cleanup_shader(&renderer->alt_shader);
  cleanup_texture(&renderer->texture);
  cleanup_vertex_buffer(&renderer->vertex_buffer);
  cleanup_frame_buffer(&renderer->frame_buffer);
}

void handle_key_events(Renderer *renderer, s32 key, s32 scancode, s32 action, s32 mods) {
  if (glfwGetKey(renderer->render_data.glfw_window, GLFW_KEY_SPACE) == GLFW_PRESS) {
    renderer->render_data.use_alt_shader = !renderer->render_data.use_alt_shader;
  }
}

void handle_movement_keys(Renderer *renderer) {
  renderer->render_data.move_forward = 0;
  if (glfwGetKey(renderer->render_data.glfw_window, GLFW_KEY_W) == GLFW_PRESS) {
    renderer->render_data.move_forward += 1;
  }

  if (glfwGetKey(renderer->render_data.glfw_window, GLFW_KEY_S) == GLFW_PRESS) {
    renderer->render_data.move_forward -= 1;
  }

  renderer->render_data.move_right = 0;
  if (glfwGetKey(renderer->render_data.glfw_window, GLFW_KEY_D) == GLFW_PRESS) {
    renderer->render_data.move_right += 1;
  }

  if (glfwGetKey(renderer->render_data.glfw_window, GLFW_KEY_A) == GLFW_PRESS) {
    renderer->render_data.move_right -= 1;
  }

  renderer->render_data.move_up = 0;
  if (glfwGetKey(renderer->render_data.glfw_window, GLFW_KEY_E) == GLFW_PRESS) {
    renderer->render_data.move_up += 1;
  }

  if (glfwGetKey(renderer->render_data.glfw_window, GLFW_KEY_Q) == GLFW_PRESS) {
    renderer->render_data.move_up -= 1;
  }
}

void handle_mouse_button_events(Renderer *renderer, s32 button, s32 action, s32 mods) {
  ImGuiIO& io = ImGui::GetIO();
  if (button >= 0 && button < ImGuiMouseButton_COUNT) {
    io.AddMouseButtonEvent(button, action == GLFW_PRESS);
  }

  if (io.WantCaptureMouse) {
    return;
  }

  if (button == GLFW_MOUSE_BUTTON_RIGHT && action == GLFW_PRESS) {
    renderer->mouse_lock = !renderer->mouse_lock;

    if (renderer->mouse_lock) {
      glfwSetInputMode(renderer->render_data.glfw_window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

      if (glfwRawMouseMotionSupported()) {
        glfwSetInputMode(renderer->render_data.glfw_window, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE);
      }
    } else {
      glfwSetInputMode(renderer->render_data.glfw_window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
    }
  }
}

void handle_mouse_position_events(Renderer *renderer, f64 x_pos, f64 y_pos) {
  ImGuiIO& io = ImGui::GetIO();
  io.AddMousePosEvent((f32)x_pos, (f32)y_pos);

  if (io.WantCaptureMouse) {
    return;
  }

  s32 mouse_move_rel_x = static_cast<s32>(x_pos) - renderer->mouse_x_position;
  s32 mouse_move_rel_y = static_cast<s32>(y_pos) - renderer->mouse_y_position;

  if (renderer->mouse_lock) {
    renderer->render_data.view_azimuth += mouse_move_rel_x / 10.0;

    if (renderer->render_data.view_azimuth < 0.0) {
      renderer->render_data.view_azimuth += 360.0;
    }

    if (renderer->render_data.view_azimuth >= 360.0) {
      renderer->render_data.view_azimuth -= 360.0;
    }

    renderer->render_data.view_elevation -= mouse_move_rel_y / 10.0;

    if (renderer->render_data.view_elevation > 89.0) {
      renderer->render_data.view_elevation = 89.0;
    }

    if (renderer->render_data.view_elevation < -89.0) {
      renderer->render_data.view_elevation = -89.0;
    }
  }

  renderer->mouse_x_position = static_cast<s32>(x_pos);
  renderer->mouse_y_position = static_cast<s32>(y_pos);
}

