
/* This file is properly part of the excellent DJGPP compiler/tools package
	put out by DJ Delorie for use under DOS.  Unfortunately, the module
	as distributed in many versions of the DJGPP is buggy -- this copy
	of gppconio.c fixes many of those bugs.  I understand that these
	changes have been sent back to the DJGPP maintainers -- hopefully
	vile will not need to include this file in the future.  -pgf 9/95
*/


#if __DJGPP__ < 2

/**********************************************************************
 *
 *  NAME:           gppconio.c
 *
 *  DESCRIPTION:    simulate Borland text video funcs for GNU C++
 *
 *  copyright (c) 1991 J. Alan Eldridge
 *
 *  M O D I F I C A T I O N   H I S T O R Y
 *
 *  when        who                 what
 *  -------------------------------------------------------------------
 *  10/27/91    J. Alan Eldridge    created
 *  01/06/92    D. Buerssner        make it work with extended characters
 * (buers@dg1.chemie.uni-konstanz.de) speed-up of cputs
 *                                  some missing brackets in VIDADDR
 *                                  don't need scrollwindow anymore
 *  07/15/93    D. Buerssner        take care of cursor tracking
 *                                    txinfo.curx and txinfo.cury
 *                                  fill in missing functionality
 *                                    - textmode
 *                                    - cscanf
 *                                    - cgets
 *                                    - getch and ungetch
 *                                    - _setcursortype
 *                                    - kbhit
 *                                  (hpefully) proper initialization of
 *                                    txinfo.normattrib and txinfo.attribute
 *                                  gotoxy(1,1) in clrscr (bug introduced
 *                                    by my previous patches)
 *                                  gotoxy(1,1) in window
 *                                  take care of BELL and BACKSPACE
 *                                    in putch and cputs
 *                                  take care of blinking bit in textcolor
 *                                    and textbackground
 *                                  declare (and ignore) directvideo
 *  10/09/93    DJ Delorie          Switch to dosmem*() for DPMI
 *  05/01/94    DJ Delorie          Add _wscroll
 *********************************************************************/

#include    <stdlib.h>
#include    <stdio.h>
#include    <stdarg.h>
#include    <dos.h>
#include    <pc.h>
#include    <go32.h>
#include    "gppconio.h"

int _wscroll = 1;

int directvideo = 1;  /* We ignore this */

static void setcursor(unsigned int shape);
static int getvideomode(void);
static void bell(void);
static int get_screenattrib(void);
static int isEGA(void);
static int _scan_getche(FILE *fp);
static int _scan_ungetch(int c, FILE *fp);

#define DBGGTINFO   0

static unsigned ScreenAddress = 0xb8000UL; /* initialize just in case */
static struct text_info txinfo;
static int ungot_char;
static int char_avail = 0;

#define VIDADDR(r,c) (ScreenAddress + 2*(((r) * txinfo.screenwidth) + (c)))

int puttext(int c, int r, int c2, int r2, void *buf)
{
  short *cbuf = (short *)buf;
  /* we should check for valid parameters, and maybe return 0 */
  r--, r2--, c--, c2--;
  for (; r <= r2; r++)
  {
    dosmemput(cbuf, (c2-c+1)*2, VIDADDR(r, c));
    cbuf += c2-c+1;
  }
  return 1;
}

int gettext(int c, int r, int c2, int r2, void *buf)
{
  short *cbuf = (short *)buf;
  /* we should check for valid parameters, and maybe return 0 */
  r--, r2--, c--, c2--;
  for (; r <= r2; r++)
  {
    dosmemget(VIDADDR(r, c), (c2-c+1)*2, cbuf);
    cbuf += c2-c+1;
  }
  return 1;
}

void gotoxy(int col, int row)
{
  ScreenSetCursor(row + txinfo.wintop - 2, col + txinfo.winleft - 2);
  txinfo.curx = col;
  txinfo.cury = row;
}

int wherex(void)
{
  int row, col;

  ScreenGetCursor(&row, &col);

  return col - txinfo.winleft + 2;
}

int wherey(void)
{
  int row, col;

  ScreenGetCursor(&row, &col);

  return row - txinfo.wintop + 2;
}

void textmode(int mode)
{
    union REGS regs;
    int mode_to_set = mode;
    if (mode == LASTMODE)
        mode = mode_to_set = txinfo.currmode;
    if (mode == C4350)
        /*
         * just set mode 3 and load 8x8 font, idea taken
         * (and code translated from Assembler to C)
         * form Csaba Biegels stdvga.asm
         */
        mode_to_set = 0x03;
    regs.h.ah = 0x00; /* set mode */
    regs.h.al = mode_to_set;
    int86(0x10, &regs, &regs);
    if (mode == C80 || mode == BW80 || mode == C4350)
    {
        if (isEGA())
        {
            /*
             * enable cursor size emulation, see Ralf Browns
             * interrupt list
             */
            regs.h.ah = 0x12;
            regs.h.bl = 0x34;
            regs.h.al = 0x00; /* 0: enable (1: disable) */
            int86(0x10, &regs, &regs);
	}
    }
    if (mode == C4350)
    {
        if (!isEGA())
            return;
        /* load 8x8 font */
        regs.x.ax = 0x1112;
        regs.x.bx = 0;
        int86(0x10, &regs, &regs);
    }
/*    _setcursortype(_NORMALCURSOR); */
    /* reinitialize txinfo structure to take into account new mode */
    gppconio_init();
#if 0
    /*
     * For mode C4350 the screen is not cleared on my OAK-VGA.
     * Should we clear it here? TURBOC doesn't so we don't bother either.
     */
    clrscr();
#endif
}

void textattr(int attr)
{
  txinfo.attribute = ScreenAttrib = (unsigned char)attr;
}

void textcolor(int color)
{
  /* strip blinking (highest) bit and textcolor */
  ScreenAttrib &= 0x70; /* strip blinking (highest) bit and textcolor */
  txinfo.attribute=(ScreenAttrib |= (color & 0x8f));
}

void textbackground(int color)
{
  /* strip background color, keep blinking bit */
  ScreenAttrib &= 0x8f;
  /* high intensity background colors (>7) are not allowed
     so we strip 0x08 bit (and higher bits) of color */
  txinfo.attribute=(ScreenAttrib |= ((color & 0x07) << 4));
}

void highvideo(void)
{
  txinfo.attribute=(ScreenAttrib |= 0x08);
}

void lowvideo(void)
{
  txinfo.attribute=(ScreenAttrib &= 0x07);
}

void normvideo(void)
{
  txinfo.attribute = ScreenAttrib = txinfo.normattr;
}

void _setcursortype(int type)
{
    unsigned cursor_shape;
    switch (type)
    {
        case _NOCURSOR:
            cursor_shape = 0x0700;
            break;
        case _SOLIDCURSOR:
            cursor_shape = 0x0007;
            break;
/*      case _NORMALCURSOR: */
        default:
            cursor_shape = 0x0607;
            break;
    }
    setcursor(cursor_shape);
}

static void setcursor(unsigned int cursor_shape)
/* Sets the shape of the cursor */
{
    union REGS      reg;

    reg.h.ah = 1;
    reg.x.cx = cursor_shape;
    int86(0x10, &reg, &reg);
}				/* setcursor */

static void getwincursor(int *row, int *col)
{
  ScreenGetCursor(row, col);
}

void clreol(void)
{
  short   image[ 256 ];
  short   val = ' ' | (ScreenAttrib << 8);
  int     c, row, col, ncols;

  getwincursor(&row, &col);
  ncols = txinfo.winright - col;

  for (c = 0; c < ncols; c++)
    image[ c ] = val;

  puttext(col + 1, row + 1, txinfo.winright, row + 1, image);
}

static void fillrow(int row, int left, int right, int fill)
{
  int col;
  short filler[right-left+1];

  for (col = left; col <= right; col++)
    filler[col-left] = fill;
  dosmemput(filler, (right-left+1)*2, VIDADDR(row, left));
}

void clrscr(void)
{
  short filler[txinfo.winright - txinfo.winleft + 1];
  int row, col;
  for (col=0; col < txinfo.winright - txinfo.winleft + 1; col++)
    filler[col] = ' ' | (ScreenAttrib << 8);
  for (row=txinfo.wintop-1; row < txinfo.winbottom; row++)
    dosmemput(filler, (txinfo.winright - txinfo.winleft + 1)*2,
     VIDADDR(row, txinfo.winleft - 1));
  gotoxy(1, 1);
}

int putch(int c)
{
  int     row, col;

  ScreenGetCursor(&row, &col);

  /*  first, handle the character */
  if (c == '\n')
    {
      row++;
    }
  else if (c == '\r')
    {
      col = txinfo.winleft - 1;
    }
  else if (c == '\b')
  {
      if (col > txinfo.winleft - 1)
          col--;
      else if (row > txinfo.wintop -1)
      {
          /*
           * Turbo-C ignores this case; we are smarter.
           */
          row--;
          col = txinfo.winright-1;
      }
  }
  else if (c == 0x07)
    bell();
  else {
    /* short   val = c | (ScreenAttrib << 8); */
    /* puttext(col + 1, row + 1, col + 1, row + 1, &val); */
    ScreenPutChar(c, ScreenAttrib, col, row);
    col++;
  }

  /* now, readjust the window     */

  if (col >= txinfo.winright) {
    col = txinfo.winleft - 1;
    row++;
  }

  if (row >= txinfo.winbottom) {
    /* scrollwin(0, txinfo.winbottom - txinfo.wintop, 1); */
    if (_wscroll)
    {
      ScreenSetCursor(txinfo.wintop-1,0);
      delline();
    }
    row--;
  }

  ScreenSetCursor(row, col);
  txinfo.cury = row - txinfo.wintop + 2;
  txinfo.curx = col - txinfo.winleft + 2;
  return c;
}

int getche(void)
{
  if (char_avail)
    /*
     * We don't know, wether the ungot char was already echoed
     * we assume yes (for example in cscanf, probably the only
     * place where ungetch is ever called.
     * There is no way to check for this really, because
     * ungetch could have been called with a character that
     * hasn't been got by a conio function.
     * We don't echo again.
     */
    return(getch());
  return (putch(getch()));
}

int getch(void)
{
    union REGS regs;
    int c;
    if (char_avail)
    {
        c = ungot_char;
        char_avail = 0;
    }
    else
    {
        regs.x.ax = 0x0700;
        int86(0x21, &regs, &regs);
        c = regs.h.al;
    }
    return(c);
}

int ungetch(int c)
{
    if (char_avail)
        return(EOF);
    ungot_char = c;
    char_avail = 1;
    return(c);
}

/*
 * kbhit from libc in libsrc/c/dos/kbhit.s doesn't check
 * for ungotten chars, so we have to provide a new one
 * Don't call it kbhit, rather use a new name (_conio_kbhit)
 * and do a #define kbhit _conio_kbhit in gppconio.h.
 * The old kbhit still can be used if gppconio.h
 * is not included of after #undef kbhit
 * If you don't use ungetch (directly or indirectly by cscanf)
 * both kbhit and _conio_kbhit are the same.
 * So this shouldn't cause any trouble with previously written
 * source, because ungetch wasn't available.
 * The only problem might be, if anybody just included gppconio.h
 * and has not linked with libpc, (I can't think of a good reason
 * for this). This will result a link error (undefined symbol _conio_kbhit).
 */

#undef kbhit  /* want to be able to call kbhit from libc */

/* The kbhit in libc doesn't work for the second byte of extended chars. */
int kbhit(void)
{
    union REGS regs;
    regs.h.ah = 0x0b;
    int86(0x21, &regs, &regs);
    return regs.h.al ? -1 : 0;
}

int _conio_kbhit(void)
{
    if (char_avail)
        return(1);
    else
        return(kbhit());
}

/*
 * The next two functions are needed by cscanf
 */
static int _scan_getche(FILE *fp)
{
    return(getche());
}

static int _scan_ungetch(int c, FILE *fp)
{
    return(ungetch(c));
}


void insline(void)
{
  int row, col, left, right, nbytes, bot, fill;
  ScreenGetCursor(&row, &col);
  left = txinfo.winleft - 1;
  right = txinfo.winright - 1;
  nbytes = (right-left+1)*2;
  bot = txinfo.winbottom-1;
  fill = ' ' | (ScreenAttrib << 8);
  while(bot > row)
    {
      movedata(_go32_conventional_mem_selector(), VIDADDR(bot-1, left),
               _go32_conventional_mem_selector(), VIDADDR(bot, left),
               nbytes);
      bot--;
    }
  if (row <= bot)
    {
      fillrow(row,left,right,fill);
    }
}


void delline(void)
{
  int row, col, left, right, nbytes, bot, fill;
  ScreenGetCursor(&row, &col);
  left = txinfo.winleft - 1;
  right = txinfo.winright - 1;
  nbytes = (right-left+1)*2;
  bot = txinfo.winbottom-1;
  fill = ' ' | (ScreenAttrib << 8);
  while(row < bot)
    {
      movedata(_go32_conventional_mem_selector(), VIDADDR(row+1, left),
               _go32_conventional_mem_selector(), VIDADDR(row, left),
               nbytes);
      row++;
    }
  fillrow(bot,left,right,fill);
}


void window(int left, int top, int right, int bottom)
{
  if (top < 1 || left < 1 || right > txinfo.screenwidth ||
      bottom > txinfo.screenheight)
    return;

  txinfo.wintop = top;
  txinfo.winleft = left;
  txinfo.winright = right;
  txinfo.winbottom = bottom;
  gotoxy(1,1);
}


int cputs(const char *s)
{
  int     row, col,c;
  const unsigned char *ss = (const unsigned char *)s;
  short *viaddr;
  short sa = ScreenAttrib << 8;
  ScreenGetCursor(&row, &col);
  viaddr = (short *)VIDADDR(row,col);
  /*
   * Instead of just calling putch; we do everything by hand here,
   * This is much faster. We don't move the cursor after each character,
   * only after the whole string is written, because ScreenSetCursor
   * needs to long because of switching to real mode needed with djgpp.
   * You won't recognize the difference.
   */
  while ((c = *ss++))
    {
      /*  first, handle the character */
      if (c == '\n')
	{
	  row++;
	  viaddr += txinfo.screenwidth;
	}
      else if (c == '\r')
	{
	  col = txinfo.winleft - 1;
	  viaddr = (short *)VIDADDR(row,col);
	}
      else if (c == '\b')
        {
          if (col > txinfo.winleft-1)
          {
              col--;
              viaddr--;
          }
          else if (row > txinfo.wintop -1)
          {
              /*
               * Turbo-C ignores this case. We want to be able to
               * edit strings with backspace in gets after
               * a linefeed, so we are smarter
               */
              row--;
              col = txinfo.winright-1;
              viaddr = (short *)VIDADDR(row,col);
          }
        }
      else if (c == 0x07)
          bell();
      else {
        short q = c | sa;
        dosmemput(&q, 2, (int)viaddr);
	viaddr++;
	col++;
      }

      /* now, readjust the window     */

      if (col >= txinfo.winright) {
	col = txinfo.winleft - 1;
	row++;
	viaddr = (short *)VIDADDR(row,col);
      }

      if (row >= txinfo.winbottom) {
	ScreenSetCursor(txinfo.wintop-1,0); /* goto first line in window */
	delline();                          /* and delete it */
	row--;
	viaddr -= txinfo.screenwidth;
      }
    }

  ScreenSetCursor(row, col);
  txinfo.cury = row - txinfo.wintop + 2;
  txinfo.curx = col - txinfo.winleft + 2;
  return(*(--ss));
}


int cprintf(const char *fmt, ...)
{
  int     cnt;
  char    buf[ 2048 ]; /* this is buggy, because buffer might be too small. */
  va_list ap;

  va_start(ap, fmt);
  cnt = vsprintf(buf, fmt, ap);
  va_end(ap);

  cputs(buf);
  return cnt;
}

char *cgets(char *string)
{
    unsigned len = 0;
    unsigned int maxlen_wanted;
    char *sp;
    int c;
    /*
     * Be smart and check for NULL pointer.
     * Don't know wether TURBOC does this.
     */
    if (!string)
        return(NULL);
    maxlen_wanted = (unsigned int)((unsigned char)string[0]);
    sp = &(string[2]);
    /*
     * Should the string be shorter maxlen_wanted including or excluding
     * the trailing '\0' ? We don't take any risk.
     */
    while(len < maxlen_wanted-1)
    {
        c=getch();
        /*
         * shold we check for backspace here?
         * TURBOC does (just checked) but doesn't in cscanf (thats harder
         * or even impossible). We do the same.
         */
        if (c == '\b')
        {
            if (len > 0)
            {
               cputs("\b \b"); /* go back, clear char on screen with space
                                  and go back again */
               len--;
               sp[len] = '\0'; /* clear the character in the string */
            }
        }
        else if (c == '\r')
        {
            sp[len] = '\0';
            break;
        }
        else if (c == 0)
        {
            /* special character ends input */
            sp[len] = '\0';
            ungetch(c); /* keep the char for later processing */
            break;
        }
        else
        {
           sp[len] = putch(c);
           len++;
        }
     }
     sp[maxlen_wanted-1] = '\0';
     string[1] = (char)((unsigned char)len);
     return(sp);
}

int cscanf(const char *fmt, ...)
{
    return(_doscan_low(NULL, _scan_getche, _scan_ungetch,
                       fmt, (void **)((&fmt)+1)));
}

int movetext(int left, int top, int right, int bottom, int dleft, int dtop)
{
  char    *buf = malloc((right - left + 1) * (bottom - top + 1) * 2);

  if (!buf)
    return 0;

  gettext(left, top, right, bottom, buf);
  puttext(dleft, dtop, dleft + right - left, dtop + bottom - top, buf);
  free(buf);
  return 1;
}

static void _gettextinfo(struct text_info *t)
{
  int row, col;

  t->winleft = t->wintop = 1;
  t->winright = t->screenwidth = ScreenCols();
  t->winbottom = t->screenheight = ScreenRows();
  ScreenAttrib = t->attribute = t->normattr = get_screenattrib();
  t->currmode = getvideomode();
  ScreenGetCursor(&row, &col);
  t->curx = col+1;
  t->cury = row+1;
#if DBGGTINFO
  printf("left=%2d,right=%2d,top=%2d,bottom=%2d\n",t->winleft,
	 t->winright,t->wintop,t->winbottom);
  printf("scrht=%2d,scrwid=%2d,norm=%2x,mode=%2d,x=%2d,y=%2d\n",
	 t->screenheight, t->screenwidth, t->normattr, t->currmode,
	 t->curx, t->cury);
#endif
}

void gettextinfo(struct text_info *t)
{
  *t = txinfo;
#if DBGGTINFO
  printf("left=%2d,right=%2d,top=%2d,bottom=%2d\n",t->winleft,
	 t->winright,t->wintop,t->winbottom);
  printf("scrht=%2d,scrwid=%2d,norm=%2x,mode=%2d,x=%2d,y=%2d\n",
	 t->screenheight, t->screenwidth, t->normattr, t->currmode,
	 t->curx, t->cury);
#endif
}

static int
getvideomode(void)
{
    int mode = ScreenMode();
    /*
     * in mode C80 we might have loaded a different font
     */
    if (mode == C80)
        if (ScreenRows() > 25)
           mode = C4350;
    return(mode);
}


static void bell(void)
{
    union REGS regs;
#if 0
    /* use BIOS */
    regs.h.ah = 0x0e; /* write */
    regs.h.al = 0x07; /* bell */
    int86(0x10, &regs, &regs);
#else
    /* use DOS */
    regs.h.ah = 0x06; /* write */
    regs.h.dl = 0x07; /* bell */
    int86(0x21, &regs, &regs);
#endif
}

static int
get_screenattrib(void)
{
    union REGS regs;
    regs.h.ah = 0x08; /* read character and attribute */
    regs.h.bh = 0;    /* video page 0 */
    int86(0x10, &regs, &regs);
    return(regs.h.ah & 0x7f); /* strip highest (BLINK) bit */
}

/* check if we have at least EGA (idea form Ralf Browns interrupt list) */
static int
isEGA(void)
{
    union REGS regs;
    regs.h.ah = 0x12;
    regs.h.bl = 0x10;
    regs.h.bh = 0xff;
    int86(0x10, &regs, &regs);
    return(regs.h.bh != 0xff);
}


extern int _gppconio_init;

void gppconio_init(void)
{
    static int oldattrib =  -1;
    if (oldattrib == -1)
        oldattrib = get_screenattrib();
    _gettextinfo(&txinfo);
    if (txinfo.currmode == 7)    /* MONO */
        ScreenAddress = 0xb0000UL;
    else
	ScreenAddress = 0xb8000UL;
    ScreenAttrib = txinfo.normattr = txinfo.attribute = oldattrib;
    _gppconio_init = 1;
}

#endif /* __DJGPP__ < 2 */
