#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 ) {
		app.buf = buffer_mv_cur_right(app.buf);
	}
	if ( args.dir == DIR_BACKWARD ) {
		app.buf = buffer_mv_cur_left(app.buf);
	}
	return app;
}

struct app
keybind_delete_char(struct app app, union keybind_args args)
{
	if ( args.dir == DIR_FORWARD ) {
		app.buf = buffer_remove_char(app.buf, app.buf.cur);
	}
	if ( args.dir == DIR_BACKWARD ) {
		app.buf = buffer_remove_char(app.buf, --app.buf.cur);
	}
	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 ) {
		RET_UNWRAP2(index, err,
			    struct ret_size_t_err,
			    buffer_index_fw_word(app.buf));
		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 ) {
		RET_UNWRAP2(index, err,
			    struct ret_size_t_err,
			    buffer_index_bw_word(app.buf));
		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;
	}
	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: {
		RET_UNWRAP2(index, err,
			    struct ret_size_t_err,
			    buffer_index_bw_word(app.buf));
		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: {
		RET_UNWRAP2(index, err,
			    struct ret_size_t_err,
			    buffer_index_fw_word(app.buf));
		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;
	}
	RET_UNWRAP3(app.buf, n_rm, err,
		    struct ret_buffer_size_t_err,
		    buffer_remove_between(app.buf, start, end));
	if ( err != BUFFER_ERR_OK ) {
		return app;
	}
	app.buf.cur -= ( args.dir == DIR_BACKWARD ) * n_rm;
	return app;
}

struct app
keybind_mv_cur_line(struct app app, union keybind_args args)
{
	(void) args;
	enum buffer_err err;
	size_t index;
	size_t col = buffer_calc_cur_col(app.buf);

	switch ( args.dir ) {
	case DIR_FORWARD: {
		RET_UNWRAP2(index, err, struct ret_size_t_err,
			    buffer_index_fw_char(app.buf,
						 app.buf.cur, '\n'));
		index = ( err != BUFFER_ERR_OK ) * (app.buf.data.size - 1)
			+ ( err == BUFFER_ERR_OK ) * (index + 1);
	} break;
	case DIR_BACKWARD: {
		RET_UNWRAP2(index, err, struct ret_size_t_err,
			    buffer_index_bw_char(app.buf,
						 app.buf.cur, '\n'));
		RET_UNWRAP2(index, err, struct ret_size_t_err,
			    buffer_index_bw_char(app.buf,
						 index, '\n'));
		index = ( err == BUFFER_ERR_OK ) * (index + 1);
	} break;
	default: {
		fprintf(stderr, "Got invalid direction in %s: %d",
			__func__, args.dir);
		exit(EXIT_FAILURE);
	} break;
	}
	size_t c;
	RET_UNWRAP2(c, err, struct ret_size_t_err,
		    buffer_count_char_between(app.buf, '\n',
					      index, index + col));
	app.buf.cur = index + ( c == 0 ) * col;

	return app;
}

struct app
keybind_mv_cur_beg_line(struct app app, union keybind_args args)
{
	(void) args;
	size_t index;
	enum buffer_err err;
	RET_UNWRAP2(index, err, struct ret_size_t_err,
		    buffer_index_bw_char(app.buf, app.buf.cur - 1, '\n'));
	if ( err != BUFFER_ERR_OK ) {
		if ( err == BUFFER_ERR_NOT_FOUND ) {
			app.buf.cur = 0;
		}
		return app;
	}
	app.buf.cur = index + 1;
	return app;
}

struct app
keybind_mv_cur_end_line(struct app app, union keybind_args args)
{
	(void) args;
	size_t index;
	enum buffer_err err;
	RET_UNWRAP2(index, err, struct ret_size_t_err,
		    buffer_index_fw_char(app.buf, app.buf.cur + 1, '\n'));
	if ( err != BUFFER_ERR_OK ) {
		if ( err == BUFFER_ERR_NOT_FOUND ) {
			app.buf.cur = app.buf.data.size;
		}
		return app;
	}
	app.buf.cur = index;
	return app;
}

struct app
keybind_insert_newline(struct app app, union keybind_args args)
{
	(void) args;
	app.buf = buffer_insert_char(app.buf, app.buf.cur, '\n');
	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 - 1;
	} 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;
	}
	return app;
}

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

#endif
