#include "color32.h"
#include "error.h"
#include "gl.h"
#include "image8.h"
#include "renderer.h"
#include "shader.h"
#include "skip.h"
#include "types.h"
#include "window.h"
#include <gl/gl.h>
#include <string.h>
#define GSA_NOMAIN
#include "gsa.h"
#include "gsa_input.h"

struct gsa_sprite sprites[256];

struct sw_image8 *_gfx;
struct sw_window *_win;

static struct sw_shaderprogram program;

static u32 vbo, vao;

#define MAX_RENDER_VERTS 10000

struct render_vert {
	f32 x, y, tx, ty;
};

static i32 next_render_vert;
static struct render_vert render_verts[MAX_RENDER_VERTS * 4];
sw_color32 palette[GSA_PALETTE_SIZE];
static f32 palette_gl[1024];
static u32 tex;

#define GSA_LOOP_STACK_SIZE 16

static bool (*loop_stack[GSA_LOOP_STACK_SIZE])();
static i8 loop_stack_i;

int gsa_main(int argc, char *argv[]) {
	(void)argc;
	(void)argv;
	i32 max_components;
	u32 size;
	u8 *data, *frag_data, *vert_data;

	sw_log("Initialising GameSkunkAdvance v0.0");
	sw_init(argc, argv);

	sw_log("Grab data");
	data = sw_skip_get(sw_skip_self, "gfx.png", &size);
	_gfx = sw_image8_load_png_data(data, size);

	memset(loop_stack, 0, sizeof(loop_stack));
	loop_stack_i = -1;

	_win = sw_window_create(sw_vec2i(304, 176), "Game Skunk Advance v0.0");

	glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, &max_components);
	sw_log("GL_MAX_VERTEX_UNIFORM_COMPONENTS: %i", max_components);
	glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &max_components);
	sw_log("GL_NUM_PROGRAM_BINARY_FORMATS: %i", max_components);

	frag_data = sw_skip_get_string(sw_skip_self, "gsa_frag.glsl");
	vert_data = sw_skip_get_string(sw_skip_self, "gsa_vert.glsl");
	program = sw_shaderprogram_create(vert_data, 0, frag_data, 0, false);

	memcpy(palette, _gfx->palette, sizeof(_gfx->palette));
	glGenTextures(1, &tex);
	glBindTexture(GL_TEXTURE_2D, tex);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexImage2D(
	    GL_TEXTURE_2D,
	    0,
	    GL_R8,
	    4096,
	    4096,
	    0,
	    GL_RED,
	    GL_UNSIGNED_BYTE,
	    _gfx->_data
	);

	sw_log("setup gsa vao");
	glGenVertexArrays(1, &vao);
	glBindVertexArray(vao);

	sw_log("setup gsa vbo");
	glGenBuffers(1, &vbo);
	glBindBuffer(GL_ARRAY_BUFFER, vbo);
	glVertexAttribPointer(
	    glGetAttribLocation(program._program, "pos"),
	    2,
	    GL_FLOAT,
	    GL_FALSE,
	    sizeof(struct render_vert),
	    0
	);
	glVertexAttribPointer(
	    glGetAttribLocation(program._program, "tex_coord"),
	    2,
	    GL_FLOAT,
	    GL_FALSE,
	    sizeof(struct render_vert),
	    (void *)(2 * sizeof(float))
	);
	glEnableVertexAttribArray(glGetAttribLocation(program._program, "pos"));
	glEnableVertexAttribArray(glGetAttribLocation(program._program, "tex_coord")
	);
	glBindVertexArray(0);

	sw_log("gsa setup done");
	init();
	sw_window_add_renderer(_win, sw_renderer_create(0, 0, 0), _gsa_tick);
	sw_window_run(_win);

	return 0;
}

void gsa_copy_palette_to(sw_color32 *dst) {
	memcpy(dst, palette, sizeof(palette));
}

void gsa_copy_palette_from(sw_color32 *src) {
	memcpy(palette, src, sizeof(palette));
}

void gsa_run_loop(bool (*loopfun)()) {
	++loop_stack_i;
	if(loop_stack_i >= GSA_LOOP_STACK_SIZE) {
		sw_error("exceeded loop stack size");
	}
	loop_stack[loop_stack_i] = loopfun;
}

static void add_render_vert(f32 x, f32 y, f32 tx, f32 ty) {
	render_verts[next_render_vert].x = x;
	render_verts[next_render_vert].y = y;
	render_verts[next_render_vert].tx = tx;
	render_verts[next_render_vert].ty = ty;
	++next_render_vert;
}

static void rect(f32 x, f32 y, f32 w, f32 h, i16 tile, bool half) {
	f32 tx, ty, ts;
	f32 tilesize = half ? 8.f : 16.f;

	tx = ((tile % 256) * tilesize) / 4096.f;
	ty = ((tile / 256) * tilesize) / 4096.f;
	ts = 1.f / (4096.f / tilesize);

	if(next_render_vert >= MAX_RENDER_VERTS) {
		sw_error(
		    "trying to draw more than the current max of %i vertices in a "
		    "frame",
		    MAX_RENDER_VERTS
		);
	}

	add_render_vert(x, y, tx, ty);
	add_render_vert(x + w, y, tx + ts, ty);
	add_render_vert(x, y + h, tx, ty + ts);
	add_render_vert(x + w, y, tx + ts, ty);
	add_render_vert(x, y + h, tx, ty + ts);
	add_render_vert(x + w, y + h, tx + ts, ty + ts);
}

static void update_palette_gl() {
	int i;
	for(i = 0; i < 256; ++i) {
		palette_gl[i * 4 + 0] = sw_color32_get_rf(palette[i]);
		palette_gl[i * 4 + 1] = sw_color32_get_gf(palette[i]);
		palette_gl[i * 4 + 2] = sw_color32_get_bf(palette[i]);
		palette_gl[i * 4 + 3] = sw_color32_get_af(palette[i]);
	}
}

void _gsa_tick() {
	i32 i, x, y, startx, starty, endx, endy, tcmult, tilesize;

	_gsa_input_tick();

	if(loop_stack_i == -1) {
		tick();
	} else {
		if(!loop_stack[loop_stack_i]()) {
			--loop_stack_i;
		}
	}

	next_render_vert = 0;

	for(i = 0; i < MAX_TILEMAPS; ++i) {
		tcmult = maps[i].half_tile ? 2 : 1;
		tilesize = maps[i].half_tile ? 8 : 16;
		startx = maps[i].scrollx / tilesize;
		starty = maps[i].scrolly / tilesize;
		endx = i32_min(TILEMAP_MAX_SIZE, startx + 20 * tcmult);
		endy = i32_min(TILEMAP_MAX_SIZE, starty + 12 * tcmult);
		startx = i32_max(0, startx);
		starty = i32_max(0, starty);

		for(x = startx; x <= endx; ++x) {
			for(y = starty; y <= endy; ++y) {
				u16 tile = maps[i].tiles[x][y];
				if(tile) {
					rect(
					    x * tilesize - maps[i].scrollx,
					    y * tilesize - maps[i].scrolly,
					    tilesize,
					    tilesize,
					    tile,
					    maps[i].half_tile
					);
				}
			}
		}
	}

	for(i = 0; i < MAX_SPRITES; ++i) {
		if(sprites[i].tile > 0) {
			rect(sprites[i].x, sprites[i].y, 16, 16, sprites[i].tile, false);
		}
	}
	glClearColor(
	    sw_color32_get_rf(palette[0]),
	    sw_color32_get_gf(palette[0]),
	    sw_color32_get_bf(palette[0]),
	    sw_color32_get_af(palette[0])
	);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glBindVertexArray(vao);
	sw_shaderprogram_use(program);
	glBindTexture(GL_TEXTURE_2D, tex);

	/* TODO: only do when palette changed */
	update_palette_gl();
	glUniform4fv(
	    glGetUniformLocation(program._program, "palette"), 256, palette_gl
	);
	glBufferData(
	    GL_ARRAY_BUFFER,
	    sizeof(struct render_vert) * (next_render_vert),
	    render_verts,
	    GL_DYNAMIC_DRAW
	);
	glDrawArrays(GL_TRIANGLES, 0, next_render_vert);
	glBindVertexArray(0);
	/* glEnd(); */
}
