// Include Config header generated by GNU autotools
#include <GL/glew.h>
// Make sure Glew is loaded first
#include <GL/gl.h>
#include <SDL2/SDL.h>
// Not used yet
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
// File reader
#include <fstream>
#include <iostream>
#include <string>
// Shader
#include "ShaderLoader.h"
// Camera
#include "PlayerCamera.h"
#include "helpers/RootDir.h"
// Objects
#include "Mesh.h"
#include "Model.h"
#include <vector>

// Include error class
#include "Error.h"
Error error("main");

int main(int argc, char **argv) {
  // Initialise SDL2
  if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
    error.crash("Unable to initialise SDL: ", SDL_GetError());
    return 1;
  }

  // Make OpenGL use double buffering (Render game first then shove to output)
  SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

  // TODO: Understand what a depth buffer is
  SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);

  // TODO: Discover if this is necessary for linux and what values they need be
  // SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
  // SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
  // SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
  // SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);

  // Create Window
  SDL_Window *window =
      SDL_CreateWindow("Game", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
                       800, 600, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL);
  if (window == NULL) {
    error.crash("Unable to initialise SDL Window: ", SDL_GetError());
    return 1;
  }

  // Create glContext
  SDL_GLContext glContext = SDL_GL_CreateContext(window);
  // TODO: Test that glContext was created successfully

  // Tell us the number of vertex attributes allowed in bug reports
  //
  int nrAttributes;
  glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
  error.log("Maximum num of vertex attributes supported: " +
            std::to_string(nrAttributes));

  // Initialise Glew
  if (glewInit() != GLEW_OK) {
    error.crash("Unable to initialise GLEW (OpenGL Extention Wrangler)");
  }

  // Create event handling struct
  SDL_Event input;

  ShaderLoader shader(ROOT_DIR "data/shaders/vertex.glsl",
                      ROOT_DIR "data/shaders/fragment.glsl");

  Model backpack(ROOT_DIR "data/models/backpack/backpack.obj");
  Model cube(ROOT_DIR "data/models/cube/cube.obj");
  cube.translate(glm::vec3(3.0f, 0.0f, -1.0f));

  // Mess with perspective
  // Projection matrix : 45° Field of View, 4:3 ratio, display range : 0.1
  // unit <-> 100 units
  float width = 800;
  float height = 600;
  glm::mat4 Projection = glm::perspective(
      glm::radians(45.0f), (float)width / (float)height, 0.1f, 100.0f);

  // Create player camera object
  PlayerCamera camera;

  // Enable Render order things don't render in front of the wrong objects
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_CULL_FACE);
  // glCullFace(GL_FRONT);
  glClearColor(0.2f, 0.3f, 0.3f, 1.0f);

  // Game loop
  bool running = true;
  while (running) {
    // SDL Event handling loop
    while (SDL_PollEvent(&input) > 0) {
      // Handle SDL quit event
      if (input.type == SDL_QUIT) {
        running = false;
      }
      switch (input.type) {
      case SDL_QUIT:
        running = false;
      case SDL_KEYDOWN:
        const Uint8 *keys = SDL_GetKeyboardState(NULL);
        if (keys[SDL_SCANCODE_ESCAPE])
          running = false;
      }
      // TODO: Do something with keys lol
    };
    camera.tick();

    // Clear screen ready for next loop
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    // Make every shader/rendering call from this point on use our shader
    // glUseProgram(shaderProgram);

    // Send our glsl shader our mvp
    shader.setMat4("MVP", camera.getMVP());

    // Draw Meshes
    cube.draw(shader);
    backpack.draw(shader);

    // Finally render everything
    shader.use();

    // I think this is meant to be here but it breaks...
    // shove vertex array into buffer
    // glBindVertexArray(VAO);

    // TODO: Run game here lol

    SDL_GL_SwapWindow(window);
  };

  // Escaped Game loop

  // On close also destroy window
  SDL_DestroyWindow(window);

  // Close all leftover SDL systems
  SDL_Quit();

  return 0;
}
