#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <limits.h>

#include "match.h"
#include "tty.h"
#include "choices.h"
#include "options.h"

#include "../config.h"

options_t options;

#define SEARCH_SIZE_MAX 4096
static char search[SEARCH_SIZE_MAX + 1] = {0};

static void clear(tty_t *tty, options_t *options) {
	tty_setcol(tty, 0);
	size_t line = 0;
	while (line++ < options->num_lines) {
		tty_newline(tty);
	}
	tty_clearline(tty);
	tty_moveup(tty, line - 1);
	tty_flush(tty);
}

static void draw_match(tty_t *tty, const char *choice, int selected, options_t *options) {
	int n = strlen(search);
	size_t positions[n + 1];
	for (int i = 0; i < n + 1; i++)
		positions[i] = -1;

	double score = match_positions(search, choice, &positions[0]);

	size_t maxwidth = tty_getwidth(tty);

	if (options->show_scores)
		tty_printf(tty, "(%5.2f) ", score);

	if (selected)
		tty_setinvert(tty);

	for (size_t i = 0, p = 0; choice[i] != '\0'; i++) {
		if (i + 1 < maxwidth) {
			if (positions[p] == i) {
				tty_setfg(tty, TTY_COLOR_HIGHLIGHT);
				p++;
			} else {
				tty_setfg(tty, TTY_COLOR_NORMAL);
			}
			tty_printf(tty, "%c", choice[i]);
		} else {
			tty_printf(tty, "$");
			break;
		}
	}
	tty_setnormal(tty);
}

static void draw(tty_t *tty, choices_t *choices, options_t *options) {
	unsigned int num_lines = options->num_lines;
	size_t start = 0;
	size_t current_selection = choices->selection;
	if (current_selection + options->scrolloff >= num_lines) {
		start = current_selection + options->scrolloff - num_lines + 1;
		if (start + num_lines >= choices_available(choices)) {
			start = choices_available(choices) - num_lines;
		}
	}
	tty_setcol(tty, 0);
	tty_printf(tty, "%s%s", options->prompt, search);
	tty_clearline(tty);
	for (size_t i = start; i < start + num_lines; i++) {
		tty_printf(tty, "\n");
		tty_clearline(tty);
		const char *choice = choices_get(choices, i);
		if (choice) {
			draw_match(tty, choice, i == choices->selection, options);
		}
	}
	tty_moveup(tty, num_lines);
	tty_setcol(tty, strlen(options->prompt) + strlen(search));
	tty_flush(tty);
}

static void emit(choices_t *choices) {
	const char *selection = choices_get(choices, choices->selection);
	if (selection) {
		/* output the selected result */
		printf("%s\n", selection);
	} else {
		/* No match, output the query instead */
		printf("%s\n", search);
	}
}

#define KEY_CTRL(key) ((key) - ('@'))
#define KEY_DEL 127
#define KEY_ESC 27

static void run(tty_t *tty, choices_t *choices, options_t *options) {
	choices_search(choices, search);
	char ch;
	do {
		draw(tty, choices, options);
		ch = tty_getchar(tty);
		size_t search_size = strlen(search);
		if (isprint(ch)) {
			if (search_size < SEARCH_SIZE_MAX) {
				search[search_size++] = ch;
				search[search_size] = '\0';
				choices_search(choices, search);
			}
		} else if (ch == KEY_DEL || ch == KEY_CTRL('H')) { /* DEL || Backspace (C-H) */
			if (search_size)
				search[--search_size] = '\0';
			choices_search(choices, search);
		} else if (ch == KEY_CTRL('U')) { /* C-U */
			search_size = 0;
			search[0] = '\0';
			choices_search(choices, search);
		} else if (ch == KEY_CTRL('W')) { /* C-W */
			if (search_size)
				search[--search_size] = '\0';
			while (search_size && !isspace(search[--search_size]))
				search[search_size] = '\0';
			choices_search(choices, search);
		} else if (ch == KEY_CTRL('N')) { /* C-N */
			choices_next(choices);
		} else if (ch == KEY_CTRL('P')) { /* C-P */
			choices_prev(choices);
		} else if (ch == KEY_CTRL('I')) { /* TAB (C-I) */
			strncpy(search, choices_get(choices, choices->selection), SEARCH_SIZE_MAX);
			choices_search(choices, search);
		} else if (ch == KEY_CTRL('C') || ch == KEY_CTRL('D')) { /* ^C || ^D */
			clear(tty, options);
			tty_close(tty);
			exit(EXIT_FAILURE);
		} else if (ch == KEY_CTRL('M')) { /* CR */
			clear(tty, options);

			/* ttyout should be flushed before outputting on stdout */
			tty_close(tty);

			emit(choices);

			/* Return to eventually exit successfully */
			return;
		} else if (ch == KEY_ESC) { /* ESC */
			ch = tty_getchar(tty);
			if (ch == '[' || ch == 'O') {
				ch = tty_getchar(tty);
				if (ch == 'A') { /* UP ARROW */
					choices_prev(choices);
				} else if (ch == 'B') { /* DOWN ARROW */
					choices_next(choices);
				}
			}
		}
	} while (1);
}

int main(int argc, char *argv[]) {
	options_parse(&options, argc, argv);

	choices_t choices;
	choices_init(&choices);
	choices_fread(&choices, stdin);

	if (options.benchmark) {
		if (!options.filter) {
			fprintf(stderr, "Must specify -e/--show-matches with --benchmark\n");
			exit(EXIT_FAILURE);
		}
		for (int i = 0; i < options.benchmark; i++)
			choices_search(&choices, options.filter);
	} else if (options.filter) {
		choices_search(&choices, options.filter);
		for (size_t i = 0; i < choices_available(&choices); i++) {
			if (options.show_scores)
				printf("%f\t", choices_getscore(&choices, i));
			printf("%s\n", choices_get(&choices, i));
		}
	} else {
		/* interactive */
		tty_t tty;
		tty_init(&tty, options.tty_filename);

		if (options.num_lines > choices.size)
			options.num_lines = choices.size;

		if (options.num_lines + 1 > tty_getheight(&tty))
			options.num_lines = tty_getheight(&tty) - 1;

		if (options.init_search)
			strncpy(search, options.init_search, SEARCH_SIZE_MAX);

		run(&tty, &choices, &options);
	}

	choices_destroy(&choices);

	return 0;
}