#ifndef SIMPLE_RENDER_H
#define SIMPLE_RENDER_H

#include <stdint.h>

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

#include "ht.h"
#include "da.h"
#include "vec2.h"
#include "vec4.h"
#include "glyph_attr.h"

enum simple_vertex_attr {
	SIMPLE_VERTEX_ATTR_POS = 0,
	SIMPLE_VERTEX_ATTR_COLOR,
	SIMPLE_VERTEX_ATTR_UV,
	SIMPLE_VERTEX_ATTR_TOTAL,
};


struct simple_vertex {
	struct vec2f pos;
	struct vec4f color;
	struct vec2f uv;
};

enum simple_shader {
	SIMPLE_SHADER_COLOR,
	SIMPLE_SHADER_IMAGE,
	SIMPLE_SHADER_RAINBOW,
	SIMPLE_SHADER_TOTAL,
};

struct simple_render {
	HT_DEF_STRUCT_ITEM(int32_t, uniforms);
	DA_DEF_STRUCT_ITEM(struct simple_vertex, vertexs);

	uint32_t vao;
	uint32_t vbo;
	struct {
		uint32_t vert;
		uint32_t frag_color;
		uint32_t frag_image;
		uint32_t frag_rainbow;
	} shaders;
	uint32_t progs[SIMPLE_SHADER_TOTAL];
	enum simple_shader cur_shader;
};


struct simple_render sr_create(void);
void sr_compile_shaders(struct simple_render *sr);
void sr_set_all_uniforms(struct simple_render *sr);
int32_t sr_set_uniform(struct simple_render *sr, const char *uniform_name);
int32_t sr_get_uniform(const struct simple_render *sr,
		       const char *uniform_name);
void sr_use(const struct simple_render *sr);
void sr_set_shader(struct simple_render *sr, enum simple_shader shader);
void sr_triangle(struct simple_render *sr,
		 struct vec2f p0, struct vec2f p1, struct vec2f p2,
		 struct vec4f c0, struct vec4f c1, struct vec4f c2,
		 struct vec2f uv0, struct vec2f uv1, struct vec2f uv2);
void sr_quad(struct simple_render *sr,
	     struct vec2f p0, struct vec2f p1, struct vec2f p2, struct vec2f p3,
	     struct vec4f c0, struct vec4f c1, struct vec4f c2, struct vec4f c3,
	     struct vec2f uv0, struct vec2f uv1,
	     struct vec2f uv2, struct vec2f uv3);
void sr_image_rect(struct simple_render *sr,
		   struct vec2f pos, struct vec2f size,
		   struct vec2f uv_pos, struct vec2f uv_size);
void sr_solid_rect(struct simple_render *sr, struct vec2f pos,
		   struct vec2f size, struct vec4f color);
void sr_draw(const struct simple_render *sr);

#if defined(SIMPLE_RENDER_IMP) || defined(IMP)

#include "shader.h"
#include "gl_util.h"

#include "util_macro.h"

struct simple_render
sr_create(void)
{
	struct simple_render sr = {0};
	HT_CREATE(sr.uniforms, 32);
	DA_CREATE(sr.vertexs);

	/* sr.vertexs.cap = 1024 * 1024; */
	/* sr.vertexs.size = 0; */
	/* sr.vertexs.items = calloc(sr.vertexs.cap, sizeof(struct simple_vertex)); */

	{
		glGenVertexArrays(1, &sr.vao);
		glBindVertexArray(sr.vao);

		glGenBuffers(1, &sr.vbo);
		glBindBuffer(GL_ARRAY_BUFFER, sr.vbo);

		/* glBufferData(GL_ARRAY_BUFFER, */
		/* 	     (ssize_t) (sizeof(struct simple_vertex) */
		/* 			* sr.vertexs.cap), */
		/* 	     sr.vertexs.items, */
		/* 	     GL_DYNAMIC_DRAW); */
#if 0
		static const struct glyph_attr
			simple_vertex_attr[SIMPLE_VERTEX_ATTR_TOTAL] = {
			[SIMPLE_VERTEX_ATTR_POS] = {
				.size = 2,
				.type = GL_FLOAT,
				.norm = GL_FALSE,
				.stride = sizeof(struct simple_vertex),
				.offset = offsetof(struct simple_vertex,
						   pos)
			},
			[SIMPLE_VERTEX_ATTR_COLOR] = {
				.size = 4,
				.type = GL_FLOAT,
				.norm = GL_FALSE,
				.stride = sizeof(struct simple_vertex),
				.offset = offsetof(struct simple_vertex,
						   color)
			},
			[SIMPLE_VERTEX_ATTR_UV] = {
				.size = 2,
				.type = GL_FLOAT,
				.norm = GL_FALSE,
				.stride = sizeof(struct simple_vertex),
				.offset = offsetof(struct simple_vertex,
						   uv)
			}
		};
		glyph_attr_declare(simple_vertex_attr,
				   SIMPLE_VERTEX_ATTR_TOTAL);
#else
		glEnableVertexAttribArray(0);
		glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE,
				      sizeof(struct simple_vertex),
				      (void *) offsetof(struct simple_vertex,
							pos));
		glEnableVertexAttribArray(1);
		glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE,
				      sizeof(struct simple_vertex),
				      (void *) offsetof(struct simple_vertex,
							color));
		glEnableVertexAttribArray(2);
		glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE,
				      sizeof(struct simple_vertex),
				      (void *) offsetof(struct simple_vertex,
							uv));
#endif
	}


	sr_compile_shaders(&sr);
	sr.progs[SIMPLE_SHADER_COLOR]
		= program_link_or_exit(sr.shaders.vert, sr.shaders.frag_color);
	sr.progs[SIMPLE_SHADER_IMAGE]
		= program_link_or_exit(sr.shaders.vert, sr.shaders.frag_image);
	sr.progs[SIMPLE_SHADER_RAINBOW]
		= program_link_or_exit(sr.shaders.vert,
				       sr.shaders.frag_rainbow);

	sr.cur_shader = SIMPLE_SHADER_COLOR;
	glUseProgram(sr.progs[sr.cur_shader]);

	sr_set_all_uniforms(&sr);
	return sr;
}

void
sr_set_all_uniforms(struct simple_render *sr)
{
	sr_set_uniform(sr, "resolution");
	sr_set_uniform(sr, "time");
	sr_set_uniform(sr, "camera");
}

int32_t
sr_set_uniform(struct simple_render *sr, const char *uniform_name)
{
	int32_t uni = get_uniform(sr->progs[sr->cur_shader], uniform_name);
	#if 0
	printf("%s: Got Uniform %s -> %d\n", __func__, uniform_name, uni);
	#endif
	HT_SET(sr->uniforms, uniform_name, strlen(uniform_name), uni);
	return uni;
}
int32_t
sr_get_uniform(const struct simple_render *sr, const char *uniform_name)
{
	int32_t uni = -1;
	HT_GET(sr->uniforms, uniform_name, strlen(uniform_name), uni);
	return uni;
}

void
sr_use(const struct simple_render *sr)
{
	glBindVertexArray(sr->vao);
	glBindBuffer(GL_ARRAY_BUFFER, sr->vbo);
	glUseProgram(sr->progs[sr->cur_shader]);
}

void
sr_set_shader(struct simple_render *sr, enum simple_shader shader)
{
	if ( sr->cur_shader == shader ) {
		return;
	}
	sr->cur_shader = shader;
	glUseProgram(sr->progs[sr->cur_shader]);
	sr_set_all_uniforms(sr);
}

void
sr_triangle(struct simple_render *sr,
	    struct vec2f p0, struct vec2f p1, struct vec2f p2,
	    struct vec4f c0, struct vec4f c1, struct vec4f c2,
	    struct vec2f uv0, struct vec2f uv1, struct vec2f uv2)
{
	struct simple_vertex sv0 = {p0, c0, uv0};
	struct simple_vertex sv1 = {p1, c1, uv1};
	struct simple_vertex sv2 = {p2, c2, uv2};
	DA_APPEND(sr->vertexs, sv0);
	DA_APPEND(sr->vertexs, sv1);
	DA_APPEND(sr->vertexs, sv2);
}

/* 2-3 */
/* |\| */
/* 0-1 */
void
sr_quad(struct simple_render *sr,
	struct vec2f p0, struct vec2f p1, struct vec2f p2, struct vec2f p3,
	struct vec4f c0, struct vec4f c1, struct vec4f c2, struct vec4f c3,
	struct vec2f uv0, struct vec2f uv1, struct vec2f uv2, struct vec2f uv3)
{
	sr_triangle(sr, p0, p1, p2, c0, c1, c2, uv0, uv1, uv2);
	sr_triangle(sr, p1, p2, p3, c1, c2, c3, uv1, uv2, uv3);
}

void
sr_image_rect(struct simple_render *sr, struct vec2f pos, struct vec2f size,
	      struct vec2f uv_pos, struct vec2f uv_size)
{
	struct vec4f color = vec4fs(0);
	sr_quad(sr,
		pos, vec2f_add(pos, vec2f(size.x, 0)),
		vec2f_add(pos, vec2f(0, size.y)), vec2f_add(pos, size),
		X4_ARGS(color),
		uv_pos, vec2f_add(uv_pos, vec2f(uv_size.x, 0)),
		vec2f_add(uv_pos,vec2f(0, uv_size.y)),
		vec2f_add(uv_pos, uv_size));
}

void
sr_solid_rect(struct simple_render *sr, struct vec2f pos, struct vec2f size,
	      struct vec4f color)
{
	struct vec2f uv = vec2fs(0);
	sr_quad(sr,
		pos, vec2f_add(pos, vec2f(size.x, 0)),
		vec2f_add(pos, vec2f(0, size.y)), vec2f_add(pos, size),
		X4_ARGS(color),
		X4_ARGS(uv));
}

void
sr_draw(const struct simple_render *sr)
{
	glBufferData(GL_ARRAY_BUFFER,
		     (ssize_t) (sizeof(struct simple_vertex)
				* sr->vertexs.cap),
		     sr->vertexs.items,
		     GL_DYNAMIC_DRAW);
	glDrawArrays(GL_TRIANGLES, 0, (int32_t) sr->vertexs.size);
}



void
sr_compile_shaders(struct simple_render *sr)
{
#define GL_VER "#version 330 core\n"
	sr->shaders.vert = shader_compile_source_or_exit(
		GL_VER
		"uniform vec2 resolution;\n"
		"uniform float time;\n"
		"uniform vec2 camera;\n"
		"layout(location = 0) in vec2 pos;\n"
		"layout(location = 1) in vec4 color;\n"
		"layout(location = 2) in vec2 uv;\n"
		"vec2 project_point(vec2 point)\n"
		"{\n"
		"  return (2.0 * (point - camera)) / resolution;\n"
		"}\n"
		"out vec4 out_color;\n"
		"out vec2 out_uv;\n"
		"void main() {\n"
		"	gl_Position = vec4(project_point(pos), 0, 1);\n"
		"	out_color = color;\n"
		"	out_uv = uv;\n"
		"}\n",
		GL_VERTEX_SHADER);
	sr->shaders.frag_color = shader_compile_source_or_exit(
		GL_VER
		"in vec4 out_color;\n"
		"void main() {\n"
		"	gl_FragColor = out_color;\n"
		"}\n"
		, GL_FRAGMENT_SHADER);
	sr->shaders.frag_image = shader_compile_source_or_exit(
		GL_VER
		"uniform sampler2D image;\n"
		"in vec2 out_uv;\n"
		"void main() {\n"
		"	gl_FragColor = texture(image, out_uv);\n"
		"}\n", GL_FRAGMENT_SHADER);
	sr->shaders.frag_rainbow = shader_compile_source_or_exit(
		GL_VER
		"uniform sampler2D font;\n"
		"uniform float time;\n"
		"uniform vec2 resolution;\n"
		"in vec2 out_uv;\n"
		"float map01(float x)\n"
		"{\n"
		"	return (x + 1) / 2;\n"
		"}\n"
		"vec3 hsl2rgb( in vec3 c )\n"
		"{\n"
		"	vec3 rgb = clamp( abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0,\n"
		"			  0.0, 1.0 );\n"
		"	return c.z + c.y * (rgb-0.5)*(1.0-abs(2.0*c.z-1.0));\n"
		"}\n"
		"void main() {\n"
		"	vec4 tc = texture(font, out_uv);\n"
		"	vec2 frag_uv = gl_FragCoord.xy / resolution;\n"
		"	vec4 rainbow = vec4(hsl2rgb(vec3((time + frag_uv.x + frag_uv.y), 0.5, 0.5)), 1.0);\n"
		"	gl_FragColor = tc.x * rainbow;\n"
		"}\n"
		, GL_FRAGMENT_SHADER);

#undef GL_VER
}


#endif /* defined(SIMPLE_RENDER_IMP) || defined(IMP) */

#endif
