#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <iostream>

#include "Rendering/Shader.h"
#include "ECS/ECS.h"
#include "ECS/Events/InputEvent.h"
#include "ECS/Events/MouseMoveEvent.h"
#include "ECS/Systems/GravitySystem.h"
#include "ECS/Systems/PositionDebugSystem.h"
#include "ECS/Systems/KeyboardMovementSystem.h"
#include "ECS/Systems/RenderSystem.h"
#include "ECS/Systems/MouseLookSystem.h"
#include "ECS/Components/ObjMesh.h"
#include "ECS/Components/Texture.h"

using namespace ECS;


World *world = World::createWorld();


static void key_callback(GLFWwindow *window, int key, int scancode, int action, int mods) {
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GLFW_TRUE);

    world->emit<InputEvent>({key, action});
}

static void mouse_callback(GLFWwindow* window, double xpos, double ypos) {
    world->emit<MouseMoveEvent>({xpos, ypos});
}

int main() {
    // TODO: Move this to RenderSystem?
    GLFWwindow *window;

    /* Initialize the library */
    if (!glfwInit())
        return -1;

    /* Create a windowed mode window and its OpenGL context */
    window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
    if (!window) {
        glfwTerminate();
        return -1;
    }

    /* Make the window's context current */
    glfwMakeContextCurrent(window);

    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

    glfwSetCursorPosCallback(window, mouse_callback);

    glfwSetKeyCallback(window, key_callback);

    // glad: load all OpenGL function pointers
    // ---------------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    glEnable(GL_DEPTH_TEST);

    // Transparency
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    // TODO: Could be automated by getting all classes within 'ECS/Systems'
    // world->registerSystem(new GravitySystem(-9.8f));
    // world->registerSystem(new PositionDebugOutputSystem());
    world->registerSystem(new KeyboardMovementSystem());
    world->registerSystem(new MouseLookSystem(640, 480));

    RenderSystem* renderSystem = new RenderSystem();
    world->registerSystem(renderSystem);

    Entity *player = world->create();
    player->assign<Transform>();
    player->assign<Movement>(glm::vec3(1.f, 1.f, 1.f));
    player->assign<Camera>(70.0f, 640, 480, 0.1f, 100.0f);
    player->assign<MouseLook>(0.1);

    Entity *box2 = world->create();
    box2->assign<Transform>();
    box2->assign<ObjMesh>("Resources/Monkey.obj");
    box2->assign<Texture>("Resources/tex.png", Texture::Settings(true, true));
    box2->get<Transform>()->translate(glm::vec3(0.0f, 0.0f, -5.0f));

    Shader defaultShader("Shaders/default-vertex.vs", "Shaders/default-fragment.fs");

    double timeInLastFrame = glfwGetTime();
    double elapsed_time = 0.0;

    /* Loop until the user closes the window */
    while (!glfwWindowShouldClose(window)) {
        // Delta time handling
        double delta = glfwGetTime() - timeInLastFrame;
        timeInLastFrame = glfwGetTime();
        elapsed_time += delta;

        world->tick(delta);
        renderSystem->render(world, defaultShader);

        /* Swap front and back buffers */
        glfwSwapBuffers(window);

        /* Poll for and process events */
        glfwPollEvents();
    }

    glfwTerminate();
    return 0;
}
