/*======================================================================*\
|*		Editor mined						*|
|*		text buffer functions					*|
\*======================================================================*/

#include "mined.h"


/*======================================================================*\
|*			Data section					*|
\*======================================================================*/

FLAG tab_left = False;	/* Set if moving up/down on TAB should go left */
FLAG tab_right = False;	/* Set if moving up/down on TAB should go left */


/*======================================================================*\
|*			Text buffer routines				*|
\*======================================================================*/

#define dont_debug_move

FLAG highlighting = True;

static
void
update_highlighting_marks (old_line, old_text, new_line, new_text)
  LINE * old_line;
  char * old_text;
  LINE * new_line;
  char * new_text;
{
  if (highlighting) {
	
  }
}

int old_x = 0;	/* previous x position */

static
int
iscombined_display (unichar, charpos, linebegin)
  unsigned long unichar;
  char * charpos;
  char * linebegin;
{
  return iscombined (unichar, charpos, linebegin) &&
	 ! (separate_isolated_combinings && (charpos == linebegin || * (charpos - 1) == '\t'));
}

/*
 * Find_x () returns the x coordinate belonging to address.
 * (Tabs are expanded).
 */
int
find_x (line, address)
  LINE * line;
  char * address;
{
  char * textp = line->text;
  register int x_left = line->shift_count * - SHIFT_SIZE;
  int x_in_line = 0;	/* must start from 0 to calculate correct 
			tab positions (since SHIFT_SIZE is not guaranteed 
			to be a multiple of tabsize (usually 8)) */
	/* Alright, SHIFT_SIZE is now guaranteed to be a multiple of 8 
	   due to lots of display problems related to that matter.
	   Leave this code anyway. */
  unsigned long unichar;
  int x_before = 0;
  int prevx;

  while (textp < address && * textp != '\0') {
	/* use "<" rather than "!=" because address may point amidst 
	   a UTF-8 character sequence after mode switching */
	prevx = x_in_line;
	advance_char_scr (& textp, & x_in_line, line->text);
	/* determine screen pos. to stay on if moving on a combining char. */
	if (x_in_line > prevx) {
		x_before = prevx;
	}
  }

  /* if on combining character, skip to base character */
  if (combining_mode && encoding_has_combining ()) {
	unichar = unicodevalue (textp);
	if (iscombined_display (unichar, textp, line->text)) {
		x_in_line = x_before;
	}
  }

#ifdef debug_move
  printf ("find_x %08X -> %d\n", address, x_in_line + x_left);
#endif
  return x_in_line + x_left;
}

/*
 * Find_address () returns the pointer in the line with given offset.
 * (Tabs are expanded).
 * find_address is only called by move_it ()
tab (cnt)		is	(((cnt) + tabsize) & ~07)
is_tab (c)		is	((c) == '\t')
 */
static
char *
find_address (line, new_x, cur_x)
  LINE * line;
  int new_x;
  int * cur_x;
{
  char * textp = line->text;
  int tx = line->shift_count * - SHIFT_SIZE;
  char * prev_poi;
  int prev_x;
  unsigned long unichar;

#define navigate_tab_to_closer_side

#ifdef debug_move
  printf ("find_address (old %d) new %d (from %d)\n", old_x, new_x, * cur_x);
#endif
  while (tx < new_x && * textp != '\n') {
	if (is_tab (* textp)) {
		if (new_x == old_x /* (* cur_x) */ - 1 && tab (tx) > new_x) {
			/* Moving left over tab */
#ifdef debug_move
			printf ("tab break 1\n");
#endif
			break;
		} else if (tab_left && new_x == old_x && tab (tx) > new_x) {
			/* Moving up/down on tab; stay left */
#ifdef debug_move
			printf ("tab break 2\n");
#endif
			break;
		} else if (! tab_right && new_x == old_x
#ifdef navigate_tab_to_closer_side
			   && tab (tx) - * cur_x > * cur_x - tx
#else
			   && tab (tx) - new_x > tabsize / 2
#endif
			   && tab (tx) > old_x /* prevent sticking left */
			  ) {
			/* Moving up/down on tab; stay left */
#ifdef debug_move
			printf ("tab break 3\n");
#endif
			break;
		} else {
			tx = tab (tx);
		}
		textp ++;
	} else {
		prev_poi = textp;
		prev_x = tx;

		advance_char_scr (& textp, & tx, line->text);

		/* skip over combining characters */
		if (combining_mode && encoding_has_combining ()) {
			unichar = unicodevalue (textp);
			while (iscombined_display (unichar, textp, line->text)) {
				advance_char_scr (& textp, & tx, line->text);
				unichar = unicodevalue (textp);
			}
		}

		if (tx > new_x && new_x < * cur_x && new_x < old_x) {
			/* moving left into wide character; stay before it */
			textp = prev_poi;
			tx = prev_x;
			new_x = tx;
		} else if (tab_left && tx > new_x && new_x == old_x) {
			/* moving up/down on wide character; stay left */
			textp = prev_poi;
			tx = prev_x;
			new_x = tx;
		}
	}
  }
#ifdef debug_move
  printf ("find_address: (old %d) new %d (from %d) to %d -> %08X\n", 
	  old_x, new_x, * cur_x, tx, textp);
#endif
  * cur_x = tx;
  return textp;
}

/*
 * move_to: move to given coordinates on screen.
 * move_y: move to given line on screen, staying in last explicit column.
 * move_address: move to given line at given text position.
 * The caller must check that scrolling is not needed.
 * If new x-position is < 0 or > XBREAK, move_it () will check if
 * the line can be shifted. If it can it sets (or resets) the shift_count
 * field of the current line accordingly. By this mechanism, the
 * pseudo-x-positions LINE_START / LINE_END (a very small / big value)
 * perform the appropriate positioning actions.
 * Move also sets cur_text to the right char.
 * "If we're moving to the same x coordinate, try to move to the x-coordinate
 * used on the other previous call." -- This worked erroneously and was 
 * replaced by an explicit old_x variable and move_y call.
 * move_address is directly called by move_next/previous_word(), 
 * re_search(), RDwin(), load_file(), justi(), JUSandreturn()
 */
static
void
move_it (new_x, new_address, new_y)
  register int new_x;
  int new_y;
  char * new_address;
{
  register LINE * line = cur_line;	/* For building new cur_line */
  register int lineno = line_number;
  int shift = 0;			/* How many shifts to make */
  char * utf_search;
  char * utf_prev;
  char * char_begin;

/*  static int rel_x = 0;	*/	/* Remember relative x position */
/*	This was used as a trick to stay virtually in the previous column 
	even when moving across shorter lines; but it had >= 2 errors.
	Renamed to old_x, made globally accessible and explicitly used 
	by appropriate calls to avoid these problems. TW */
  int tx = x;

/* Check for illegal values */
  if (new_y < 0 || new_y > last_y) {
	return;
  }

/* Adjust y-coordinate and cur_line */
  if (new_y < y) {
	while (y != new_y) {
		y --;
		line = line->prev;
		lineno --;
	}
  } else {
	while (y != new_y) {
		y ++;
		line = line->next;
		lineno ++;
	}
  }

/* Set or unset relative x-coordinate */
  if (new_address == NIL_PTR) {
	new_address = find_address (line, new_x, & tx);
	new_x = tx;
  } else {
	/* rel_x = */ new_x = find_x (line, new_address);
  }

/* Adjust on character boundary */
  if (cjk_text) {
	char_begin = charbegin (line->text, new_address);
	if (char_begin != new_address) {
		/* adjust position which is currently within a 
		   CJK double-width, multi-byte character */
		if (new_x >= x) {
			if (* new_address != '\n') {
				new_x ++;
				new_address = char_begin;
				advance_char (& new_address);
			} else {
				/* incomplete CJK half character */
			}
		} else {
			new_x --;
			new_address = char_begin;
		}
	}
  } else if (utf8_text) {
	if ((* new_address & 0xC0) == 0x80) {	/* UTF-8 sequence byte? */
	/* adjust position which is currently within a UTF-8 character */
	/* adjust new_x for multi-width characters ? */
		utf_search = line->text;
		utf_prev = utf_search;
		while (utf_search < new_address) {
			utf_prev = utf_search;
			advance_utf8 (& utf_search);
		}
		if (utf_search != new_address) {
			if (new_x >= x) {
				new_address = utf_search;
			} else {
				new_address = utf_prev;
			}
		}
	}
  }

/* Adjust shift_count if new_x lower than 0 or higher than XBREAK */
/* Allow adjustment also if new_x == 0 to enable left shift mark */
  if (new_x <= 0
      || new_x >= XBREAK
      || (new_x == XBREAK - 1 && iswide (unicodevalue (new_address)))
     ) {
	if (new_x > XBREAK
	    || (new_x == XBREAK && * new_address != '\n')
	    || new_x == XBREAK - 1
	   ) {
		shift = (new_x - XBREAK) / SHIFT_SIZE + 1;
	} else {
		shift = new_x / SHIFT_SIZE;
		if (new_x % SHIFT_SIZE) {
			shift --;
		}
		if (new_x == 0 && line->shift_count != 0 && marker_defined (SHIFT_BEG_marker, UTF_SHIFT_BEG_marker)) {
			/* do not stay on left shift marker; adjust line */
			shift --;
		}
	}

	if (shift != 0) {
		line->shift_count += shift;
		new_x = find_x (line, new_address);
		if (new_x == 0 && line->shift_count != 0 && marker_defined (SHIFT_BEG_marker, UTF_SHIFT_BEG_marker)) {
			/* do not stay on left shift marker; adjust line */
			line->shift_count --;
			new_x = find_x (line, new_address);
		}
		print_line (y, line);
		/* rel_x = new_x; */
	}
  }

/* Update internal highlighting marks */
  update_highlighting_marks (cur_line, cur_text, line, new_address);

/* Assign and position cursor */
  x = new_x;
  cur_text = new_address;
  cur_line = line;
  calc_line_no ();
  line_number = lineno;
  set_cursor_xy ();
}

void
move_y (ny)
  register int ny;
{
  move_it (old_x, NIL_PTR, ny);
}

void
move_to (nx, ny)
  register int nx;
  register int ny;
{
  old_x = x;
  move_it (nx, NIL_PTR, ny);
  old_x = x;
}

void
move_address (nadd, ny)
  register char * nadd;
  register int ny;
{
  old_x = x;
  move_it (0, nadd, ny);
  old_x = x;
}

void
move_address_w_o_RD (nadd, ny)
  register char * nadd;
  register int ny;
{
  old_x = x;
  move_it (0, nadd, ny);
  old_x = x;
}


/*
 * Just count to determine the current line number.
 */
void
calc_line_no ()
{
  register LINE * line;
  register int line_num = 0;

	for (line = header->next; line != cur_line; line = line->next) {
		line_num ++;
	}
	line_number = line_num + 1;
}

/*
 * Proceed returns the count'th line after 'line'. When count is negative
 * it returns the count'th line before 'line'. When the next (previous)
 * line is the tail (header) indicating EOF (TOF) it stops.
 */
LINE *
proceed (line, count)
  register LINE * line;
  register int count;
{
  if (count < 0) {
	while (count ++ < 0 && line != header) {
		line = line->prev;
	}
  } else {
	while (count -- > 0 && line != tail) {
		line = line->next;
	}
  }
  return line;
}

/*
 * Reset assigns bot_line, top_line and cur_line according to 'head_line'
 * which must be the first line of the screen, and a y-coordinate,
 * which will be the current y-coordinate (if it isn't larger than last_y)
 */
void
reset (head_line, screen_y)
  LINE * head_line;
  int screen_y;
{
  register LINE * line;

  top_line = line = head_line;

/* Search for bot_line (might be last line in file) */
  for (last_y = 0; 
	last_y < total_lines - 1 && last_y < SCREENMAX && line->next != tail; 
	last_y ++
      ) {
	line = line->next;
  }

  bot_line = line;
  y = (screen_y > last_y) ? last_y : screen_y;

/* Set cur_line according to the new y value */
  cur_line = proceed (top_line, y);
  calc_line_no ();
}


/*
   Determine current character position within line (# characters).
 */
int
get_cur_pos ()
{
  int cur_pos = 0;
  char * cpoi = cur_line->text;
  while (* cpoi != '\0' && cpoi < cur_text) {
	advance_char (& cpoi);
	cur_pos ++;
  }
  return cur_pos;
}

/*
   Return current column position within line (# screen columns + line shift).
 */
int
get_cur_col ()
{
  return cur_line->shift_count * SHIFT_SIZE + x;
}


/*======================================================================*\
|*			Text/file information				*|
\*======================================================================*/

/*
 * Display a line telling how many chars and lines the file contains. Also tell
 * whether the file is readonly and/or modified.
called via fstatus macro:
fstatus (mess, bytes, chars) is
	file_status ((mess), (bytes), (chars), file_name, \
		     total_lines, True, writable, modified, viewonly)
also called directly from WB
*/
void
file_status (message, bytecount, charcount, filename, lines, textstat, writefl, changed, viewing)
  char * message;
  register long bytecount;	/* Contains number of bytes in file */
  register long charcount;	/* Contains number of characters in file */
  char * filename;
  int lines;
  FLAG textstat;
  FLAG writefl;
  FLAG changed;
  FLAG viewing;
{
  LINE * line;
  int line_num = 0;
  char fileinfo [maxLINE_LEN];
  char textinfo [maxLINE_LEN];
  char * filesep = "";

  if (* message) {
	filesep = " ";
  }

  /* determine information */
  if (bytecount < 0) {	/* # chars not given; count them */
	bytecount = 0L;
	charcount = 0L;
	for (line = header->next; line != tail; line = line->next) {
		bytecount += length_of (line->text);
		charcount += char_count (line->text);
		if (line->return_type == lineend_NONE) {
			charcount --;
			bytecount --;
		} else if (line->return_type == lineend_CRLF) {
			bytecount ++;
		} else if (line->return_type == lineend_LS
			|| line->return_type == lineend_PS) {
			bytecount += 2;
		}
		line_num ++;
		if (line == cur_line) {
			line_number = line_num;
		}
	}
  } else {		/* check for current line # only */
	for (line = header->next; line != cur_line->next; line = line->next) {
		line_num ++;
		if (line == cur_line) {
			line_number = line_num;
		}
	}
  }

  build_string (fileinfo, "%s%s%s%s%s%s, ",
		message, filesep,
		(reading_pipe && * message != '[') ?
			"standard input" : filename,
		viewing ? " (View only)" : "",
		changed ? " (modified)" : "",
		writefl ? "" : file_is_dir ? " (Directory)" : " (Readonly)");

  if (textstat) {
	build_string (textinfo, "line:char %d:%ld (total %d:%ld)%s%s",
		line_number, get_cur_pos () + 1, lines, charcount,

		(lines_per_page != 0) ? ", page " : "",
		(lines_per_page != 0)
			? num_out ((line_number - 1) / lines_per_page + 1, 10)
			: ""
		);
  } else {
	build_string (textinfo, "lines:chars(bytes) %d:%ld(%ld)",
		lines,
		charcount,
		bytecount
		);
  }

  /* display the information; note that msg contains filename characters */
  status_fmt2 (fileinfo, textinfo);
}


/*======================================================================*\
|*				End					*|
\*======================================================================*/
