#ifndef KEYBIND_H
#define KEYBIND_H

#include <stdint.h>

#include "app.h"

enum direction {
	DIR_FORWARD,
	DIR_BACKWARD,
};

union keybind_args {
	enum direction dir;
	void *ptr;
};

typedef struct app (*keybind_func)(struct app, union keybind_args);

struct bind {
	const SDL_Keymod mod;
	keybind_func func;
	union keybind_args args;
};

#define BINDS_SIZE 10
struct keybinds {
	uint8_t size;
	const struct bind binds[BINDS_SIZE];
};

struct app keybind_save_buffer(struct app app, union keybind_args args);

struct app keybind_mv_cur_char(struct app app, union keybind_args args);
struct app keybind_forward_char(struct app app, union keybind_args args);
struct app keybind_delete_char(struct app app, union keybind_args args);

struct app keybind_mv_cur_word(struct app app, union keybind_args args);
struct app keybind_delete_word(struct app app, union keybind_args args);

struct app keybind_mv_cur_line(struct app app, union keybind_args args);
struct app keybind_mv_cur_beg_line(struct app app, union keybind_args args);
struct app keybind_mv_cur_end_line(struct app app, union keybind_args args);

struct app keybind_insert_newline(struct app app, union keybind_args args);

struct app keybind_toggle_fps(struct app app, union keybind_args args);

struct app keybind_mv_cur_end_buffer(struct app app, union keybind_args args);

#if defined(KEYBIND_IMP) || defined(IMP)

struct app
keybind_save_buffer(struct app app, union keybind_args args)
{
	(void) args;
	/* buffer_save(app.buf); */
	return app;
}

struct app
keybind_mv_cur_char(struct app app, union keybind_args args)
{
	if ( args.dir == DIR_FORWARD ) {
		buffer_mv_cur_right(&app.buf);
	}
	if ( args.dir == DIR_BACKWARD ) {
		buffer_mv_cur_left(&app.buf);
	}
	app.buf.high_col = buffer_calc_cur_col(&app.buf);
	return app;
}

struct app
keybind_delete_char(struct app app, union keybind_args args)
{
	if ( args.dir == DIR_FORWARD ) {
		buffer_remove_char(&app.buf, app.buf.cur);
	}
	if ( args.dir == DIR_BACKWARD ) {
		if ( app.buf.cur == 0 ) {
			return app;
		}
		buffer_remove_char(&app.buf, --app.buf.cur);
	}
	app.buf.high_col = buffer_calc_cur_col(&app.buf);
	return app;
}

struct app
keybind_mv_cur_word(struct app app, union keybind_args args)
{
	size_t index;
	enum buffer_err err;
	if ( args.dir == DIR_FORWARD ) {
		index = buffer_index_fw_word(&app.buf, &err);
		if ( err != BUFFER_ERR_OK ) {
			if ( err == BUFFER_ERR_INVALID_CUR_POS ) {
				app.buf.cur = app.buf.data.size;
			}
			return app;
		}
		app.buf.cur = index;
	}
	if ( args.dir == DIR_BACKWARD ) {
		index = buffer_index_bw_word(&app.buf, &err);
		if ( err != BUFFER_ERR_OK ) {
			if ( err == BUFFER_ERR_INVALID_CUR_POS ) {
				app.buf.cur = app.buf.data.size;
			}
			return app;
		}
		app.buf.cur = index;
	}
	app.buf.high_col = buffer_calc_cur_col(&app.buf);
	return app;
}


struct app
keybind_delete_word(struct app app, union keybind_args args)
{
	size_t index, n_rm;
	size_t start, end ;
	enum buffer_err err;
	switch ( args.dir ) {
	case DIR_BACKWARD: {
		index = buffer_index_bw_word(&app.buf, &err);
		if ( err != BUFFER_ERR_OK ) {
			if ( err == BUFFER_ERR_INVALID_CUR_POS ) {
				app.buf.cur = app.buf.data.size;
			}
			return app;
		}
		start = index;
		end = app.buf.cur;
	} break;
	case DIR_FORWARD: {
		index = buffer_index_fw_word(&app.buf, &err);
		if ( err != BUFFER_ERR_OK ) {
			if ( err == BUFFER_ERR_INVALID_CUR_POS ) {
				app.buf.cur = app.buf.data.size;
			}
			return app;
		}
		start = app.buf.cur;
		end = index;
	} break;
	default: {
		fprintf(stderr, "Got invalid direction in %s: %d",
			__func__, args.dir);
		exit(EXIT_FAILURE);
	} break;
	}
	n_rm = buffer_remove_between(&app.buf, start, end, &err);
	if ( err != BUFFER_ERR_OK ) {
		return app;
	}
	app.buf.cur -= ( args.dir == DIR_BACKWARD ) * n_rm;
	app.buf.high_col = buffer_calc_cur_col(&app.buf);
	return app;
}

struct app
keybind_mv_cur_line(struct app app, union keybind_args args)
{
	(void) args;
	switch ( args.dir ) {
	case DIR_FORWARD: {
		buffer_mv_cur_down(&app.buf);
	} break;
	case DIR_BACKWARD: {
		buffer_mv_cur_up(&app.buf);
	} break;
	default: {
		fprintf(stderr, "Got invalid direction in %s: %d",
			__func__, args.dir);
		exit(EXIT_FAILURE);
	} break;
	}
	return app;
}

struct app
keybind_mv_cur_beg_line(struct app app, union keybind_args args)
{
	(void) args;
	struct buffer_line l = buffer_find_line(&app.buf, app.buf.cur-1);
	app.buf.cur = l.start;
	app.buf.high_col = buffer_calc_cur_col(&app.buf);
	return app;
}

struct app
keybind_mv_cur_end_line(struct app app, union keybind_args args)
{
	(void) args;
	struct buffer_line l = buffer_find_line(&app.buf, app.buf.cur+1);
	app.buf.cur = l.end;
	app.buf.high_col = buffer_calc_cur_col(&app.buf);
	return app;
}

struct app
keybind_insert_newline(struct app app, union keybind_args args)
{
	(void) args;
	buffer_insert_char(&app.buf, '\n', app.buf.cur);
	app.buf.high_col = buffer_calc_cur_col(&app.buf);
	return app;
}

struct app
keybind_toggle_fps(struct app app, union keybind_args args)
{
	(void) args;
	app.show_fps = ! app.show_fps;
	return app;
}

struct app
keybind_mv_cur_end_buffer(struct app app, union keybind_args args)
{
	switch ( args.dir ) {
	case DIR_FORWARD: {
		app.buf.cur = app.buf.data.size;
	} break;
	case DIR_BACKWARD: {
		app.buf.cur = 0;
	} break;
	default: {
		fprintf(stderr, "Got invalid direction in %s: %d",
			__func__, args.dir);
		exit(EXIT_FAILURE);
	} break;
	}
	app.buf.high_col = buffer_calc_cur_col(&app.buf);
	return app;
}

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

#endif
