/* ------------------------------------------------------------------------ */
/*                                sstedit.c                                 */
/*                         high-level editor routines                       */
/*                                                                          */
/*      CopyRight (C) 1991,1992  Steven Lutrov.   All rights reserved.      */
/* ------------------------------------------------------------------------ */
#include <stdio.h>
#include <ctype.h>
#include <mem.h>
#include <conio.h>
#include <alloc.h>

#include "sstvid.h"
#include "sstwin.h"
#include "sstkey.h"


/* ------------------------------------------------------------------------ */
/*                                macros                                    */
/* ------------------------------------------------------------------------ */
#define NEXTTAB     (TAB-(x%TAB))
#define LASTTAB     ((wwd/TAB)*TAB)
#define PREVTAB     (((x-1)%TAB)+1)
#define curr(x,y)   (bfptr+(y)*wwd+(x))
#define lineno(y)   ((int)(bfptr-topptr)/wwd+(y))

/* ------------------------------------------------------------------------ */
/*                            local variables                               */
/* ------------------------------------------------------------------------ */
extern int VSG;
int last_x, last_y;
static int wht;
static int wwd;
static int wsz;
static char *topptr;
static char *bfptr;
static char *lstptr;
static int lines;
static char *endptr;
static int blkbeg;
static int blkend;
static int inserting;
static WINDOW *wnd;
static int do_display_text = 1;


/* ------------------------------------------------------------------------ */
/*                        local function prototypes                         */
/* ------------------------------------------------------------------------ */
void  erase_buffer    (int *x, int *y);
int   lastword        (int x, int y);
void  last_char       (int *x, int *y);
void  test_para       (int x, int y);
int   trailing_spaces (int y);
int   first_wordlen   (int y);
void  paraform        (int x, int y);
int   blankline       (int line);
void  delete_word     (int x, int y);
void  delete_line     (int y);
void  delete_block    (void);
void  copy_block      (int y);
void  move_block      (int y);
void  mvblock         (int y, int moving);
void  findlast        (void);
void  find_end        (int *x, int *y);
void  carrtn          (int *x, int *y, int insert);
void  backspace       (int *x, int *y);
void  fore_word       (int *x, int *y, char *bf);
int   spaceup         (int *x, int *y, char **bf);
void  back_word       (int *x, int *y, char *bf);
int   spacedn         (int *x, int *y, char **bf);
void  forward         (int *x, int *y);
int   downward        (int *y);
void  upward          (int *y);
void  display_text    (void);
void  disp_line       (int y);
void  insert_line     (void);


/* ------------------------------------------------------------------------ */
/*                      Process text entry for a window.                    */
/* ------------------------------------------------------------------------ */
void Eopen(WINDOW *wnd1, char *bf, int bsize)
{
	char *b, *buff;
	int depart = FALSE, i, c;
	int x, y, svx, svlw, tx, tabctr = 0;

	wnd = wnd1;
	wht = HEIGHT-2;
	wwd = WIDTH-2;
	wsz = wwd * wht;
	topptr = bfptr = bf;
	lines = bsize / wwd;
	endptr = bf + wwd * lines;
	blkbeg = 0;
	blkend = 0;
	inserting = FALSE;
	x = 0;
	y = 0;
	display_text();
	/* --------- read in text from the keyboard ---------- */
	findlast();
	while (TRUE)	{
		last_x = COL + 1 + x;
		last_y = ROW + 1 + y;
        vsetcur(last_x, last_y);
		buff = curr(x, y);
		if (tabctr)	{
			--tabctr;
			c = ' ';
		}
		else	{
			c = kgetch();
            /* Wemsgclear();  */
		}
		switch (c)	{
			case '\r':	carrtn(&x, &y, inserting);
						break;
			case DOWN:	downward(&y);
						break;
			case PGUP:	y = 0;
						for (i = 0; i < wht; i++)
							upward(&y);
						break;
			case PGDN:	y = HEIGHT - 2;
						for (i = 0; i < wht; i++)
							downward(&y);
						y = 0;
						break;
			case '\t':	if (x + NEXTTAB < wwd)	{
							if (inserting)
								tabctr = NEXTTAB;
							else
								x += NEXTTAB;
						}
						else
							carrtn(&x, &y, inserting);
						break;
			case SHIFT_TAB:
						if (x < TAB)	{
							upward(&y);
							x = LASTTAB;
						}
						else
							x -= PREVTAB;
						break;
			case CTRL_RIGHT:
						fore_word(&x, &y, buff);
						break;
			case CTRL_LEFT:
						back_word(&x, &y, buff);
						break;
			case CTRL_PGUP:
						y = wht - 1;
						break;
			case CTRL_PGDN:
						y = 0;
						break;
			case CTRL_HOME:
						x = y = 0;
						bfptr = topptr;
						display_text();
						break;
			case HOME:	x = 0;
						break;
			case CTRL_END:
						find_end(&x, &y);
						display_text();
						break;
			case END:	last_char(&x, &y);
						break;
			case UP:	upward(&y);
						break;
			case F2:
			case ESC:	depart = TRUE;
						break;
			case '\b':
			case LEFT:	if (!(x || y))
							break;
						backspace(&x, &y);
						if (x == wwd - 1)
							last_char(&x, &y);
						if (c == LEFT)
							break;
						buff = curr(x, y);
			case DEL:	movmem(buff+1, buff, wwd-1-x);
						*(buff+wwd-1-x) = ' ';
						disp_line(y);
						test_para(x+1, y);
						break;
			case CTRL_Y:	delete_line(y);
						break;
			case CTRL_D:delete_word(x, y);
						test_para(x, y);
						break;
			case INS:	inserting ^= TRUE;
						insert_line();
						break;
			case F3:	erase_buffer(&x, &y);
						break;
			case F4:	paraform(0, y);
						break;
			case F7:	blkbeg = lineno(y) + 1;
						if (blkbeg > blkend)
							blkend = lines;
						display_text();
						break;
			case F8:	blkend = lineno(y) + 1;
						if (blkend < blkbeg)
							blkbeg = 1;
						display_text();
						break;
			case ALT_F8:	move_block(y);
						break;
			case ALT_F7:	copy_block(y);
						break;
			case F9:	delete_block();
						break;
			case F10:	blkbeg = blkend = 0;
						display_text();
						break;
			case RIGHT:	forward(&x, &y);
						break;
			default:	if (!isprint(c))
							break;
						if (curr(x, y) == endptr-1 ||
						   (lineno(y)+1 >= lines && inserting
								&& *curr(wwd-2, y) != ' '))	{
                            /* Wemsg(" End of Buffer "); */
							break;
						}
						if (inserting)	{
							buff = curr(x, y);
							movmem(buff, buff + 1, wwd-1-x);
						}
						buff = curr(x, y);
						if (buff < endptr)	{
							if (buff >= lstptr)
								lstptr = buff + 1;
							*buff = c;
							disp_line(y);
						}
						buff = curr(wwd-1, y);
						if (endptr && *buff != ' ')	{
							for (b = buff+1; b < endptr; b++)
								if (*b==' ' && *(b + 1)==' ')
									break;
							movmem(buff+1, buff+2, b-buff-1);
							*(buff+1) = ' ';
							svx = x;
							svlw = lastword(x, y);
							x = wwd-1;
							if (*(buff-1) != ' ')
								back_word(&x, &y, buff);
							tx = x;
							carrtn(&x, &y, TRUE);
							if (svlw)
								x = svx-tx;
							else	{
								x = svx;
								--y;
							}
						}
						forward(&x, &y);
						break;
		}
		if (depart)
			break;
	}
	inserting = FALSE;
	insert_line();
}
/* ------------------------------------------------------------------------ */
/*                           erase the buffer                               */
/* ------------------------------------------------------------------------ */
static void erase_buffer(int *x, int *y)
{
	int c = 0;
	WINDOW *sur;

	sur = Westablish(28, 11, 4, 24);
	Wsetcolour(sur, WIN_ALL, RED, YELLOW, BRIGHT);
	Wshow(sur);
	Wprintf(sur, " Erase text window\n Are you sure? (y/n)");
	while (c != 'y' && c != 'n')	{
		c = kgetch();
		c = tolower(c);
		if (c == 'y')	{
			lstptr = bfptr = topptr;
			*x = *y = 0;
			setmem(bfptr, lines * wwd, ' ');
			blkbeg = blkend = 0;
			display_text();
		}
	}
	Wdelete(sur);
}

/* ------------------------------------------------------------------------ */
/*                see if a word is the last word on the line                */
/* ------------------------------------------------------------------------ */
static int lastword(int x, int y)
{
	char *bf = curr(x, y);

	while (x++ < wwd-1)
		if (*bf++ == ' ')
			return 0;
	return 1;
}

/* ------------------------------------------------------------------------ */
/*                 go to last displayable character on the line             */
/* ------------------------------------------------------------------------ */
static void last_char(int *x, int *y)
{
	char *bf;

	*x = wwd-1;
	bf = curr(0, *y);
	while (*x && *(bf + *x) == ' ')
		--(*x);
	if (*x && *x < wwd - 1)
		(*x)++;
}

/* ------------------------------------------------------------------------ */
/*                 test to see if paragraph should be reformed              */
/* ------------------------------------------------------------------------ */
static void test_para(int x, int y)
{
	int ts, fw;
	int far *c;

	c = kstate();
	if (! (*c & K_SCROLLLOCKON) && y < lines)	{
		ts = trailing_spaces(y);
		fw = first_wordlen(y+1);
		if (fw && ts > fw)
			paraform(x, y);
	}
}

/* ------------------------------------------------------------------------ */
/*                   count the trailing spaces on a line                    */
/* ------------------------------------------------------------------------ */
static int trailing_spaces(int y)
{
	int x = wwd-1, ct = 0;
	char *bf = curr(0, y);

	while (x >= 0)	{
		if (*(bf + x) != ' ')
			break;
		--x;
		ct++;
	}
	return ct;
}
/* ------------------------------------------------------------------------ */
/*              count the length of the first word on a line                */
/* ------------------------------------------------------------------------ */
static int first_wordlen(int y)
{
	int ct = 0, x = 0;
	char *bf = curr(0, y);

	while (x < wwd-1 && *(bf+x) == ' ')
		x++;
	while (x+ct < wwd-1 && *(bf+x+ct) != ' ')
		ct++;
	return ct;
}


/* ------------------------------------------------------------------------ */
/*                             form a paragraph                             */
/* ------------------------------------------------------------------------ */
static void paraform(int x, int y)
{
	char *cp1, *cp2, *cpend, *svcp;
	int x1;

	if (blankline(lineno(y)+1))
		return;
	if (!blkbeg)	{
		blkbeg = blkend = lineno(y)+1;
		blkend++;
		while (blkend < lines)	{
			if (blankline(blkend))
				break;
			blkend++;
		}
		--blkend;
	}
	if (lineno(y) != blkbeg-1)
		x = 0;
	x1 = x;
	cp1 = cp2 = topptr + (blkbeg - 1) * wwd + x;
	cpend = topptr + blkend * wwd;
	while (cp2 < cpend)	{
		while (*cp2 == ' ' && cp2 < cpend)
			cp2++;
		if (cp2 == cpend)
			break;
		/* at a word */
		while (*cp2 != ' ' && cp2 < cpend)	{
			if (x1 >= wwd - 1)	{
				/* wrap the word */
				svcp = cp1 + (wwd - x1);
				while (*--cp1 != ' ')	{
					*cp1 = ' ';
					--cp2;
				}
				x1 = 0;
				blkbeg++;
				cp1 = svcp;
			}
			*cp1++ = *cp2++;
			x1++;
		}
		if (cp2 < cpend)	{
			*cp1++ = ' ';
			x1++;
		}
	}
	while (cp1 < cpend)
		*cp1++ = ' ';
	blkbeg++;
	if (blkbeg <= blkend)
		delete_block();
	blkbeg = blkend = 0;
	display_text();
	findlast();
}

/* ------------------------------------------------------------------------ */
/*                            test for a blank line                         */
/* ------------------------------------------------------------------------ */
static int blankline(int line)
{
	char *cp;
	int x;

	cp = topptr + (line-1) * wwd;
	for (x = 0; x < wwd; x++)
		if (*(cp + x) != ' ')
			break;
	return (x == wwd);
}

/* ------------------------------------------------------------------------ */
/*                             delete a word                                */
/* ------------------------------------------------------------------------ */
static void delete_word(int x, int y)
{
	int wct = 0;
	char *cp1, *cp2;

	cp1 = cp2 = curr(x, y);
	if (*cp2 == ' ')
		while (*cp2 == ' ' && x + wct < wwd)	{
			wct++;
			cp2++;
		}
	else	{
		while (*cp2 != ' ' && x + wct < wwd)	{
			wct++;
			cp2++;
		}
		while (*cp2 == ' ' && x + wct < wwd)	{
			wct++;
			cp2++;
		}
	}
	movmem(cp2, cp1, wwd - x - wct);
	setmem(cp1 + wwd - x - wct, wct, ' ');
	display_text();
	findlast();
}

/* ------------------------------------------------------------------------ */
/*                              delete a line                               */
/* ------------------------------------------------------------------------ */
static void delete_line(int y)
{
	char *cp1, *cp2;
	int len;

	cp1 = bfptr + y * wwd;
	cp2 = cp1 + wwd;
	if (cp1 < lstptr)	{
		len = endptr - cp2;
		movmem(cp2, cp1, len);
		lstptr -= wwd;
		setmem(endptr - wwd, wwd, ' ');
		display_text();
	}
}

/* ------------------------------------------------------------------------ */
/*                             delete a block                               */
/* ------------------------------------------------------------------------ */
static void delete_block()
{
	char *cp1, *cp2;
	int len;

	if (!blkbeg || !blkend)	{
		putchar(7);
		return;
	}
	cp1 = topptr + blkend * wwd;
	cp2 = topptr + (blkbeg - 1) * wwd;
	len = endptr - cp1;
	movmem(cp1, cp2, len);
	setmem(cp2 + len, endptr - (cp2 + len), ' ');
	blkbeg = blkend = 0;
	lstptr -= (cp1 - cp2);
	display_text();
}

/* ------------------------------------------------------------------------ */
/*                       move and copy text blocks                          */
/* ------------------------------------------------------------------------ */
static void mvblock(int y, int moving)
{
	char *cp1, *cp2, *hd;
	int len;
	if (!blkbeg || !blkend)	{
        vbeep(ERROR);
		return;
	}
	if (lineno(y) >= blkbeg-1 && lineno(y) <= blkend-1)	{
        /* Wemsg("Can't move/copy a block into itself"); */
		return;
	}
	len = (blkend - blkbeg + 1) * wwd;
	if ((hd = malloc(len)) == 0)
		return;
	cp1 = topptr + (blkbeg-1) * wwd;
	movmem(cp1, hd, len);
	cp2 = topptr + lineno(y) * wwd;
	if (moving)	{
		if (lineno(y) > blkbeg-1)
			cp2 -= len;
		do_display_text = 0;
		delete_block();
		do_display_text = 1;
	}
	if (cp2+len <= endptr)	{
		movmem(cp2, cp2 + len, endptr - cp2 - len);
		movmem(hd, cp2, len);
	}
	free(hd);
	blkbeg = blkend = 0;
	display_text();
}
/* ------------------------------------------------------------------------ */
/*                              copy a block                                */
/* ------------------------------------------------------------------------ */
static void copy_block(int y)
{
	mvblock(y, FALSE);
	findlast();
}

/* ------------------------------------------------------------------------ */
/*                             move a block                                 */
/* ------------------------------------------------------------------------ */
static void move_block(int y)
{
	mvblock(y, TRUE);
}

/* ------------------------------------------------------------------------ */
/*                  find the last character in the buffer                   */
/* ------------------------------------------------------------------------ */
static void findlast()
{
	register char *lp = endptr - 1;
	register char *tp = topptr;

	while (lp > tp && (*lp == ' ' || *lp == '\0'))	{
		if (*lp == '\0')
			*lp = ' ';
		--lp;
	}
	if (*lp != ' ')
		lp++;
	lstptr = lp;
}

/* ------------------------------------------------------------------------ */
/*                 go to the end of the data in the buffer                  */
/* ------------------------------------------------------------------------ */
static void find_end(int *x, int *y)
{
	int ct;

	bfptr = lstptr;
	ct = (lstptr - topptr) % wsz;
	bfptr -= ct;
	if (bfptr + wsz > endptr)
		bfptr = endptr - wsz;
	*y = (ct / wwd);
	*x = 0;
	downward(y);
}

/* ------------------------------------------------------------------------ */
/*                              carriage return                             */
/* ------------------------------------------------------------------------ */
static void carrtn(int *x, int *y, int insert)
{
	int insct;
	char *cp, *nl;
	int ctl = 2;

	cp = curr(*x, *y);
	nl = cp + ((cp - topptr) % wwd);
	if (lineno(*y) + 2 < lines)
		if (insert && nl < endptr)	{
			insct = wwd - *x;
			while (ctl--)	{
				if (endptr > cp + insct)	{
					movmem(cp, cp+insct, endptr-insct-cp);
					setmem(cp, insct, ' ');
				}
				else if (ctl == 1)
					setmem(cp, endptr - cp, ' ');
				cp += insct * 2;
				insct = *x;
			}
		}
	*x = 0;
	downward(y);
	if (insert)	{
		test_para(*x, *y);
		display_text();
	}
	if (lineno(*y) + 2 < lines)
		if (insert)
			if ((lstptr + wwd) <= endptr)
				if (lstptr > curr(*x, *y))
					lstptr += wwd;
}


/* ------------------------------------------------------------------------ */
/*                move the buffer offset back one position                  */
/* ------------------------------------------------------------------------ */
static void backspace(int *x, int *y)
{
	if (*x == 0)	{
		if (*y)
			*x = wwd - 1;
		upward(y);
	}
	else
		--(*x);
}

/* ------------------------------------------------------------------------ */
/*                  move the buffer offset forward one word                 */
/* ------------------------------------------------------------------------ */
static void fore_word(int *x, int *y, char *bf)
{
	while (*bf != ' ')	{
		if (spaceup(x, y, &bf) == 0)
			return;
		if (*x == 0)
			break;
	}
	while (*bf == ' ')
		if (spaceup(x, y, &bf) == 0)
			return;
}

/* ------------------------------------------------------------------------ */
static int spaceup(int *x, int *y, char **bf)
{
	if (*bf == lstptr)
		return 0;
	(*bf)++;
	forward(x, y);
	return 1;
}

/* ------------------------------------------------------------------------ */
/*                 move the buffer offset backward one word                 */
/* ------------------------------------------------------------------------ */
static void back_word(int *x, int *y, char *bf)
{
	spacedn(x, y, &bf);
	while (*bf == ' ')
		if (spacedn(x, y, &bf) == 0)
			return;
	while (*bf != ' ')	{
		if (*x == 0)
			return;
		if (spacedn(x, y, &bf) == 0)
			return;
	}
	spaceup(x, y, &bf);
}

/* ------------------------------------------------------------------------ */
static int spacedn(int *x, int *y, char **bf)
{
	if (*bf == topptr)
		return 0;
	--(*bf);
	backspace(x, y);
	return 1;
}


/* ------------------------------------------------------------------------ */
/*               move the buffer offset forward one position                */
/* ------------------------------------------------------------------------ */
static void forward(int *x, int *y)
{
	int ww = wwd;

	(*x)++;
	if (*x == ww)	{
		downward(y);
		*x = 0;
	}
}

/* ------------------------------------------------------------------------ */
/*                move the buffer offset down one position                  */
/* ------------------------------------------------------------------------ */
static int downward(int *y)
{
	if (*y < wht - 1)	{
		(*y)++;
		return 1;
	}
	else if ((bfptr + wsz) < endptr)	{
		bfptr += wwd;
		scroll(wnd, UP);
		disp_line(wht-1);
		return 1;
	}
	return 0;
}

/* ------------------------------------------------------------------------ */
/*                 move the buffer offset up one position                   */
/* ------------------------------------------------------------------------ */
static void upward(int *y)
{
	if (*y)
		--(*y);
	else if ((topptr + wwd) <= bfptr)	{
		bfptr -= wwd;
		scroll(wnd, DOWN);
		disp_line(0);
	}
}

/* ------------------------------------------------------------------------ */
/*                     display all the lines in a window                    */
/* ------------------------------------------------------------------------ */
static void display_text()
{
	int y = 0;

	if (do_display_text)
		while (y < wht)
			disp_line(y++);
}

/* ------------------------------------------------------------------------ */
/*                           Display a line                                 */
/* ------------------------------------------------------------------------ */
static void disp_line(int y)
{
	int x = 0, atr = WFACE;

	if (blkbeg || blkend)
		if (lineno(y) >= blkbeg-1)
			if (lineno(y) <= blkend-1)
				atr = WACCENT;
	while (x < wwd)	{
		displ(wnd, x+1, y+1, *(bfptr+y * wwd+x), atr);
		x++;
	}
}

/* ------------------------------------------------------------------------ */
/*                       set insert/exchange cursor shape                   */
/* ------------------------------------------------------------------------ */
static void insert_line()
{
    vsetcurtype(inserting ? 0x0106 : 0x0607);
}

