/*
 *  acm - Virtual terminal
 *  Copyright (C) 2007 Umberto Salsi
 *
 *  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; version 2 dated June, 1991.
 *
 *  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., 675 Mass Ave., Cambridge, MA 02139, USA.
 */

#include <string.h>
#include "../util/memory.h"
#include "../V/Vlib.h"
#include "pm.h"

#define terminal_IMPORT
#include "terminal.h"

#define TAB_LEN 8

#define ROWS 20
#define COLS 40
#define KBD_BUF_SIZE 50

typedef struct _terminal_data {
	struct _terminal_data *next;

	_BOOL    show;  /* show the terminal on the viewport */

	int      cursor_row, cursor_col;
	char     screen[ROWS][COLS];

	char     kbd_buf[KBD_BUF_SIZE];  /* chars got from keyboard */
	int      kbd_n;                  /* no. of chars in keyboard buffer */
} terminal_data;


static terminal_data *free_list = NULL;

static _BOOL blink = FALSE;
static double blink_toggle_time = 0.0;


static void terminal_cleanup()
{
	terminal_data *trm;

	while( free_list != NULL ){
		trm = free_list;
		free_list = trm->next;
		memory_dispose(trm);
	}

	free_list = NULL;
}


static void terminal_clear(terminal_data *trm)
{
	memset(&trm->screen, ' ', ROWS*COLS);
	trm->cursor_row = 0;
	trm->cursor_col = 0;
}


static void terminal_scroll(terminal_data *trm)
{
	memmove(&trm->screen, &trm->screen[1], (ROWS-1)*COLS);
	memset(&trm->screen[ROWS-1], ' ', COLS);
}


static void terminal_putc(terminal_data *trm, int ch)
{
	int c;

	if( ch == '\r' ){
		trm->cursor_col = 0;
	
	} else if( ch == '\n' ){
		trm->cursor_row++;
		if( trm->cursor_row >= ROWS ){
			trm->cursor_row = ROWS-1;
			terminal_scroll(trm);
		}
	
	} else if( ch == '\t' ){
		c = trm->cursor_col;
		c += TAB_LEN;
		c -= c % TAB_LEN;
		trm->cursor_col = c;
		while( trm->cursor_col >= COLS ){
			trm->cursor_col -= COLS;
			trm->cursor_row += 1;
			if( trm->cursor_row >= ROWS ){
				trm->cursor_row = ROWS-1;
				terminal_scroll(trm);
			}
		}
	
	} else if( ch == '\010' /* BS */ ){
		if( trm->cursor_row == 0 && trm->cursor_col == 0 )
			return;
		trm->cursor_col--;
		if( trm->cursor_col < 0 ){
			trm->cursor_row--;
			trm->cursor_col = COLS-1;
		}

	} else if( ch >= 32 ){
		
		if( ch >= 127 )
			ch = '?';

		trm->screen[ trm->cursor_row ] [ trm->cursor_col ] = ch;

		trm->cursor_col++;
		if( trm->cursor_col >= COLS ){
			trm->cursor_col = 0;
			trm->cursor_row++;
			if( trm->cursor_row >= ROWS ){
				trm->cursor_row = ROWS-1;
				terminal_scroll(trm);
			}
		}
	
	} else {
		/* ignore */
	
	}
}


void terminal_write(viewer *u, char *s)
{
	terminal_data *trm;

	trm = (terminal_data *) u->terminal;
	while( *s != '\0' ){
		terminal_putc(trm, *s);
		s++;
	}
}


void terminal_enable(viewer *u)
{
	terminal_data *trm;

	if( u->terminal != NULL ){
		trm = (terminal_data *) u->terminal;
		trm->show = TRUE;
		return;
	}

	if( free_list == NULL ){
		memory_registerCleanup(terminal_cleanup);
		trm = memory_allocate( sizeof(terminal_data), NULL );
	} else {
		trm = free_list;
		free_list = free_list->next;
	}

	trm->show = TRUE;
	terminal_clear(trm);
	trm->kbd_n = 0;
	u->terminal = trm;

	terminal_write(u, "Terminal ready. Press F1 again to exit from the terminal. Type \"help\" to get help.\n\r");
}


void terminal_disable(viewer *u)
{
	terminal_data *trm;

	if( u->terminal == NULL )
		return;
	
	trm = (terminal_data *) u->terminal;
	trm->show = FALSE;
}


_BOOL terminal_enabled(viewer *u)
{
	return (u->terminal != NULL) && ((terminal_data *)u->terminal)->show;
}


void terminal_toggle(viewer *u)
{
	if( terminal_enabled(u) )
		terminal_disable(u);
	else
		terminal_enable(u);
}


void terminal_input_char(viewer *u, int ch)
{
	terminal_data * trm;

	if( ! terminal_enabled(u) )
		return;
	
	trm = (terminal_data *) u->terminal;

	if( (ch >= ' ' && ch <= 126) || (ch == '\r') || (ch == '\n') ){
		if( trm->kbd_n < KBD_BUF_SIZE ){
			if( ch ==  '\r' || ch == '\n' ){
				/* FIXME: set a flag telling we already have a complete line */
				trm->kbd_buf[ trm->kbd_n++ ] = '\0';
				terminal_write(u, "\r\n");
			} else if( trm->kbd_n <= KBD_BUF_SIZE-2 ){
				trm->kbd_buf[ trm->kbd_n++ ] = ch;
				terminal_putc(trm, ch);
			} else {
				/* keyboard buffer full */
			}
		} else {
			/* FIXME: keyboard buffer full */
		}
	} else if( ch == '\010' /* BS */ || ch == '\177' /* DEL */ ){
		if( trm->kbd_n > 0 && trm->kbd_buf[ trm->kbd_n - 1 ] != '\0' ){
			trm->kbd_n--;
			terminal_write(u, "\010 \010");
		}
	}
}


_BOOL terminal_read_string(viewer *u, char *s, int s_len)
{
	terminal_data *trm;
	char *eol;
	int n;

	if( s_len <= 0 )
		return FALSE;

	if( u->terminal == NULL )
		return FALSE;
	
	trm = (terminal_data *) u->terminal;

	if( trm->kbd_n == 0 )
		return FALSE;
	
	eol = (char *) memchr(&trm->kbd_buf, 0, trm->kbd_n);

	if( eol == NULL ){
		/* incomplete line */
		if( trm->kbd_n >= KBD_BUF_SIZE ){
			/* FIXME: always returns a full buffer as if it were a
			   complete line */
			n = trm->kbd_n;
			if( n >= s_len )
				n = s_len-1;
			memcpy(s, &trm->kbd_buf, n);
			s[n] = '\0';
			memmove(&trm->kbd_buf, &trm->kbd_buf[n], trm->kbd_n - n);
			trm->kbd_n -= n;
			return TRUE;
		}
		return FALSE; /* incomplete line */

	} else {
		/* complete line available */
		n = eol - &trm->kbd_buf[0];
		if( n < s_len ){
			memcpy(s, &trm->kbd_buf, n);
			s[n] = '\0';
			memmove(&trm->kbd_buf, eol+1, trm->kbd_n - n-1);
			trm->kbd_n -= n+1;
		} else {
			n = s_len-1;
			memcpy(s, &trm->kbd_buf, n);
			s[n] = '\0';
			memmove(&trm->kbd_buf, &trm->kbd_buf[n], trm->kbd_n - n);
			trm->kbd_n -= n;
		}
		return TRUE;
	}
}


void terminal_draw(viewer *u)
{
	double fh, fw, lh;
	terminal_data *trm;
	int row;
	Alib_Rect rect;

	if( ! terminal_enabled(u) )
		return;
	
	trm = (terminal_data *) u->terminal;

	if( ! trm->show )
		return;

	/* FIXME: dynamically determinate terminal size and font height
	   based on the current dimension of u->width, u->height */
	fh = 10.0;         /* font height */
	fw = fh;           /* font width */
	lh = fh + fh/2.0;  /* line height */

	Alib_setRect(&rect, 0, 0, (int)(COLS*fw+0.5), (int)(ROWS*lh+0.5));
	VSetClipRect(u->v, &rect);
	for( row=0; row<ROWS; row++ )
		VDrawStrokeString(u->v, 0, (int)(row*lh+fh+0.5),
			&(trm->screen[row][0]), COLS, (int)(fh+0.5), whiteColor);
	
	/* Set blink flag: */
	if( curTime >= blink_toggle_time ){
		blink = ! blink;
		if( blink )
			blink_toggle_time = curTime + 0.3;
		else
			blink_toggle_time = curTime + 0.7;
	}

	if( ! blink ){
		VDrawStrokeString(u->v,
			(int)(trm->cursor_col*fw+0.5), (int)(trm->cursor_row*lh+fh+0.5),
			"_", 1, (int)(fh+0.5), whiteColor);
	}
}


void terminal_free(viewer *u)
{
	terminal_data *trm;

	if( u->terminal == NULL )
		return;
	
	trm = (terminal_data *) u->terminal;
	trm->next = free_list;
	free_list = trm;
	u->terminal = NULL;
}
