/* Display handling functions with optional update delay.

   Copyright (C) 1993 Sebastiano Vigna

    This file is part of ne, the nice editor.

    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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA.

In other words, you are welcome to use, share and improve this program.
You are forbidden to forbid anyone else to use, share and improve
what you give them.   Help stamp out software-hoarding!  */

#include "ne.h"
#include "termchar.h"

/* The functions in this file act as an interface between the main code and the
raw screen updating functions of term.c. The basic idea is that one has a
series of functions which normally just call the basic functions; however, if
b->turbo is nonzero and more than b->turbo lines have been updated, the update
stops and is delayed to the next call to refresh_window(). This function should
be called whenever the screen has to be sync'd with its contents (for instance,
whenever the user gets back in control). The mechanism allows for fast,
responsive screen updates for short operations, and one-in-all updates for long
operations. */



/* If window_needs_refresh, the window has to be refreshed from scratch,
starting from first_line and ending on last_line. Update calls keeps
track of the number of lines updated. If this number becomes greater
than b->turbo, and the turbo flag is set, we enter turbo mode. */

static window_needs_refresh, first_line, last_line, updated_lines;



/* This function prints a line descriptor at coordinates y,x starting from a
given x position, and continuing for at most len characters. The TABs are
expanded and considered in the computation of the position. A NULL string is
a valid argument, and represents an empty string. start and len are not
constrained by the length of the string (the string is really considered as
terminating with an infinite sequence of spaces). cleared_at_end has the same
meaning as in update_partial_line(). */

void mvaddstrn(int y, int x, line_desc *ld, int start, int len, int tab_size, int cleared_at_end) {
	int i,j,pos, cursor_moved = FALSE;
	unsigned char *s = ld->line;

	assert(ld != NULL);
	assert(y<lines && x<columns);


	for(i=pos=0; i<start+len && pos<ld->line_len; i++,pos++) {
		if (*s == '\t') {
			for(j=0; j < tab_size - i%tab_size; j++)
				if (i+j>=start && i+j<start+len) {
					if (!cursor_moved) {
						cursor_moved = TRUE;
						move_cursor(y,x);
					}
					output_chars(NULL, 1);
				}
			i += j-1;
		}
		else if (i>=start) {
			if (!cursor_moved) {
				cursor_moved = TRUE;
				move_cursor(y,x);
			}
			output_chars(s, 1);
		}
		s++;
	}

	if (i<start+len && !cleared_at_end) {
		if (!cursor_moved) {
			cursor_moved = TRUE;
			move_cursor(y,x);
		}
		clear_to_eol();
	}
}

/* This function updates part of a line given its number and a starting x
position. It can handle lines with no associated line descriptor (such as
lines after the end of the buffer). It checks for updated_lines bypassing
b->turbo. if cleared_at_end is TRUE, this function assumes that
it doesn't have to clean up the rest of the line if the string is
not long enough to fill the line. */

void update_partial_line(buffer *b, int n, int start_x, int cleared_at_end) {
	int i;
	line_desc *ld = b->top_line_desc;

	assert(n<lines-1);
	assert_line_desc(ld);

	if (b->turbo && ++updated_lines > b->turbo) window_needs_refresh = TRUE;

	if (window_needs_refresh) {
		if (n<first_line) first_line = n;
		if (n>last_line) last_line = n;
		return;
	}

	for(i=0; i<n && ld->ld_node.next; i++) ld = (line_desc *)ld->ld_node.next;

	if (!ld->ld_node.next) {
		move_cursor(n, start_x);
		clear_to_eol();
		return;
	}

	mvaddstrn(n, start_x, ld, start_x+b->win_x, columns-start_x, b->tab_size, cleared_at_end);
}


/* Similar to the previous call, but updates the whole line. */

void update_line(buffer *b, int n) {

	update_partial_line(b, n, 0, FALSE);

}



/* This function updates the text window between given lines. If doit is not
true, b->turbo is nonzero, and the number of lines that have been
updated bypasses b->turbo, the update is not done. Rather,
first_line, last_line and window_needs_refresh record that some refresh is
needed, and from where it should be done. Setting doit to TRUE forces a real
update. Generally, doit should be FALSE. */

void update_window_lines(buffer *b, int start_line, int end_line, int doit) {

	int i;
	line_desc *ld = b->top_line_desc;

	assert_line_desc(ld);

	window_needs_refresh = TRUE;

	if (first_line > start_line) first_line = start_line;
	if (last_line < end_line) last_line = end_line;

	if (b->turbo && (updated_lines += (end_line-start_line+1)) > b->turbo && !doit) return;

	for(i=0; i<=last_line && i+b->win_y<b->line_num; i++) {

		assert(ld->ld_node.next != NULL);

		if (i >= first_line) mvaddstrn(i, 0, ld, b->win_x, columns, b->tab_size, FALSE);
		ld = (line_desc *)ld->ld_node.next;
	}

	for( ; i<=last_line; i++) {
		move_cursor(i, 0);
		clear_to_eol();
	}

	window_needs_refresh = FALSE;
	first_line = lines;
	last_line = -1;
}


/* This function is like the previous one, but it updates the whole window,
and never forces an update. */

void update_window(buffer *b) {

	update_window_lines(b, 0, lines-2, FALSE);

}


/* The following functions update a character on the screen. Three operations
are supported---insertion, deletion, overwriting. The semantics is a bit involved.
Essentially, they should be called *immediately* after the modification has been
done. They assume that the video is perfectly up to date, and that only the
given modification has been performed. Thus, for instance, update_inserted_char()
assumes that ld->line[pos] contains the inserted character. The tough part is
expanding/contracting the tabs following the modified position in such a way to
maintain them consistent. Moreover, a lot of special cases are considered and
optimized (such as writing a space at the end of a line). b->turbo is considered. */


void update_deleted_char(buffer *b, unsigned char c, line_desc *ld, int pos, int line, int x) {

	int i, j, width, old_width;

	if (b->turbo && ++updated_lines > b->turbo) window_needs_refresh = TRUE;

	if (window_needs_refresh) {
		if (line<first_line) first_line = line;
		if (line>last_line) last_line = line;
		return;
	}

	if (pos > ld->line_len || (pos == ld->line_len && (c == '\t' || c == ' '))) return;

	move_cursor(line, x);

	if (c == '\t') width = b->tab_size - x % b->tab_size;
	else width = 1;


	if (c != '\t' && (pos == ld->line_len || (ld->line[pos] == '\t' && (x+1) % b->tab_size))) {

		/* Just overwrite the deleted char if it is followed by an expandable tab. */

		output_chars(NULL, 1);
		return;
	}

	if (!char_ins_del_ok) {

		/* Argh! We can't insert or delete! Just update the rest of the line. */

		update_partial_line(b, line, x, FALSE);
		return;
	}

	/* Now we search for a visible tab. If none is found, we just delete
	width characters and update the end of the line. */

	for(i=x, j=pos; i<columns && j<ld->line_len; i++,j++) {

		if (ld->line[j] == '\t') {

			old_width = b->tab_size - (i+width) % b->tab_size;

			if (width+old_width > b->tab_size) {

				delete_chars(width);

				if (width != b->tab_size) {
					move_cursor(line, i);
					delete_chars(b->tab_size-width);
				}

				update_partial_line(b, line, columns-b->tab_size, TRUE);
			}
			else {
				/* Note that this is slower than inserting and
				deleting, but MUCH nicer to see. */

				output_chars(&ld->line[pos], j-pos);
				output_chars(NULL, width);
			}
			return;
		}
	}

	delete_chars(width);
	update_partial_line(b, line, columns-width, TRUE);
}


/* See comments for update_deleted_char(). */

void update_inserted_char(buffer *b, line_desc *ld, int pos, int line, int x) {

	int i, j, width, old_width;
	unsigned char new_char = ld->line[pos];

	assert(pos < ld->line_len);

	if (b->turbo && ++updated_lines > b->turbo) window_needs_refresh = TRUE;

	if (window_needs_refresh) {
		if (line<first_line) first_line = line;
		if (line>last_line) last_line = line;
		return;
	}

	if (pos == ld->line_len && (new_char == '\t' || new_char == ' ')) return;

	move_cursor(line, x);

	if (new_char == '\t') width = b->tab_size - x % b->tab_size;
	else width = 1;

	if (pos == ld->line_len-1 || (new_char != '\t' && ld->line[pos+1] == '\t' && (x+1) % b->tab_size)) {
		if (new_char != '\t' && new_char != ' ') output_chars(&new_char, 1);
		return;
	}

	if (!char_ins_del_ok) {
		update_partial_line(b, line, x, FALSE);
		return;
	}

	for(i=x+width, j=pos+1; i<columns && j<ld->line_len; i++,j++) {

		if (ld->line[j] == '\t') {

			old_width = b->tab_size - (i-width) % b->tab_size;

			if (old_width > width) {

				if (new_char == '\t') output_chars(NULL, width);
				else output_chars(&new_char, 1);

				output_chars(&ld->line[pos+1], j-(pos+1));
			}
			else {
				insert_chars(new_char == '\t' ? NULL : &new_char, width);
				move_cursor(line, i);
				insert_chars(NULL, b->tab_size-width);
			}
			return;
		}
	}

	insert_chars(new_char == '\t' ? NULL : &new_char, width);
}


/* See comments for update_deleted_char(). */

void update_overwritten_char(buffer *b, unsigned char old_char, line_desc *ld, int pos, int line, int x) {

	int i, j, width, new_width, old_width;
	unsigned char new_char = ld->line[pos];

	assert(ld != NULL);
	assert(pos < ld->line_len);

	if (b->turbo && ++updated_lines > b->turbo) window_needs_refresh = TRUE;

	if (window_needs_refresh) {
		if (line<first_line) first_line = line;
		if (line>last_line) last_line = line;
		return;
	}

	if (old_char == '\t') old_width = b->tab_size - x % b->tab_size;
	else old_width = 1;

	if (new_char == '\t') new_width = b->tab_size - x % b->tab_size;
	else new_width = 1;

	move_cursor(line, x);

	if (old_width == new_width) {
		if (old_char != new_char) {
			if (new_char == '\t') output_chars(NULL, old_width);
			else output_chars(&new_char, 1);
		}
		return;
	}

	if (!char_ins_del_ok) {
		update_partial_line(b, line, x, FALSE);
		return;
	}

	output_chars(new_char == '\t' ? NULL : &new_char, 1);

	if (old_width > new_width) {

		width = old_width-new_width;

		for(i=x, j=pos; i<columns && j<ld->line_len; i++,j++) {

			if (ld->line[j] == '\t') {

				old_width = b->tab_size - (i+width) % b->tab_size;

				if (width+old_width > b->tab_size) {

					delete_chars(width);

					if (width != b->tab_size) {
						move_cursor(line, i);
						delete_chars(b->tab_size-width);
					}

					update_partial_line(b, line, columns-b->tab_size, TRUE);
				}
				else {
					output_chars(&ld->line[pos], j-pos);
					output_chars(NULL, width);
				}
				return;
			}
		}

		delete_chars(width);
		update_partial_line(b, line, columns-width, TRUE);
	}
	else {
		width = new_width-old_width;

		for(i=x+width, j=pos+1; i<columns && j<ld->line_len; i++,j++) {

			if (ld->line[j] == '\t') {

				old_width = b->tab_size - (i-width) % b->tab_size;

				if (old_width > width) {

					if (new_char == '\t') output_chars(NULL, width);
					else output_chars(&new_char, 1);

					output_chars(&ld->line[pos+1], j-(pos+1));
				}
				else {
					insert_chars(new_char == '\t' ? NULL : &new_char, width);
					move_cursor(line, i);
					insert_chars(NULL, b->tab_size-width);
				}
				return;
			}
		}
		insert_chars(new_char == '\t' ? NULL : &new_char, width);
	}
}


/* This function completely resets the terminal status, updating the whole
window and resetting the status bar. It *never* does any real update; it is
just used to mark that the window and the status bar have to be completely
rebuilt. */


void reset_window(void) {

	window_needs_refresh = TRUE;
	first_line = 0;
	last_line = lines-2;
	reset_status_bar();
}


/* This function forces the screen update. It should be called whenever
the user has to interact, so that he is presented with a correctly
updated display. */

void refresh_window(buffer *b) {

	if (window_needs_refresh)
		update_window_lines(b, first_line, last_line, TRUE);

	updated_lines = 0;
}



/* This function scrolls a region starting at a given line upward (n == -1)
or downward (n == -1). b->turbo is checked. */

void scroll_window(buffer *b, int line, int n) {

	assert(n == -1 || n == 1);
	assert(line >= 0);
	assert(line < lines);

	if (line_ins_del_ok) {
		if (b->turbo && updated_lines++ > b->turbo || window_needs_refresh) {
			window_needs_refresh = TRUE;
			if (first_line > line) first_line = line;
			last_line = lines-2;
			return;
		}
	}
	else {
		/* Argh! We can't insert or delete lines. The only chance is
		rewriting the last lines of the screen. */

		update_window_lines(b, line, lines-2, FALSE);
		return;
	}

	if (n>0) {
		ins_del_lines(line, 1);
		update_line(b, line);
	}
	else {
		ins_del_lines(line, -1);
		update_line(b, lines-2);
	}
}
