#define IMP
#include "simple_render.h"
#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 "gl_util.h"
#include "free_glyph.h"
#include "util_macro.h"

#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


void handle_events(struct app *app);

void render_buffer_into_fgr(struct free_glyph_render *fgr,
			    struct simple_render *sr,
			    struct app *app);

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"; */
	/* const char *ffont_path = "./fonts/unifont-15.1.05.otf"; */
	const char *ffont_path = "./fonts/ComicMono-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");

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

	struct free_glyph_render fgr = free_glyph_render_create(
		face,
		"./shaders/free_font.vert",
		"./shaders/free_font.frag");

	struct simple_render sr = sr_create(
		"./shaders/simple.vert",
		"./shaders/simple.frag");
	(void) sr;

	app.fgr = &fgr;

	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();

		handle_events(&app);

		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);

		render_buffer_into_fgr(&fgr, &sr, &app);

		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);
}

void
handle_events(struct app *app)
{
	SDL_Event e = {0};
	bool done = false;
	while ( SDL_PollEvent(&e) ) {
		switch ( e.type ) {
		case SDL_QUIT: {
			app->running = false;
		} break;
		case SDL_KEYDOWN: {
			app->last_press = SDL_GetTicks();

			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' ) {
				buffer_insert_char(&app->buf, *t++,
						   app->buf.cur);
			}
			app->buf.high_col = buffer_calc_cur_col(&app->buf);
		} break;
		case SDL_WINDOWEVENT: {
			WINDOW_UP_SIZE(app->win);
			glViewport(0, 0 ,app->win.w, app->win.h);
		} break;
		}
	}
	buffer_check_and_fix_cur(&app->buf);
}

void
render_buffer_into_fgr(struct free_glyph_render *fgr, struct simple_render *sr,
		       struct app *app)
{
	(void) fgr;
	(void) app;
	uint32_t ticks = SDL_GetTicks();
	float cur_row = (float)(buffer_calc_cur_row(&app->buf)
				* fgr->atlas_dim.y);
	float cur_col = free_glyph_render_calc_col_buffer(*fgr, *app);

	struct vec2 cur_pos = vec2((double) cur_col, -(double) cur_row);
	struct vec2 cpos = app->buf.cam.pos;
	struct vec2 cvel = app->buf.cam.vel;
	cvel = vec2_sub(cur_pos, cpos);
	cpos = vec2_add(cpos, vec2_mul(cvel, vec2s(app->dt)));
	app->buf.cam.pos = cpos;
	app->buf.cam.vel = cvel;

	float time = ((float) ticks) / 1000.0f;
	free_glyph_render_use(*fgr);
	{
		glUniform1f(fgr->uniforms.time, time);
		glUniform2f(fgr->uniforms.camera,
			    (float)cpos.x,
			    (float)cpos.y);
		glUniform2f(fgr->uniforms.resolution,
			    (float) app->win.w,
			    (float) app->win.h);

		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);
	}

	sr_use(sr);
	{
		glUniform1f(sr_get_uniform(sr, "time"), time);
		glUniform2f(sr_get_uniform(sr, "camera"),
			    (float)cpos.x, (float)cpos.y);
		glUniform2f(sr_get_uniform(sr, "resolution"),
			    (float) app->win.w, (float) app->win.h);
		/* struct vec2f uv = vec2fs(0); */
		sr->vertexs.size = 0;
		/* sr_triangle( */
		/* 	sr, */
		/* 	vec2f(-50.f,-50.f), vec2f(50.f,-50.f), vec2f(0.0f,50.f), */
		/* 	vec4f(1,0,0,1), vec4f(0,1,0,1), vec4f(0,0,1,1), */
		/* 	X3_ARGS(uv)); */

		uint32_t t = ticks - app->last_press;
		if ( t < 500 || (t/1000)%2 != 0 ) {
			sr_solid_rect(sr, vec2_to_f(cur_pos), vec2f(3, 32), vec4fs(1));
		}
		sr_draw(sr);
	}
}
