/*******************************************************************************

  Copyright(c) 2003-2004 Aurelien Reynaud.

  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 2 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, write to the Free Software Foundation, Inc., 59
  Temple Place - Suite 330, Boston, MA  02111-1307, USA.

  The full GNU General Public License is included in this distribution in the
  file called COPYING.

*******************************************************************************/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "ggiterm.h"
#include "debug.h"
#include "lowlevel/lowlevel.h"
#include "history.h"
#include "terminal.h"


#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>   /* Solaris has ioctl() there... */
#include <termios.h>  /* for tcgetattr, tcsetattr */

#ifdef MULTIBYTE
# include <locale.h>
/* MacOS X doesn't have langinfo.h, although the docs say otherwise. It's a
   bug on their part since it's a Single Unix Specification requirement */
# ifdef HAVE_NL_LANGINFO
#  include <langinfo.h>
# endif
#endif /* MULTIBYTE */


extern int mode;
extern int cursor_x, cursor_y;

int saved_mode;
int savedcursor_x, savedcursor_y;
int terminal_width, terminal_height;
int default_bgcolor, default_fgcolor;
int terminal_fd;


int tabstop_init (int);
void tabstop_exit ();
int parse_regular_chars (unsigned char **, size_t *);
int parse_special_chars (unsigned char **, size_t *);
void expose_handle_events ();
int keyboard_handle_event (ggi_event);
#ifdef JUMP_SCROLL
void scroll_up_resync();
#endif


void save_state ()
{
	debug (DEBUG_FUNCTION, "called with no args");
	/* Should we save current colors too? */
	saved_mode = mode;
	savedcursor_x = cursor_x;
	savedcursor_y = cursor_y;
	debug (DEBUG_FUNCTION, "Leaving");
}

void restore_state ()
{
	debug (DEBUG_FUNCTION, "called with no args");
	mode = saved_mode;
	cursor_x = savedcursor_x;
	cursor_y = savedcursor_y;
	debug (DEBUG_FUNCTION, "Leaving");
}

int terminal_init (int width, int height, int history, int fd)
{
	int err;
	int history_lines;
	struct winsize ws;
	struct termios tc;
#ifdef DEBUG
	wchar_t cur_char;
#endif /* DEBUG */

	debug (DEBUG_FUNCTION,
	       "called with args (width=%d, height=%d, history=%d, fd=[file descriptor])",
	       width, height, history);

	terminal_width  = width;
	terminal_height = height;
	terminal_fd     = fd;

	/* Get current locale */
#ifdef MULTIBYTE
	setlocale (LC_CTYPE, "");
# ifdef HAVE_NL_LANGINFO
	debug (DEBUG_INIT, "Current codeset is %s", nl_langinfo (CODESET));
# else
	debug (DEBUG_INIT, "Current codeset is unknown - nl_langinfo() undefined");
# endif
	debug (DEBUG_INIT, "Maximum character length is %d", MB_CUR_MAX);
#endif /* MULTIBYTE */

	scroll_change_region (0, terminal_height);
	
	if (ioctl (terminal_fd, TIOCGWINSZ, &ws) < 0) {
		error ("Error getting window size.\n");
	}
	ws.ws_col = terminal_width;
	ws.ws_row = terminal_height;
	if (ioctl (terminal_fd, TIOCSWINSZ, &ws) < 0) {
		error ("Error setting window size.\n");
	}

	debug (DEBUG_INIT, "Setting terminal attributes");
	if (tcgetattr(fd, &tc) < 0) {
		error("Could not get current attributes");
	} else {
		/* set ^H as the ERASE character */
		/*tc.c_cc[VERASE] = 0x08;*/
		/* set canonical mode (one line at a time) */
		/*tc.c_lflag |= ICANON;*/
		if (tcsetattr(fd, TCSANOW, &tc) < 0) {
			error("Could not set new attributes");
		}
	}

	default_fgcolor = WHITE;
	default_bgcolor = BLACK;
	text_set_bgcolor (default_bgcolor);
	text_set_fgcolor (default_fgcolor);
	
	/* Allocate history buffer */
	debug (DEBUG_INIT, "Initializating history buffer");
	if (history == -1) history_lines = terminal_height;
	else history_lines = history;
	err = history_init (terminal_width+1, terminal_height, history_lines,
	                   default_bgcolor, default_fgcolor);
	if (err) {
		debug (DEBUG_FUNCTION, "Leaving");
		return 1;
	}
	debug (DEBUG_INIT, "History buffer initialization complete");
	
	/* Allocate and populate the array for tab stops */
	debug (DEBUG_INIT, "Initializating tabstops array");
	err = tabstop_init (terminal_width);
	if (err) {
		history_exit ();
		debug (DEBUG_FUNCTION, "Leaving");
		return 1;
	}
	debug (DEBUG_INIT, "Tabstops array initialization complete");

	cursor_move (0, 0);
	debug (DEBUG_INIT, "Terminal is %d x %d cells",
	       terminal_width, terminal_height);
		
#ifdef DEBUG
	if (debuglevel) {
		while ((cur_char = lowlevel_get_charmap ()))
			text_write_char (cur_char);
		cursor_move (0, cursor_y + 1);
	}
#endif /* DEBUG */
	
	debug (DEBUG_FUNCTION, "Leaving");
	return 0;
}

#ifndef DISABLE_RESIZE
int terminal_resize (int width, int height)
{
	int i, err;
	
	debug (DEBUG_FUNCTION, "Entering");
	
	/* Realloc tabstops */
	if (width > tabstops_width) {
		tabstops = realloc (tabstops, width * sizeof(char));
		if (tabstops == NULL) {
			error ("Tab stops realloc failed");
			debug (DEBUG_FUNCTION, "Leaving");
			return 1;
		}
		for (i=tabstops_width; i<width; i++) {
			tabstops[i] = (i%8 ? 0 : 1);
		}
		tabstops_width = width;
	}
	
	/* Extend buffer width if necessary */
	if (width+1 > buffer_width) {
		err = buffer_add_columns (width+1-buffer_width);
		if (err) {
			error ("buffer_add_columns() failed");
			debug (DEBUG_FUNCTION, "Leaving");
			return 1;
		}
	}
	/* Extend buffer height if necessary */
	if (height+history_lines > num_buffer_lines) {
		err = buffer_add_lines (height-terminal_height);
		if (err) {
			error ("buffer_add_lines() failed");
			debug (DEBUG_FUNCTION, "Leaving");
			return 1;
		}
	}
	debug (DEBUG_INIT, "Buffer size: %d lines of %d cells",
	       num_buffer_lines, buffer_width);
	/* Scroll buffer if necessary */
#warning FIXME: race condition! If the expose event arrives before we are finished, draw_area will cause a coredump
	if (first_visible_line + height > num_buffer_lines) {
		for (i=num_buffer_lines; i<first_visible_line+height; i++) {
			buffer_scroll_up (0, num_buffer_lines-1);
		}
		first_visible_line = num_buffer_lines - height;
	}
	if (width > terminal_width) {
		draw_area (terminal_width, 0, width-terminal_width, height);
	}
	if (height > terminal_height) {
		draw_area (0, terminal_height, width, height-terminal_height);
	}
	
	debug (DEBUG_INIT, "terminal_height=%d, num_buffer_lines=%d",
	       terminal_height, num_buffer_lines);
	/*region_top = 0;*/
	if (region_bottom == terminal_height) {
		region_bottom = height;
	}
	
	terminal_width  = width;
	terminal_height = height;
	
	debug (DEBUG_INIT, "Terminal is now %d x %d characters",
	       terminal_width, terminal_height);
	
	debug (DEBUG_FUNCTION, "Leaving");
	return 0;
}
#endif /* DISABLE_RESIZE */

void terminal_exit ()
{
	debug (DEBUG_FUNCTION, "called with no args");
	cursor_set_mode (CURSOR_HIDE);
	tabstop_exit ();
	history_exit ();
	debug (DEBUG_FUNCTION, "Leaving");
}

void terminal_send (unsigned char *buffer, size_t *buffer_size)
{
	int err;
#ifdef JUMP_SCROLL
	static size_t buf_size;
#else
	size_t buf_size;
#endif
	unsigned char *buf;

	debug (DEBUG_FUNCTION, "called with args (*buffer, *buffer_size)");

#ifdef JUMP_SCROLL
	if (*buffer_size == buf_size) {
		/* Nothing to do in this case, but a good time to catch up with
		   delayed scrolling */
		scroll_up_resync();
		if (blink()) {
			render_flush();
		}
		debug (DEBUG_FUNCTION, "Leaving");
		return;
	}
#endif

	cursor_set_mode (CURSOR_HIDE);
	buf = buffer;
	buf_size = *buffer_size;
	
	debug (DEBUG_OUTPUT, "Parsing program output: [%s]",
	       binary_to_printable (buf, (int)buf_size));
	
	/* if viewing history, back to last page */
	internal_page_last ();
	
	do {
		err = parse_special_chars (&buf, &buf_size);
		if (err == 1) {
			err = parse_regular_chars (&buf, &buf_size);
		}
		if (err == 1) {
			debug (DEBUG_OUTPUT, "Invalid byte, skipping it");
			buf++; buf_size--;
		}
	} while (buf_size > 0 && err < 2);
	cursor_unset_mode (CURSOR_HIDE);
	render_flush ();
	memmove (buffer, buf, buf_size);
	*buffer_size = buf_size;
	debug (DEBUG_FUNCTION, "Leaving");
}

void terminal_recv (unsigned char **buffer, size_t *size)
{
	struct timeval tv;
	ggi_event event;
	int i;

	debug (DEBUG_FUNCTION, "called with args (**buffer, *size)");
	tv.tv_sec = 0;
	tv.tv_usec = 0;

#ifndef JUMP_SCROLL
	if(blink ()) {
		render_flush();
	}
#endif
	
	if (ggiEventPoll (vis, emKeyPress|emKeyRepeat, &tv)) {
		i = ggiEventsQueued (vis, emKeyPress|emKeyRepeat);
		debug (DEBUG_INPUT, "Number of GGI events queued: %d", i);
		while (i > 0) {
		/*if (i) {*/
			ggiEventRead (vis, &event, emKeyPress|emKeyRepeat);
			i--;
			switch (event.any.type) {
			/*case evCommand:
				if (event.cmd.code == GGICMD_REQUEST_SWITCH) {*/
					/* We need to handle this for xrender to work */
					/*ggi_cmddata_switchrequest *req;
					char dummy[64];
					
					req = (ggi_cmddata_switchrequest *)&event.cmd.data;
					debug (DEBUG_INPUT, "Command event received for mode changing to:");
					ggiSPrintMode (dummy, &(req->mode));
					debug (DEBUG_INPUT, "Requested mode is: %s", dummy);
				}
				break;*/
			/*case evExpose:
				debug (DEBUG_INPUT,
				       "Expose event received for area %dx%d at (%d, %d)",
				       event.expose.w, event.expose.h,
				       event.expose.x, event.expose.y);
				redraw_area (event.expose.x, event.expose.y,
				             event.expose.w, event.expose.h);
				break;*/
			case evKeyRepeat:
			case evKeyPress:
				/* Reinject in the queue if buffer full */
				if (keyboard_handle_event (event)) {
					ggiEventSend (vis, &event);
					i = 0;
				}
				break;
			}
		}
	}
	buffer_get_and_reset (buffer, size);
	expose_handle_events ();
	debug (DEBUG_FUNCTION, "Leaving");
}
