#if defined (__DJGPP__)
/*  If compiled with DJGPP do not check for the term environment
    variable to decided which value should color_option have.
*/
#define IS_TERMINAL     (isatty(STDOUT_FILENO))


/*  Screen write redirection.  We need this to support colorization
    without requiring ANSI.SYS driver (or its work-alike) to be loaded.

    This function uses the DJGPP filesystem extensions mechanism.  It is
    installed as a handler for handle-based functions (read/write/close)
    for the standard output (but actually only handles writes, only if
    the standard output is connected to the terminal, and only if user
    asked for colorization).  When a buffer is written to the screen by
    low-level functions of the DJGPP C library, our handler will be
    called.  For any request that doesn't require colored screen writes
    we return a zero to the caller, in which case the caller will handle
    the output in the usual way (by eventually calling DOS).

    When colorization *is* required, the buffer is written directly to
    the screen while converting the ANSI escape sequences into calls to
    DJGPP conio functions which change text attributes.  A non-zero value is
    then returned to the caller to signal that the output has been handled.
*/


#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <conio.h>
#include <sys/fsext.h>
#include <go32.h> /* for `_dos_ds' */
#include <sys/farptr.h>
#include <pc.h> /* for `ScreenGetCursor' */
#include "dosmalloc.h" /* for `xmalloc' and `xrealloc' */

static int norm_blink = -1, cur_blink = -1;
static unsigned char norm_attr = 0, cur_attr = 0;
static int isatty_stdout = -1;
static size_t leftover = 0;


/* Restore the BIOS blinking bit to its original value.  Called at exit.  */
static void
restore_blink_bit (void)
{
  if (cur_blink != norm_blink)
    {
      if (norm_blink > 0)
        blinkvideo ();
      else
        intensevideo ();
    }
}


/* Write a buffer to the screen video memory.  This expands the TAB
   characters to the appropriate number of spaces, and also does TRT
   with null characters and other non-printable characters, if
   any.  */
static void
screen_puts (char *buf, char *buf_end)
{
  register char *p = buf, *q = p;
  int row, col;
  unsigned char c;

  while (p < buf_end)
    {
      if (*p < ' ')
        {
          switch (*p)
            {
              case '\b':
              case '\r':
              case '\n':
                /* nothing: cputs already does TRT with these */
                break;
              case '\t':
                *p = '\0';
                cputs (q);
                ScreenGetCursor (&row, &col);
                for (cputs (" "), col += 1; col % 8; col++)
                  cputs (" ");
                q = p + 1;
                *p = '\t';
                break;
              default:
                c = *p;
                *p = '\0';
                cputs (q);
                cputs ("^");
                putch (c | 0x40);
                q = p + 1;
                *p = c;
                break;
            }
        }
      p++;
    }
  /* Output whatever is left.  */
  cputs (q);
}


#define ESC '\033'
#define IS_SGR(s) (((s)[1] == '[') && ((s)[2] == 'm'))
#define IS_EL(s)  (((s)[1] == '[') && ((s)[2] == 'K'))

/* Screen writes redirector function.  */
static int
msdos_screen_write (__FSEXT_Fnumber func, int *retval, va_list rest_args)
{
  static char *cbuf = NULL;
  static size_t cbuf_len = 0;
  /* Only dark colors mentioned here, so that bold has visible effect.  */
  static enum COLORS screen_color[] = {
    BLACK,
    RED,
    GREEN,
    BROWN,
    BLUE,
    MAGENTA,
    CYAN,
    LIGHTGRAY
  };
  char *anchor, *p_next;
  unsigned char fg, bg;

  int handle;
  char *buf, *buf_end;
  size_t buflen;
#if 0
  /*  Do not care about TERM.
      If STDOUT is connected to a TTY
      use colors.  */
  char *term = get_string_value ("TERM");
#endif

  /* Avoid direct screen writes unless colorization was actually requested.
     Otherwise, we will break programs that catch I/O from their children.  */
  handle = va_arg (rest_args, int);
  if (!IS_TERMINAL || func != __FSEXT_write ||
       !(handle == STDOUT_FILENO ? isatty_stdout : isatty (handle)))
    return 0;

  buf = va_arg (rest_args, char *);
  if (!buf)
    {
      errno = EINVAL;
      *retval = -1;
      return 1;
    }

  /* Allocate a sufficiently large buffer to hold the output.  */
  buflen = va_arg (rest_args, size_t);
  if (!cbuf)
    {
      struct text_info txtinfo;

      cbuf_len = buflen + 1;
      cbuf = (char *)xmalloc (cbuf_len);
      gettextinfo (&txtinfo);
      norm_attr = txtinfo.attribute; /* save the original text attribute */
      cur_attr = norm_attr;
      /* Does it normally blink when bg has its 3rd bit set?  */
      norm_blink = (_farpeekb (_dos_ds, 0x465) & 0x20) ? 1 : 0;
      cur_blink = norm_blink;
    }
  else if (buflen >= cbuf_len)
    {
      cbuf_len = buflen + 1 + leftover;
      cbuf = (char *)xrealloc (cbuf, cbuf_len);
    }
  memcpy (cbuf + leftover, buf, buflen);
  buf_end = cbuf + buflen + leftover;
  *buf_end = '\0';

  /* Current text attributes are used as baseline.  */
  fg = cur_attr & 15;
  bg = (cur_attr >> 4) & 15;

  /* Walk the buffer, writing text directly to video RAM,
     changing color attributes when an escape sequence is seen.  */
  for (anchor = p_next = cbuf;
       (p_next = memchr (p_next, ESC, buflen - (p_next - cbuf))) != 0; )
    {
      char *p = p_next;

      /* If some chars seen since the last escape sequence,
         write it out to the screen using current text attributes.  */
      if (p > anchor)
        {
          *p = '\0'; /* `cputs' needs ASCIIZ string */
          screen_puts (anchor, p);
          *p = ESC; /* restore the ESC character */
          anchor = p;
        }

      /* Handle the null escape sequence (ESC-[m), which is used to
         restore the original color. */
      if (IS_SGR(p))
        {
          textattr (norm_attr);
          p += 3;
          anchor = p_next = p;
          continue;
        }

      /* Handle the erase in line to the right escape sequence (ESC-[K). */
      if (IS_EL(p))
        {
          clreol();
          p += 3;
          anchor = p_next = p;
          continue;
        }

      if (p[1] == '[') /* "Esc-[" sequence */
        {
          p += 2; /* get past "Esc-[" sequence */
          p_next = p;
          while (*p != 'm') /* `m' ends the escape sequence */
            {
              char *q;
              long code = strtol (p, &q, 10);

              if (!*q)
                {
                  /* Incomplete escape sequence.  Remember the part
                     we've seen for the next time.  */
                  leftover = q - anchor;
                  if (leftover >= cbuf_len)
                    {
                      cbuf_len += 1 + leftover;
                      cbuf = (char *)xrealloc (cbuf, cbuf_len);
                    }
                  strcpy (cbuf, anchor);
                  *retval = buflen; /* that's a lie, but we have to! */
                  return 1;
                }

              /* 
                 Sanity checks:

                 q > p unless p doesn't point to a number;
                 SGR codes supported by ANSI.SYS are between 0 and 49;
                 Each SGR code ends with a `;' or an `m'.

                 If any of the above is violated, we just ignore the bogon.
              */
              if (q == p || code > 49 || code < 0 || (*q != 'm' && *q != ';'))
                {
                  p_next = q;
                  break;
                }
              if (*q == ';') /* more codes to follow */
                q++;

              /* Convert ANSI codes to color fore- and background.  */
              switch (code)
                {
                  case 0: /* all attributes off */
                    fg = norm_attr & 15;
                    bg = (norm_attr >> 4) & 15;
                    break;
                  case 1: /* intensity on */
                    fg |= 8;
                    break;
                  case 4: /* underline on */
                    fg |= 8; /* we can't, so make it bold instead */
                    break;
                  case 5: /* blink */
                    if (cur_blink != 1)
                      {
                        blinkvideo (); /* ensure we are'nt in bright bg mode */
                        cur_blink = 1;
                      }
                    bg |= 8;
                    break;
                  case 7: /* reverse video */
                    {
                      unsigned char t = fg;
                      fg = bg;
                      bg = t;

                      /* If it was blinking before, let it blink after.  */
                      if (fg & 8)
                        bg |= 8;

                      /* If the fg was bold, let the background be bold.  */
                      if ((t & 8) && cur_blink != 0)
                        {
                          intensevideo ();
                          cur_blink = 0;
                        }
                    }
                    break;
                  case 8: /* concealed on */
                    fg = (bg & 7) | 8; /* make fg be like bg, only bright */
                    break;
                  case 30: case 31: case 32: case 33: /* foreground color */
                  case 34: case 35: case 36: case 37:
                    fg = (fg & 8) | (screen_color[code - 30] & 15);
                    break;
                  case 40: case 41: case 42: case 43: /* background color */
                  case 44: case 45: case 46: case 47:
                    bg = (bg & 8) | (screen_color[code - 40] & 15);
                    break;
                  case 39: /* default fg */
                    fg = norm_attr & 15;
                    break;
                  case 49:
                    bg = (norm_attr >> 4) & 15;
                    break;
                  default:
                    p_next = q; /* ignore unknown codes */
                    break;
                }
              p = q;
            } /* while loop */

          if (*p == 'm' && p > p_next)
            {
              /* They don't *really* want it invisible, do they?  */
              if (fg == (bg & 7))
                fg |= 8; /* make it concealed instead */

              /* Construct the text attribute and set it.  */
              cur_attr = (bg << 4) | fg;
              textattr (cur_attr);
              p_next = anchor = p + 1;
            }
          else
            break;
        }
      else
        p_next++;
    }  /* for loop */

  /* Output what's left in the buffer.  */
  screen_puts (anchor, buf_end);
  leftover = 0;
  *retval = buflen;
  return 1;
}


/* This is called before `main' to install our STDOUT redirector.  */
static void __attribute__((constructor))
djgpp_bash_startup (void)
{
  __FSEXT_set_function (STDOUT_FILENO, msdos_screen_write);
  isatty_stdout = isatty (STDOUT_FILENO);
  atexit (restore_blink_bit);
}

#endif  /* __DJGPP__ */
