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

  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 "terminal.h"


typedef struct {
	cell_t	*cell;
	int	length;
	int	blinking;
} history_line_t;

history_line_t *history_line;
cell_t cell_null;
int num_history_lines, history_width;
int first_visible_line, num_visible_lines;
int saved_first_visible_line;
int history_page;


void history_delete_chars (int column, int line, unsigned int nb_chars)
{
	int i;
	
	debug (DEBUG_FUNCTION,
	       "called with args (column=%d, line=%d, nb_chars=%d)",
	       column, line, nb_chars);
	line += first_visible_line;
	debug (DEBUG_HISTORY,
	       "Deleting %d chars at buffer coords (%d,%d)",
	       nb_chars, column, line);
	/* Note: the case where nb_chars>history_width is handled OK this way */
	for (i=column; i<history_width-nb_chars; i++) {
		if (history_line[line].cell[i].mode & MODE_BLINK) {
			history_line[line].blinking--;
		}
		history_line[line].cell[i] = history_line[line].cell[i+nb_chars];
	}
	for (; i<history_width; i++) {
		history_line[line].cell[i] = cell_null;
	}
	history_line[line].length -= nb_chars;
	debug (DEBUG_FUNCTION, "Leaving");
}

void history_insert_chars (int column, int line, unsigned int nb_chars)
{
	int i;
	
	debug (DEBUG_FUNCTION,
	       "called with args (column=%d, line=%d, nb_chars=%d)",
	       column, line, nb_chars);
	line += first_visible_line;
	debug (DEBUG_HISTORY,
	       "Inserting %d chars at buffer coords (%d,%d)",
	       nb_chars, column, line);
	for (i=history_width-1 ; i>column+nb_chars-1 ;i--) {
		history_line[line].cell[i] = history_line[line].cell[i-nb_chars];
	}
	for (i=column+nb_chars-1 ; i>column-1 ;i--) {
		history_line[line].cell[i] = cell_null;
	}
	history_line[line].length += nb_chars;
	debug (DEBUG_FUNCTION, "Leaving");
}

void history_erase_chars (int column, int line, unsigned int nb_chars)
{
	int i;
	
	debug (DEBUG_FUNCTION,
	       "called with args (column=%d, line=%d, nb_chars=%d)",
	       column, line, nb_chars);
	line += first_visible_line;
	debug (DEBUG_HISTORY,
	       "Erasing %d chars at buffer coords (%d,%d)",
	       nb_chars, column, line);
	for (i=column ; i<column+nb_chars ;i++) {
		if (history_line[line].cell[i].mode & MODE_BLINK) {
			history_line[line].blinking--;
		}
		history_line[line].cell[i] = cell_null;
	}
	debug (DEBUG_FUNCTION, "Leaving");
}

void history_erase_line_from (int column, int line)
{
	int i;
	
	debug (DEBUG_FUNCTION,
	       "called with args (column=%d, line=%d)",
	       column, line);
	line += first_visible_line;
	debug (DEBUG_HISTORY,
	       "Erasing buffer line %d from char %d", line, column);
	for (i=column ; i<history_width ;i++) {
		if (history_line[line].cell[i].mode & MODE_BLINK) {
			history_line[line].blinking--;
		}
		history_line[line].cell[i] = cell_null;
	}
	history_line[line].length = column;
	debug (DEBUG_FUNCTION, "Leaving");
}

void history_erase_line_to (int column, int line)
{
	int i;
	
	debug (DEBUG_FUNCTION,
	       "called with args (column=%d, line=%d)",
	       column, line);
	line += first_visible_line;
	debug (DEBUG_HISTORY,
	       "Erasing buffer line %d up to char %d", line, column);
	for (i=0 ; i<=column ;i++) {
		if (history_line[line].cell[i].mode & MODE_BLINK) {
			history_line[line].blinking--;
		}
		history_line[line].cell[i] = cell_null;
	}
	debug (DEBUG_FUNCTION, "Leaving");
}

void history_erase_line (int line)
{
	int i;
	
	debug (DEBUG_FUNCTION, "called with args (line=%d)", line);
	line += first_visible_line;
	debug (DEBUG_HISTORY, "Erasing history line %d", line);
	for (i=0 ; i<history_width ;i++) {
		history_line[line].cell[i] = cell_null;
	}
	history_line[line].length = 0;
	history_line[line].blinking = 0;
	debug (DEBUG_FUNCTION, "Leaving");
}

int history_page_up ()
{
	if (first_visible_line == 0)
		return history_page;
	if (saved_first_visible_line == -1)
		saved_first_visible_line = first_visible_line;
	if (first_visible_line >= num_visible_lines) {
		first_visible_line -= num_visible_lines;
	} else {
		first_visible_line = 0;
	}
	history_page++;
	return history_page;
}

int history_page_down ()
{
	if (saved_first_visible_line == -1)
		return history_page;
	first_visible_line += num_visible_lines;
	if (first_visible_line >= saved_first_visible_line) {
		first_visible_line = saved_first_visible_line;
		saved_first_visible_line = -1;
	}
	history_page--;
	return history_page;
}

void history_scroll_up (int count)
{
	int i;
	history_line_t dummy_line;
	
	debug (DEBUG_FUNCTION, "called with args (count=%d)", count);
	while (first_visible_line+num_visible_lines < num_history_lines && count) {
		count--;
		first_visible_line++;
		debug (DEBUG_HISTORY,
		       "First visible line = %d", first_visible_line);
	}
	while (count) {
		count--;
		debug (DEBUG_HISTORY, "Scrolling up whole buffer");
		dummy_line = history_line[0];
		for (i=0; i<num_history_lines-1; i++) {
			history_line[i] = history_line[i+1];
		}
		history_line[num_history_lines-1] = dummy_line;
		history_erase_line (num_history_lines-1-first_visible_line);
	}
	debug (DEBUG_FUNCTION, "Leaving");
}

void history_scroll_up_region (int first_line, int last_line, int count)
{
	int i;
	history_line_t dummy_line;
	
	debug (DEBUG_FUNCTION,
	       "called with args (first_line=%d, last_line=%d)",
	       first_line, last_line);
	first_line += first_visible_line;
	last_line += first_visible_line;
	while (count) {
		count--;
		debug (DEBUG_HISTORY,
		       "Scrolling up history lines %d-%d", first_line, last_line-1);
		dummy_line = history_line[first_line];
		for (i=first_line; i<last_line-1; i++) {
			history_line[i] = history_line[i+1];
		}
		history_line[last_line-1] = dummy_line;
		history_erase_line (last_line-1-first_visible_line);
	}
	debug (DEBUG_FUNCTION, "Leaving");
}

void history_scroll_down_region (int first_line, int last_line, int count)
{
	int i;
	history_line_t dummy_line;
	
	debug (DEBUG_FUNCTION,
	       "called with args (first_line=%d, last_line=%d)",
	       first_line, last_line);
	first_line += first_visible_line;
	last_line += first_visible_line;
	while (count) {
		count--;
		debug (DEBUG_HISTORY,
		       "Scrolling down buffer lines %d-%d",
		       first_line, last_line-1);
		dummy_line = history_line[last_line-1];
		for (i=last_line-1; i>first_line; i--) {
			history_line[i] = history_line[i-1];
		}
		history_line[first_line] = dummy_line;
		history_erase_line (first_line-first_visible_line);
	}
	debug (DEBUG_FUNCTION, "Leaving");
}

cell_t history_get_char (int column, int line)
{
	debug (DEBUG_FUNCTION,
	       "called with args (column=%d, line=%d)", column, line);
	line += first_visible_line;
	debug (DEBUG_HISTORY,
	       "Returning char at buffer coords (%d,%d)", column, line);
	debug (DEBUG_FUNCTION, "Leaving");
	return history_line[line].cell[column];
}

void history_get_line (int line, cell_t **cell_p, int *length, int *has_blink)
{
	debug (DEBUG_FUNCTION,
	       "called with args (line=%d)", line);
	line += first_visible_line;
	debug (DEBUG_HISTORY,
	       "Length of history line %d: %d",
	       line, history_line[line].length);
	*cell_p = history_line[line].cell;
	*length = history_line[line].length;
	*has_blink = history_line[line].blinking;
	debug (DEBUG_FUNCTION, "Leaving");
}

void history_write_char (int column, int line, cell_t new_char)
{
	debug (DEBUG_FUNCTION,
	       "called with args (column=%d, line=%d, new_char=0x%X)",
	       column, line, (unsigned int)new_char.charcode);
	line += first_visible_line;
	debug (DEBUG_HISTORY,
	       "Writing char 0x%X at buffer coords (%d,%d)",
	       (unsigned int)new_char.charcode, column, line);
	history_line[line].cell[column] = new_char;
	if (column >= history_line[line].length)
		history_line[line].length = column+1;
	if (history_line[line].cell[column].mode & MODE_BLINK) {
		history_line[line].blinking++;
	}
	debug (DEBUG_FUNCTION, "Leaving");
}

int history_init (int width, int height, int history, int bg, int fg)
{
	long memory_size;
	int i;
	
	debug (DEBUG_FUNCTION,
	       "called with args (width=%d, height=%d, history=%d, bg=%d, fg=%d)",
	       width, height, history, bg, fg);
	
	cell_null.charcode = 32;
	cell_null.bgcolor = bg;
	cell_null.fgcolor = fg;
	cell_null.mode = 0;
	
	num_visible_lines = height;
	num_history_lines = num_visible_lines + history;
	first_visible_line = 0;
	saved_first_visible_line = -1;
	history_page = 0;
	history_width = width;
	memory_size = (num_history_lines) * sizeof (history_line_t);
	history_line = malloc (memory_size);
	if (history_line == NULL) {
		debug (DEBUG_FUNCTION, "Leaving with error");
		return 1;
	}
	for (i=0; i<num_history_lines; i++) {
		history_line[i].cell = malloc (history_width * sizeof (cell_t));
		if (history_line[i].cell == NULL) {
			/* free the already allocated memory */
			while (i--) free (history_line[i].cell);
			free (history_line);
			debug (DEBUG_FUNCTION, "Leaving with error");
			return 1;
		}
		memory_size += history_width * sizeof (cell_t);
		history_erase_line (i-first_visible_line);
	}
	debug (DEBUG_INIT,
	       "History buffer size is %dx%d cells", history_width, num_history_lines);
	debug (DEBUG_INIT, "Using %ld bytes of memory", memory_size);
	debug (DEBUG_FUNCTION, "Leaving");
	return 0;
}

void history_exit ()
{
	int i;
	
	debug (DEBUG_FUNCTION, "called with no args");
	for (i=0; i<num_history_lines; i++) {
		free (history_line[i].cell);
	}
	free (history_line);
	debug (DEBUG_FUNCTION, "Leaving");
}

#ifndef DISABLE_RESIZE
int history_add_columns (int columns)
{
	int i, j;
	long memory_size = 0;
	
	debug (DEBUG_FUNCTION, "Entering");
	for (i=0; i<num_history_lines; i++) {
		history_line[i].cell = realloc (history_line[i].cell,
		                               (history_width+columns) *
		                               sizeof (cell_t));
		if (history_line[i].cell == NULL) {
			debug (DEBUG_FUNCTION, "Leaving with error");
			return 1;
		}
		memory_size += columns * sizeof (cell_t);
		for (j=history_width; j<history_width+columns; j++) {
			history_line[i].cell[j] = cell_null;
		}
	}
	history_width += columns;
	debug (DEBUG_INIT, "%9ld bytes added for history buffer", memory_size);
	debug (DEBUG_FUNCTION, "Leaving");
	return 0;
}

int history_add_lines (int lines)
{
	long memory_size;
	int i, old_num_history_lines;
	
	debug (DEBUG_FUNCTION, "Entering", lines);
	old_num_history_lines = num_history_lines;
	history_line = realloc (history_line,
	                       (old_num_history_lines+lines) *
	                       sizeof (history_line_t));
	if (history_line == NULL) {
		debug (DEBUG_FUNCTION, "Leaving with error");
		return 1;
	}
	memory_size = lines * sizeof (history_line_t);
	for (i=old_num_history_lines; i<old_num_history_lines+lines; i++) {
		history_line[i].cell = malloc (history_width * sizeof (cell_t));
		if (history_line[i].cell == NULL) {
			debug (DEBUG_FUNCTION, "Leaving with error");
			return 1;
		}
		memory_size += history_width * sizeof (cell_t);
		history_erase_line (i-first_visible_line);
		num_history_lines ++;
	}
	debug (DEBUG_INIT, "%9ld bytes added for history buffer", memory_size);
	debug (DEBUG_INIT, "num_history_lines=%d", num_history_lines);
	debug (DEBUG_FUNCTION, "Leaving");
	return 0;
}
#endif /* DISABLE_RESIZE */
