#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <GLM/glm.hpp>

#include <iostream>

#include "Scene.h"
#include "Shader.h"
#include "Lights.h"

#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600
#define WINDOW_CAPTION "OPENGL notes on rekovalev.site"

// Функция-callback для изменения размеров буфера кадра в случае изменения размеров поверхности окна
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);
}
 
bool firstMouse = true;
float lastX, lastY;

void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    if (firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }
  
    glm::vec2 offset(xpos - lastX, lastY - ypos); 
    lastX = xpos;
    lastY = ypos;

    Camera::current().rotate(offset);
}  

int main(void)
{
    GLFWwindow* window; // Указатель на окно GLFW3

    // Инициализация GLFW3
    if (!glfwInit())
    {
        std::cout << "GLFW init error\n";
        return -1;
    }

    // Завершение работы с GLFW3 перед выходом
    atexit(glfwTerminate);

    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); // Мажорная версия спецификаций OpenGL
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6); // Минорная версия спецификаций OpenGL
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Контекст OpenGL, который поддерживает только основные функции
 
    // Создание окна GLFW3 с заданными шириной, высотой и заголовком окна
    window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_CAPTION, NULL, NULL);
    if (!window)
    {
        std::cout << "GLFW create window error\n";
        return -1;
    }

    // Установка основного контекста окна
    glfwMakeContextCurrent(window);
    // Установка callback-функции для изменения размеров окна и буфера кадра
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    
    glfwSwapInterval(1); // Вертикальная синхронизация

    // Установка callback-функции для мыши и камеры
    glfwSetCursorPosCallback(window, mouse_callback);

    // Загрузка функций OpenGL с помощью GLAD
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "GLAD load GL error\n";
        return -1;
    }

    // Включаем проверку по буферу глубины
    glEnable(GL_DEPTH_TEST);

    // Базовый шейдер
    ShaderProgram base;
    // Загрузка и компиляция шейдеров
    base.load(GL_VERTEX_SHADER, "shaders/shader.vert");
    base.load(GL_FRAGMENT_SHADER, "shaders/shader.frag");
    base.link();
    // Установим значения текстур
    const char* textures_base_shader_names[] = {"tex_diffuse", "tex_ambient", "tex_specular"};
    base.bindTextures(textures_base_shader_names, sizeof(textures_base_shader_names)/sizeof(const char*));


    // Загрузка сцены из obj файла
    Scene scene = loadOBJtoScene("../resources/models/blob.obj", "../resources/models/", "../resources/textures/"); 
    scene.root.e_scale() = glm::vec3(0.01);
    scene.root.e_position().z = 1;
    
    // Установка цвета очистки буфера цвета
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

    // Расположение Uniform-переменной
    GLuint model_uniform = base.getUniformLoc("model");

    // Источник света
    LightData light = { {1.0f, 3.0f, 0.0f} // позиция
                      , {1.0f, 1.0f, 1.0f} // цвет
                      };

    // Uniform-буферы
    UBO cameraUB(sizeof(CameraData), 0);
    UBO material_data(sizeof(Material), 1);
    UBO light_data(&light, sizeof(LightData), 2);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // Использование уменьшенных версий mipmap  

    // Пока не произойдет событие запроса закрытия окна
    while(!glfwWindowShouldClose(window))
    {
        // Загрузка данных о камере
        cameraUB.loadSub(&Camera::current().getData(), sizeof(CameraData));

        // Очистка буфера цвета
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // Тут производится рендер
        scene.render(model_uniform, material_data);
        
        // Представление содержимого буфера цепочки показа на окно
        glfwSwapBuffers(window);
        // Обработка системных событий
        glfwPollEvents();
    }
    
    return 0;
}