
#include <iomanip>
#include <iostream>

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "stb/stb_easy_font.h"

#include <engine/mesh.h>
#include <engine/shader.h>
#include <engine/texture.h>

const int SCREEN_WIDTH = 1920;
const int SCREEN_HEIGHT = 1080;

static bool drawWireframe = false;
static bool wireframeHeld = false;

void window_resize_callback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);
}

void processInput(GLFWwindow* window)
{
    // Close on Escape
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);

    // Wireframe toggle
    if (glfwGetKey(window, GLFW_KEY_F1) == GLFW_PRESS && !wireframeHeld)
    {
        wireframeHeld = true;
        drawWireframe = !drawWireframe;
        if (drawWireframe)
        {
            glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
        }
        else
        {
            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
        }
    }
    if (glfwGetKey(window, GLFW_KEY_F1) == GLFW_RELEASE)
        wireframeHeld = false;
}

int main(int argc, char* argv[])
{
    // Initialize GLFW
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    // Create GLFW Window
    GLFWwindow* window = glfwCreateWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Renderer", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);

    // Initialize GLAD
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    // Initialize Viewport & setup resize callback
    glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
    glfwSetFramebufferSizeCallback(window, window_resize_callback);

    // Load Shader
    Shader shaderTest("assets/shaders/basicVertex.vert", "assets/shaders/basicFragment.frag");
    std::vector<Vertex> vertices = {
        { -0.5f, -0.5f, -0.5f,   1.0f, 0.2f, 0.2f,   0.0f, 0.0f },
    {  0.5f, -0.5f, -0.5f,   1.0f, 0.2f, 0.2f,   1.0f, 0.0f },
    {  0.5f,  0.5f, -0.5f,   1.0f, 0.2f, 0.2f,   1.0f, 1.0f },
    {  0.5f,  0.5f, -0.5f,   1.0f, 0.2f, 0.2f,   1.0f, 1.0f },
    { -0.5f,  0.5f, -0.5f,   1.0f, 0.2f, 0.2f,   0.0f, 1.0f },
    { -0.5f, -0.5f, -0.5f,   1.0f, 0.2f, 0.2f,   0.0f, 0.0f },

    // Back face (+z) – green-ish
    { -0.5f, -0.5f,  0.5f,   0.2f, 1.0f, 0.2f,   0.0f, 0.0f },
    {  0.5f, -0.5f,  0.5f,   0.2f, 1.0f, 0.2f,   1.0f, 0.0f },
    {  0.5f,  0.5f,  0.5f,   0.2f, 1.0f, 0.2f,   1.0f, 1.0f },
    {  0.5f,  0.5f,  0.5f,   0.2f, 1.0f, 0.2f,   1.0f, 1.0f },
    { -0.5f,  0.5f,  0.5f,   0.2f, 1.0f, 0.2f,   0.0f, 1.0f },
    { -0.5f, -0.5f,  0.5f,   0.2f, 1.0f, 0.2f,   0.0f, 0.0f },

    // Left face (-x) – blue-ish
    { -0.5f,  0.5f,  0.5f,   0.2f, 0.2f, 1.0f,   1.0f, 0.0f },
    { -0.5f,  0.5f, -0.5f,   0.2f, 0.2f, 1.0f,   1.0f, 1.0f },
    { -0.5f, -0.5f, -0.5f,   0.2f, 0.2f, 1.0f,   0.0f, 1.0f },
    { -0.5f, -0.5f, -0.5f,   0.2f, 0.2f, 1.0f,   0.0f, 1.0f },
    { -0.5f, -0.5f,  0.5f,   0.2f, 0.2f, 1.0f,   0.0f, 0.0f },
    { -0.5f,  0.5f,  0.5f,   0.2f, 0.2f, 1.0f,   1.0f, 0.0f },

    // Right face (+x) – yellow-ish
    {  0.5f,  0.5f,  0.5f,   1.0f, 1.0f, 0.2f,   1.0f, 0.0f },
    {  0.5f,  0.5f, -0.5f,   1.0f, 1.0f, 0.2f,   1.0f, 1.0f },
    {  0.5f, -0.5f, -0.5f,   1.0f, 1.0f, 0.2f,   0.0f, 1.0f },
    {  0.5f, -0.5f, -0.5f,   1.0f, 1.0f, 0.2f,   0.0f, 1.0f },
    {  0.5f, -0.5f,  0.5f,   1.0f, 1.0f, 0.2f,   0.0f, 0.0f },
    {  0.5f,  0.5f,  0.5f,   1.0f, 1.0f, 0.2f,   1.0f, 0.0f },

    // Bottom face (-y) – magenta-ish
    { -0.5f, -0.5f, -0.5f,   1.0f, 0.2f, 1.0f,   0.0f, 1.0f },
    {  0.5f, -0.5f, -0.5f,   1.0f, 0.2f, 1.0f,   1.0f, 1.0f },
    {  0.5f, -0.5f,  0.5f,   1.0f, 0.2f, 1.0f,   1.0f, 0.0f },
    {  0.5f, -0.5f,  0.5f,   1.0f, 0.2f, 1.0f,   1.0f, 0.0f },
    { -0.5f, -0.5f,  0.5f,   1.0f, 0.2f, 1.0f,   0.0f, 0.0f },
    { -0.5f, -0.5f, -0.5f,   1.0f, 0.2f, 1.0f,   0.0f, 1.0f },

    // Top face (+y) – cyan-ish
    { -0.5f,  0.5f, -0.5f,   0.2f, 1.0f, 1.0f,   0.0f, 1.0f },
    {  0.5f,  0.5f, -0.5f,   0.2f, 1.0f, 1.0f,   1.0f, 1.0f },
    {  0.5f,  0.5f,  0.5f,   0.2f, 1.0f, 1.0f,   1.0f, 0.0f },
    {  0.5f,  0.5f,  0.5f,   0.2f, 1.0f, 1.0f,   1.0f, 0.0f },
    { -0.5f,  0.5f,  0.5f,   0.2f, 1.0f, 1.0f,   0.0f, 0.0f },
    { -0.5f,  0.5f, -0.5f,   0.2f, 1.0f, 1.0f,   0.0f, 1.0f }
    };
    std::vector<unsigned int> indices = {
        0, 1, 3,
        1, 2, 3
    };

    Mesh testMesh(vertices, indices);

    Texture texture1("assets/wall.jpg");
    Texture texture2("assets/awesomeface.png", true);

    shaderTest.Use(); // don't forget to activate/use the shader before setting uniforms!
    shaderTest.setInt("ourTexture", 0);
    shaderTest.setInt("decal", 1);

    glm::vec3 cubePositions[] = {
        glm::vec3( 0.0f,  0.0f,  0.0f),
        glm::vec3( 2.0f,  5.0f, -15.0f),
        glm::vec3(-1.5f, -2.2f, -2.5f),
        glm::vec3(-3.8f, -2.0f, -12.3f),
        glm::vec3( 2.4f, -0.4f, -3.5f),
        glm::vec3(-1.7f,  3.0f, -7.5f),
        glm::vec3( 1.3f, -2.0f, -2.5f),
        glm::vec3( 1.5f,  2.0f, -2.5f),
        glm::vec3( 1.5f,  0.2f, -1.5f),
        glm::vec3(-1.3f,  1.0f, -1.5f)
    };

    // Render Loop
    double lastTime = 0.0;
    int nbFrames = 0;
    while (!glfwWindowShouldClose(window))
    {
        // Calculate FPS
        double currentTime = glfwGetTime();
        nbFrames++;
        if (currentTime - lastTime >= 1.0) {  // Update every second
            double fps = nbFrames / (currentTime - lastTime);
            std::cout << fps << std::endl;

            nbFrames = 0;
            lastTime += 1.0;
        }

        // Input processing
        processInput(window);

        // Rendering
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glEnable(GL_DEPTH_TEST);

        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture1.ID);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, texture2.ID);

        shaderTest.Use();

        // Test
        glm::mat4 model = glm::mat4(1.0f);
        model = glm::rotate(model, (float)glfwGetTime() * glm::radians(50.0f), glm::vec3(0.5f, 1.0f, 0.0f));

        glm::mat4 view = glm::mat4(1.0f);
        // note that we're translating the scene in the reverse direction of where we want to move
        view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));

        glm::mat4 projection;
        projection = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);

        shaderTest.setMat4("model", model);
        shaderTest.setMat4("view", view);
        shaderTest.setMat4("projection", projection);


        //testMesh.Draw();

        glBindVertexArray(testMesh.VAO);
        for(unsigned int i = 0; i < 10; i++)
        {
            glm::mat4 model = glm::mat4(1.0f);
            model = glm::translate(model, cubePositions[i]);
            float angle = 20.0f * i;
            model = glm::rotate(model, (float)glfwGetTime() * glm::radians(50.0f), glm::vec3(0.5f, 1.0f, 0.0f));
            shaderTest.setMat4("model", model);

            glDrawArrays(GL_TRIANGLES, 0, 36);
        }

        // Call events & swap buffers
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // Clean up memory
    glfwTerminate();
    return 0;
}
