/*
 *	Window system
 *	Copyright
 *		(C) 1992 Joseph H. Allen
 *
 *	This file is part of JOE (Joe's Own Editor)
 */
#include "config.h"
#include "types.h"

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif

#include "blocks.h"
#include "kbd.h"
#include "poshist.h"
#include "queue.h"
#include "rc.h"
#include "scrn.h"
#include "utils.h"
#include "w.h"

extern int dspasis;		/* Set to display chars above 127 as-is */
extern int staen;		/* 0 if top-most status line not displayed */

/* Count no. of main windows */

int countmain(SCREEN *t)
{
	int nmain = 1;
	W *m = t->curwin->main;
	W *q;

	for (q = t->curwin->link.next; q != t->curwin; q = q->link.next)
		if (q->main != m) {
			++nmain;
			m = q->main;
		}
	return nmain;
}

/* Redraw a window */

void wredraw(W *w)
{
	msetI(w->t->t->updtab + w->y, 1, w->h);
}

/* Find first window in a group */

W *findtopw(W *w)
{
	W *x;

	for (x = w; x->link.prev->main == w->main && x->link.prev != w; x = x->link.prev) ;
	return x;
}

/* Determine height of a window.  Returns reqh if it is set, otherwise
 * used fixed or hh scaled to the current screen size */

static int geth(W *w)
{
	if (w->reqh)
		return w->reqh;
	else if (w->fixed)
		return w->fixed;
	else
		return (((long) w->t->h - w->t->wind) * w->hh) / 1000;
}

/* Set the height of a window */

static void seth(W *w, int h)
{
	long tmp;

	w->reqh = h;
	tmp = 1000L * h;
	w->hh = tmp / (w->t->h - w->t->wind) + (tmp % (w->t->h - w->t->wind) ? 1 : 0);
}

/* Determine height of a family of windows.  Uses 'reqh' if it's set */

int getgrouph(W *w)
{
	W *x;
	int h;

	/* Find first window in family */
	x = findtopw(w);

	/* Add heights of all windows in family */
	for (w = x, h = geth(w); w->link.next != x && w->link.next->main == x->main; w = w->link.next, h += geth(w)) ;

	return h;
}

/* Determine minimum height of a family */

static int getminh(W *w)
{
	W *x;
	int h;

	x = findtopw(w);
	for (w = x, h = (w->fixed ? w->fixed : 2); w->link.next != x && w->link.next->main == x->main; w = w->link.next, h += (w->fixed ? w->fixed : 2)) ;

	return h;
}

/* Find last window in a group */

W *findbotw(W *w)
{
	W *x;

	for (x = w; x->link.next->main == w->main && x->link.next != w; x = x->link.next) ;
	return x;
}

/* Demote group of window to end of window list.  Returns true if top window
   was demoted */

int demotegroup(W *w)
{
	W *top = findtopw(w);
	W *bot = findbotw(w);
	W *next;
	int flg = 0;

	for (w = top; w != bot; w = next) {
		next = w->link.next;
		if (w == w->t->topwin) {
			flg = 1;
			w->t->topwin = next;
		} else
			demote(W, link, w->t->topwin, w);
		w->y = -1;
	}
	if (w == w->t->topwin)
		flg = 1;
	else
		demote(W, link, w->t->topwin, w);
	w->y = -1;
	return flg;
}

/* Find last window on the screen */

W *lastw(SCREEN *t)
{
	W *x;

	for (x = t->topwin; x->link.next != t->topwin && x->link.next->y >= 0; x = x->link.next) ;
	return x;
}

/* Create a screen object */

SCREEN *scr;

SCREEN *screate(SCRN *scrn)
{
	SCREEN *t = (SCREEN *) joe_malloc(sizeof(SCREEN));

	t->t = scrn;
	t->w = scrn->co;
	t->h = scrn->li;
	t->topwin = NULL;
	t->curwin = NULL;
	t->wind = skiptop;
	scr = t;
	return t;
}

void sresize(SCREEN *t)
{
	SCRN *scrn = t->t;
	W *w;

	t->w = scrn->co;
	t->h = scrn->li;
	if (t->h - t->wind < FITHEIGHT)
		t->wind = t->h - FITHEIGHT;
	if (t->wind < 0)
		t->wind = 0;
	w = t->topwin;
	do {
		w->y = -1;
		w->w = t->w - 1;
		w = w->link.next;
	} while (w != t->topwin);
	wfit(t);
	updall();
}

void updall(void)
{
	int y;

	for (y = 0; y != scr->h; ++y)
		scr->t->updtab[y] = 1;
}

void scrins(B *b, long l, long n, int flg)
{
	W *w;

	if ((w = scr->topwin) != NULL) {
		do {
			if (w->y >= 0) {
				if (w->object && w->watom->ins)
					w->watom->ins(w->object, b, l, n, flg);
			}
		w = w->link.next;
		} while (w != scr->topwin);
	}
}

void scrdel(B *b, long l, long n, int flg)
{
	W *w;

	if ((w = scr->topwin) != NULL) {
		do {
			if (w->y >= 0) {
				if (w->object && w->watom->del)
					w->watom->del(w->object, b, l, n, flg);
			}
		w = w->link.next;
		} while (w != scr->topwin);
	}
}

/* Fit as many windows on the screen as is possible beginning with the window
 * at topwin.  Give any extra space which couldn't be used to fit in another
 * window to the last text window on the screen.  This function guarentees
 * to fit on the window with the cursor in it (moves topwin to next group
 * of windows until window with cursor fits on screen).
 */

static int doabort(W *w, int *ret);
extern volatile int dostaupd;

void wfit(SCREEN *t)
{
	int y;			/* Where next window goes */
	int left;		/* Lines left on screen */
	W *w;			/* Current window we're fitting */
	W *pw;			/* Main window of previous family */
	int req;		/* Amount this family needs */
	int adj;		/* Amount family needs to be adjusted */
	int flg = 0;		/* Set if cursor window was placed on screen */
	int ret;

	dostaupd = 1;

      tryagain:
	y = t->wind;
	left = t->h - y;
	pw = NULL;

	w = t->topwin;
	do {
		w->ny = -1;
		w->nh = geth(w);
		w = w->link.next;
	} while (w != t->topwin);

	/* Fit a group of windows on the screen */
	w = t->topwin;
	do {
		req = getgrouph(w);
		if (req > left)	/* If group is taller than lines left */
			adj = req - left;	/* then family gets shorter */
		else
			adj = 0;

		/* Fit a family of windows on the screen */
		do {
			w->ny = y;	/* Set window's y position */
			if (!w->win) {
				pw = w;
				w->nh -= adj;	/* Adjust main window of the group */
			}
			if (!w->win && w->nh < 2)
				while (w->nh < 2)
					w->nh += doabort(w->link.next, &ret);
			if (w == t->curwin)
				flg = 1;	/* Set if we got window with cursor */
			y += w->nh;
			left -= w->nh;	/* Increment y value by height of window */
			w = w->link.next;	/* Next window */
		} while (w != t->topwin && w->main == w->link.prev->main);
	} while (w != t->topwin && left >= FITHEIGHT);

	/* We can't use extra space to fit a new family on, so give space to parent of
	 * previous family */
	pw->nh += left;

	/* Adjust that family's children which are below the parent */
	while ((pw = pw->link.next) != w)
		pw->ny += left;

	/* Make sure the cursor window got on the screen */
	if (!flg) {
		t->topwin = findbotw(t->topwin)->link.next;
		goto tryagain;
	}

	/* All of the windows are now on the screen.  Scroll the screen to reflect what
	 * happened
	 */
	w = t->topwin;
	do {
		if (w->y >= 0 && w->ny >= 0)
			if (w->ny > w->y) {
				W *l = pw = w;

				while (pw->link.next != t->topwin && (pw->link.next->y < 0 || pw->link.next->ny < 0 || pw->link.next->ny > pw->link.next->y)) {
					pw = pw->link.next;
					if (pw->ny >= 0 && pw->y >= 0)
						l = pw;
				}
				/* Scroll windows between l and w */
			      loop1:
				if (l->ny >= 0 && l->y >= 0)
					nscrldn(t->t, l->y, l->ny + uns_min(l->h, l->nh), l->ny - l->y);
				if (w != l) {
					l = l->link.prev;
					goto loop1;
				}
				w = pw->link.next;
			} else if (w->ny < w->y) {
				W *l = pw = w;

				while (pw->link.next != t->topwin && (pw->link.next->y < 0 || pw->link.next->ny < 0 || pw->link.next->ny < pw->link.next->y)) {
					pw = pw->link.next;
					if (pw->ny >= 0 && pw->y >= 0)
						l = pw;
				}
				/* Scroll windows between l and w */
			      loop0:
				if (w->ny >= 0 && w->y >= 0)
					nscrlup(t->t, w->ny, w->y + uns_min(w->h, w->nh), w->y - w->ny);
				if (w != l) {
					w = w->link.next;
					goto loop0;
				}
				w = pw->link.next;
			} else
				w = w->link.next;
		else
			w = w->link.next;
	} while (w != t->topwin);

	/* Update current height and position values */
	w = t->topwin;
	do {
		if (w->ny >= 0) {
			if (w->object) {
				if (w->watom->move)
					w->watom->move(w->object, w->x, w->ny);
				if (w->watom->resize)
					w->watom->resize(w->object, w->w, w->nh);
			}
			if (w->y == -1)
				msetI(t->t->updtab + w->ny, 1, w->nh);
			w->y = w->ny;
		} else
			w->y = -1;
		w->h = w->nh;
		w->reqh = 0;
		w = w->link.next;
	} while (w != t->topwin);
}

/* Goto next window */

int wnext(SCREEN *t)
{
	if (t->curwin->link.next != t->curwin) {
		t->curwin = t->curwin->link.next;
		if (t->curwin->y == -1)
			wfit(t);
		return 0;
	} else
		return -1;
}

/* Goto previous window */

int wprev(SCREEN *t)
{
	if (t->curwin->link.prev != t->curwin) {
		t->curwin = t->curwin->link.prev;
		if (t->curwin->y == -1) {
			t->topwin = findtopw(t->curwin);
			wfit(t);
		}
		return 0;
	} else
		return -1;
}

/* Grow window */

int wgrow(W *w)
{
	W *nextw;

	/* If we're the last window on the screen, shrink the previous window */
	if ((w->link.next == w->t->topwin || w->link.next->y == -1) && w != w->t->topwin)
		return wshrink(w->link.prev->main);

	/* Get to next variable size window */
	for (nextw = w->link.next; nextw->fixed && nextw != w->t->topwin; nextw = nextw->link.next) ;

	/* Is it below us, on screen and big enough to take space from? */
	if (nextw == w->t->topwin || nextw->y == -1 || nextw->h <= FITHEIGHT)
		return -1;

	/* Increase this window's height */
	seth(w, w->h + 1);

	/* Decrease next window's height */
	seth(nextw, nextw->h - 1);

	/* Do it */
	wfit(w->t);

	return 0;
}

/* Shrink window */

int wshrink(W *w)
{
	W *nextw;

	/* If we're the last window on the screen, grow the previous window */
	if ((w->link.next == w->t->topwin || w->link.next->y == -1) && w != w->t->topwin)
		return wgrow(w->link.prev->main);

	/* Is this window too small already? */
	if (w->h <= FITHEIGHT)
		return -1;

	/* Get to window below us */
	for (nextw = w->link.next; nextw != w->t->topwin && nextw->fixed; nextw = nextw->link.next) ;
	if (nextw == w->t->topwin)
		return -1;

	/* Decrease the size of this window */
	seth(w, w->h - 1);

	/* Increase the size of next window */
	seth(nextw, nextw->h + 1);

	/* Do it */
	wfit(w->t);

	return 0;
}

/* Show all windows */

void wshowall(SCREEN *t)
{
	int n = 0;
	int set;
	W *w;

	/* Count no. of main windows */
	w = t->topwin;
	do {
		if (!w->win)
			++n;
		w = w->link.next;
	} while (w != t->topwin);

	/* Compute size to set each window */
	if ((set = (t->h - t->wind) / n) < FITHEIGHT)
		set = FITHEIGHT;

	/* Set size of each variable size window */
	w = t->topwin;
	do {
		if (!w->win) {
			int h = getminh(w);

			if (h >= set)
				seth(w, 2);
			else
				seth(w, set - (h - 2));
			w->orgwin = NULL;
		}
		w = w->link.next;
	} while (w != t->topwin);

	/* Do it */
	wfit(t);
}

static void wspread(SCREEN *t)
{
	int n = 0;
	W *w = t->topwin;

	do {
		if (w->y >= 0 && !w->win)
			++n;
		w = w->link.next;
	} while (w != t->topwin);
	if (!n) {
		wfit(t);
		return;
	}
	if ((t->h - t->wind) / n >= FITHEIGHT)
		n = (t->h - t->wind) / n;
	else
		n = FITHEIGHT;
	w = t->topwin;
	do {
		if (!w->win) {
			int h = getminh(w);

			if (h >= n)
				seth(w, 2);
			else
				seth(w, n - (h - 2));
			w->orgwin = NULL;
		}
		w = w->link.next;
	} while (w != t->topwin);
	wfit(t);
}

/* Show just one family of windows */

void wshowone(W *w)
{
	W *q = w->t->topwin;

	do {
		if (!q->win) {
			seth(q, w->t->h - w->t->wind - (getminh(q) - 2));
			q->orgwin = NULL;
		}
		q = q->link.next;
	} while (q != w->t->topwin);
	wfit(w->t);
}

/* Create a window */

W *wcreate(SCREEN *t, WATOM *watom, W *where, W *target, W *original, int height, char *huh, int *notify)
{
	W *new;

	if (height < 1)
		return NULL;

	/* Create the window */
	new = (W *) joe_malloc(sizeof(W));
	new->notify = notify;
	new->t = t;
	new->w = t->w - 1;
	seth(new, height);
	new->h = new->reqh;
	new->y = -1;
	new->ny = 0;
	new->nh = 0;
	new->x = 0;
	new->huh = huh;
	new->orgwin = original;
	new->watom = watom;
	new->object = NULL;
	new->msgb = NULL;
	new->msgt = NULL;
	/* Set window's target and family */
/* was:	if (new->win = target) {	which may be mistyped == */
	if ((new->win = target) != NULL) {	/* A subwindow */
		new->main = target->main;
		new->fixed = height;
	} else {		/* A parent window */
		new->main = new;
		new->fixed = 0;
	}

	/* Get space for window */
	if (original) {
		if (original->h - height <= 2) {
			/* Not enough space for window */
			joe_free(new);
			return NULL;
		} else
			seth(original, original->h - height);
	}

	/* Create new keyboard handler for window */
	if (watom->context)
		new->kbd = mkkbd(kmap_getcontext(watom->context));
	else
		new->kbd = NULL;

	/* Put window on the screen */
	if (where)
		enquef(W, link, where, new);
	else {
		if (t->topwin)
			enqueb(W, link, t->topwin, new);
		else {
			izque(W, link, new);
			t->curwin = t->topwin = new;
		}
	}

	return new;
}

/* Abort group of windows */

static int doabort(W *w, int *ret)
{
	int amnt = geth(w);
	W *z;

	w->y = -2;
	if (w->t->topwin == w)
		w->t->topwin = w->link.next;
      loop:
	z = w->t->topwin;
	do {
		if (z->orgwin == w)
			z->orgwin = NULL;
		if ((z->win == w || z->main == w) && z->y != -2) {
			amnt += doabort(z, ret);
			goto loop;
		}
	} while (z = z->link.next, z != w->t->topwin);
	if (w->orgwin)
		seth(w->orgwin, geth(w->orgwin) + geth(w));
	if (w->t->curwin == w) {
		if (w->t->curwin->win)
			w->t->curwin = w->t->curwin->win;
		else if (w->orgwin)
			w->t->curwin = w->orgwin;
		else
			w->t->curwin = w->link.next;
	}
	if (qempty(W, link, w)) {
		leave = 1;
		amnt = 0;
	}
	deque(W, link, w);
	if (w->watom->abort && w->object) {
		*ret = w->watom->abort(w->object, MAXINT);
		if (w->notify)
			*w->notify = -1;
	} else {
		*ret = -1;
		if (w->notify)
			*w->notify = 1;
	}
	rmkbd(w->kbd);
	joe_free(w);
	windie(w);
	return amnt;
}

/* Abort a window and its children */

int wabort(W *w)
{
	SCREEN *t = w->t;
	int ret;

	if (w != w->main) {
		doabort(w, &ret);
		if (!leave)
			wfit(t);
	} else {
		doabort(w, &ret);
		if (!leave) {
			if (lastw(t)->link.next != t->topwin)
				wfit(t);
			else
				wspread(t);
		}
	}
	return ret;
}

/* Generate text with formatting escape sequences */

void genfmt(SCRN *t, int x, int y, int ofst, char *s, int flg)
{
	int *scrn = t->scrn + y * t->co + x;
	int atr = 0;
	int col = 0;
	int c;

	while ((c = *s++) != '\0')
		if (c == '\\') {
			switch (c = *s++) {
			case 'u':
			case 'U':
				atr ^= UNDERLINE;
				break;
			case 'i':
			case 'I':
				atr ^= INVERSE;
				break;
			case 'b':
			case 'B':
				atr ^= BOLD;
				break;
			case 'd':
			case 'D':
				atr ^= DIM;
				break;
			case 'f':
			case 'F':
				atr ^= BLINK;
				break;
			case 0:
				--s;
				break;
			default:
				if (col++ >= ofst) {
					outatr(t, scrn, x, y, c, atr);
					++scrn;
					++x;
				}
				break;
			}
		} else if (col++ >= ofst) {
			if (c == '\t')
				c = ' ';
			outatr(t, scrn, x, y, c, atr);
			++scrn;
			++x;
		}
	if (flg)
		eraeol(t, x, y);
}

/* Generate text: no formatting */

void gentxt(SCRN *t, int x, int y, int ofst, char *s, int len, int flg)
{
	int *scrn = t->scrn + y * t->co + x;
	int col;
	unsigned char c;
	int a;

	for (col = 0; col != len; ++col)
		if (col >= ofst) {
			c = s[col];
			if (c == '\t')
				c = ' ';
			xlat(&a, &c);
			outatr(t, scrn, x, y, c, a);
			++scrn;
			++x;
		}
	if (flg)
		eraeol(t, x, y);
}

/* Determine column width of string with format codes */

int fmtlen(char *s)
{
	int col = 0;

	while (*s) {
		if (*s == '\\') {
			switch (*++s) {
			case 'u':
			case 'i':
			case 'd':
			case 'f':
			case 'b':
			case 'U':
			case 'I':
			case 'D':
			case 'F':
			case 'B':
				++s;
				continue;
			case 0:
				--s;
			}
		}
		++col;
		++s;
	}
	return col;
}

/* Return offset within format string which corresponds to a particular
   column */

int fmtpos(char *s, int goal)
{
	char *org = s;
	int col = 0;

	while (*s && col != goal) {
		if (*s == '\\')
			switch (*++s) {
			case 'u':
			case 'i':
			case 'd':
			case 'f':
			case 'b':
			case 'U':
			case 'I':
			case 'D':
			case 'F':
			case 'B':
				++s;
				continue;
			case 0:
				--s;
			}
		++col;
		++s;
	}
	return s - org + goal - col;
}

/* Display a message and skip the next key */

static void mdisp(SCRN *t, int y, char *s)
{
	int ofst;
	int len;

	len = fmtlen(s);
	if (len <= (t->co - 1))
		ofst = 0;
	else
		ofst = len - (t->co - 1);
	genfmt(t, 0, y, ofst, s, 1);
	t->updtab[y] = 1;
}

void msgout(W *w)
{
	SCRN *t = w->t->t;

	if (w->msgb) {
		mdisp(t, w->y + w->h - 1, w->msgb);
		w->msgb = 0;
	}
	if (w->msgt) {
		mdisp(t, w->y + ((w->h > 1 && (w->y || !staen)) ? 1 : 0), w->msgt);
		w->msgt = 0;
	}
}

/* Set temporary message */

char msgbuf[JOE_MSGBUFSIZE];

/* display message on bottom line of window */
void msgnw(W *w, char *s)
{
	w->msgb = s;
}

void msgnwt(W *w, char *s)
{
	w->msgt = s;
}

int urtn(BASE *b, int k)
{
	if (b->parent->watom->rtn)
		return b->parent->watom->rtn(b, k);
	else
		return -1;
}

int utype(BASE *b, int k)
{
	if (b->parent->watom->type)
		return b->parent->watom->type(b, k);
	else
		return -1;
}

/* Window user commands */

int uprevw(BASE *bw)
{
	return wprev(bw->parent->t);
}

int unextw(BASE *bw)
{
	return wnext(bw->parent->t);
}

int ugroww(BASE *bw)
{
	return wgrow(bw->parent);
}

int ushrnk(BASE *bw)
{
	return wshrink(bw->parent);
}

int uexpld(BASE *bw)
{
	if (bw->parent->t->h - bw->parent->t->wind == getgrouph(bw->parent))
		wshowall(bw->parent->t);
	else
		wshowone(bw->parent);
	return 0;
}

int uretyp(BASE *bw)
{
	nredraw(bw->parent->t->t);
	return 0;
}
