#include <tui.h>
#define NCURSES_WIDECHAR 1
#include <curses.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <time.h>
#include <locale.h>
#include <stdlib.h>
#include <stdarg.h>
#include <panel.h>
#include <string.h>

#include <helper.h>
#include <config.h>
#include <algorithm.h>

static PANEL *stdpanel = NULL;
static WINDOW *logbox = NULL;
static WINDOW *logwin = NULL;
static WINDOW *charbox = NULL;
static WINDOW *charwin = NULL;
static WINDOW *mapbox = NULL;
static WINDOW *mapwin = NULL;
static bool initialized = false;

static bool setup_log(struct winsize w)
{
	int ystart = 0;
	int xstart = w.ws_col/2+1;
	int ysize = w.ws_row;
	int xsize = w.ws_col/2-1;
	logbox = subwin(stdscr, ysize, xsize, ystart, xstart);
	if(!logbox)
		return false;
	box(logbox,'|', '-');
	wmove(logbox, 0,1);
	wprintw(logbox, "Log");
	logwin = subwin(logbox, ysize-2, xsize-2, ystart+1, xstart+1);
	if(!logwin)
		return false;
// 	scrollok(logwin, true);
	return true;
}

static bool setup_char(struct winsize w)
{
	int ystart = 0;
	int xstart = 0;
	int ysize = w.ws_row/3;
	int xsize = w.ws_col/2;
	charbox = subwin(stdscr, ysize, xsize, ystart, xstart);
	if(!charbox)
		return false;
	box(charbox,'|', '-');
	wmove(charbox, 0,1);
	wprintw(charbox, "Character");
	charwin = subwin(charbox, ysize-2, xsize-2, ystart+1, xstart+1);
	if(!charwin)
		return false;
	return true;
}

static bool setup_map(struct winsize w)
{
	int ystart = w.ws_row/3+1;
	int xstart = 0;
	int ysize = w.ws_row-w.ws_row/3-1;
	int xsize = w.ws_col/2;
	mapbox = subwin(stdscr, ysize, xsize, ystart, xstart);
	if(!mapbox)
		return false;
	box(mapbox,'|', '-');
	wmove(mapbox, 0,1);
	wprintw(mapbox, "Map");
	mapwin = subwin(mapbox, ysize-2, xsize-2, ystart+1, xstart+1);
	if(!mapwin)
		return false;
	return true;
}

void quit(void)
{
	endwin();
}

bool setup()
{
	if(initialized) {
		erase();

		if(logwin != NULL)
			delwin(logwin);
		if(logbox != NULL)
			delwin(logbox);

		if(charwin != NULL)
			delwin(charwin);
		if(charbox != NULL)
			delwin(charbox);

		if(mapwin != NULL)
			delwin(mapwin);
		if(mapbox != NULL)
			delwin(mapbox);

	} else {
		atexit(quit);
		setlocale(LC_ALL,"");
		stdscr = initscr();
		stdpanel = new_panel(stdscr);
		assert(stdpanel != NULL);
		noecho();
		cbreak();
		curs_set(0);
		keypad(stdscr, true);
		initialized = true;
	}


	struct winsize w;
	assert(ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1);
	if(w.ws_col < 25 || w.ws_row < 10) {
		endwin();
		puts("Terminal too small");
		abort();
	}

	if(!setup_char(w)) {
		fprintf(stderr, "char\n");
		return false;
	}
	if(!setup_log(w)) {
		fprintf(stderr, "log\n");
		return false;
	}

	if(!setup_map(w)) {
		fprintf(stderr, "map\n");
		return false;
	}
	update_panels();
	doupdate();
	return true;
}

size_t logindex = 0;
size_t loglength;
char **logs = NULL;

void printlog() //TODO: fix logs if they go multiline
{
	werase(logwin);
	wmove(logwin, 0, 0);
	int ymax = getmaxy(logwin);
	for(int i = 0; i < ymax && i+logindex < loglength; i++)
	{
		waddstr(logwin, logs[i+logindex]);
		waddstr(logwin, "\n");
	}
	wrefresh(logwin);
}

void logstr(const char *format, ...)
{
	static size_t capacity;

	if(logs == NULL) {
		logs = xmalloc(8*sizeof(*logs));
		capacity = 8;
		loglength = 0;
	}

	if(capacity == loglength++) {
		logs = xrealloc(logs, (capacity*=2) * sizeof(*logs));
	}

	va_list ap;
    va_start(ap, format);
    va_list ap2;
    va_copy(ap2, ap);
	int bufsz = vsnprintf(NULL, 0, format, ap);
	va_end(ap);
	struct tm mytime = *localtime(&(time_t){time(NULL)});
	logs[loglength-1] = xmalloc(bufsz+11+2); //11 == size needed for the time header;
	strftime(logs[loglength-1], 11, "%T> ", &mytime);
	vsprintf(logs[loglength-1]+10, format, ap2);
	va_end(ap2);

	if(loglength - logindex == (size_t)getmaxy(logwin)) //cast is fine, getmaxy only returns ERR if WINDOW is NULL
		logindex++;

	printlog();
}

void charclear()
{
	werase(charwin);
	wrefresh(charwin);
}


void charprint(const char* format, ...)
{
	va_list ap;
	va_start(ap, format);

	vw_printw(charwin, format, ap);
	wprintw(charwin, "\n");
	wrefresh(charwin);

	va_end(ap);
}


void log_scroll(enum direction d)
{
	if(d == UP && logindex != 0)
		logindex--;
	else if(d == DOWN && logindex != loglength-1)
		logindex++;

	printlog();

	wrefresh(logwin);
}


void draw_map(const struct game *g, struct point p)
{
	int x, y;
	getmaxyx(mapwin, y, x);
	size_t starty = MAX(0, p.y-y/2);
	size_t endy = MIN(starty + y, g->ys);
	size_t startx = MAX(0, p.x-x/2);
	size_t endx = MIN(startx + x, g->xs);

	werase(mapwin);
	int line = 0;
	for(size_t i = starty; i < endy; i++)
	{
		wmove(mapwin, line++, 0);
		for(size_t j = startx; j < endx; j++)
		{
			struct field *f = game_at(g, (struct point){.y = i, .x = j});
			bool sight = insight(g, p, ((struct point){.y = i, .x = j}));
			if(SIGHT_EMPTY || sight) { //TODO: add colors to unkown but sighted areas
				if(!sight)
						wattron(mapwin, A_DIM);
				
				if(f->has_monster && sight) {
					waddch(mapwin, f->m.symbol);
				} else if(f->u->ground.type == ITEM && !sight) {
					waddch(mapwin, ' ');
				} else {
					waddch(mapwin, f->u->ground.symbol);
				}
				if(!sight)
						wattroff(mapwin, A_DIM);
			} else {
				waddch(mapwin, '.');
			}
		}
	}

	wrefresh(mapwin);
}

bool messagebox(const char *str, size_t y, size_t x)
{
	int yorig = y, xorig = x;
	struct winsize w;
	assert(ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1);

	const char *returnstr = "Press return to continue";
	x = MAX(strlen(returnstr), x)+2;
	y += 4;

	if(y > w.ws_row || x > w.ws_col)
		return false;

	int ystart = w.ws_row/2-y/2;
	int xstart = w.ws_col/2-x/2;

	WINDOW *msgbox = newwin(y, x, ystart, xstart);
	PANEL *msgboxpanel = new_panel(msgbox);
	box(msgbox, '|', '-');
	WINDOW *msgwin = newwin(y-2, x-2, ystart+1, xstart+1);
	PANEL *msgwinpanel = new_panel(msgwin);
	waddstr(msgwin, str);
	waddstr(msgwin, "\n\n");
	waddstr(msgwin, returnstr);
	update_panels();
	doupdate();
	int c;
	while((c = getch()) != '\n' && c != KEY_RESIZE);

	del_panel(msgwinpanel);
	del_panel(msgboxpanel);

	delwin(msgbox);
	delwin(msgwin);

	update_panels();
	doupdate();

	if(c == KEY_RESIZE)
	{
		while(!setup());
		return messagebox(str, yorig, xorig);
	}
	printlog(); //incase we resized
	return true;

}

void itemselect(struct monster *m, itemselectfn f)
{
	WINDOW *menu = newwin(0, 0, 0, 0);
	PANEL *menupanel = new_panel(menu);

	size_t i = 0; //we put the index before the label so we keep it between redraws

redraw:;
	struct winsize w;
	assert(ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1);

	for(size_t i = 0; i < m->items.size; i++)
	{
		struct item *it = &darray_item(m->items, i);
		if(it->type != NOITEM) {
			if(it->type == EQUIP) {
				mvwprintw(menu, i, 0, "%.*s (%c)", w.ws_col, it->name, it->u.e.isequipped ? '*' : ' ');
			} else {
				mvwprintw(menu, i, 0, "%.*s",  w.ws_col, it->name);
			}
		} else {
			mvwaddstr(menu, i, 0, "EMPTY");
		}
	}

	mvwchgat(menu, i, 0, -1, A_REVERSE, 0, NULL);
	update_panels();
	doupdate();

	int c;
	while((c = getch()) != 'i' && c != 'q')
	{
		switch(c)
		{
		case ' ':
			if(m->items.size != 0 && f(m, i))
				i = 0;
			werase(menu);
			goto redraw; //items might have changed, so we redraw the list
		break;
		case 's':
			if(i != (m->items.size == 0 ? 0 :m->items.size-1)) {
				wchgat(menu , -1, A_NORMAL, 0, NULL);
				mvwchgat(menu, ++i, 0, -1, A_REVERSE, 0, NULL);
			}
		break;
		case 'w':
			if(i != 0) {
				wchgat(menu, -1, A_NORMAL, 0, NULL);
				mvwchgat(menu, --i, 0, -1, A_REVERSE, 0, NULL);
			}
		break;
		case KEY_RESIZE:
			werase(menu);
			goto redraw;
		break;
		}
		update_panels();
		doupdate();
	}


	del_panel(menupanel);
	delwin(menu);
	update_panels();
	doupdate();
}



