#include <iostream>

#include "client/rendering/wrapper/GLFWWrapper.h"
#include "client/rendering/WindowSize.h"
#include "client/rendering/wrapper/Window.h"
#include "client/rendering/Shaders.h"
#include "client/rendering/Framebuffers.h"
#include "client/input/Control.h"
#include "client/rendering/Engine.h"
#include "client/utils/Clock.h"
#include "client/rendering/RenderSettings.h"

bool initGLEW() {
    GLenum err = glewInit();
    if(err != GLEW_OK) {
        std::cout << "could not initialize GLEW: " << glewGetErrorString(err) << "\n";
        return true;
    }
    std::cout << "using GLEW " << glewGetString(GLEW_VERSION) << "\n";
    return false;
}

void initCallbacks(Window& w, WindowSize& size, Framebuffers& framebuffers, Control& control) {
    static WindowSize& cSize = size;
    static Framebuffers& cFramebuffers = framebuffers;
    static Control& cControl = control;
    w.setFramebufferSizeCallback([](GLFWwindow*, int newWidth, int newHeight) {
        glViewport(0, 0, newWidth, newHeight);
        cSize.width = newWidth;
        cSize.height = newHeight;
        cFramebuffers.resize(newWidth, newHeight);
    });
    w.setKeyCallback([](GLFWwindow*, int key, int, int action, int) {
        if(action == GLFW_PRESS) {
            cControl.keys.press(key);
        } else if(action == GLFW_RELEASE) {
            cControl.keys.release(key);
        }
    });
    w.setMouseButtonCallback([](GLFWwindow*, int button, int action, int) {
        if(action == GLFW_PRESS) {
            cControl.buttons.press(button);
        } else if(action == GLFW_RELEASE) {
            cControl.buttons.release(button);
        }
    });
    w.setCursorPosCallback([](GLFWwindow*, double x, double y) {
        cControl.buttons.move(x, y);
    });
}

int main() {
    if(GLFWWrapper::hasError()) {
        return 0;
    }
    
    WindowSize size(1024, 620);
    Window window(size, "Test");
    if(window.hasError() || initGLEW()) {
        return 0;
    }
    
    Shaders shaders;
    if(shaders.hasError()) {
        return 0;
    }
    
    Framebuffers framebuffers(size);
    if(framebuffers.hasError()) {
        return 0;
    }
    
    Ray ray;
    Camera camera(ray);
    RenderSettings renderSettings;
    Engine engine(shaders, framebuffers, camera, size, renderSettings);
    
    Control control;
    Clock fps;
    Clock tps;
    Game game(control, camera, ray, fps, tps, renderSettings);
    
    initCallbacks(window, size, framebuffers, control);
    window.show();

    const u64 nanosPerTick = 50000000;
    u64 lag = 0;
    while(!window.shouldClose() && game.isRunning()) {
        engine.renderTick((float) lag / nanosPerTick, game);
        window.swapBuffers();
        lag += fps.update();
        while(lag >= nanosPerTick) {
            lag -= nanosPerTick;
            tps.update();
            control.tick();
            game.tick();
        }
        glfwPollEvents();
    }
    return 0;
}