#include "ui.h"
#include "ui_internal.h"
#include "sys_log.h"
#include "fontrender.h"
#include "system.h"
#include "sys_render.h"

static GLuint screen_tex = 0;
static GLuint screen_fb = 0;
static GLsizei screen_w = 640;
static GLsizei screen_h = 400;
static float screen_ar = 1.0f;
static float screen_scale = 640.0f;

static int font_ready = 0;

static GLuint elemquad_vao = 0;
static GLuint elemquad_vbo = 0;
static GLuint elemprog = 0;
static GLuint elemnotex = 0;
static GLuint elemssbo = 0;
static struct yr_ui_rendertexes elemuni = {0};
extern const GLchar* VtxShader_ui_elem;
extern const GLchar* FragShader_ui_elem;

static yrUiScreen screens[us_end] = {NULL};
static enum UiScreenId screen_active = us_end;

GLuint loadimgtex(const char* path); //"stealing" texture loader from ui_parse
void draw_flush(GLuint ssbo); //sneaky access to flush

int yrUi_load(unsigned w, unsigned h)
{
	//load ui screens
	for(size_t i = 0; i < us_end; ++i) {
		screens[i] = yrUiScreen_load(i);
		if(!screens[i]) return -1;
	}
	screen_active = yrSystem_vr_fromstart() ? usVRMode : usFile;

	//init font renderer
	int err = yrFontRender_init("data/font/lato/Lato-Regular.ttf");
	if(err) return -1;
	font_ready = 1;
	
	//create screenbuffer
	yrUi_resize(w, h);
	if(!screen_tex) return -1;

	//elemquad
	glGenVertexArrays(1, &elemquad_vao);
	glGenBuffers(1, &elemquad_vbo);
	glBindVertexArray(elemquad_vao);
	glBindBuffer(GL_ARRAY_BUFFER, elemquad_vbo);
	glBufferData(GL_ARRAY_BUFFER, 4 * 2 * sizeof(GLfloat), NULL, GL_STATIC_DRAW);
	glVertexAttribPointer(0, 2, GL_FLOAT, FALSE, 0, (GLvoid*)0);
	glEnableVertexAttribArray(0);

	float* buf = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
	if(!buf) {
		yrLog(0, "Could not map OpenGL buffer: %x", glGetError());
		return -1;
	}
	buf[0*2 + 0] = 0.0f;
	buf[0*2 + 1] = 0.0f;
	buf[1*2 + 0] = 1.0f;
	buf[1*2 + 1] = 0.0f;
	buf[2*2 + 0] = 1.0f;
	buf[2*2 + 1] = 1.0f;
	buf[3*2 + 0] = 0.0f;
	buf[3*2 + 1] = 1.0f;
	glUnmapBuffer(GL_ARRAY_BUFFER);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);
	GLenum glerr = glGetError();
	if(glerr != GL_NO_ERROR) {
		yrLog(0, "OpenGL error during ui elemquad buffer write: %x", glGetError());
		return -1;
	}

	//ui element shader
	err = 0;
	elemprog = glCreateProgram();
	err = OpenGL_shader(elemprog, GL_VERTEX_SHADER, VtxShader_ui_elem);
	if(err) { yrLog(0, "Compilation error(s) were in ui element vertex shader."); return -1; }
	err = OpenGL_shader(elemprog, GL_FRAGMENT_SHADER, FragShader_ui_elem);
	if(err) { yrLog(0, "Compilation error(s) were in ui element fragment shader."); return -1; }
	glLinkProgram(elemprog);
	glerr = glGetError();
	if(glerr != GL_NO_ERROR) { yrLog(0, "Ui element shaderprogram failed to link: %x", glerr); return -1; }

	elemuni.ar		= glGetUniformLocation(elemprog, "ar");
	glerr = glGetError();
	if(glerr != GL_NO_ERROR) {
		yrLog(0, "OpenGL error during ui element shader uniform location acquisition: %x", glerr);
		return -1;
	}

	//"no texture"-texture
	uint32_t transparent = 0x00000000ul;
	glGenTextures(1, &elemnotex);
	glBindTexture(GL_TEXTURE_2D, elemnotex);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexStorage2D(GL_TEXTURE_2D, 1, GL_SRGB8_ALPHA8, 1, 1);
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, &transparent);
	glBindTexture(GL_TEXTURE_2D, 0);
	glerr = glGetError();
	if(glerr != GL_NO_ERROR) {
		yrLog(0, "OpenGL error during 1x1 transparent texture creation: %x", glerr);
		return -1;
	}
	elemuni.notex = elemnotex;

	//checked/unchecked texture
	elemuni.checktex = loadimgtex("data/ui/checked.png");
	elemuni.nocheck = loadimgtex("data/ui/unchecked.png");
	if(!elemuni.checktex || !elemuni.nocheck) return -1;

	//element ssbo
	glGenBuffers(1, &elemssbo);
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, elemssbo);
	glBufferData(GL_SHADER_STORAGE_BUFFER, UIRENDER_BUFFER * sizeof(struct yr_ui_ssbo), NULL, GL_DYNAMIC_DRAW);
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
		glerr = glGetError();
	if(glerr != GL_NO_ERROR) {
		yrLog(0, "OpenGL error during ui ssbo creation: %x", glerr);
		return -1;
	}
	elemuni.ssbo = elemssbo;

	//texture handles
	elemuni.handle_notex	= glGetTextureHandleARB(elemuni.notex);
	elemuni.handle_checktex = glGetTextureHandleARB(elemuni.checktex);
	elemuni.handle_nocheck	= glGetTextureHandleARB(elemuni.nocheck);
	glMakeTextureHandleResidentARB(elemuni.handle_notex);
	glMakeTextureHandleResidentARB(elemuni.handle_checktex);
	glMakeTextureHandleResidentARB(elemuni.handle_nocheck);
	glerr = glGetError();
	if(glerr != GL_NO_ERROR) {
		yrLog(0, "OpenGL error during ui texture handle acquisitionn: %x", glerr);
		return -1;
	}

	return 0;
}

void yrUi_shutdown(void)
{
	if(font_ready) {
		yrFontRender_cleanup();
		font_ready = 0;
	}
	glDeleteBuffers(1, &elemssbo);
	elemssbo = 0;
	glDeleteTextures(1, &elemuni.checktex);
	glDeleteTextures(1, &elemuni.notex);
	glDeleteTextures(1, &screen_tex);
	glDeleteTextures(1, &elemnotex);
	glDeleteFramebuffers(1, &screen_fb);
	glDeleteVertexArrays(1, &elemquad_vao);
	glDeleteBuffers(1, &elemquad_vbo);
	glDeleteProgram(elemprog);
	elemquad_vao = 0;
	elemquad_vbo = 0;
	elemprog = 0;
	elemnotex = 0;
	elemuni.checktex = 0;
	elemuni.notex = 0;
	screen_tex = 0;
	screen_fb = 0;

	for(size_t i = 0; i < us_end; ++i) {
		if(screens[i]) yrUiScreen_destroy(screens[i]);
		screens[i] = NULL;
	}
}

void yrUi_resize(unsigned w, unsigned h)
{
	//cleanup old screenbuffer
	glDeleteTextures(1, &screen_tex);
	glDeleteFramebuffers(1, &screen_fb);
	screen_tex = 0;
	screen_fb = 0;

	//screen metrics
	screen_w = (GLsizei) w;
	screen_h = (GLsizei) h;
	screen_scale = (float) w;
	screen_ar = (float) w / h;

	//create new screenbuffer
	glGenFramebuffers(1, &screen_fb);
	glGenTextures(1, &screen_tex);
	glBindFramebuffer(GL_FRAMEBUFFER, screen_fb);
	glBindTexture(GL_TEXTURE_2D, screen_tex);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
	glTexStorage2D(GL_TEXTURE_2D, 1, GL_SRGB8_ALPHA8, w, h);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, screen_tex, 0);

	int ok = (glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
	glBindTexture(GL_TEXTURE_2D, 0);
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	if(!ok) {
		yrLog(0, "OpenGL error while creating ui screen framebuffer: %x", glGetError());
		glDeleteTextures(1, &screen_tex);
		glDeleteFramebuffers(1, &screen_fb);
		screen_tex = 0;
		screen_fb = 0;
		return;
	}

	//update active screen
	yrUiScreen_resize(screens[screen_active], screen_scale, screen_ar);
}

static void updatemirror(struct yr_ui_rendertexes* t)
{
	t->mirror = yrRender_mirrortex();
	if(t->mirror && !t->handle_mirror) {
		t->handle_mirror = glGetTextureHandleARB(t->mirror);
		glMakeTextureHandleResidentARB(t->handle_mirror);
	}
	if(!t->mirror && t->handle_mirror) {
		glMakeTextureHandleNonResidentARB(t->handle_mirror);
		t->handle_mirror = 0;
	}
}

GLuint yrUi_renderupdate(void)
{
	//switch screen if requested
	yrUiScreen scr = screens[screen_active];
	enum UiScreenId nextscr = yrUiScreen_nextscreen(scr);
	if(nextscr != us_end && nextscr != screen_active) {
		screen_active = nextscr;
		scr = screens[screen_active];
		yrUiScreen_reset(scr);
		yrUiScreen_resize(scr, screen_scale, screen_ar);
	}
	//update screen
	yrUiScreen_tick(scr);
	//prep render
	glDisable(GL_MULTISAMPLE);
	glDisable(GL_DEPTH_TEST);
	glEnable(GL_BLEND);
	glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
	glBindFramebuffer(GL_FRAMEBUFFER, screen_fb);
	glViewport(0, 0, screen_w, screen_h);
	glUseProgram(elemprog);
	glBindVertexArray(elemquad_vao);
	glBindBuffer(GL_ARRAY_BUFFER, elemquad_vbo);
	glUniform1f(elemuni.ar, screen_ar);
	updatemirror(&elemuni);
	//do render
	glClearColor(0.0f,0.0f,0.0f,1.0f);
	glClear(GL_COLOR_BUFFER_BIT);
	yrUiScreen_render(scr, &elemuni);
	draw_flush(elemuni.ssbo);
	//done
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	return screen_tex;
}

void yrUi_mousepos(int x, int y)
{
	yrUiScreen_mousepos(screens[screen_active], 100.0f * x / screen_scale, 100.0f * y / screen_scale);
}

void yrUi_keyevent(unsigned keycode, int down)
{
	yrUiScreen_keyevent(screens[screen_active], keycode, down);
}

void yrUi_char(unsigned c)
{
	yrUiScreen_char(screens[screen_active], c);
}