#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <time.h>

#include <SDL2/SDL.h>
#define GLEW_STATIC
#include <GL/glew.h>
#define GL_GLEXT_PROTOTYPES
#include <SDL2/SDL_opengl.h>

#include <ft2build.h>
#include FT_FREETYPE_H

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wall"
#pragma GCC diagnostic ignored "-Wsign-conversion"
#pragma GCC diagnostic ignored "-Wdouble-promotion"
#pragma GCC diagnostic ignored "-Wconversion"

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

#pragma GCC diagnostic pop

#define IMP
#include "app.h"
#include "vec2.h"
#include "vec4.h"
#include "da.h"
#include "unwrap.h"
#include "file.h"
#include "keybind.h"
#include "config.h"
#include "shader.h"
#include "tile_glyph.h"
#include "gl_util.h"
#include "free_glyph.h"

struct app handle_events(struct app app, int32_t resolution_uniform);
struct tile_glyph_render render_buffer_into_tgr(struct tile_glyph_render tgr,
						struct app app,
						struct vec2 cpos);

struct free_glyph_render render_buffer_into_fgr(struct free_glyph_render fgr,
						struct app app,
						struct vec2 cpos);

FT_Library library;

int32_t
main(int32_t argc, char **argv)
{
	enum buffer_err buffer_err;

	if ( argc != 2 ) {
		fprintf(stderr, "MUST pass file to open\n");
		exit(EXIT_FAILURE);
	}

	const char *ffont_path = "./fonts/VictorMono-Regular.ttf";

	FT_Face face;
	{
		FT_Error error = FT_Init_FreeType( &library );
		if ( error ) {
			fprintf(stderr,
				"[ERROR] Failed to load FreeType2 library\n");
			exit(EXIT_FAILURE);
		}

		error = FT_New_Face(library, ffont_path, 0, &face);
		if ( error == FT_Err_Unknown_File_Format ) {
			fprintf(stderr, "[ERROR] Font %s has unknown format\n",
				ffont_path);
			exit(EXIT_FAILURE);
		} else if ( error ) {
			fprintf(stderr, "[ERROR] Failed to load font: %s\n",
				ffont_path);
			exit(EXIT_FAILURE);
		}

		FT_UInt pixel_size = 32;
		error = FT_Set_Pixel_Sizes(face, 0, pixel_size);
		if ( error ) {
			fprintf(stderr, "[ERROR] Failed to set font size: %u\n",
				pixel_size);
			exit(EXIT_FAILURE);
		}
	}

	struct app app = app_create("ged");
	const char *font_path = "./fonts/charmap-oldschool_white.png";
	app.font = font_create(app.rdr, font_path, 7, 18);
	app.font.scale = 4.0;

	RET_UNWRAP2(app.buf, buffer_err,
		    struct ret_buffer_err,
		    buffer_load_from_file(app.buf, argv[1]));
	if ( buffer_err != BUFFER_ERR_OK ) {
		fprintf(stderr, "Failed to open buffer: %d\n", buffer_err);
		exit(EXIT_FAILURE);
	}

	/* struct tile_glyph_render tgr = tile_glyph_render_create( */
	/* 	font_path, */
	/* 	"./shaders/tile_font.vert", */
	/* 	"./shaders/tile_font.frag"); */
	struct free_glyph_render fgr = free_glyph_render_create(
		face,
		"./shaders/free_font.vert",
		"./shaders/free_font.frag");

	struct vec2 cpos;	/* camera pos */

	double dt = (1.0 / (double)app.target_fps) * 3;
	uint32_t frame_target_dur = (uint32_t)(1000 / app.target_fps);

	char *fps_text = calloc(1, 128);
	size_t fps_text_size = 0;

	uint32_t fr_end = 0;
	uint32_t fr_start = 0;
	uint32_t fr_dur = 1;

	while ( app.running ) {
		fr_start = SDL_GetTicks();

		/* app = handle_events(app, tgr.uniforms.resolution); */
		app = handle_events(app, fgr.uniforms.resolution);

		if ( app.show_fps == true ) {
			fps_text_size = (size_t) snprintf(fps_text, 128,
							  "FPS: %.2f",
							  1000.0 / fr_dur);
			(void) fps_text_size;
			/* app_render_text(app, fps_text, fps_text_size, */
			/* 		vec2s(0), 0xFF00FFFF, 5); */
		}

		glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		RET_UNWRAP2(app, cpos,
			    struct ret_app_vec2,
			    app_calc_buffer_cam(app, dt));

		/* tgr = render_buffer_into_tgr(tgr, app, cpos); */
		fgr = render_buffer_into_fgr(fgr, app, cpos);

		SDL_GL_SwapWindow(app.win.ptr);

		fr_end = SDL_GetTicks();
		fr_dur = (fr_end - fr_start);
		if ( fr_dur < frame_target_dur ) {
			SDL_Delay(frame_target_dur - fr_dur);
		}
	}

	/* app_destroy(app); */
	exit(EXIT_SUCCESS);
}

struct app
handle_events(struct app app, int32_t resolution_uniform)
{
	SDL_Event e = {0};
	bool done = false;
	while ( SDL_PollEvent(&e) ) {
		switch ( e.type ) {
		case SDL_QUIT: {
			app.running = false;
		} break;
		case SDL_KEYDOWN: {
			SDL_KeyboardEvent key = e.key;
			SDL_Keysym ks = key.keysym;
			struct keybinds kb = keybinds[ks.sym % KEYBINDS_MAX_SIZE];
			for ( size_t i = 0; i < kb.size; ++i ) {
				struct bind b = kb.binds[i];
				if ( b.func == NULL ) {
					continue;
				}
				if ( (! (b.mod == KMOD_NONE
					 && b.mod == ks.mod))
				     && (! (b.mod & ks.mod)) ) {
					continue;
				}
				app = b.func(app, b.args);
				done = true;
			}
		} break;
		case SDL_TEXTINPUT: {
			if ( done == true ) {
				break;
			}
			char *t = e.text.text;
			while( *t != '\0' ) {
				app.buf = buffer_insert_char(
					app.buf, app.buf.cur, *t++);
			}
		} break;
		case SDL_WINDOWEVENT: {
			WINDOW_UP_SIZE(app.win);
			glUniform2f(resolution_uniform,
				    (float) app.win.w,
				    (float) app.win.h);
			glViewport(0, 0 ,app.win.w, app.win.h);
		} break;
		}
	}
	if ( app.buf.cur > app.buf.data.size ) {
		app.buf.cur = app.buf.data.size - 1;
	}

	return app;
}

struct tile_glyph_render
render_buffer_into_tgr(struct tile_glyph_render tgr, struct app app,
		       struct vec2 cpos)
{
	glUniform1f(tgr.uniforms.scale,(float) app.font.scale);
	glUniform2f(tgr.uniforms.camera,
		    (float)cpos.x,
		    (float)cpos.y);

	tgr.glyphs.size = 0;
	tgr = tile_glyph_render_calc_buffer(tgr, app.buf, vec2is(0));
	tile_glyph_render_sync(tgr);
	glDrawArraysInstanced(GL_TRIANGLE_STRIP,
			      0, 4,
			      (int32_t) tgr.glyphs.size);

	{
		size_t col = 0;
		size_t row = 0;
		RET_UNWRAP2(col, row,
			    struct ret_size_t_size_t,
			    buffer_calc_cur_pos(app.buf));

		const char *c = app.buf.data.items + app.buf.cur;
		tgr.glyphs.size = 0;
		tgr = tile_glyph_render_calc(tgr,
					     ( *c == '\n' ) ? " " : c, 1,
					     vec2i((int32_t)col,
						   -(int32_t)row),
					     vec4fs(0), vec4fs(1));
		tile_glyph_render_sync(tgr);
		glDrawArraysInstanced(GL_TRIANGLE_STRIP,
				      0, 4,
				      (int32_t) tgr.glyphs.size);
	}
	glUniform1f(tgr.uniforms.time,
		    (float) (SDL_GetTicks() / 1000));
	return tgr;
}

struct free_glyph_render
render_buffer_into_fgr(struct free_glyph_render fgr, struct app app,
		       struct vec2 cpos)
{
	glUniform2f(fgr.uniforms.camera,
		    (float)cpos.x,
		    (float)cpos.y);

	fgr.glyphs.size = 0;
	fgr = free_glyph_render_calc_buffer(fgr, app, vec2fs(0));
	free_glyph_render_sync(fgr);
	glDrawArraysInstanced(GL_TRIANGLE_STRIP,
			      0, 4,
			      (int32_t) fgr.glyphs.size);

	/* { */
	/* 	size_t col = 0; */
	/* 	size_t row = 0; */
	/* 	RET_UNWRAP2(col, row, */
	/* 		    struct ret_size_t_size_t, */
	/* 		    buffer_calc_cur_pos(app.buf)); */

	/* 	const char *c = app.buf.data.items + app.buf.cur; */
	/* 	fgr.glyphs.size = 0; */
	/* 	fgr = free_glyph_render_calc(fgr, app, */
	/* 				     ( *c == '\n' ) ? " " : c, 1, */
	/* 				     vec2f((float)col, */
	/* 					   -(float)(row*fgr.atlas_dim.y)), */
	/* 				     vec4fs(0), vec4fs(1)); */
	/* 	free_glyph_render_sync(fgr); */
	/* 	glDrawArraysInstanced(GL_TRIANGLE_STRIP, */
	/* 			      0, 4, */
	/* 			      (int32_t) fgr.glyphs.size); */
	/* } */
	uint32_t ticks = SDL_GetTicks();
	glUniform1f(fgr.uniforms.time,
		    ((float) ticks) / 1000.0f);
	return fgr;
}
