#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <sys/ioctl.h>

#include <termios.h>

#define IMP
#include "./file.h"
#include "./path.h"

enum m {
	MN = 0,
	ME,
	MI,
	MV
};

/* ------ START struct b Defs ------ */

enum be {
	BE_OK = 0,
	BE_FILE_DOES_NOT_EXIST,
	BE_FAILED_READ_FILE
};

const char *be2cs(enum be be);

struct l {
	uint64_t s;
	uint64_t e;
	uint64_t n;
};

struct b {
	struct str fp;
	struct str fn;
	struct {
		char *d;
		uint64_t s;
		uint64_t c;
	} b;
	struct {
		struct l *d;
		uint64_t s;
		uint64_t c;
	} ls;
	uint64_t c;
	enum m m;
};

struct b bc(const char *fp, enum be *e);
struct b bd(struct b b);

struct b bfl(struct b b);
struct l bcl(struct b b);

struct b bmcu(struct b b);
struct b bmcd(struct b b);
struct b bmcr(struct b b);
struct b bmcl(struct b b);

void _bse(enum be *e, enum be e_);

/* ------ END struct b Defs ------ */

/* ------ START struct a Defs ------ */

enum ee {
	EE_OK = 0,
	EE_NULL_ARGS
};

struct e {
	struct {
		struct b *d;
		uint64_t s;
		uint64_t c;
	} bs;
	struct b *cb;
	bool r;
};

struct e ec(void);
struct e ed(struct e e);

void epcb(struct e e);

struct e escb(struct e e, struct b *b, enum ee *err);
bool ecbp(struct e e, struct b *b);

struct e eab(struct e e, struct b b);

void _ese(enum ee *e, enum ee e_);

/* ------ END struct a Defs ------ */

struct {
	int32_t r;
	int32_t c;
} TS = {0};
struct {
	char *d;
	uint64_t s;
	uint64_t c;
} WB = {0};

void w(const char *t);
void ws(struct str s);
void c2r(int32_t r);
void s(void);

void* c(size_t s);
#define C(var, size) \
	var = calloc(size, 1); \
	if ( var == NULL ) { \
		fprintf(stderr, "%s:%d: Failed to calloc: BUY MORE MEMORY!\n", __FILE__, __LINE__); \
		exit(EXIT_FAILURE); \
	} \

int
main(int argc, const char *argv[])
{
	struct termios tbak;
	struct termios tnew;
	const char *filepath = "Makefile";
	struct winsize ws = {0};
	enum be be;
	struct b b = {0};
	struct e e = {0};

	e = ec();

	ioctl(1, TIOCGWINSZ, &ws);
	TS.r = ws.ws_row;
	TS.c = ws.ws_col;

	tcgetattr(0, &tbak);

	tnew = tbak;
	tnew.c_lflag = (tcflag_t) (((int) tbak.c_lflag) & (~ (ICANON | ECHO)));
	tnew.c_cc[VMIN]  = 1;
	tnew.c_cc[VTIME] = 0;
	tcsetattr(0, TCSANOW, &tnew);

	b = bc(filepath, &be);
	if ( be != BE_OK ) {
		printf("Failed to create buffer: %s", be2cs(be));
		exit(EXIT_FAILURE);
	}

	e = eab(e, b);

	w("\x1B[?47h");

	w("\x1B[2J");
	w("\x1B[1;1H");
	w("\x1B[1;1H");
	s();

	while ( e.r == true ) {
		char bu[5] = {0};

		w("\x1B[2J");
		epcb(e);
		s();
		/* There is smears when moving up and down */
		/* I need to change the arch TB **should** work like an graphic buffer */
		/* Unless i want to clear the screen no every frame */
		read(0, bu, 5);

		switch ( bu[0] ) {
		case 'q':
			e.r = false;
			continue;
		case '\x1B':
			if ( bu[1] != '[' ) {
				break;
			}
			switch ( bu[2] ) {
			case 'A': *e.cb = bmcu(*e.cb); break;
			case 'B': *e.cb = bmcd(*e.cb); break;
			case 'C': *e.cb = bmcr(*e.cb); break;
			case 'D': *e.cb = bmcl(*e.cb); break;
			}
		}
		/* printf("\n%3d | %3d | %3d | %3d\n", bu[0], bu[1], bu[2], bu[3]); */
	};

	e = ed(e);

	/*c2r(TS.r);
	w("\x1B[31m");
	w("Mode Line");
	w("\x1B[0m");*/

	w("\x1B[?47l");
	s();

	tcsetattr(0, TCSANOW, &tbak);
	(void) argc; (void) argv;
	return 0;
}

void
w(const char *t)
{
	struct str s = {0};
	s = str_from_cstr(t, strlen(t));
	ws(s);
}

void
ws(struct str s)
{
	if ( WB.d == NULL ) {
		WB.c = (uint64_t) (TS.r * TS.c) + 10;
		C(WB.d, WB.c);
		WB.s = 0;
	}

	if ( WB.s + s.size >= WB.c ) {
		WB.c *= 2;
		WB.d = realloc(WB.d, WB.c);
	}

	memcpy(WB.d + WB.s, s.data, s.size);
	WB.s += s.size;
}

void
c2r(int32_t r)
{
	char b[64] = {0};
	sprintf(b, "\033[%d;1H", r);
	w(b);
}

void
s(void)
{
	if ( WB.s == 0 || WB.d == NULL ) {
		return;
	}

	write(1, WB.d, WB.s);
	memset(WB.d, 0, WB.c);
	WB.s = 0;
}

void *
c(size_t s)
{
	void *ptr = NULL;
	ptr = calloc(s, 1);
	if ( ptr == NULL ) {
		fprintf(stderr, "Failed to calloc: BUY MORE MEMORY!\n");
		exit(EXIT_FAILURE);
	}
	return ptr;
}

/* ------ START struct b Funcs ------ */

struct b
bc(const char *fp, enum be *e)
{
	struct b ept = {0};
	struct b b = {0};
	enum file_err fe;

	b.fp = str_from_cstr(fp, strlen(fp));

	if ( path_exists(b.fp) == false ) {
		_bse(e, BE_FILE_DOES_NOT_EXIST);
		return ept;
	}

	b.fn = path_dirname(b.fp);

	b.b.d = (char *) file_read_all(b.fp.data, &b.b.s, &fe);
	if ( fe != FILE_ERR_OK ) {
		_bse(e, BE_FAILED_READ_FILE);
		return ept;
	}

	b.b.c = b.b.s;

	b = bfl(b);

	_bse(e, BE_OK);
	return b;
}

struct b
bd(struct b b)
{
	struct b ept = {0};
	free(b.b.d);
	free(b.ls.d);
	return ept;
}

struct b
bfl(struct b b)
{
	uint64_t i = 0;
	struct l l1 = {0};

	if ( b.ls.d == NULL ) {
		b.ls.c = 16;
		b.ls.d = calloc(b.ls.c, sizeof(*b.ls.d));
	}

	for ( i = 0; i < b.b.s; ++i ) {
		if ( b.b.d[i] == '\n' ) {
			l1.e = i;

			if ( b.ls.s + 1 >= b.ls.c ) {
				b.ls.c *= 2;
				b.ls.d = realloc(b.ls.d, b.ls.c * sizeof(*b.ls.d));
			}

			l1.n = b.ls.s;
			b.ls.d[b.ls.s++] = l1;

			l1.s = ++i;
		}
	}

	return b;
}

struct l
bcl(struct b b)
{
	uint64_t i = 0;
	struct l l = {0};

	for ( i = 0; i < b.ls.s; ++i ) {
		l = b.ls.d[i];

		if ( b.c >= l.s && b.c <= l.e ) {
			return l;
		}
	}

	return l;
}

void
_bse(enum be *e, enum be e_)
{
	if ( e == NULL ) return;
	*e = e_;
}

struct b
bmcu(struct b b)
{
	struct l cl = {0};
	struct l nl = {0};
	uint64_t co = 0;

	if ( b.c == 0 ) {
		return b;
	}

	cl = bcl(b);

	if ( cl.n == 0 ) {
		return b;
	}

	nl = b.ls.d[cl.n-1];

	co = b.c - cl.s;
	if ( co > nl.e ) {
		b.c = nl.e;
	} else {
		b.c = nl.s + co;
	}

	return b;
}

struct b
bmcd(struct b b)
{
	struct l cl = {0};
	struct l nl = {0};
	uint64_t co = 0;

	if ( b.c >= b.b.s ) {
		b.c = b.b.s-1;
		return b;
	}

	cl = bcl(b);

	if ( cl.n == b.ls.s ) {
		return b;
	}

	nl = b.ls.d[cl.n+1];

	co = b.c - cl.s;
	if ( co > nl.e ) {
		b.c = nl.e;
	} else {
		b.c = nl.s + co;
	}

	if ( b.c >= b.b.s ) {
		b.c = b.b.s-1;
		return b;
	}

	return b;
}

struct b
bmcr(struct b b)
{
	if ( b.c + 1 >= b.b.s ) {
		b.c = b.b.s - 1;
		return b;
	}

	b.c++;

	return b;
}

struct b
bmcl(struct b b)
{
	if ( b.c == 0 ) {
		return b;
	}

	b.c--;

	return b;
}

const char *
be2cs(enum be be)
{
	switch ( be ) {
	case BE_OK: return "BE_OK";
	case BE_FILE_DOES_NOT_EXIST: return "BE_FILE_DOES_NOT_EXIST";
	case BE_FAILED_READ_FILE: return "BE_FAILED_READ_FILE";
	}

	return "Invalid BE Error";
}

/* ------ END struct b Funcs ------ */

/* ------ START struct a Funcs ------ */

struct e
ec(void)
{
	struct e e = {0};

	e.r = true;

	return e;
}

struct e
ed(struct e e)
{
	struct e ept = {0};
	size_t i = 0;

	for ( i = 0; i < e.bs.s; ++i ) {
		e.bs.d[i] = bd(e.bs.d[i]);
	}

	free(e.bs.d);

	return ept;
}

void
epcb(struct e e)
{
	uint64_t i = 0;
	uint64_t r = 0;
	struct b *b = e.cb;
	struct str s = {0};
	struct l cl = {0};

	s = str_from_cstr(b->b.d, b->b.s);

	cl = bcl(*b);

	/* TODO: There are missing lines */
	for ( i = cl.n; i < b->ls.s; ++i ) {
		struct l l = b->ls.d[i];

		c2r((int32_t) r++);

		ws(str_slice(s, l.s, l.e));
	}
}

struct e
escb(struct e e, struct b *b, enum ee *err)
{
	if ( b == NULL ) {
		_ese(err, EE_NULL_ARGS);
	}

	if ( ecbp(e, b) == false ) {
		e = eab(e, *b);
		e.cb = &e.bs.d[e.bs.s-1];
		goto exit;
	}

	e.cb = b;

exit:
	_ese(err, EE_OK);
	return e;
}

bool
ecbp(struct e e, struct b *b)
{
	size_t i = 0;

	for ( i = 0; i < e.bs.s; ++i ) {
		if ( &e.bs.d[i] == b ) {
			return true;
		}
	}

	return false;
}

struct e
eab(struct e e, struct b b)
{
	if ( e.bs.s + 1 >= e.bs.c ) {
		e.bs.c *= 2;
		e.bs.d = realloc(e.bs.d, e.bs.c * sizeof(*e.bs.d));
	}

	e.bs.d[e.bs.s++] = b;

	if ( e.bs.s == 1 ) {
		e.cb = &e.bs.d[0];
	}

	return e;
}

void
_ese(enum ee *e, enum ee e_)
{
	if ( e != NULL ) {
		*e = e_;
	}
}

/* ------ END struct a Funcs ------ */
