#include "window.h"
#include "gl.h"

#include <gl/gl.h>
#include <stdlib.h>

#include "GLFW/glfw3.h"
#include "error.h"
#include "framebuffer.h"
#include "gamepad.h"
#include "scaler.h"
#include "shaders.h"
#include "vec2i.h"

static void _tick();
static struct sw_window *_tick_window;
static void (*_tick_fn)();
static void win_callback(GLFWwindow *glfw_win, i32 width, i32 height);

#ifndef __EMSCRIPTEN__
static void GLAPIENTRY gldebug(
    GLenum source,
    GLenum type,
    GLuint id,
    GLenum severity,
    GLsizei length,
    const char *msg,
    const void *user
);
#endif

struct sw_window *sw_window_create(struct sw_vec2i size, char *title) {
	struct sw_window *win;

	win = malloc(sizeof(struct sw_window));
	win->window_size = size;
	win->game_size = size;

	sw_log("init glfw");
	glfwInit();
	glfwDefaultWindowHints();

	win->_window = glfwCreateWindow(size.x, size.y, title, 0, 0);
	glfwSetWindowUserPointer(win->_window, win);
	glfwShowWindow(win->_window);
	glfwMakeContextCurrent(win->_window);
	glewInit();
	glEnable(GL_DEBUG_OUTPUT);
#ifndef __EMSCRIPTEN__
	glDebugMessageCallback(gldebug, 0);
#endif
	glfwSwapInterval(1);
	sw_log("setup ogl defaults");
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_BLEND);
	/* glEnable(GL_DEPTH_TEST); */

	sw_gamepad_static_init();
	sw_shaders_static_init();
	sw_framebuffer_static_init(); /* TODO: move elsewhere */
	win->_scaler_fb = sw_framebuffer_create(size);

	glfwSetWindowSizeCallback(win->_window, win_callback);

	glClearColor(0.1f, 0.2f, 0.3f, 1.f);
	sw_log("window setup complete");
	return win;
}

void sw_window_run(struct sw_window *window, void (*callback)()) {
	sw_log("run window");
	_tick_window = window;
	_tick_fn = callback;
#ifdef __EMSCRIPTEN__
	emscripten_set_main_loop(_tick, 60, 1);
#else
	while(!glfwWindowShouldClose(window->_window)) {
		_tick();
	}
#endif
}

/* private functions */

void _tick() {
	sw_gamepad_tick();
	glViewport(0, 0, _tick_window->game_size.x, _tick_window->game_size.y);
	glBindFramebuffer(GL_FRAMEBUFFER, _tick_window->_scaler_fb->_fb);
	_tick_fn();

	glViewport(0, 0, _tick_window->window_size.x, _tick_window->window_size.y);
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	glBindTexture(GL_TEXTURE_2D, _tick_window->_scaler_fb->_fbtex);
	glClearColor(0, 0, 0, 1);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	sw_scaler_apply(
	    SW_SCALE_INTEGER, _tick_window->game_size, _tick_window->window_size
	);
	glfwSwapBuffers(_tick_window->_window);
	glfwPollEvents();
}

static void win_callback(GLFWwindow *glfw_win, i32 width, i32 height) {
	struct sw_window *win;

	sw_log("resizing window to %ix%i", width, height);

	win = glfwGetWindowUserPointer(glfw_win);
	win->window_size = sw_vec2i(width, height);
}

#ifndef __EMSCRIPTEN__
void GLAPIENTRY gldebug(
    GLenum source,
    GLenum type,
    GLuint id,
    GLenum severity,
    GLsizei length,
    const char *msg,
    const void *user
) {
	(void)source;
	(void)id;
	(void)severity;
	(void)length;
	(void)user;

	if(type == GL_DEBUG_TYPE_ERROR) {
		sw_error("%s", msg);
	} else {
		sw_log("%s", msg);
	}
}
#endif
