/*
 * Copyright (C) 2012-2014 Robin Haberkorn
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#ifdef EMSCRIPTEN
#include 
#endif
#include "sciteco.h"
#include "cmdline.h"
#include "qregisters.h"
#include "ring.h"
#include "interface.h"
#include "interface-ncurses.h"
extern "C" {
static void scintilla_notify(Scintilla *sci, int idFrom,
			    void *notify, void *user_data);
}
#define UNNAMED_FILE "(Unnamed)"
#define SCI_COLOR_ATTR(f, b) \
	COLOR_PAIR(SCI_COLOR_PAIR(f, b))
void
InterfaceNCurses::main(int &argc, char **&argv)
{
	init_screen();
	cbreak();
	noecho();
	curs_set(0); /* Scintilla draws its own cursor */
	setlocale(LC_CTYPE, ""); /* for displaying UTF-8 characters properly */
	info_window = newwin(1, 0, 0, 0);
	info_current = g_strdup(PACKAGE_NAME);
	/* NOTE: Scintilla initializes color pairs */
	sci = scintilla_new(scintilla_notify);
	sci_window = scintilla_get_window(sci);
	wresize(sci_window, LINES - 3, COLS);
	mvwin(sci_window, 1, 0);
	msg_window = newwin(1, 0, LINES - 2, 0);
	cmdline_window = newwin(0, 0, LINES - 1, 0);
	cmdline_current = NULL;
	ssm(SCI_SETFOCUS, TRUE);
	draw_info();
	/* scintilla will be refreshed in event loop */
	msg_clear();
	cmdline_update("");
#ifdef EMSCRIPTEN
        nodelay(cmdline_window, TRUE);
#else
#ifndef PDCURSES_WIN32A
	/* workaround: endwin() is somewhat broken in the win32a port */
	endwin();
#endif
#endif
}
#ifdef __PDCURSES__
void
InterfaceNCurses::init_screen(void)
{
#ifdef PDCURSES_WIN32A
	/* enables window resizing on Win32a port */
	PDC_set_resize_limits(25, 0xFFFF, 80, 0xFFFF);
#endif
	initscr();
	screen_tty = NULL;
	screen = NULL;
}
#else
void
InterfaceNCurses::init_screen(void)
{
	/*
	 * Prevent the initial redraw and any escape sequences that may
	 * interfere with stdout, so we may use the terminal in
	 * cooked mode, for commandline help and batch processing.
	 * Scintilla must be initialized for batch processing to work.
	 * (Frankly I have no idea why this works!)
	 */
	screen_tty = g_fopen("/dev/tty", "r+b");
	screen = newterm(NULL, screen_tty, screen_tty);
	set_term(screen);
}
#endif /* !__PDCURSES__ */
void
InterfaceNCurses::resize_all_windows(void)
{
	int lines, cols; /* screen dimensions */
	getmaxyx(stdscr, lines, cols);
	wresize(info_window, 1, cols);
	wresize(sci_window, lines - 3, cols);
	wresize(msg_window, 1, cols);
	mvwin(msg_window, lines - 2, 0);
	wresize(cmdline_window, 1, cols);
	mvwin(cmdline_window, lines - 1, 0);
	draw_info();
	/* scintilla will be refreshed in event loop */
	msg_clear(); /* FIXME: use saved message */
	cmdline_update();
}
void
InterfaceNCurses::vmsg(MessageType type, const gchar *fmt, va_list ap)
{
	static const chtype type2attr[] = {
		SCI_COLOR_ATTR(COLOR_BLACK, COLOR_WHITE),  /* MSG_USER */
		SCI_COLOR_ATTR(COLOR_BLACK, COLOR_GREEN),  /* MSG_INFO */
		SCI_COLOR_ATTR(COLOR_BLACK, COLOR_YELLOW), /* MSG_WARNING */
		SCI_COLOR_ATTR(COLOR_BLACK, COLOR_RED)	   /* MSG_ERROR */
	};
#ifdef PDCURSES_WIN32A
	stdio_vmsg(type, fmt, ap);
	if (isendwin()) /* batch mode */
		return;
#else
	if (isendwin()) { /* batch mode */
		stdio_vmsg(type, fmt, ap);
		return;
	}
#endif
	wmove(msg_window, 0, 0);
	wbkgdset(msg_window, ' ' | type2attr[type]);
	vw_printw(msg_window, fmt, ap);
	wclrtoeol(msg_window);
	wrefresh(msg_window);
}
void
InterfaceNCurses::msg_clear(void)
{
	if (isendwin()) /* batch mode */
		return;
	wmove(msg_window, 0, 0);
	wbkgdset(msg_window, ' ' | SCI_COLOR_ATTR(COLOR_BLACK, COLOR_WHITE));
	wclrtoeol(msg_window);
	wrefresh(msg_window);
}
void
InterfaceNCurses::draw_info(void)
{
	if (isendwin()) /* batch mode */
		return;
	wmove(info_window, 0, 0);
	wbkgdset(info_window, ' ' | SCI_COLOR_ATTR(COLOR_BLACK, COLOR_WHITE));
	waddstr(info_window, info_current);
	wclrtoeol(info_window);
	wrefresh(info_window);
}
void
InterfaceNCurses::info_update(QRegister *reg)
{
	g_free(info_current);
	info_current = g_strdup_printf("%s -  %s", PACKAGE_NAME,
				       reg->name);
	draw_info();
}
void
InterfaceNCurses::info_update(Buffer *buffer)
{
	g_free(info_current);
	info_current = g_strdup_printf("%s -  %s%s", PACKAGE_NAME,
				       buffer->filename ? : UNNAMED_FILE,
				       buffer->dirty ? "*" : "");
	draw_info();
}
void
InterfaceNCurses::cmdline_update(const gchar *cmdline)
{
	size_t len;
	int half_line = (getmaxx(stdscr) - 2) / 2;
	const gchar *line;
	if (cmdline) {
		g_free(cmdline_current);
		cmdline_current = g_strdup(cmdline);
	} else {
		cmdline = cmdline_current;
	}
	len = strlen(cmdline);
	/* FIXME: optimize */
	line = cmdline + len - MIN(len, half_line + len % half_line);
	mvwaddch(cmdline_window, 0, 0, '*');
	waddstr(cmdline_window, line);
	waddch(cmdline_window, ' ' | A_REVERSE);
	wclrtoeol(cmdline_window);
	wrefresh(cmdline_window);
}
void
InterfaceNCurses::popup_add(PopupEntryType type,
			    const gchar *name, bool highlight)
{
	gchar *entry;
	if (isendwin()) /* batch mode */
		return;
	entry = g_strconcat(highlight ? "*" : " ", name, NIL);
	popup.longest = MAX(popup.longest, (gint)strlen(name));
	popup.length++;
	popup.list = g_slist_prepend(popup.list, entry);
}
void
InterfaceNCurses::popup_show(void)
{
	int lines, cols; /* screen dimensions */
	int popup_lines;
	gint popup_cols;
	gint cur_file, cur_line;
	if (isendwin()) /* batch mode */
		goto cleanup;
	getmaxyx(stdscr, lines, cols);
	popup.longest += 3;
	popup.list = g_slist_reverse(popup.list);
	/* popup_cols = floor(cols / popup.longest) */
	popup_cols = MAX(cols / popup.longest, 1);
	/* popup_lines = ceil(popup.length / popup_cols) */
	popup_lines = popup.length / popup_cols;
	if ((popup.length % popup_cols))
		popup_lines++;
	popup_lines = MIN(popup_lines, lines - 1);
	/* window covers message, scintilla and info windows */
	popup.window = newwin(popup_lines, 0, lines - 1 - popup_lines, 0);
	wbkgdset(popup.window, ' ' | SCI_COLOR_ATTR(COLOR_BLACK, COLOR_BLUE));
	cur_file = 0;
	cur_line = 1;
	for (GSList *cur = popup.list; cur; cur = g_slist_next(cur)) {
		gchar *entry = (gchar *)cur->data;
		if (cur_file && !(cur_file % popup_cols)) {
			wclrtoeol(popup.window);
			waddch(popup.window, '\n');
			cur_line++;
		}
		cur_file++;
		if (cur_line == popup_lines && !(cur_file % popup_cols) &&
		    cur_file < popup.length) {
			(void)wattrset(popup.window, A_BOLD);
			waddstr(popup.window, "...");
			break;
		}
		(void)wattrset(popup.window, *entry == '*' ? A_BOLD : A_NORMAL);
		waddstr(popup.window, entry + 1);
		for (int i = popup.longest - strlen(entry) + 1; i; i--)
			waddch(popup.window, ' ');
		g_free(cur->data);
	}
	wclrtoeol(popup.window);
cleanup:
	g_slist_free(popup.list);
	popup.list = NULL;
	popup.longest = popup.length = 0;
}
void
InterfaceNCurses::popup_clear(void)
{
	if (!popup.window)
		return;
	redrawwin(info_window);
	wrefresh(info_window);
	redrawwin(sci_window);
	scintilla_refresh(sci);
	redrawwin(msg_window);
	wrefresh(msg_window);
	delwin(popup.window);
	popup.window = NULL;
}
/**
 * One iteration of the event loop.
 *
 * This is a global function, so it may
 * be used as an Emscripten callback.
 *
 * @bug
 * Can probably be defined as a static method,
 * so we can avoid declaring it a fried function of
 * InterfaceNCurses.
 */
void
event_loop_iter()
{
	int key;
	keypad(interface.cmdline_window, Flags::ed & Flags::ED_FNKEYS);
	/* no special  handling */
	raw();
	key = wgetch(interface.cmdline_window);
	/* allow asynchronous interruptions on  */
	cbreak();
	if (key == ERR)
		return;
	switch (key) {
#ifdef KEY_RESIZE
	case KEY_RESIZE:
#ifdef PDCURSES
		resize_term(0, 0);
#endif
		interface.resize_all_windows();
		break;
#endif
	case 0x7F: /* DEL */
	case KEY_BACKSPACE:
		cmdline_keypress('\b');
		break;
	case KEY_ENTER:
	case '\r':
	case '\n':
		cmdline_keypress(get_eol());
		break;
	/*
	 * Function key macros
	 */
#define FN(KEY) case KEY_##KEY: cmdline_fnmacro(#KEY); break
#define FNS(KEY) FN(KEY); FN(S##KEY)
	FN(DOWN); FN(UP); FNS(LEFT); FNS(RIGHT);
	FNS(HOME);
	case KEY_F(0)...KEY_F(63): {
		gchar macro_name[3+1];
		g_snprintf(macro_name, sizeof(macro_name),
			   "F%d", key - KEY_F0);
		cmdline_fnmacro(macro_name);
		break;
	}
	FNS(DC);
	FNS(IC);
	FN(NPAGE); FN(PPAGE);
	FNS(PRINT);
	FN(A1); FN(A3); FN(B2); FN(C1); FN(C3);
	FNS(END);
	FNS(HELP);
#undef FNS
#undef FN
	/*
	 * Control keys and keys with printable representation
	 */
	default:
		if (key <= 0xFF)
			cmdline_keypress((gchar)key);
	}
	sigint_occurred = FALSE;
	scintilla_refresh(interface.sci);
	if (interface.popup.window)
		wrefresh(interface.popup.window);
}
void
InterfaceNCurses::event_loop(void)
{
	/* initial refresh: window might have been changed in batch mode */
	scintilla_refresh(sci);
	draw_info();
#ifdef EMSCRIPTEN
	emscripten_set_main_loop(event_loop_iter, 1000/100, TRUE);
#else
	for (;;)
		event_loop_iter();
#endif
}
InterfaceNCurses::Popup::~Popup()
{
	if (window)
		delwin(window);
	if (list)
		g_slist_free(list);
}
InterfaceNCurses::~InterfaceNCurses()
{
	if (info_window)
		delwin(info_window);
	g_free(info_current);
	/* also deletes curses window */
	if (sci)
		scintilla_delete(sci);
	if (cmdline_window)
		delwin(cmdline_window);
	g_free(cmdline_current);
	if (msg_window)
		delwin(msg_window);
	if (!isendwin())
		endwin();
	if (screen)
		delscreen(screen);
	if (screen_tty)
		fclose(screen_tty);
}
/*
 * Callbacks
 */
static void
scintilla_notify(Scintilla *sci, int idFrom, void *notify, void *user_data)
{
	interface.process_notify((SCNotification *)notify);
}