/***************************************************************************
 *                                                                         *
 *   VIDAPI.C                                                              *
 *                                                                         *
 *   Copyright (C) 1996 Galacticomm, Inc.    All Rights Reserved.          *
 *                                                                         *
 *   This is the cross-platform Galacticomm video API as implemented for   *
 *   Windows NT.                                                           *
 *                                                                         *
 *                                        - Ilya Minkin 01/26/96           *
 *                                                                         *
 ***************************************************************************/

#include "gcommlib.h"

#if defined( _MSC_VER )
#pragma warning( disable : 4305 4309 )  // truncation from const int to char
#endif

#define ESCAPE 27                  // ANSI sequence start (followed by '[')
                                   // ansifls bit flag values
#define ANSIOF 1                   // ANSI recognition disabled
#define ANSIER 2                   // ANSI escape-received
#define ANSIWD 4                   // ANSI waiting for first digit
#define ANSIAN 8                   // ANSI accumulating a number

#define CR curatr
#define DL (dx & 0xFF)
#define DH (dx >> 8)
#define DLEQ(x) { dx &= 0xFF00 ; dx |=  (x & 0xFF); }
#define DHEQ(x) { dx &= 0x00FF ; dx |= ((x & 0xFF)<<8); }
#define DHPP { INT i; i= (dx>>8) + 1 ; dx &=0x00FF; dx |= (i<<8); }
#define DHMM { INT i; i= (dx>>8) - 1 ; dx &=0x00FF; dx |= (i<<8); }

#define SCRWIDTH    (80)
#define SCRHEIGHT   (25)
#define SCRBUFSZ    (SCRWIDTH*SCRHEIGHT)

static VOID donewl(VOID);
static VOID pransi(CHAR);
static VOID doansi(CHAR);
static VOID pinhvp(VOID);
static VOID wrcurp(USHORT);
static USHORT rdcurp(VOID);
static VOID supisc(VOID);
static VOID compsi(VOID);
static VOID ceolut(VOID);
static VOID outbyt(CHAR);

static VOID updatevideo(VOID);
static VOID scrbuf2video(UINT offset,UINT count);
static VOID video2scrbuf(UINT offset,UINT count);

static COORD off2coord(UINT offset);
static COORD pos2coord(UINT cursorpos);
static UINT coord2pos(COORD coord);
static WORD att2color(CHAR attribute);

static BOOL hndl_rout(DWORD ctrltype);

static GBOOL fBeepOn=FALSE;

struct curatr curatr={
     7,
     ANSIOF,
     0,
     {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
     0,
     0,
     7,
     0,
     0,
     0,
     1,
     0,
     0,
     0x700,
     0,
     0,
     SCRWIDTH-1,
     SCRHEIGHT-1,
     1,
     0,
     SCRHEIGHT-1,
     0,
     0,
     SCRWIDTH-1,
     SCRHEIGHT-1,
     1,
     0,
     0
};

static CHAR canscro=0;             // flag, can we scroll?
static CHAR *iscstt=NULL;
static CHAR *prtbuf=NULL;          // temp buf to store printf()-style stgs
INT pxoff,pyoff;
GBOOL color=TRUE;                  // flag indicating color display avail

static CHAR *si;
static USHORT dx;

static INT hbstk[GVIDCSZSTK]={     // cursiz()/rstcur() stack
     GVIDLILCURS,GVIDLILCURS,GVIDLILCURS,GVIDLILCURS,GVIDLILCURS,
     GVIDLILCURS,GVIDLILCURS,GVIDLILCURS,GVIDLILCURS,GVIDLILCURS
};

static HANDLE hconsole_in=INVALID_HANDLE_VALUE;
static HANDLE hconsole_out=INVALID_HANDLE_VALUE;
static struct {
     COORD scrbufsize;
     CONSOLE_CURSOR_INFO cursinfo;
     WORD color;
     DWORD mode;
     GBOOL areansi;
} con_default;

static GBOOL row2update[SCRHEIGHT];
static CHAR scrbuffer[SCRBUFSZ*2];
static CHAR scrbuf_chr[SCRBUFSZ];
static WORD scrbuf_col[SCRBUFSZ];

static GBOOL fVideoEnabled=TRUE;

/*****************************************************************************
*                 Application Program Interface (API)                        *
*****************************************************************************/
VOID
disableConsoleVideo(VOID)          // disable video output
{
     fVideoEnabled=FALSE;
}

VOID
initvid(VOID)                      // initialize video subsystem
{
     CONSOLE_SCREEN_BUFFER_INFO scrbuf_info;
     SMALL_RECT winrect;
     COORD coord;
     DWORD mode;

     if (!fVideoEnabled) {
          return;
     }
     if (hconsole_in != INVALID_HANDLE_VALUE
      && hconsole_out != INVALID_HANDLE_VALUE) {
          return;
     }
     hconsole_in=CreateFile("CONIN$",
                            GENERIC_READ|GENERIC_WRITE,
                            FILE_SHARE_READ|FILE_SHARE_WRITE,
                            NULL,
                            OPEN_EXISTING,
                            FILE_ATTRIBUTE_NORMAL,
                            NULL);
     if (hconsole_in == INVALID_HANDLE_VALUE) {
          return;
     }
     hconsole_out=CreateFile("CONOUT$",
                             GENERIC_READ|GENERIC_WRITE,
                             FILE_SHARE_READ|FILE_SHARE_WRITE,
                             NULL,
                             OPEN_EXISTING,
                             FILE_ATTRIBUTE_NORMAL,
                             NULL);
     if (hconsole_out ==  INVALID_HANDLE_VALUE) {
          CloseHandle(hconsole_in);
          return;
     }
     GetConsoleMode(hconsole_in,&mode);
     con_default.mode=mode;
     // we will handle ^C ourselves and do not care about mouse input
     mode&=~(ENABLE_PROCESSED_INPUT|ENABLE_MOUSE_INPUT);
     SetConsoleMode(hconsole_in,mode);
     if (isWinNT() && GetModuleHandle("wgserver.exe") == NULL) {
           // HACK WGSERVER has it's own handler
          SetConsoleCtrlHandler((PHANDLER_ROUTINE)hndl_rout,TRUE);
     }
     GetConsoleScreenBufferInfo(hconsole_out, &scrbuf_info);
     con_default.scrbufsize=scrbuf_info.dwSize;
     con_default.color=scrbuf_info.wAttributes;
     GetConsoleCursorInfo(hconsole_out,&con_default.cursinfo);
     coord.X=SCRWIDTH;
     coord.Y=SCRHEIGHT;
     SetConsoleScreenBufferSize(hconsole_out,coord);
     winrect.Left=winrect.Top=0;
     winrect.Right=SCRWIDTH-1;
     winrect.Bottom=SCRHEIGHT-1;
     SetConsoleWindowInfo(hconsole_out,TRUE,&winrect);
     SetConsoleTextAttribute(hconsole_out,att2color(CR.attrib));
     // HACK Do we need this ????
     if ((con_default.areansi=(GBOOL)AreFileApisANSI()) == TRUE) {
          SetFileApisToOEM();
     }
     cursiz(GVIDLILCURS);
     rdcurp();
     video2scrbuf(0,SCRBUFSZ*2);
     setwin(NULL,0,0,SCRWIDTH-1,SCRHEIGHT-1,1);
     prtbuf=(CHAR *)alcmem(MAXPFSIZ);
}

VOID
clsvid(VOID)
{
     if (!fVideoEnabled) {
          return;
     }
     SetConsoleMode(hconsole_in,con_default.mode);
     if (isWinNT() && GetModuleHandle("wgserver.exe") == NULL) {
          SetConsoleCtrlHandler((PHANDLER_ROUTINE)hndl_rout,FALSE);
     }
     SetConsoleScreenBufferSize(hconsole_out,con_default.scrbufsize);
     SetConsoleCursorInfo(hconsole_out,&con_default.cursinfo);
     SetConsoleTextAttribute(hconsole_out,con_default.color);
     // HACK Do we need this ????
     if (con_default.areansi) {
          SetFileApisToANSI();
     }
     else {
          SetFileApisToOEM();
     }
     CloseHandle(hconsole_in);
     CloseHandle(hconsole_out);
     free(prtbuf);
     prtbuf=NULL;
}

VOID
scn2mem(                           // move real screen memory to other mem 
VOID *dest,                        //   pointer to destination memory      
UINT offset,                       //   offset from start of the video scr 
UINT count)                        //   number of bytes to move            
{
     if (!fVideoEnabled) {
          return;
     }
     assert((offset+count) <= (SCRBUFSZ*2));
     assert(hconsole_out != INVALID_HANDLE_VALUE);
     memmove(dest,&scrbuffer[offset],count);
}

VOID
mem2scn(                           // move other memory to the real screen 
VOID *src,                         //   pointer to source memory           
UINT offset,                       //   offset into the video screen       
UINT count)                        //   number of bytes to move            
{
     if (!fVideoEnabled) {
          return;
     }
     assert(offset+count <= SCRBUFSZ*2);
     assert(hconsole_out != INVALID_HANDLE_VALUE);
     memmove(&scrbuffer[offset],src,count);
     scrbuf2video(offset,count);
}

VOID
scn2scn(                           // move real screen to real screen      
UINT srcoff,                       //   source offset for move             
UINT dstoff,                       //   destination offset for move        
UINT count)                        //   number of bytes to move            
{
     if (!fVideoEnabled) {
          return;
     }
     assert(srcoff+count <= SCRBUFSZ*2);
     assert(dstoff+count <= SCRBUFSZ*2);
     assert(hconsole_out != INVALID_HANDLE_VALUE);
     memmove(&scrbuffer[dstoff],&scrbuffer[srcoff],count);
     scrbuf2video(dstoff,count);
}

CHAR
scngetc(                           // read a char/attr from the "REAL" scr 
UINT offset)                       //   offset into the video screen       
{
     if (!fVideoEnabled) {
          return(0);
     }
     assert(offset < SCRBUFSZ*2);
     assert(hconsole_out != INVALID_HANDLE_VALUE);
     return(scrbuffer[offset]);
}

VOID
scnputc(                           // change a char/attr on the "REAL" scr 
UINT offset,                       //   offset into the video screen       
CHAR byt)                          //   new byte to be written to the scr  
{
     if (!fVideoEnabled) {
          return;
     }
     assert(offset < SCRBUFSZ*2);
     assert(hconsole_out != INVALID_HANDLE_VALUE);
     scrbuffer[offset]=byt;
     scrbuf2video(offset,2);
}

VOID
scnputw(                           // change a char/attr pair on "REAL" scr
UINT offset,                       //   offset into the video screen       
CHAR byt,                          //   new character                      
CHAR atr,                          //   new attribute                      
UINT nwords)                       //   number of char/attr pairs to put   
{
     UINT i;
     CHAR *ptr;

     if (!fVideoEnabled) {
          return;
     }
     assert(offset+nwords*2 <= SCRBUFSZ*2);
     assert(hconsole_out != INVALID_HANDLE_VALUE);
     for (i=0,ptr=&scrbuffer[offset] ; i < nwords ; i++) {
          *ptr++=byt;
          *ptr++=atr;
     }
     scrbuf2video(offset,nwords*2);
}

CHAR *
scblank(                           // clear a screen to blanks             
CHAR *buf,                         //   ptr to buffer to clear 0=real vid  
CHAR attrib)                       //   attribute to clear to              
{
     UINT i;
     CHAR *orgbuf;

     if (!fVideoEnabled) {
          return(buf);
     }
     attrib=scncolor(attrib);
     if (buf != NULL) {
          orgbuf=buf;
          for (i=0 ; i < SCRBUFSZ ; i++) {
               *buf++=' ';
               *buf++=attrib;
          }
          return(orgbuf);
     }
     scnputw(0,' ',attrib,SCRBUFSZ);
     return(buf);
}

CHAR
scncolor(                          // ret. scr. attrib equiv if monochrome 
INT attrib)
{
     CHAR retval;

     if (!fVideoEnabled) {
          return((CHAR)attrib);
     }
     retval=(CHAR)attrib;
     if (!color) {
          if ((retval&0x70) == 0x70) {
               retval=0x70;
          }
          else {
               retval=((retval&0x88)|0x07);
          }
     }
     else {
          retval&=~0x80;
     }
     return(retval);
}

VOID
monorcol(VOID)                     // set 'color' flag based on CNF setting
{                                  //   (or auto-sense if no MSG file)     
     CHAR *opt;

     if ((opt=msgscan("wgsmajor.msg","CRT")) != NULL) {
          if (sameas(opt,"AUTO")) {
               imonorcol();
          }
          else if (sameas(opt,"COLOR")) {
               color=TRUE;
          }
          else {
               color=FALSE;
          }
     }
     else {
          imonorcol();
     }
}

VOID
imonorcol(VOID)                    // set 'color' flag on based on system  
{
     color=TRUE;                   // always treat NT consoles as color    
}

VOID
setwin(                            // set window parameters                
CHAR *scn,                         //   ptr to screen to write to 0=video  
INT xul,                           //   upper left x coord                 
INT yul,                           //   upper left y coord                 
INT xlr,                           //   lower right x coord                
INT ylr,                           //   lower right y coord                
INT sen)                           //   scroll enable (1=yes)              
{
     CR.oscnstt=CR.scnstt;
     CR.oulx=CR.ulx;
     CR.ouly=CR.uly;
     CR.olrx=CR.lrx;
     CR.olry=CR.lry;
     CR.oscropt=CR.scropt;
     CR.scnstt=scn;
     CR.ulx=(CHAR)xul;
     CR.uly=(CHAR)yul;
     CR.lrx=(CHAR)xlr;
     CR.lry=(CHAR)ylr;
     CR.scropt=sen;
}

VOID
rstwin(VOID)                       // restore previous window parameters 
{
     CR.scnstt=CR.oscnstt;
     CR.ulx=CR.oulx;
     CR.uly=CR.ouly;
     CR.lrx=CR.olrx;
     CR.lry=CR.olry;
     CR.scropt=CR.oscropt;
}

VOID
belper(                            // set bell ('\7') period               
INT prd)                           //   pitch and duration                 
{
     fBeepOn=prd == 0 ? FALSE : TRUE;
}

VOID
ansion(                            // turn ANSI-handling on or off         
INT yes)                           // 1=on (default) ; 0=off               
{
     if (yes) {                    // turn ansi on                         
          CR.ansifls&=~(ANSIOF);
          CR.attrib=CR.ansiatr;
          if (CR.uly > CR.topscr) {// make sure we are in bounds           
               CR.topscr=CR.uly;
          }
          if (CR.lry < CR.botscr) {
               CR.botscr=CR.lry;
          }
     }
     else {                        // turn ANSI off (but save context bits)
          CR.ansifls|=ANSIOF;
          CR.ansiatr=CR.attrib;    // save ANSI attrib for later           
     }
}

VOID
locate(                            // move cursor to x,y                   
INT x,                             // dest x (0=left margin)               
INT y)                             // dest y (0=top line)                  
{
     USHORT coord;

     if (!fVideoEnabled) {
          return;
     }
     assert(hconsole_out != INVALID_HANDLE_VALUE);
     CR.oldcur=rdcurp();           // save current cursor                  
     coord=(y<<8)|(x&0xFF);        // load with dest coords                
     wrcurp(coord);                // put cursor there                     
}

VOID
rstloc(VOID)                       // restore cursor to previous x,y pos
{
     if (!fVideoEnabled) {
          return;
     }
     assert(hconsole_out != INVALID_HANDLE_VALUE);
     wrcurp(CR.oldcur);
}

VOID
cursact(                           // enable moving of blinking cursor     
CHAR movit)                        //   1=move blinking cursor, 0=still    
{
     if (!fVideoEnabled) {
          return;
     }
     assert(hconsole_out != INVALID_HANDLE_VALUE);
     if (CR.actcur != movit) {     // any change in status here            
          if (movit) {             // yes, are we going active             
               CR.actcur=movit;
               if (CR.nacurp != CR.lacurp) {    // no, need to call wrcurp 
                    wrcurp(CR.nacurp);
               }
          }
          else {                   // no, inactive, remember current pos   
               CR.nacurp=rdcurp();
               CR.lacurp=CR.nacurp;// remeber last-active spot too         
               CR.actcur=0;
          }
     }
}

VOID
cursiz(                            // set cursor size                      
INT howbig)                        //   off, little or block               
{
     CONSOLE_CURSOR_INFO cursinfo;

     if (!fVideoEnabled) {
          return;
     }
     assert(hconsole_out != INVALID_HANDLE_VALUE);
     memmove(hbstk+1,hbstk,sizeof(INT)*(GVIDCSZSTK-1));
     hbstk[0]=howbig;
     switch (howbig) {
     case GVIDNOCURS:
          cursinfo.dwSize=1;
          cursinfo.bVisible=FALSE;
          break;
     case GVIDBIGCURS:
          cursinfo.dwSize=100;
          cursinfo.bVisible=TRUE;
          break;
     default:
          hbstk[0]=GVIDLILCURS;
     case GVIDLILCURS:
          cursinfo.dwSize=30;
          cursinfo.bVisible=TRUE;
          break;
     }
     SetConsoleCursorInfo(hconsole_out,&cursinfo);
}

VOID
rstcur(VOID)                       // restore cursor from cursor-size stack
{
     if (!fVideoEnabled) {
          return;
     }
     assert(hconsole_out != INVALID_HANDLE_VALUE);
     memmove(hbstk,hbstk+1,sizeof(INT)*(GVIDCSZSTK-1));
     cursiz(hbstk[0]);
}

INT
curcurs(VOID)                      // return current cursor size code
{
     return(hbstk[0]);
}

INT
curcurx(VOID)                      // get current cursor x coord 
{
     USHORT col;

     if (!fVideoEnabled) {
          return(0);
     }
     assert(hconsole_out != INVALID_HANDLE_VALUE);
     col=rdcurp();
     return(col&0xFF);
}

INT
curcury(VOID)                      // get current cursor y coord 
{
     USHORT row;

     if (!fVideoEnabled) {
          return(0);
     }
     assert(hconsole_out != INVALID_HANDLE_VALUE);
     row=rdcurp();
     return(row>>8);
}

VOID
setatr(                            // sets video attributes               
CHAR atr)
{
     if (!fVideoEnabled) {
          return;
     }
     CR.attrib=scncolor(atr);
}

VOID
cleareol(VOID)                     // clear from cursor to end of line    
{
     if (!fVideoEnabled) {
          return;
     }
     assert(hconsole_out != INVALID_HANDLE_VALUE);
     supisc();
     rdcurp();
     compsi();
     ceolut();
     updatevideo();
}

INT
printfat(                          // Galacticomm printf at position x,y   
INT x,                             //   x position to print at             
INT y,                             //   y position to print at             
CHAR *fmt,                         //   format string                      
...)                               //   variable number of parameters      
{
     va_list ap;
     INT nprnt;
     CHAR *str;

     if (!fVideoEnabled) {
          return(0);
     }
     assert(hconsole_out != INVALID_HANDLE_VALUE);
     locate(x,y);
     va_start(ap,fmt);
     nprnt=vsprintf(prtbuf,fmt,ap);
     va_end(ap);
     supisc();
     rdcurp();
     compsi();
     str=prtbuf;
     while(*str) {
          outbyt(*str);
          str++;
     }
     updatevideo();
     wrcurp(dx);
     return(nprnt);
}

VOID
proff(                             // set offsets for certain printing     
INT x,                             //   the x offset                       
INT y)                             //   the y offset                       
{
     pxoff=x;
     pyoff=y;
}

VOID
prat(                              // print at a location relative to proff
INT x,                             //   x offset to start at               
INT y,                             //   y offset to start at               
CHAR *ctlstg,                      //   control string                     
...)                               //   variable arguments                 
{
     va_list ap;
     CHAR *str;

     if (!fVideoEnabled) {
          return;
     }
     assert(hconsole_out != INVALID_HANDLE_VALUE);
     locate(x+pxoff,y+pyoff);
     va_start(ap,ctlstg);
     vsprintf((CHAR *)prtbuf,(CHAR *)ctlstg,ap);
     va_end(ap);
     supisc();
     rdcurp();
     compsi();
     str=prtbuf;
     while (*str) {
          outbyt(*str);
          str++;
     }
     updatevideo();
     wrcurp(dx);
}

CHAR *
auxcrt(VOID)                       // pointer to auxillary CRT            
{
     return(NULL);
}

INT
gprintf(                           // Galacticomm flavor of printf()      
CHAR *fmt,
...)
{
     va_list ap;
     INT nprnt;
     CHAR *str;

     if (!fVideoEnabled) {
          return(0);
     }
     assert(hconsole_out != INVALID_HANDLE_VALUE);
     va_start(ap,fmt);
     nprnt=vsprintf((CHAR *)prtbuf,(CHAR *)fmt,ap);
     va_end(ap);
     supisc();
     rdcurp();
     compsi();
     str=prtbuf;
     while (*str) {
          outbyt(*str);
          str++;
     }
     updatevideo();
     wrcurp(dx);
     return(nprnt);
}

/*****************************************************************************
*   Internal/Low Level Functions called only by the above functions          *
*****************************************************************************/

static VOID
wrcurp(                            // write cursor position               
USHORT coord)                      //   input-format= row:col             
{
     COORD wincoord;

     if (CR.actcur) {
          wincoord=pos2coord(coord);
          SetConsoleCursorPosition(hconsole_out,wincoord);
     }
     else {
          CR.nacurp=coord;
     }
}

static USHORT                      //   returns row:col                   
rdcurp(VOID)                       // write cursor position               
{
     CONSOLE_SCREEN_BUFFER_INFO  scrinfo;

     if (CR.actcur) {
          GetConsoleScreenBufferInfo(hconsole_out,&scrinfo);
          dx=coord2pos(scrinfo.dwCursorPosition);
     }
     else {
          dx=CR.nacurp;
     }
     return(dx);
}

static VOID
supisc(VOID)                       // set ptr to beginning of video memory 
{
     if (CR.scnstt) {              // in-memory screen                     
          iscstt=CR.scnstt;
     }
     else {                        // real screen                          
          iscstt=scrbuffer;
     }
}

static VOID
compsi(VOID)                       // set ptr to video memory              
{
     si=iscstt+(((dx&0xFF)+((dx>>8)*SCRWIDTH))*2);
}

static VOID
ceolut(VOID)                       // clear to the end of line             
{
     INT nchars;
     CHAR *pos;

     nchars=CR.lrx+1-DL;           // number of chars to zap               
     for (pos=si ; nchars > 0 ; nchars--) {
          *pos++=' ';
          *pos++=CR.attrib;
     }
     row2update[DH]=TRUE;
}

static VOID
outlit(                            // output a single character            
CHAR ch)
{
     *si++=ch;
     *si++=CR.attrib;
     row2update[DH]=TRUE;
     if (DL == CR.lrx) {           // last guy on this line?               
          donewl();                // yes, do new-line function            
     }
     else {
          dx++;                    // no, bump column                      
     }
}

static VOID
prep4r(VOID)                       // prepare usebot and usetop            
{                                  // for screen writes                    
     if (CR.ansifls&ANSIOF) {      // if not ANSI, use setwin lims         
          CR.usetop=CR.uly;
          CR.usebot=CR.lry;
     }
     else {                        // if ANSI, use greater of uly & topscr 
                                   // and lesser of lry and botscr         
          CR.usetop=max(CR.uly,CR.topscr);
          CR.usebot=min(CR.lry,CR.botscr);
     }
}

static VOID
ceoscn(VOID)                       // clear to end of screen               
{
     INT cnt,curcnt,i;
     USHORT savedx;
     CHAR *sav1,*sav2;

     savedx=dx;
     prep4r();                     // prep 'usebot' for writes             
     cnt=CR.lrx-CR.ulx+1;          // char count for full lines            
     curcnt=CR.lrx-DL+1;           // # of chars left on this line         
     sav1=si;                      // save current si                      
     DLEQ(CR.ulx);                 // point at first char of current line  
     compsi();
     sav2=si;                      // save for later                       
     si=sav1;                      // restore to current cursor pointer    
     do {
          for (i=0 ; i < curcnt ; i++) { // zap characters                 
               *si++=' ';
               *si++=CR.attrib;
          }
          row2update[DH]=TRUE;
          curcnt=cnt;              // not set count for full lines         
          sav2+=SCRWIDTH*2;        // point to first char of next line     
          si=sav2;
          DHPP;
     } while ((USHORT)DH <= CR.usebot);
     dx=savedx;
}

static VOID
docr(VOID)                         // do a carriage return                 
{
     DLEQ(CR.ulx);                 // cursor back to start of line         
     compsi();                     // figure si anew                       
}

static VOID
ffeed(VOID)                        // do a form-feed (clear screen window) 
{
     USHORT cnt,i;
     CHAR *di;

     prep4r();
     cnt=CR.lrx-CR.ulx+1;          // get count of bytes of clear each time
     DLEQ(CR.ulx);                 // point at first byte to clear         
     DHEQ(CR.usetop);
     compsi();
     do {
          for (i=0,di=si ; i < cnt ; i++) {
               *di++=' ';
               *di++= CR.attrib;
          }
          row2update[DH]=TRUE;
          si+=SCRWIDTH*2;          // bump to next line                    
          DHPP;
     } while ((USHORT)DH <= CR.usebot);
     DLEQ(CR.ulx);                 // point si at "home" byte of window    
     DHEQ(CR.usetop);
     compsi();
}

static VOID
donewl(VOID)                       // do a newline                         
{
     INT cnt, i, savedx;
     CHAR *di;

     prep4r();
     DLEQ(CR.ulx);                 // cursor back to start of line         
     if ((USHORT)DH < CR.usebot) { // will this involve scrolling          
          DHPP;                    // no                                   
          compsi();                // figure si anew                       
          return;
     }
     DHEQ(CR.usebot);
     savedx=dx;
     if (CR.scropt) {              // scrolling enabled?
          prep4r();
          cnt=CR.lrx-CR.ulx+1;     // get count of bytes to move each time
          DLEQ(CR.ulx);
          DHEQ(CR.usetop);
          compsi();                // point at first destination byte
          while (DH != CR.usebot) {
               di=si;         // make last source the new destination
               si+=SCRWIDTH*2;// and the new source one line beyond
               for(i=0; i < cnt; i++) {
                    *(di+i*2)=*(si+i*2);
                    *(di+i*2+1)=*(si+i*2+1);
               }
               row2update[DH]=TRUE;
               DHPP;
          }
          for(i=0; i < cnt; i++) { // now clear out bottom line
               *si++=' ';
               *si++=CR.attrib;
          }
          row2update[DH]=TRUE;
     }
     dx=savedx;
     compsi();                     // figure si anew                       
}

static VOID
dorevl(VOID)                       // do a reverse scroll                  
{
     INT cnt, i, savedx;
     CHAR *di;

     prep4r();
     DLEQ(CR.ulx);                 // cursor back to start of line         
     if (DH != CR.usetop) {        // will this involve scrolling?         
          DHPP;                    // no                                   
          compsi();                // but compute si anew                  
          return;
     }
     savedx=dx;
     if (CR.scropt) {              // scrolling enabled?                   
          prep4r();
          cnt=CR.lrx-CR.ulx+1;
          DLEQ(CR.ulx);
          DHEQ(CR.usebot);         // point at first destination byte      
          compsi();
          while (DH != CR.usetop) {
               di=si;              // make last source the new destination 
               si-=SCRWIDTH*2;     // and the new source one line beyond   
               for(i=0 ; i < cnt ; i++) { // scroll one line               
                    *(di+i*2)=*(si+i*2);
                    *(di+i*2+1)=*(si+i*2+1);
               }
               row2update[DH]=TRUE;
               DHMM;
          }
          for(i=0 ; i < cnt ; i++) { // clear out top line                 
               *si++=' ';
               *si++=CR.attrib;
          }
          row2update[DH]=TRUE;
     }
     dx=savedx;
     compsi();                     // figure si anew                       
}

static VOID
dobell(VOID)                       // do a bell                            
{
     if (fBeepOn) {
          MessageBeep(0xFFFFFFFF);
     }
}

static VOID
doback(VOID)                       // do a backspace                       
{
     prep4r();
     if (DL == CR.ulx) {           // at left margin?                      
          if (DH == CR.usetop) {   // yes, on top line too?                
               return;             // yes, ignore                          
          }
          else {                   // no, goto end of prev line
               DHMM;
               DLEQ(CR.lrx);
               dx++;
               compsi();
          }
     }
     si-=2;                        // back up                              
     *si=' ';                      // overwrite with space                 
     row2update[DH]=TRUE;
     dx--;                         // keep dh/dl in sync                   
}

static VOID
outbyt(                            // output byte (update dx & si)         
CHAR ch)
{
     if (CR.ansifls&(ANSIER|ANSIWD|ANSIAN)) {
          pransi(ch);              // go process ansi                      
     }
     else if (ch == ESCAPE && !(CR.ansifls&ANSIOF)) {// start of ANSI seq?
          CR.ansifls|=ANSIER;      // ANSI escape received                
          CR.ansicnt=0;            // no args at first                     
          CR.ansiarg[0]=1;         // default first two to 1's             
          CR.ansiarg[1]=1;         // default first two to 1's             
     }
     else {
          switch (ch) {
          case 10:                 // c newline char?                     
               donewl();
               break;
          case 7:                  // bell?                               
               dobell();
               break;
          case 13:                 // carriage return?                    
               docr();
               break;
          case 12:                 // form feed?                          
               ffeed();
               break;
          case 8:                  // backspace?                          
               doback();
               break;
          default:
               *si++=ch;
               *si++=CR.attrib;
               row2update[DH]=TRUE;
               if (DL == CR.lrx) { // last guy on this line?              
                    donewl();      // yes, do new-line function           
               }
               else {
                    dx++;          // no, bump column                     
               }
               break;
          }
     }
}

static VOID
pransi(                            // process ANSI activity                
CHAR ch)
{
     canscro=0;                    // reset scroll flag                    
     if (CR.ansifls&ANSIAN) {      // accumulating a number yet            
          if (ch >= '0' && ch <= '9') { // is this latest a continuation ? 
               ch-='0';                 // yes, convert digit to binary    
               CR.ansiarg[CR.ansicnt]*=10;
               CR.ansiarg[CR.ansicnt]+=ch;
          }
          else {
               CR.ansicnt++;       // one more argument in place           
               if (ch == ';') {    // yes, end of it, with more to follow  
                    CR.ansifls&=~(ANSIAN);
               }
               else {
                    doansi(ch);
               }
          }
     }
     else if (CR.ansifls&ANSIWD) { // waiting for digits yet?
          if (ch == ';') {         // terminated already
               CR.ansicnt++;       // one more argument in place
          }
          else if (ch < '0' || ch > '9') { // is this a digit or a letter  
               doansi(ch);            // not a digit                       
          }
          else {
               ch-='0';                   // convert digit to binary           
               CR.ansiarg[CR.ansicnt]=ch; // drop digit into next array element
               CR.ansifls|=ANSIAN;        // flag as accumulating a number     
          }
     }
     else {
          switch(ch) {
          case '[':                // is this the confirmation we waited for
               CR.ansifls|=ANSIWD; // yes, now awaiting digits or whatnot  
               break;
          case 'D':                // ANSI scroll down command?            
               CR.ansiarg[0]=1;    // Do a ESC[1B with scroll              
               canscro=1;
               doansi('B');
               break;
          case 'M':                // ANSI scroll up command             
               CR.ansiarg[0]=1;    // Do a ESC[1A with scroll            
               canscro=1;
               doansi('A');
               break;
          default:
               outlit(ESCAPE);     // emit data as presented             
               CR.ansifls=0;       // return to normal text processing   
               outbyt(ch);
               break;
          }
     }
}

static VOID
doansi(                            // execute ANSI function              
CHAR ch)
{
     INT i,t,b;
     static CHAR andmsk[]={
          0x00,0xFF,0xF7,0xFF,0xF8,0xFF,0xFF,0xF8,
          0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
          0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
          0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF8,0xF8,
          0xF8,0xF8,0xF8,0xF8,0xF8,0xF8,0xFF,0xFF,
          0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,
          0xFF,0xFF
     };
     static CHAR orbits[]={
          0x07,0x08,0x00,0x00,0x01,0x80,0x80,0x70,
          0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
          0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
          0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,
          0x02,0x06,0x01,0x05,0x03,0x07,0x00,0x00,
          0x00,0x40,0x20,0x60,0x10,0x50,0x30,0x70,
          0x00,0x00
     };

     CR.ansifls=0;                 // munching it, return to norm afterward
     CR.saveal=ch;                 // save original char in case need echo 
     ch&=0xDF;                     // make case-independent            
     if (ch == 'M') {              // set graphics rendition               
          if (CR.ansicnt == 0) {
               CR.ansicnt=1;       // ^[[m becomes the same as ^[[0m       
               CR.ansiarg[0]=0;
               CR.ansiarg[1]=1;
          }
          for (i = 0 ; i < (INT)CR.ansicnt ; i++) {
               CR.attrib&=andmsk[CR.ansiarg[i]];     // AND-out attrib bits
               CR.attrib|=orbits[CR.ansiarg[i]];     // OR-in new ones     
          }
     }
     else if (ch == 'A') {
          i=DH-CR.ansiarg[0];
          DHEQ(i);
          pinhvp();
     }
     else if (ch == 'B') {         // cursor down                          
          i=DH+CR.ansiarg[0];
          DHEQ(i);
          pinhvp();
     }
     else if (ch == 'C') {         // cursor forward                       
          dx+=CR.ansiarg[0];
          pinhvp();
     }
     else if (ch == 'D') {         // cursor backward                      
          dx-=CR.ansiarg[0];
          pinhvp();
     }
     else if (ch == 'S') {         // save cursor position                 
          CR.scpreg=dx;
     }
     else if (ch == 'U') {         // restore cursor position              
          dx=CR.scpreg;
          pinhvp();                // ignore ? ansi commands               
     }
     else if (ch == 'J') {         // clear?                               
          if (CR.ansicnt == 0 || CR.ansiarg[0] == 0) {// any ansi args?    
               ceoscn();           // no, clear to end of screen           
          }
          else {
               if (CR.ansiarg[0] == 2) {
                    ffeed();
               }
          }
     }
     else if (CR.saveal == 'r') {  // set scroll range                     
          if (CR.ansiarg[1] != 0 && CR.ansiarg[0] != 0) {
               t=CR.ansiarg[0]-1+CR.uly;
               b=CR.ansiarg[1]-1+CR.uly;
               if (b <= (INT)CR.lry && b >= t) {
                    CR.botscr=b;
                    CR.topscr=t;
               }
          }
     }
     else if (ch == 'K') {         // clear to end of line                
          ceolut();
     }
     else if (ch == 'H' || ch == 'F') { //  cursor position function       
          if (CR.ansiarg[0] == 0) {// this should not be necessary         
               CR.ansiarg[0]=1;
          }
          if (CR.ansiarg[1] == 0) {// this should not be necessary         
               CR.ansiarg[1]=1;
          }
          i=CR.ansiarg[0]+CR.uly-1;
          DHEQ(i);
          i=CR.ansiarg[1]+CR.ulx-1;
          DLEQ(i);
          pinhvp();
     }
     else {
          outlit(ESCAPE);
          outlit('[');
          outlit(CR.saveal);
     }
}

static VOID
pinhvp(VOID)
{
     prep4r();
     if ((USHORT)DH < CR.usetop) {// don't let y go above window top
          DHEQ(CR.usetop);
          if (canscro) {
               dorevl();
          }
     }
     if ((USHORT)DH > CR.usebot) {// don't let y go below window bottom
          DHEQ(CR.usebot);
          if (canscro) {
               donewl();
          }
     }
     if ((USHORT)DL < CR.ulx) {    // don't let x go beyond left edge      
          DLEQ(CR.ulx);
     }
     if ((USHORT)DL > CR.lrx) {
          DLEQ(CR.lrx);
     }
     compsi();                     // recompute data destination           
}

static COORD                       //   returns screen coordinates (X,Y)   
off2coord(                         // cnvt. buffer offset into scr. coord  
UINT offset)                       //    offset in video buffer            
{
     COORD coord;

     assert(offset%2 == 0);
     offset>>=1;
     coord.X=offset%SCRWIDTH;
     coord.Y=offset/SCRWIDTH;
     return(coord);
}

static COORD                       //   returns screen coordinates (X,Y)   
pos2coord(                         // cnvt. packed coord into screen coord 
UINT cursorpos)                    //   packed coordinates (Y:X)           
{
     COORD coord;

     coord.X=(SHORT)(cursorpos&0xFF);
     coord.Y=(SHORT)(cursorpos>>8);
     return(coord);
}

static UINT                        //   returns packed coordinates (Y:X)   
coord2pos(                         // cnvt. screen coord into packed coord 
COORD coord)                       //   screen coordinates (X,Y)           
{
     return((UINT)coord.X+(((UINT)coord.Y)<<8));
}

static WORD                        //   returns Windows color value        
att2color(                         // cnvt. DOS style color into Windows   
CHAR attribute)                    //   DOS color                          
{
     return((WORD)attribute);
}

static CHAR                        //   returns DOS attribute value        
color2att(                         // cnvt. Windows style color into DOS   
WORD color)                        //   Windows color                      
{
     return((CHAR)(color&0xFF));
}

static VOID
updatevideo(VOID)                  // send changed lines to Windows console
{
     UINT i,linewidth,scroff;

     if (CR.scnstt == NULL) {
          linewidth=SCRWIDTH*2;
          for (i=0 ; i < SCRHEIGHT ; i++) {
               if (row2update[i]) {
                    scroff=i*linewidth;
                    scrbuf2video(scroff,linewidth);
               }
          }
          memset(row2update,0,sizeof(row2update));
     }
}

static VOID
scrbuf2video(                      // sends video buffer to Windows console
UINT offset,                       //   offset to start from               
UINT count)                        //   number of bytes to output          
{
     DWORD numofcells,notused,i;
     COORD coord;
     CHAR *ptr;

     offset=(offset>>1)<<1;        // offset always points to char not att 
     assert(offset+count <= SCRBUFSZ*2);
     assert(hconsole_out != INVALID_HANDLE_VALUE);
     coord=off2coord(offset);
     numofcells=count>>1;
     for (i=0,ptr=&scrbuffer[offset] ; i < numofcells ; i++) {
          scrbuf_chr[i]=*ptr++;
          scrbuf_col[i]=att2color(*ptr++);
     }
     WriteConsoleOutputCharacter(hconsole_out,
                                 scrbuf_chr,
                                 numofcells,
                                 coord,
                                 &notused);
     WriteConsoleOutputAttribute(hconsole_out,
                                 scrbuf_col,
                                 numofcells,
                                 coord,
                                 &notused);
}

static VOID
video2scrbuf(                      // reads Windows console to video buffer
UINT offset,                       //   offset to start from               
UINT count)                        //   number of bytes to output          
{
     DWORD numofcells,notused,i;
     COORD coord;
     CHAR *ptr;

     offset=(offset>>1)<<1;        // offset always points to char not att 
     assert(offset+count <= SCRBUFSZ*2);
     assert(hconsole_out != INVALID_HANDLE_VALUE);
     coord=off2coord(offset);
     numofcells=count>>1;
     ReadConsoleOutputCharacter(hconsole_out,
                                scrbuf_chr,
                                numofcells,
                                coord,
                                &notused);
     ReadConsoleOutputAttribute(hconsole_out,
                                scrbuf_col,
                                numofcells,
                                coord,
                                &notused);
     for (i=0,ptr=&scrbuffer[offset] ; i < numofcells ; i++) {
          *ptr++=scrbuf_chr[i];
          *ptr++=color2att(scrbuf_col[i]);
     }
}

static BOOL
hndl_rout(                         // stub routine to handle Windows events
DWORD ctrltype)                    //   event type
{
     switch (ctrltype) {
     case CTRL_C_EVENT:
          return(TRUE);
     case CTRL_BREAK_EVENT:
          MessageBox(NULL,"Please use F10 F9 to exit.","Error",
                     MB_ICONSTOP|MB_OK|MB_SETFOREGROUND|MB_TASKMODAL);
          return(TRUE);
     case CTRL_CLOSE_EVENT:
          MessageBox(NULL,"Please use F10 F9 to exit.","Error",
                     MB_ICONSTOP|MB_OK|MB_SETFOREGROUND|MB_TASKMODAL);
          return(TRUE);
     case CTRL_LOGOFF_EVENT:
          return(TRUE);
     case CTRL_SHUTDOWN_EVENT:
          return(TRUE);
     default:
          return(FALSE);
     }
}
