#ifndef BUFFER_H
#define BUFFER_H



struct buffer {
	DA_DEF_STRUCT_ITEM(char, data);
	size_t render_start;
	size_t cur;
	const char *filepath;
	struct {
		struct vec2 pos;
		struct vec2 vel;
	} cam;
};

enum buffer_err {
	BUFFER_ERR_OK = 0,
	BUFFER_ERR_EMPTY,
	BUFFER_ERR_NOT_FOUND,
	BUFFER_ERR_INVALID_CUR_POS,
	BUFFER_ERR_FAIL_READ_FILE,
	BUFFER_ERR_START_GT_END,
	BUFFER_ERR_INVALID_END_SIZE,
	BUFFER_ERR_FAIL_GET_REALPATH,
	BUFFER_ERR_FAIL_SAVE,
	BUFFER_ERR_START_EQ_END,
};

struct ret_buffer_err {
	struct buffer f1;
	enum buffer_err f2;
};

struct ret_size_t_err {
	size_t f1;
	enum buffer_err f2;
};

struct ret_buffer_size_t_err {
	struct buffer f1;
	size_t f2;
	enum buffer_err f3;
};

struct ret_size_t_size_t {
	size_t f1;
	size_t f2;
};

struct buffer buffer_create(void);
void buffer_destroy(struct buffer buf);

struct ret_buffer_err buffer_load_from_file(struct buffer buf,
					    const char *filepath);
enum buffer_err buffer_save(struct buffer buf);
enum buffer_err buffer_save_to_file(struct buffer buf, const char *filepath);

struct buffer buffer_mv_cur_left(struct buffer buf);
struct buffer buffer_mv_cur_right(struct buffer buf);
struct buffer buffer_mv_cur_up(struct buffer buf);
struct buffer buffer_mv_cur_down(struct buffer buf);

struct buffer buffer_insert_char(struct buffer buf, size_t index, char c);
struct buffer buffer_remove_char_mv_cur(struct buffer buf, size_t index);
struct buffer buffer_remove_char(struct buffer buf, size_t index);
struct ret_buffer_size_t_err buffer_remove_between(struct buffer buf,
						   size_t start, size_t end);

struct ret_size_t_err buffer_index_bw_word(struct buffer buf);
struct ret_size_t_err buffer_index_fw_word(struct buffer buf);

struct ret_size_t_err buffer_index_bw_char(struct buffer buf,
					   size_t start, char c);
struct ret_size_t_err buffer_index_fw_char(struct buffer buf,
					   size_t start, char c);

struct ret_size_t_err buffer_count_char_between(struct buffer buf, char c,
						size_t start, size_t end);

size_t buffer_calc_cur_col(struct buffer buf);
size_t buffer_calc_cur_row(struct buffer buf);
struct ret_size_t_size_t buffer_calc_cur_pos(struct buffer buf);

#if defined(BUFFER_IMP) || defined(IMP)

#include <stdlib.h>
#include <stdbool.h>
#include <limits.h>
#include <ctype.h>

#include "vec2.h"
#include "file.h"
#include "da.h"
#include "unwrap.h"

struct buffer
buffer_create(void)
{
	struct buffer buf = {0};
	DA_CREATE(buf.data, sizeof(char));

	return buf;
}

void
buffer_destroy(struct buffer buf)
{
	free(buf.data.items);
	if ( buf.filepath != NULL ) {
		free((char *) buf.filepath);
	}
}

struct ret_buffer_err
buffer_load_from_file(struct buffer buf, const char *filepath)
{
	void *ptr;
	size_t file_size;
	enum file_err err;
	char *fp = realpath(filepath, NULL);
	if ( fp == NULL ) {
		return (struct ret_buffer_err) {
			.f1 = buf,
			.f2 = BUFFER_ERR_FAIL_GET_REALPATH
		};
	}

	RET_UNWRAP3(ptr, file_size, err,
		    struct ret_void_p_err, file_read_all(fp));

	if ( err != FILE_ERR_OK ) {
		if ( err == FILE_ERR_EMPTY ) {
			buf.filepath = fp;
			goto exit;
		}
		free(fp);
		return (struct ret_buffer_err) {
			.f1 = buf,
			.f2 = BUFFER_ERR_FAIL_READ_FILE
		};
	}

	buffer_destroy(buf);

	buf.filepath = fp;
	buf.data.items = ptr;
	buf.data.size = file_size - 1;
	buf.data.cap = file_size;

exit: ;
	return (struct ret_buffer_err) {
		.f1 = buf,
		.f2 = BUFFER_ERR_OK
	};
}

enum buffer_err
buffer_save(struct buffer buf)
{
	return buffer_save_to_file(buf, buf.filepath);
}

enum buffer_err
buffer_save_to_file(struct buffer buf, const char *filepath)
{
	enum file_err err = file_save(filepath, buf.data.items, buf.data.size);
	if ( err != FILE_ERR_OK ) {
		return BUFFER_ERR_FAIL_SAVE;
	}

	return BUFFER_ERR_OK;
}


struct buffer
buffer_mv_cur_left(struct buffer buf)
{
	if ( buf.cur == 0 ) {
		return buf;
	}
	--buf.cur;
	return buf;
}

struct buffer
buffer_mv_cur_right(struct buffer buf)
{
	if ( buf.cur == buf.data.size ) {
		return buf;
	}
	++buf.cur;
	return buf;
}

struct buffer
buffer_mv_cur_up(struct buffer buf)
{
	return buf;
}

struct buffer
buffer_mv_cur_down(struct buffer buf)
{
	return buf;
}


struct buffer
buffer_insert_char(struct buffer buf, size_t index, char c)
{
	if ( index > buf.data.size ) {
		DA_APPEND(buf.data, c);
		++buf.cur;
		return buf;
	}

	DA_INSERT(buf.data, c, index);
	++buf.cur;
	return buf;

}

struct buffer
buffer_remove_char_mv_cur(struct buffer buf, size_t index)
{
	if ( buf.data.size == 0 ) {
		return buf;
	}

	if ( index > buf.data.size ) {
		return buf;
	}

	if ( buf.data.size == index ) {
		buf.data.items[--buf.data.size] = 0;
		--buf.cur;
		return buf;
	}

	DA_DELETE(buf.data, index);
	--buf.cur;

	return buf;
}

struct buffer
buffer_remove_char(struct buffer buf, size_t index)
{
	if ( buf.data.size == 0 ) {
		return buf;
	}

	if ( index > buf.data.size ) {
		return buf;
	}

	if ( buf.data.size == index ) {
		buf.data.items[--buf.data.size] = 0;
		return buf;
	}

	DA_DELETE(buf.data, index);

	return buf;
}

struct ret_buffer_size_t_err
buffer_remove_between(struct buffer buf, size_t start, size_t end)
{
	enum buffer_err err = BUFFER_ERR_OK;
	if ( buf.data.size == 0 ) {
		err = BUFFER_ERR_EMPTY;
		goto err;
	}

	if ( start == end ) {
		return (struct ret_buffer_size_t_err) {
			.f1 = buffer_remove_char(buf, start),
			.f2 = 1,
			.f3 = BUFFER_ERR_OK,
		};
	}

	if ( start > end ) {
		err = BUFFER_ERR_START_GT_END;
		goto err;
	}

	DA_DEF_STRUCT_ITEM(char, da);
	DA_ASSIGN(da, buf.data);

	/* There is no need to check start > buf.data.size, because */
	/* we know that start < end */
	if ( end > da.size ) {
		err = BUFFER_ERR_INVALID_END_SIZE;
		goto err;
	}

	size_t nrm = end - start;
	memmove(da.items+start, da.items+end, da.size - nrm);
	da.size -= nrm;
	memset(da.items+da.size, 0, da.cap - da.size);

	DA_ASSIGN(buf.data, da);

	return (struct ret_buffer_size_t_err) {
		.f1 = buf,
		.f2 = nrm,
		.f3 = BUFFER_ERR_OK,
	};

err: ;
	return (struct ret_buffer_size_t_err) {
		.f1 = buf,
		.f2 = 0,
		.f3 = err
	};
}


struct ret_size_t_err
buffer_index_bw_word(struct buffer buf)
{
	enum buffer_err err = BUFFER_ERR_OK;
	if ( buf.data.size == 0 ) {
		err = BUFFER_ERR_EMPTY;
		goto err;
	}

	if ( buf.cur > buf.data.size ) {
		err = BUFFER_ERR_INVALID_CUR_POS;
		goto err;
	}

	bool found_letter = false;
	size_t i = buf.cur - 1;
	for ( ; i > 0; --i) {
		char c = buf.data.items[i];
		if ( isalnum(c) ) {
			found_letter = true;
		} else if ( found_letter ) {
			++i;
			goto exit;
		}
	}
	if ( i == 0 ) {
		char c = buf.data.items[i];
		if ( isalnum(c) ) {
			found_letter = true;
		} else if ( found_letter ) {
			++i;
			goto exit;
		}
	}

exit: ;
	return (struct ret_size_t_err) {
		.f1 = i,
		.f2 = BUFFER_ERR_OK,
	};

err: ;
	return (struct ret_size_t_err) {
		.f1 = 0,
		.f2 = err,
	};
}

struct ret_size_t_err
buffer_index_fw_word(struct buffer buf)
{
	enum buffer_err err = BUFFER_ERR_OK;
	if ( buf.data.size == 0 ) {
		err = BUFFER_ERR_EMPTY;
		goto err;
	}

	if ( buf.cur > buf.data.size ) {
		err = BUFFER_ERR_INVALID_CUR_POS;
		goto err;
	}
	bool found_letter = false;
	size_t i = buf.cur;
	for ( ; i < buf.data.size; ++i) {
		char c = buf.data.items[i];
		if ( isalnum(c) ) {
			found_letter = true;
		} else if ( found_letter ) {
			goto exit;
		}
	}
	if ( i == 0 ) {
		char c = buf.data.items[i];
		if ( isalnum(c) ) {
			found_letter = true;
		} else if ( found_letter ) {
			goto exit;
		}
	}

exit: ;
	return (struct ret_size_t_err) {
		.f1 = i,
		.f2 = BUFFER_ERR_OK,
	};

err: ;
	return (struct ret_size_t_err) {
		.f1 = 0,
		.f2 = err,
	};
}

struct ret_size_t_err
buffer_index_bw_char(struct buffer buf, size_t start, char c)
{
	enum buffer_err err;
	if ( buf.data.size == 0 ) {
		err = BUFFER_ERR_EMPTY;
		goto err;
	}

	if ( buf.cur > buf.data.size ) {
		err = BUFFER_ERR_INVALID_CUR_POS;
		goto err;
	}

	const char *str = buf.data.items;

	bool cond = false;
	/* TODO: Find a way to not use ssize_t buf size_t */
	ssize_t closest = -1;
	for ( ssize_t i = 0; i < (ssize_t) start; ++i ) {
		cond = (str[i] == c);
		closest = ( cond ) * i + ( ! cond ) * closest;
	}
	if ( closest == -1 ) {
		err = BUFFER_ERR_NOT_FOUND;
		goto err;
	}

	return (struct ret_size_t_err) {
		.f1 = (size_t) closest,
		.f2 = BUFFER_ERR_OK,
	};

err: ;
	return (struct ret_size_t_err) {
		.f1 = 0,
		.f2 = err,
	};
}

struct ret_size_t_err
buffer_index_fw_char(struct buffer buf, size_t start, char c)
{
	enum buffer_err err;
	if ( buf.data.size == 0 ) {
		err = BUFFER_ERR_EMPTY;
		goto err;
	}

	if ( buf.cur > buf.data.size ) {
		err = BUFFER_ERR_INVALID_CUR_POS;
		goto err;
	}

	const char *str = buf.data.items;

	for ( size_t i = start; i < buf.data.size; ++i ) {
		if ( str[i] == c ) {
			return (struct ret_size_t_err) {
				.f1 = i,
				.f2 = BUFFER_ERR_OK,
			};
		}
	}
	err = BUFFER_ERR_NOT_FOUND;

err: ;
	return (struct ret_size_t_err) {
		.f1 = 0,
		.f2 = err,
	};
}

struct ret_size_t_err
buffer_count_char_between(struct buffer buf, char c, size_t start, size_t end)
{
	enum buffer_err err = BUFFER_ERR_OK;
	if ( buf.data.size == 0 ) {
		err = BUFFER_ERR_EMPTY;
		goto err;
	}

	if ( start == end ) {
		err = BUFFER_ERR_START_EQ_END;
		goto err;
	}

	if ( start > end ) {
		err = BUFFER_ERR_START_GT_END;
		goto err;
	}

	DA_DEF_STRUCT_ITEM(char, da);
	DA_ASSIGN(da, buf.data);

	/* There is no need to check start > buf.data.size, because */
	/* we know that start < end */
	if ( end > da.size ) {
		err = BUFFER_ERR_INVALID_END_SIZE;
		goto err;
	}

	size_t count = 0;

	for ( size_t i = start; i < end; ++i ) {
		count += ( da.items[i] == c );
	}

	return (struct ret_size_t_err) {
		.f1 = count,
		.f2 = BUFFER_ERR_OK,
	};

err: ;
	return (struct ret_size_t_err) {
		.f1 = 0,
		.f2 = err
	};
}

size_t
buffer_calc_cur_col(struct buffer buf)
{
	/* DA_DEF_STRUCT_ITEM(char, da); */
	/* DA_ASSIGN(da, buf.data); */

	/* size_t start_line = 0; */
	/* for ( size_t i = buf.cur; i > 0; --i ) { */
	/* 	if ( da.items[i] == '\n' ) { */
	/* 		start_line = i; */
	/* 		break; */
	/* 	} */
	/* } */

	size_t col;
	enum buffer_err err;
	RET_UNWRAP2(col, err, struct ret_size_t_err,
		    buffer_index_bw_char(buf, buf.cur, '\n'));
	col = (buf.cur - col) - 1;
	if ( err == BUFFER_ERR_NOT_FOUND ) {
		col = buf.cur;
	}

	return col;
}

size_t
buffer_calc_cur_row(struct buffer buf)
{
	size_t row = 0;
	enum buffer_err err;
	RET_UNWRAP2(row, err,
		    struct ret_size_t_err,
		    buffer_count_char_between(buf, '\n', 0, buf.cur));
	(void) err;

	return row;
}

struct ret_size_t_size_t
buffer_calc_cur_pos(struct buffer buf)
{
	size_t col = buffer_calc_cur_col(buf);
	size_t row = buffer_calc_cur_row(buf);

	return (struct ret_size_t_size_t) {
		.f1 = col,
		.f2 = row
	};
}

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

#endif
