#include <ctype.h>
#include <err.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/ttydefaults.h>
#include <termios.h>
#include <unistd.h>

#define ESC "\033"
#define CSI ESC "["

#define prn(...) dprintf(ttyfd, __VA_ARGS__)
#define rerr(...) do { raw(false); err(__VA_ARGS__); } while (0)

char **lines;
int lines_c;
int lines_s;

char *src; /* source text */
int src_l;
int src_s;

int *breaks; /* positions of visual line breaks in source text */
             /* i.e. breaks[src index] = 1 if line broken before, else 0 */

int x, y; /* current cursor position */
int w, h; /* terminal width, height */
int margin = 10;

int current = -1; /* currently selected item */
int xorig, yorig; /* original cursor position in cols, rows */
int ttyfd;

/* enable/disable "raw" mode */
void raw(bool enable) {
	int r;
	static struct termios orig;
	struct termios raw;

	if (enable) {
		r = tcgetattr(ttyfd, &orig);
		if (r == -1) err(1, "tcgetattr");

		raw = orig;
		raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
		raw.c_oflag &= ~OPOST;
		raw.c_cflag |= CS8;
		raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);

		r = tcsetattr(ttyfd, TCSAFLUSH, &raw);
		if (r == -1) err(1, "tcsetattr");

	} else
		tcsetattr(ttyfd, TCSAFLUSH, &orig);
}

/* cursor position report */
int cpr(int *x, int *y) {
	char c, xbuf[4], ybuf[4];
	int i;

	prn(CSI "6n");

	/* get CSI */
	read(ttyfd, &c, 1);
	if (c != '\033') return -1;
	read(ttyfd, &c, 1);
	if (c != '[') return -1;

	/* get x */
	i = 0;
	while (read(ttyfd, &c, 1) && c != ';') {
		if (i > 3 || !isdigit(c)) return -1;
		ybuf[i++] = c;
	}
	if (i == 0) return -1;
	ybuf[i] = '\0';

	/* get y */
	i = 0;
	while (read(ttyfd, &c, 1) && c != 'R') {
		if (i > 3 || !isdigit(c)) return -1;
		xbuf[i++] = c;
	}
	if (i == 0) return -1;
	xbuf[i] = '\0';

	*x = atoi(xbuf);
	*y = atoi(ybuf);

	return 0;
}

int up() { }
int down() { }
int right() {}
int left() {}

void breakline() {
	prn("\r\n");
	breaks[src_l + 1] = 1;
	x = margin + 1;
	prn(CSI "%dC", margin - 1);
	if (y == h) yorig--;
	else y++;
}

void addc(char c) {
	char *tmp;
	int i, *tmp2;

	if (c != '\n' && x + 1 > w) breakline();
	else x++;
	if (src_l + 2 > src_s) {
		src_s += 50;
		tmp = realloc(src, (src_s + 1) * sizeof(char));
		if (tmp == NULL) rerr(1, "realloc");
		src = tmp;

		tmp2 = realloc(breaks, src_s * sizeof(int));
		if (breaks == NULL) err(1, "malloc");
		breaks = tmp2;
		for (i = src_s - 50; i < src_s; i++)
			breaks[i] = 0;
	}
	src[++src_l] = c;
	src[src_l + 1] = '\0';
}

int main() {
	bool dot;
	char c, *line, *p, *tmp;
	int i, line_s, r;
	struct winsize ws;

	src_s = 100;
	src = malloc((src_s + 1) * sizeof(char));
	if (src == NULL) err(1, "malloc");
	src_l = -1;

	breaks = malloc(src_s * sizeof(int));
	if (breaks == NULL) err(1, "malloc");
	for (i = 0; i < src_s; i++)
		breaks[i] = 0;

	ttyfd = open("/dev/tty", O_RDWR);
	if (ttyfd == -1) rerr(1, "open");

	/* read file */

/*
	line_s = 100;
	line = malloc((line_s + 1) * sizeof(char));
	if (line == NULL) err(1, "malloc");

	lines_c = 0;
	lines_s = 50;
	lines = malloc(lines_s * sizeof(char *));
	if (lines == NULL) err(1, "malloc");

	i = 0;
	while (read(STDIN_FILENO, &c, 1) != 0) {
		if (c == '\n') {
			line[i] = '\0';
			lines[lines_c] = malloc((strlen(line) + 1) * sizeof(char));
			if (lines[lines_c] == NULL) err(1, "malloc");
			strcpy(lines[lines_c], line);
			lines_c++;
			i = 0;
		} else {
			if (i > line_s) {
				line_s += 50;
				tmp = realloc(line, (line_s + 1) * sizeof(char));
				if (tmp == NULL) err(1, "realloc");
				line = tmp;
			}
			line[i++] = c;
		}
	}
*/

	raw(true);

	/* save original cursor position */
	r = cpr(&xorig, &yorig);
	if (r == -1) {
		fprintf(stderr, "could not retrieve cursor position");
		goto quit;
	}

	/* print output */
	for (i = 0; i < lines_c; i++)
		prn("%s\r\n", lines[i]);

	/* get height of terminal */
	r = ioctl(ttyfd, TIOCGWINSZ, &ws);
	if (r == -1) rerr(1, "ioctl");
	w = ws.ws_col;
	h = ws.ws_row;

	/* correct cursor position if original cursor was near bottom */
	r = y + lines_c - h;
	if (r > 0)
		y = y - r;

	/* restore original cursor position (CUP) */
	prn(CSI "%d;%dH", yorig, xorig + margin - 1);
	x = margin + 1;
	y = yorig;

	dot = false;
	while (read(ttyfd, &c, 1) != 0) {
		switch (c) {
		case '\033': /* escape */
			read(ttyfd, &c, 1);
			if (c != '[') break;
			read(ttyfd, &c, 1);
			if (c == 'A') up();
			if (c == 'B') down();
			if (c == 'C') right();
			if (c == 'D') left();
			break;
		case 3: /* ctrl-c */
			goto quit;
			break;
		case 13: /* enter */
			addc('\n');
			breakline();
			prn(CSI "0m");
			break;
		default:
			if (iscntrl(c)) break;
			if (x == margin + 1 && c == '.') {
				prn(CSI "%dD", margin - 1);
				prn(CSI "2m");
			}
			addc(c);
			prn("%c", c);
			break;
		}
	}
quit:
	prn(CSI "%d;%dH", yorig, xorig);
	prn(CSI "J"); /* delete from cursor to end of display (ED) */
	raw(false);
	printf("%s", src);
	return 0;
die:
	raw(false);
	puts("died");
	return 0;
}