#ifndef APP_H
#define APP_H

#include "da.h"
#include "font.h"

#include <SDL2/SDL.h>

#include "vec2.h"
#include "window.h"

#include "buffer.h"

struct app {
	struct window win;
	SDL_Renderer *rdr;
	struct font font;
	struct buffer buf;
	bool running;
	uint64_t target_fps;
	struct {
		uint8_t tab_size;
	} cfg;
	bool show_fps;
};

struct app app_create(const char *win_title);

void app_set_text_color(struct app app, uint32_t color);
uint32_t app_get_text_color(struct app app);

void app_render_cursor_in_pos(struct app app, struct vec2 pos, uint32_t color);
void app_render_cursor(struct app app, uint32_t color);
void app_render_char(struct app app, const char c, struct vec2 pos,
		     double scale);

void app_render_buffer(struct app app, struct vec2 pos,
		       uint32_t color, double scale);
void app_render_text(struct app app, const char *text, size_t text_size,
		     struct vec2 pos, uint32_t color, double scale);

struct vec2 app_calc_cur_pos(struct app app);

#if defined(APP_IMP) || defined(IMP)

#define UNHEX_COLOR(hex)			\
	(uint8_t) ((hex >> 24) & 0xFF),	\
	(uint8_t) ((hex >> 16) & 0xFF),	\
	(uint8_t) ((hex >> 8 ) & 0xFF),	\
	(uint8_t) ((hex >> 0 ) & 0xFF)

#include "sc.h"

struct app
app_create(const char *win_title)
{
	SCE(SDL_Init(SDL_INIT_VIDEO));

	struct app app = {
		.win = window_create(win_title),
		.rdr = NULL,
		.running = true,
		.buf = buffer_create(),
		.cfg = {
			.tab_size = 8
		},
		.target_fps = 120,
	};

	app.rdr = SDL_CreateRenderer(app.win.ptr, -1, SDL_RENDERER_ACCELERATED);
	if ( app.rdr == NULL ) {
		printf("Using software acceleration: %s\n", SDL_GetError());
		app.rdr = SDL_CreateRenderer(app.win.ptr, -1,
					     SDL_RENDERER_SOFTWARE);
	}
	SCP(app.rdr);


	return app;
}

void
app_render_char(struct app app, const char c, struct vec2 pos, double scale)
{
	SDL_Rect dst = {
		.x = (int32_t) pos.x,
		.y = (int32_t) pos.y,
		.w = (int32_t)(app.font.ch_w * scale),
		.h = (int32_t)(app.font.ch_h * scale)
	};

	SCE(SDL_RenderCopy(app.rdr, app.font.tex,
			   app.font.glyphs + (c - 32),
			   &dst));
}

void
app_set_text_color(struct app app, uint32_t color) {
	uint8_t r = (uint8_t) ((color >> 24) & 0xFF);
	uint8_t g = (uint8_t) ((color >> 16) & 0xFF);
	uint8_t b = (uint8_t) ((color >> 8 ) & 0xFF);
	uint8_t a = (uint8_t) ((color >> 0 ) & 0xFF);

	SCE(SDL_SetTextureColorMod(app.font.tex, r, g, b));
	SCE(SDL_SetTextureAlphaMod(app.font.tex, a));
}

uint32_t
app_get_text_color(struct app app) {
	uint8_t r = 0;
	uint8_t g = 0;
	uint8_t b = 0;
	uint8_t a = 0;

	SCE(SDL_GetTextureColorMod(app.font.tex, &r, &g, &b));
	SCE(SDL_GetTextureAlphaMod(app.font.tex, &a));

	return (r << 24) | (g << 16) | (b << 8) | (a << 0);
}


#define BRANCHLESS_IF(cond, when_true, when_false) \
	(((cond)) * (when_true) + (!(cond)) * (when_false))

void
app_render_buffer(struct app app, struct vec2 pos,
		  uint32_t color, double scale)
{
	app_set_text_color(app, color);

	struct buffer buf = app.buf;
	DA_DEF_STRUCT_ITEM(char, da);
	DA_ASSIGN(da, buf.data);

	struct vec2 pen = pos;
	size_t i = buf.render_start;
	char c;
	for ( ; i < da.size; ++i ) {
		c = da.items[i];
		/* bool cond = (pen.x > app.win.w); */
		/* pen.x = BRANCHLESS_IF(cond, pos.x, pen.x); */
		/* pen.y += BRANCHLESS_IF(cond, */
		/* 		       (double) (app.font.ch_h * scale), 0); */
		switch ( c ) {
		case '\n': {
			if ( i == app.buf.cur ) {
				app_render_cursor_in_pos(app, pen, 0xFF00FFFF);
			}
			pen.x = pos.x;
			pen.y += (double) (app.font.ch_h * scale);
			continue;
		} break;
		case '\t': {
			if ( i == app.buf.cur ) {
				app_render_cursor_in_pos(app, pen, 0xFF00FFFF);
			}
			pen.x += (double) ((app.font.ch_w * scale)
					   * app.cfg.tab_size);
			continue;
		} break;
		}
		app_render_char(app, c, pen, scale);
		if ( i == app.buf.cur ) {
			app_render_cursor_in_pos(app, pen, 0xFF00FFFF);
		}
		pen.x += (double) (app.font.ch_w * scale);
	}
	if ( i == app.buf.cur ) {
		app_render_cursor_in_pos(app, pen, 0xFF00FFFF);
	}
}

void
app_render_text(struct app app, const char *text, size_t text_size,
		struct vec2 pos, uint32_t color, double scale)
{
	app_set_text_color(app, color);

	struct vec2 pen = pos;
	size_t i = 0;
	char c;
	for ( ; i < text_size; ++i ) {
		c = text[i];
		switch ( c ) {
		case '\n': {
			pen.x = pos.x;
			pen.y += (double) (app.font.ch_h * scale);
			continue;
		} break;
		case '\t': {
			pen.x += (double) ((app.font.ch_w * scale)
					   * app.cfg.tab_size);
			continue;
		} break;
		}
		app_render_char(app, c, pen, scale);
		pen.x += (double) (app.font.ch_w * scale);
	}
}

void
app_render_cursor_in_pos(struct app app, struct vec2 pos, uint32_t color)
{
	SDL_Rect rect = {
		.x = (int32_t) pos.x,
		.y = (int32_t) pos.y,
		.w = (int32_t) (app.font.ch_w * app.font.scale),
		.h = (int32_t) (app.font.ch_h * app.font.scale)
	};

	SCE(SDL_SetRenderDrawColor(app.rdr, UNHEX_COLOR(color)));
	SCE(SDL_RenderFillRect(app.rdr, &rect));

	if ( app.buf.cur < app.buf.data.size ) {
		char c = app.buf.data.items[app.buf.cur];
		if ( c == '\n' ) return ;

		uint32_t old_color = app_get_text_color(app);
		app_set_text_color(app, ((~color) | 0xFF));
		app_render_char(app, c, pos, app.font.scale);
		app_set_text_color(app, old_color);
	}
}

void
app_render_cursor(struct app app, uint32_t color)
{
	struct vec2 pos = {
		.x = ((double)(app.buf.cur) * (app.font.ch_w * app.font.scale)),
		.y = ((double)(app.buf.cur) * (app.font.ch_h * app.font.scale))
	};

	app_render_cursor_in_pos(app, pos, color);
}

struct vec2
app_calc_cur_pos(struct app app)
{
	size_t col = 0;
	size_t row = buffer_calc_cur_row(app.buf);
	enum buffer_err err;
	/* RET_UNWRAP2(col, row, */
	/* 	    struct ret_size_t_size_t, */
	/* 	    buffer_calc_cur_pos(app.buf)); */
	size_t start_line;
	RET_UNWRAP2(start_line, err, struct ret_size_t_err,
		    buffer_index_bw_char(app.buf, app.buf.cur, '\n'));
	col = (app.buf.cur - start_line) - 1;
	if ( err == BUFFER_ERR_NOT_FOUND ) {
		col = app.buf.cur;
	}

	size_t tabs_n = 0;
	RET_UNWRAP2(tabs_n, err,
		    struct ret_size_t_err,
		    buffer_count_char_between(app.buf, '\t',
					      start_line, app.buf.cur));
	col += ( tabs_n > 0 ) * ((tabs_n * app.cfg.tab_size) - tabs_n);

	double row_px = (((double)row)
			  * (app.font.ch_h * app.font.scale));
	double col_px = (((double) col) *
			 (app.font.ch_w * app.font.scale));

	return vec2(col_px, row_px);
}


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

#endif
