/* ------------------------------------------------------------------------ */
/*                                 sstwin.c                                 */
/*                         high-level window routines                       */
/*                                                                          */
/*      CopyRight (C) 1991,1992  Steven Lutrov.   All rights reserved.      */
/*                                                                          */
/* ------------------------------------------------------------------------ */
#include <stdio.h>
#include <ctype.h>
#include <stdarg.h>
#include <dos.h>
#include <alloc.h>
#include <stdlib.h>
#include <string.h>

#include "sstvid.h"
#include "sstwin.h"
#include "sstedt.h"
#include "sstkey.h"

/* ------------------------------------------------------------------------ */
/*                            local prototypes                              */
/* ------------------------------------------------------------------------ */
static void redraw       (WINDOW *wnd);
static void wframe       (WINDOW *wnd);
static void dtitle       (WINDOW *wnd);
static void dfooter      (WINDOW *wnd);
static int  *waddr       (WINDOW *wnd, int x, int y);
static void vswap        (WINDOW *wnd);

static void listadd      (WINDOW *wnd);
static void listbegin    (WINDOW *wnd);
static void listdelete   (WINDOW *wnd);
static void listinsert   (WINDOW *w1, WINDOW *w2);
static int  wpeek        (WINDOW *wnd, int x, int y);
static int  wndverify    (WINDOW **wndc);
void wndrepos    (WINDOW *wnd, int x, int y, int z);



/* ------------------------------------------------------------------------ */
/*                 window structure linked list head & tail                 */
/* ------------------------------------------------------------------------ */
WINDOW *listhead = NULL;     /* head address */
WINDOW *listtail = NULL;     /* tail address */

/* ------------------------------------------------------------------------ */
/*                   array of border character sets                         */
/* ------------------------------------------------------------------------ */
struct {
	unsigned char topleft, topright, botright, botleft, vert, hor;
} wcs[] = {
	{ 32, 32, 32, 32, 32, 32},	/* blank line */
	{218,191,217,192,179,196},	/* single line */
	{201,187,188,200,186,205},	/* double line */
	{214,183,189,211,186,196},	/* single top, double side */
	{213,184,190,212,179,205},	/* double top, single side */
	{194,194,217,192,179,196},	/* single - pop-down menu */
	{203,203,188,200,186,205},	/* double - pop-down menu */
	{210,210,189,211,186,196},	/* single top pop-down menu */
	{209,209,190,212,179,205},	/* double top pop-down menu */
	{ 32, 32, 32, 32, 32, 32},	/* blank line */
};
/* ------------------------------------------------------------------------ */
/*                          establish a new window                          */
/* ------------------------------------------------------------------------ */
WINDOW *Westablish(int x, int y, int h, int w)
{
	WINDOW *wnd;

	if ((wnd = (WINDOW *) malloc(sizeof (WINDOW))) == NULL)
		return NULL;
	WTITLE = "";
	WFOOT  = "";
	TJUST = FJUST = JUST_C;
	HEIGHT = min(h, SCREENHEIGHT);
	WIDTH = min(w, SCREENWIDTH);
	COL = max(0, min(x, SCREENWIDTH-WIDTH));
	ROW = max(0, min(y, SCREENHEIGHT-HEIGHT));
	WCURS = 0;
	SCROLL = 0;
	SELECT = 1;
	BTYPE = 1;
	VISIBLE = HIDDEN = 0;
	PREV = NEXT = NULL;
	FHEAD = FTAIL = NULL;
	WBORDER=WFACE=PWIN_FACE=WTITLEC=WFIELD = CLR(BLACK, WHITE, BRIGHT);
	WACCENT = CLR(WHITE, BLACK, DIM);
	if ((SAV = (char *)malloc(WIDTH * HEIGHT * 2)) == (char *) 0)
		return NULL;
	listadd(wnd);
	Wclear(wnd);
	wframe(wnd);
	return wnd;
}

/* ------------------------------------------------------------------------ */
/*                         set the window's border                          */
/* ------------------------------------------------------------------------ */
void Wsetborder(WINDOW *wnd, int btype)
{
   if (wndverify(&wnd)) {
	BTYPE = btype;
    wframe(wnd);
	redraw(wnd);
  }
}

/* ------------------------------------------------------------------------ */
/*                               set colours                                */
/* ------------------------------------------------------------------------ */
void Wsetcolour(WINDOW *wnd,int area,int bg,int fg,int inten)
{
	if (ISMONO() )	{
		if (bg != WHITE && bg != BLACK)
			return;
		if (fg != WHITE && fg != BLACK)
			return;
	}
	if (wndverify(&wnd))	{
		if (area == WIN_ALL)
			while (area)
		WCOLOUR [--area] = CLR(bg, fg, inten);
		else
	    WCOLOUR [area] = CLR(bg, fg, inten);
		redraw(wnd);
	}
}

/* ------------------------------------------------------------------------ */
/*                      set the intensity of a window                       */
/* ------------------------------------------------------------------------ */
void Wsetintensity(WINDOW *wnd, int inten)
{
	int area = WIN_ALL;

	if (wndverify(&wnd))	{
		while (area)	{
            WCOLOUR [--area] &= ~BRIGHT;
	    WCOLOUR [area] |= inten;
		}
		redraw(wnd);
	}
}

/* ------------------------------------------------------------------------ */
/*                               set title                                  */
/* ------------------------------------------------------------------------ */
void Wsettitle(WINDOW *wnd, char *title, int just)
{
	if (wndverify(&wnd))	{
		WTITLE = title;
		TJUST = just;
		redraw(wnd);
	}
}
/* ------------------------------------------------------------------------ */
/*                           set window footer                              */
/* ------------------------------------------------------------------------ */
void Wsetfooter(WINDOW *wnd, char *s, int j)
{
	if (wndverify(&wnd))	{
		WFOOT = s;
		FJUST = j;
		redraw(wnd);
	}
}

/* ------------------------------------------------------------------------ */
/*                redraw a window when an attribute changes                 */
/* ------------------------------------------------------------------------ */
static void redraw(WINDOW *wnd)
{
    int x, y, chat, atr;

    for (y = BORDER_OFFSET; y < HEIGHT-BORDER_OFFSET; y++)
	for (x = BORDER_OFFSET; x < WIDTH-BORDER_OFFSET; x++)	{
		chat = wpeek(wnd, x, y);
		atr = (((chat>>8)&255) == PWIN_FACE ? WFACE : WACCENT);
		  displ(wnd, x, y, chat&255, atr);
		}
    if (BTYPE != BRD_NOBORDER)
       wframe(wnd);
    PWIN_FACE = WFACE;
}

/* ------------------------------------------------------------------------ */
/*                      display an established window                       */
/* ------------------------------------------------------------------------ */
void Wshow(WINDOW *wnd)
{
  if (wndverify(&wnd) && !VISIBLE)	{
	VISIBLE = 1;
	vswap(wnd);
  }
}

/* ------------------------------------------------------------------------ */
/*                          close all windows                               */
/* ------------------------------------------------------------------------ */
void Wdeleteall()
{
	WINDOW *sav, *wnd = listtail;

	while (wnd)	{
		sav = PREV;
		Wdelete(wnd);
		wnd = sav;
	}
}

/* ------------------------------------------------------------------------ */
/*                           remove a window                                */
/* ------------------------------------------------------------------------ */
void Wdelete(WINDOW *wnd)
{
	if (wndverify(&wnd))		{
		Whide(wnd);
		free(SAV);
		listdelete(wnd);	/* remove window from list */
		free(wnd);
	}
}
/* ------------------------------------------------------------------------ */
/*                             hide a window                                */
/* ------------------------------------------------------------------------ */
void Whide(WINDOW *wnd)
{
	if (wndverify(&wnd) && VISIBLE)	{
		vswap(wnd);
		HIDDEN = 1;
		VISIBLE = 0;
	}
}

/* ------------------------------------------------------------------------ */
/*                 reposition the window in its 3-axis plane                */
/* ------------------------------------------------------------------------ */
void wndrepos(WINDOW *wnd, int x, int y, int z)
{
    WINDOW *twnd;
    int x1, y1, chat;

    if (!wndverify(&wnd))
		return;
    twnd = Westablish(x+COL, y+ROW, HEIGHT, WIDTH);
    twnd->_title = WTITLE;
    twnd->_tjust = TJUST;
    twnd->_footer = WFOOT;
    twnd->_fjust = FJUST;
    twnd->btype = BTYPE;
    twnd->wcolour[WIN_BORDER] = WBORDER;
    twnd->wcolour[WIN_TITLE] = WTITLEC;
    twnd->wcolour[WIN_ACCENT] = WACCENT;
    twnd->wcolour[WIN_FACE] = WFACE;
	twnd->_wsp = SCROLL;
	twnd->_cr = WCURS;
	if (z != 1)	{
		listdelete(twnd);
		if (z == 0)
			listinsert(twnd, wnd);
		else
			listbegin(twnd);
	}
	for (y1 = 0; y1 < twnd->_wh; y1++)
		for (x1 = 0; x1 < twnd->_ww; x1++)	{
			chat = wpeek(wnd, x1, y1);
			displ(twnd, x1, y1, chat&255, (chat>>8)&255);
		}
	twnd->_wv = 1;
	vswap(twnd);
	Whide(wnd);
	free(SAV);
	listdelete(wnd);
	*wnd = *twnd;
	listinsert(wnd, twnd);
	listdelete(twnd);
	free(twnd);
}

/* ------------------------------------------------------------------------ */
/*                           clear the window area                          */
/* ------------------------------------------------------------------------ */
void Wclear(WINDOW *wnd)
{
   register int x1, y1;

   if (wndverify(&wnd))
	for (y1 = BORDER_OFFSET; y1 < HEIGHT-BORDER_OFFSET; y1++)
		for (x1 = BORDER_OFFSET; x1 < WIDTH-BORDER_OFFSET; x1++)
			displ(wnd,x1, y1, ' ', WFACE);
}

/* ------------------------------------------------------------------------ */
/*                           window-oriented printf                         */
/* ------------------------------------------------------------------------ */
void Wprintf(WINDOW *wnd, char *ln, ...)
{
   char dlin [100], *dl = dlin;

   if (wndverify(&wnd))   {
	 va_list ap;
	 va_start(ap, ln);
	 vsprintf(dlin, ln, ap);
	 va_end(ap);
	 while (*dl)
        Wputch(wnd, *dl++);
      }
}

/* ------------------------------------------------------------------------ */
/*                     write a character to the window                      */
/* ------------------------------------------------------------------------ */
void Wputch(WINDOW *wnd, int c)
{
	if (!wndverify(&wnd))
		return;
	switch (c)	{
		case '\n':
			if (SCROLL == HEIGHT-(2*BORDER_OFFSET)-1)
				scroll(wnd, UP);
			else
				SCROLL++;
			WCURS = 0;
			break;
		case '\t':
         	do {
	       displ(wnd,(WCURS++)+BORDER_OFFSET,SCROLL+BORDER_OFFSET,' ',WFACE);
            }while ((WCURS%TABS) && (WCURS+BORDER_OFFSET) < WIDTH-(BORDER_OFFSET*2));
			break;
		default:
			if ((WCURS+BORDER_OFFSET) < WIDTH-BORDER_OFFSET)	{
				displ(wnd, WCURS+BORDER_OFFSET, SCROLL+BORDER_OFFSET, c, WFACE);
				WCURS++;
			}
			break;
	}
}

/* ------------------------------------------------------------------------ */
/*                write a character and attribute to the window             */
/* ------------------------------------------------------------------------ */
void Wputchat(WINDOW *wnd, int c, int bg, int fg, int i)
{
	if (!wndverify(&wnd))
		return;
	switch (c)	{
		case '\n':
			if (SCROLL == HEIGHT-(2*BORDER_OFFSET)-1)
				scroll(wnd, UP);
			else
				SCROLL++;
			WCURS = 0;
			break;
		case '\t':
        	do {
               displ(wnd,(WCURS++)+BORDER_OFFSET,SCROLL+BORDER_OFFSET,' ',WFACE);
            }while ((WCURS%TABS) && (WCURS+BORDER_OFFSET) < WIDTH-(BORDER_OFFSET*2));
			break;
		default:
			if ((WCURS+BORDER_OFFSET) < WIDTH-BORDER_OFFSET)	{
				displ(wnd, WCURS+BORDER_OFFSET, SCROLL+BORDER_OFFSET, c,CLR(bg,fg,i));
				WCURS++;
			}
			break;
	}
}

/* ------------------------------------------------------------------------ */
/*                             set window cursor                            */
/* ------------------------------------------------------------------------ */
void Wcursor(WINDOW *wnd, int x, int y)
{
    if (wndverify(&wnd) &&
	x < WIDTH-BORDER_OFFSET && y < HEIGHT-BORDER_OFFSET)   {
		WCURS = x;
		SCROLL = y;
	vsetcur(COL+x+BORDER_OFFSET, ROW+y+BORDER_OFFSET);
     }
}

/* ------------------------------------------------------------------------ */
/*               allow the user to make a window selection                  */
/* ------------------------------------------------------------------------ */
int Wgetsel(WINDOW *wnd, int s, char *keys)
{
   int c = 0, ky;
   if (!wndverify(&wnd))
		return 0;
   SELECT = s;
   while (c != ESC && c != '\r' && c != LEFT && c != RIGHT)	{
	accent(wnd);
	c = kgetch();
	deaccent(wnd);
	switch (c)   {
	      case UP:	if (SELECT >  1)
				SELECT--;
			    else
				SELECT = SCROLL+1;
			break;
	      case DOWN: if (SELECT < SCROLL+1)
				SELECT++;
			   else
				SELECT = 1;
			   break;
	      case '\r' :
	      case ESC  :
	      case RIGHT:
	      case LEFT :	break;

	      default   :   if (keys)	{
			      ky = 0;
			      while (*(keys + ky))	{
				if (*(keys+ky)==toupper(c) ||
					*(keys+ky)==tolower(c))
						return ky + 1;
						ky++;
				}
			      }
			     break;
		}
	}
	return 	c == '\r' ? SELECT : c == ESC ? 0 : c;
}

union REGS rg;

/* ------------------------------------------------------------------------ */
/*                   scroll a window's contents up or down                  */
/* ------------------------------------------------------------------------ */
void scroll(WINDOW *wnd, int dir)
{
	int row = HEIGHT-1, col, chat;

	if (!wndverify(&wnd))
		return;

	if (NEXT == NULL && HEIGHT > 3 && VISIBLE)	{
		rg.h.ah = dir == UP ? 6 : 7;
		rg.h.al = 1;
		rg.h.bh = WFACE;
		rg.h.cl = COL + BORDER_OFFSET;
		rg.h.ch = ROW + BORDER_OFFSET;
		rg.h.dl = COL + WIDTH - (BORDER_OFFSET+1);
		rg.h.dh = ROW + HEIGHT - (BORDER_OFFSET+1);
		int86(VIDEO, &rg, &rg);
		return;
	}
	if (dir == UP)	{
		for (row = 1+BORDER_OFFSET; row < HEIGHT-BORDER_OFFSET; row++)
		   for (col = BORDER_OFFSET; col < WIDTH-BORDER_OFFSET; col++)	{
			 chat = wpeek(wnd, col, row);
			 displ(wnd,col,row-1,chat&255,(chat>>8)&255);
			}
		for (col = BORDER_OFFSET; col < WIDTH-BORDER_OFFSET; col++)
			displ(wnd, col, row-1, ' ', WFACE);
	}
	else	{
		for (row = HEIGHT-(BORDER_OFFSET+1); row > BORDER_OFFSET; --row)
		     for (col = BORDER_OFFSET; col < WIDTH-BORDER_OFFSET; col++)	{
			 chat = wpeek(wnd, col, row);
			 displ(wnd,col,row+1,chat&255,(chat>>8)&255);
			}
		for (col = BORDER_OFFSET; col < WIDTH-BORDER_OFFSET; col++)
			displ(wnd, col, row+1, ' ', WFACE);
	}
}

/* ------------------------------------------------------------------------ */
/*                         draw the window frame                            */
/* ------------------------------------------------------------------------ */
static void wframe(WINDOW *wnd)
{
	register int x, y;

	if (!wndverify(&wnd))
		return;
	/* --------- window title -------------- */
	displ(wnd,0, 0, NW, WBORDER);
	dtitle(wnd);
	displ(wnd,WIDTH-1, 0, NE, WBORDER);
	/* ---------- do window sides -------------- */
	for (y = 1; y < HEIGHT-1; y++)	{
		displ(wnd,0, y, SIDE, WBORDER);
		displ(wnd,WIDTH-1, y, SIDE, WBORDER);
	}
	/* --------------- do bottom of frame ---------------- */
	displ(wnd,0, y, SW, WBORDER);
	for (x = 1; x < WIDTH-1; x++)
		displ(wnd,x, y, LINE, WBORDER);
	displ(wnd,x, y, SE, WBORDER);
	dfooter(wnd);
}

/* ------------------------------------------------------------------------ */
/*                         display the window title                         */
/* ------------------------------------------------------------------------ */
static void dtitle(WINDOW *wnd)

{
   int x = 1, i, ln;
   char *s = WTITLE;

   if (!wndverify(&wnd))
	return;

   switch (TJUST) {
	case JUST_L : {
		      x =1;
			 while (*s)
			     displ(wnd, x++, 0, *s++, WTITLEC);
			 while (x < WIDTH-1)
			     displ(wnd, x++, 0, LINE, WBORDER);
		      }
		     break;
	case JUST_R : {   i = 1;
			 while (i < WIDTH-1)
			     displ(wnd, i++, 0, LINE, WBORDER);
			 x = ( (WIDTH-1) - strlen(s));
			 while (*s)
			     displ(wnd, x++, 0, *s++, WTITLEC);
		      }
		     break;
	default    : if (s) {
			ln = strlen(s);
			if (ln > WIDTH-2) /* if max screen length -2 */
			i = 0;
			 else
			  i = ((WIDTH-2-ln) / 2);  /* centre */
			  if (i > 0)
				while (i--)
				    displ(wnd, x++, 0, LINE, WBORDER);
			  while (*s && x < WIDTH-1)
			     displ(wnd, x++, 0, *s++, WTITLEC);
		       }
		     while (x < WIDTH-1)
		     displ(wnd, x++, 0, LINE, WBORDER);
   } /* switch */


}

/* ------------------------------------------------------------------------ */
/*                         display the window title                         */
/* ------------------------------------------------------------------------ */
static void dfooter(WINDOW *wnd)

{
   int x = 1, i, ln;
   char *s = WFOOT;

   if (!wndverify(&wnd))
	return;
   if (! s)
      return;

   switch (FJUST) {
	case JUST_L : {
		      x =1;
			 while (*s)
			     displ(wnd, x++, HEIGHT-1, *s++, WFOOTC);
			 while (x < WIDTH-1)
			     displ(wnd, x++, HEIGHT-1, LINE, WBORDER);
		      }
		     break;
	case JUST_R : {   i = 1;
			 while (i < WIDTH-1)
			     displ(wnd, i++, HEIGHT-1, LINE, WBORDER);
			 x = ( (WIDTH-1) - strlen(s));
			 while (*s)
			     displ(wnd, x++, HEIGHT-1, *s++, WFOOTC);
		      }
		     break;
	default    : ln = strlen(s);
		     if (ln > WIDTH-2) /* if max screen length -2 */
		     i = 0;
			 else
			  i = ((WIDTH-2-ln) / 2);  /* centre */
			  if (i > 0)
				while (i--)
				    displ(wnd, x++, HEIGHT-1, LINE, WBORDER);
			  while (*s && x < WIDTH-1)
			     displ(wnd, x++, HEIGHT-1, *s++, WFOOTC);
		     while (x < WIDTH-1)
		     displ(wnd, x++, HEIGHT-1, LINE, WBORDER);
   } /* switch */
}

/* ------------------------------------------------------------------------ */
/*              compute address of a window's display character             */
/* ------------------------------------------------------------------------ */
static int *waddr(WINDOW *wnd, int x, int y)
{
   WINDOW *nxt = NEXT;
   int *vp;

   if (!VISIBLE)
	return (int *) (SAV+y*(WIDTH*2)+x*2);
   x += COL;
   y += ROW;
   while (nxt)	{
	if (nxt->_wv)
	  if (x >= nxt->_wx && x <= nxt->_wx + nxt->_ww-1)
	      if (y >= nxt->_wy && y <= nxt->_wy + nxt->_wh-1)	{
			  x -= nxt->_wx;
			  y -= nxt->_wy;
			  vp = (int *)	((nxt->_ws) +y*(nxt->_ww*2)+x*2);
			  return vp;
		      }
	     nxt = nxt->_nx;
	}
 return NULL;
}

/* ------------------------------------------------------------------------ */
/*                    display a character to a window                       */
/* ------------------------------------------------------------------------ */
void displ(WINDOW *wnd, int x, int y, int ch, int at)
{
     int *vp;
     int vch = (ch&255)|(at<<8);

     if ((vp = waddr(wnd, x, y)) != NULL)
	*vp = vch;
     else
	vputch(x+COL,y+ROW,at,ch);
}

/* ------------------------------------------------------------------------ */
/*                    get a displayed character from a window               */
/* ------------------------------------------------------------------------ */
static int wpeek(WINDOW *wnd, int x, int y)
{
     int *vp;

     if ((vp = waddr(wnd, x, y)) != NULL)
		return *vp;
     return (vget(x+COL,y+ROW));
}

/* ------------------------------------------------------------------------ */
/*                  swap the video image with the save buffer               */
/* ------------------------------------------------------------------------ */
static void vswap(WINDOW *wnd)
{
	int x, y, chat;
	int *bf = (int *) SAV;

	for (y = 0; y < HEIGHT; y++)
		for (x = 0; x < WIDTH; x++)	{
			chat = *bf;
			*bf++ = wpeek(wnd, x, y);
			displ(wnd, x, y, chat&255, (chat>>8)&255);
		}
}

/* ------------------------------------------------------------------------ */
/*                 (de)accent the line where SELECT points                  */
/* ------------------------------------------------------------------------ */
void acline(WINDOW *wnd, int set)
{
    int x, ch;

    if (!wndverify(&wnd))
		return;
    for (x = 1; x < WIDTH - 1; x++)	{
		ch = wpeek(wnd, x, SELECT) & 255;
		displ(wnd, x, SELECT, ch, set);
    }
}

/* ------------------------------------------------------------------------ */
/*                   add a window to the end of the list                    */
/* ------------------------------------------------------------------------ */
static void listadd(WINDOW *wnd)
{
	if (listtail)	{
		PREV = listtail;
		listtail->_nx = wnd;
	}
	listtail = wnd;
	if (!listhead)
		listhead = wnd;
}

/* ------------------------------------------------------------------------ */
/*                add a window to the beginning of the list                 */
/* ------------------------------------------------------------------------ */
static void listbegin(WINDOW *wnd)
{
	if (listhead)	{
		NEXT = listhead;
		listhead->_pv = wnd;
	}
	listhead = wnd;
	if (!listtail)
		listtail = wnd;
}

/* ------------------------------------------------------------------------ */
/*                     remove a window from the list                        */
/* ------------------------------------------------------------------------ */
static void listdelete(WINDOW *wnd)
{
	if (NEXT)
		NEXT->_pv = PREV;
	if (PREV)
		PREV->_nx = NEXT;
	if (listhead == wnd)
		listhead = NEXT;
	if (listtail == wnd)
		listtail = PREV;
	NEXT = PREV = NULL;
}

/* ------------------------------------------------------------------------ */
/*                            insert w1 after w2                             */
/* ------------------------------------------------------------------------ */
static void listinsert(WINDOW *w1, WINDOW *w2)
{
	w1->_pv = w2;
	w1->_nx = w2->_nx;
	w2->_nx = w1;
	if (w1->_nx == NULL)
		listtail = w1;
	else
		w1->_nx->_pv = w1;
}

/* ------------------------------------------------------------------------ */
/*                 verify the presence of a window in the list              */
/* ------------------------------------------------------------------------ */
static int wndverify(WINDOW **wndc)

{
     WINDOW *wnd;

     if (*wndc == NULL)
		*wndc = listtail;
     else	{
	wnd = listhead;
	while (wnd != NULL)	{
		if (*wndc == wnd)
			break;
		wnd = NEXT;
	}
     }
    return *wndc != NULL;
}


