#define STB_IMAGE_IMPLEMENTATION
#include "file.h"
#include "renderer.h"
#include "logger.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) {
  // FPS time
  static f32 previous_frame_start_time = 0.0;
  f32 frame_start_time = glfwGetTime();

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

  Vector3 camera_position = Vector3(0.4f, 0.3f, 1.0f);
  Vector3 camera_look_at_position = Vector3(0.0f, 0.0f, 0.0f);
  Vector3 camera_up_vector = Vector3(0.0f, 1.0f, 0.0f);

  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 = glm::lookAt(camera_position, camera_look_at_position, camera_up_vector) * model;
  
  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);
  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;
}

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


/*
 * Frame buffer
 */
bool init_frame_buffer(Frame_Buffer *frame_buffer, u32 width, u32 height) {
  frame_buffer->width = width;
  frame_buffer->height = height;

  glGenFramebuffers(1, &frame_buffer->buffer);
  glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer->buffer);
  glEnable(GL_FRAMEBUFFER_SRGB);

  glGenTextures(1, &frame_buffer->color_tex);
  glBindTexture(GL_TEXTURE_2D, frame_buffer->color_tex);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  glBindTexture(GL_TEXTURE_2D, 0);

  glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, frame_buffer->color_tex, 0);
  log(1, "%s: added color buffer\n", __FUNCTION__);

  glGenRenderbuffers(1, &frame_buffer->depth_buffer);
  glBindRenderbuffer(GL_RENDERBUFFER, frame_buffer->depth_buffer);
  glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height);
  glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, frame_buffer->depth_buffer);
  glBindRenderbuffer(GL_RENDERBUFFER, 0);
  log(1, "%s: added depth render buffer\n", __FUNCTION__);

  glBindFramebuffer(GL_FRAMEBUFFER, 0);

  return check_frame_buffer_complete(frame_buffer);
}

bool check_frame_buffer_complete(Frame_Buffer *frame_buffer) {
  glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer->buffer);

  GLenum result = glCheckFramebufferStatus(GL_FRAMEBUFFER);
  if (result != GL_FRAMEBUFFER_COMPLETE) {
    log(1, "%s error: framebuffer is NOT complete\n", __FUNCTION__);
    return false;
  }

  glBindFramebuffer(GL_FRAMEBUFFER, 0);
    log(1, "%s: framebuffer is complete\n", __FUNCTION__);
  return true;
}

bool resize_frame_buffer(Frame_Buffer *frame_buffer, u32 width, u32 height) {
  frame_buffer->width = width;
  frame_buffer->height = height;

  glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
  glDeleteTextures(1, &frame_buffer->color_tex);
  glDeleteRenderbuffers(1, &frame_buffer->depth_buffer);
  glDeleteFramebuffers(1, &frame_buffer->buffer);

  return init_frame_buffer(frame_buffer, width, height);
}

void draw_frame_buffer(Frame_Buffer *frame_buffer) {
  glBindFramebuffer(GL_READ_FRAMEBUFFER, frame_buffer->buffer);
  glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
  glBlitFramebuffer(0, 0, frame_buffer->width, frame_buffer->height, 0, 0, frame_buffer->width, frame_buffer->height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
  glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
}

void bind_frame_buffer(Frame_Buffer *frame_buffer) {
  glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frame_buffer->buffer);
}

void unbind_frame_buffer(Frame_Buffer *frame_buffer) {
  glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}

void cleanup_frame_buffer(Frame_Buffer *frame_buffer) {
  unbind_frame_buffer(frame_buffer);

  glDeleteTextures(1, &frame_buffer->color_tex);
  glDeleteRenderbuffers(1, &frame_buffer->depth_buffer);
  glDeleteFramebuffers(1, &frame_buffer->buffer);
}


/*
 * Vertex buffer
 */
bool init_vertex_buffer(Vertex_Buffer *vertex_buffer) {
  glGenVertexArrays(1, &vertex_buffer->vao);
  glGenBuffers(1, &vertex_buffer->vbo);

  glBindVertexArray(vertex_buffer->vao);
  glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer->vbo);

  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*) offsetof(Vertex, position));
  glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*) offsetof(Vertex, color));
  glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*) offsetof(Vertex, uv));
  
  glEnableVertexAttribArray(0);
  glEnableVertexAttribArray(1);
  glEnableVertexAttribArray(2);

  glBindBuffer(GL_ARRAY_BUFFER, 0);
  glBindVertexArray(0);

  return true;
}

void cleanup_vertex_buffer(Vertex_Buffer *vertex_buffer) {
  glDeleteBuffers(1, &vertex_buffer->vbo);
  glDeleteVertexArrays(1, &vertex_buffer->vao);
}

void upload_vertex_buffer_data(Vertex_Buffer *vertex_buffer, Mesh *vertex_data) {
  glBindVertexArray(vertex_buffer->vao);
  glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer->vbo);
  glBufferData(GL_ARRAY_BUFFER, vertex_data->vertices.size() * sizeof(Vertex), &vertex_data->vertices.at(0), GL_DYNAMIC_DRAW);

  glBindBuffer(GL_ARRAY_BUFFER, 0);
  glBindVertexArray(0);
}

void bind_vertex_buffer(Vertex_Buffer *vertex_buffer) {
  glBindVertexArray(vertex_buffer->vao);
}

void unbind_vertex_buffer() {
  glBindVertexArray(0);
}

void draw_vertex_buffer(u32 mode, u32 start, u32 num) {
  glDrawArrays(mode, start, num);
}

/*
 * Uniform buffer
 */
bool init_uniform_buffer(Uniform_Buffer *uniform_buffer) {
  glGenBuffers(1, &uniform_buffer->ubo_buffer);
  glBindBuffer(GL_UNIFORM_BUFFER, uniform_buffer->ubo_buffer);
  glBufferData(GL_UNIFORM_BUFFER, 2 * sizeof(Matrix4), nullptr, GL_STATIC_DRAW);
  glBindBuffer(GL_UNIFORM_BUFFER, 0);

  return true;
}

void upload_uniform_buffer_data(Uniform_Buffer *uniform_buffer, Matrix4 view_matrix, Matrix4 projection_matrix) {
  glBindBuffer(GL_UNIFORM_BUFFER, uniform_buffer->ubo_buffer);
  glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(Matrix4), glm::value_ptr(view_matrix));
  glBufferSubData(GL_UNIFORM_BUFFER, sizeof(Matrix4), sizeof(Matrix4), glm::value_ptr(projection_matrix));
  glBindBufferRange(GL_UNIFORM_BUFFER, 0, uniform_buffer->ubo_buffer, 0, 2 * sizeof(Matrix4));
  glBindBuffer(GL_UNIFORM_BUFFER, 0);
}

void cleanup(Uniform_Buffer *uniform_buffer) {
  glDeleteBuffers(1, &uniform_buffer->ubo_buffer);
}


/*
 * Texture
 */
bool load_texture(Texture *texture, string filepath) {
  texture->name = filepath;

  stbi_set_flip_vertically_on_load(true);
  u8* texture_data = stbi_load(filepath.c_str(), &texture->width, &texture->height, &texture->number_of_channels, 0);

  if (!texture_data) {
    stbi_image_free(texture_data);
    return false;
  }

  glGenTextures(1, &texture->texture);
  glBindTexture(GL_TEXTURE_2D, texture->texture);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture->width, texture->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, texture_data);

  glGenerateMipmap(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, 0);

  stbi_image_free(texture_data);

  return true;
}

void bind_texture(Texture *texture) {
  glBindTexture(GL_TEXTURE_2D, texture->texture);
}

void unbind_texture() {
  glBindTexture(GL_TEXTURE_2D, 0);
}

void cleanup_texture(Texture *texture) {
  glDeleteTextures(1, &texture->texture);
}


/*
 * Shaders
 */
bool load_shaders(Shader *shader, string vertex_shader_filepath, string fragment_shader_filepath) {
  u32 vertex_shader = load_shader(vertex_shader_filepath, GL_VERTEX_SHADER);
  if (!vertex_shader) {
    return false;
  }

  u32 fragment_shader = load_shader(fragment_shader_filepath, GL_FRAGMENT_SHADER);
  if (!fragment_shader) {
    return false;
  }

  shader->program = glCreateProgram();
  glAttachShader(shader->program, vertex_shader);
  glAttachShader(shader->program, fragment_shader);
  glLinkProgram(shader->program);

  s32 is_program_linked;
  glGetProgramiv(shader->program, GL_LINK_STATUS, &is_program_linked);
  if (!is_program_linked) {
    glDeleteShader(vertex_shader);
    glDeleteShader(fragment_shader);
    return false;
  }

  s32 ubo_index = glGetUniformBlockIndex(shader->program, "Matrices");
  glUniformBlockBinding(shader->program, ubo_index, 0);

  glDeleteShader(vertex_shader);
  glDeleteShader(fragment_shader);

  return true;
}

void cleanup_shader(Shader *shader) {
  glDeleteProgram(shader->program);
}

u32 load_shader(string filepath, u32 shader_type) {
  string shader_as_text; 
  shader_as_text = load_file_to_string(filepath);

  const char*shader_source = shader_as_text.c_str();
  u32 shader = glCreateShader(shader_type);
  glShaderSource(shader, 1, (const GLchar**) &shader_source, 0);
  glCompileShader(shader);

  s32 is_shader_compiled;
  glGetShaderiv(shader, GL_COMPILE_STATUS, &is_shader_compiled);
  if (!is_shader_compiled) {
    return 0;
  }

  return shader;
}

void use_shader(Shader *shader) {
  glUseProgram(shader->program);
}
