#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <sys/time.h>
#ifndef __WIN32
#include <sys/signal.h>
#endif

#include <string.h>

#include <GL/glew.h>

#include <GLFW/glfw3.h>

#include "include/logger.h"
#include "include/input.h"
#include "include/render.h"

#define clamp(x,y,z) x>y?y:x<z?z:x

int _developer_log_level = LOGLEVEL_DEBUG;

int skibiloper = 0; // skibidi developer

GLFWwindow* main_window = NULL;


float deltaTime, fps;
long sleep_us, frametime;


void signalHandler(int signalNo) {
    logData(LOGLEVEL_WARN, "Recieved signal %d", __FILE__,__LINE__, (void*[]){(void*)1, &signalNo});
    glfwSetWindowShouldClose(main_window, GLFW_TRUE);
}


void shutdown() {
    logData(LOGLEVEL_DEBUG, "Shutting down", __FILE__,__LINE__, NULL);
    glfwTerminate();
    logData(LOGLEVEL_INFO, "Application shut down", __FILE__, __LINE__, NULL);
    exit(0);
}

int main(int argc, char ** argv) {
    #ifndef __WIN32
    if ((argc >1 && strcmp(argv[1], "-nosigs")) || argc <= 1) {
        signal(SIGSEGV, segmentationFaultCallback);
        signal(SIGINT, signalHandler);
    }
    #endif
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // minimum required parameters
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    if (logInit()) {
        perror("failed to initialize logging!");
        return 1;
    }
    glfwSetErrorCallback(glfwErrorCallback);
    if (!glfwInit()) {
        logData(LOGLEVEL_ERR, "Failed to initialize glfw", __FILE__, __LINE__, NULL);
        return 1;
    }
    logData(LOGLEVEL_DEBUG, "Initialized GLFW", __FILE__,__LINE__, NULL);
    if ((main_window = glfwCreateWindow(640, 480, "Magik", NULL, NULL)) == NULL) {
        logData(LOGLEVEL_ERR, "Failed to initialize window", __FILE__, __LINE__, NULL);
        return 1;
    }

    logData(LOGLEVEL_INFO, "Initialized window", __FILE__, __LINE__, NULL);

    glfwSetKeyCallback(main_window, keyboardCallback);
    glfwSetWindowCloseCallback(main_window, windowCloseCallback);
    glfwSetWindowSizeCallback(main_window, windowResizedCallback);
    glfwSetCursorPosCallback(main_window, mousePosCallback);

    glfwSetInputMode(main_window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); // cursor hidden, infinite space

    glfwMakeContextCurrent(main_window);
    glewExperimental = 1;
    int status = glewInit();
    if(status != GLEW_OK) {
        logData(LOGLEVEL_ERR, "Failed to initialize glew %d", __FILE__, __LINE__, (void*[]){(void*)1, &status});
        return 1;
    }

    initializeRenderer();

    struct timeval __timer, __timer2, __timer3;
    gettimeofday(&__timer, NULL);
    setbuf(stdout, NULL);

    do { // update loop
        gettimeofday(&__timer3, NULL);

        update();
        renderWorld();
        gettimeofday(&__timer2, NULL);

        deltaTime = clamp(((__timer2.tv_sec*1000000+__timer2.tv_usec) - (__timer.tv_sec*1000000+__timer.tv_usec))/(1000000.0/TARGETTPS), 1000, 0);
        glfwPollEvents();
        
        frametime = ((__timer2.tv_sec*1000000+__timer2.tv_usec) - (__timer3.tv_sec*1000000+__timer3.tv_usec))/1000;
        fps = 1000000.0/((__timer2.tv_sec*1000000+__timer2.tv_usec) - (__timer.tv_sec*1000000+__timer.tv_usec));

        sleep_us = (1000000.0/TARGETFPS)-((__timer2.tv_sec*1000000+__timer2.tv_usec) - (__timer3.tv_sec*1000000+__timer3.tv_usec));
        //printf("\rTarget: %d, DeltaTime: %lf, FPS: %lf, Sleep: %ld    " , TARGETFPS, deltaTime, fps, sleep_us>0?sleep_us:0);
        
        gettimeofday(&__timer, NULL);
        
        if (sleep_us >0) { // else we're below target fps
            usleep(sleep_us);
        }
     

    } while (!glfwWindowShouldClose(main_window));
    shutdown();
    return 0;
}