/* dosbuf.c
   Copyright (C) 1992, 1997-2002, 2004-2010 Free Software Foundation, Inc.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA
   02110-1301, USA.  */

/* Messy DOS-specific code for correctly treating binary, Unix text
   and DOS text files, and for emulating a Posix terminal driver
   wrt SGR (a.k.a. ANSI) color escape sequences.

   This has several aspects:

     * Guessing the file type (unless the user tells us);
     * Stripping CR characters from DOS text files (otherwise regex
       functions won't work correctly);
     * Reporting correct byte count with -b for any kind of file.
     * Redirecting output with ANSI color commands to direct screen
       writes.

*/

typedef enum {
  UNKNOWN, DOS_BINARY, DOS_TEXT, UNIX_TEXT
} File_type;

struct dos_map {
  off_t pos;	/* position in buffer passed to matcher */
  off_t add;	/* how much to add when reporting char position */
};

static int       dos_report_unix_offset = 0;

static File_type dos_file_type     = UNKNOWN;
static File_type dos_use_file_type = UNKNOWN;
static off_t     dos_stripped_crs  = 0;
static struct dos_map *dos_pos_map;
static int       dos_pos_map_size  = 0;
static int       dos_pos_map_used  = 0;
static int       inp_map_idx = 0, out_map_idx = 1;

/* Guess DOS file type by looking at its contents.  */
static inline File_type
guess_type (char *buf, size_t buflen)
{
  int crlf_seen = 0;
  char *bp = buf;

  while (buflen--)
    {
      /* Treat a file as binary if it has a NUL character.  */
      if (!*bp)
        return DOS_BINARY;

      /* CR before LF means DOS text file (unless we later see
         binary characters).  */
      else if (*bp == '\r' && buflen && bp[1] == '\n')
        crlf_seen = 1;

      bp++;
    }

  return crlf_seen ? DOS_TEXT : UNIX_TEXT;
}

/* Convert external DOS file representation to internal.
   Return the count of characters left in the buffer.
   Build table to map character positions when reporting byte counts.  */
static inline int
undossify_input (char *buf, size_t buflen)
{
  int chars_left = 0;

  if (totalcc == 0)
    {
      /* New file: forget everything we knew about character
         position mapping table and file type.  */
      inp_map_idx = 0;
      out_map_idx = 1;
      dos_pos_map_used = 0;
      dos_stripped_crs = 0;
      dos_file_type = dos_use_file_type;
    }

  /* Guess if this file is binary, unless we already know that.  */
  if (dos_file_type == UNKNOWN)
    dos_file_type = guess_type(buf, buflen);

  /* If this file is to be treated as DOS Text, strip the CR characters
     and maybe build the table for character position mapping on output.  */
  if (dos_file_type == DOS_TEXT)
    {
      char   *destp   = buf;

      while (buflen--)
        {
          if (*buf != '\r')
            {
              *destp++ = *buf++;
              chars_left++;
            }
          else
            {
              buf++;
              if (out_byte && !dos_report_unix_offset)
                {
                  dos_stripped_crs++;
                  while (buflen && *buf == '\r')
                    {
                      dos_stripped_crs++;
                      buflen--;
                      buf++;
                    }
                  if (inp_map_idx >= dos_pos_map_size - 1)
                    {
                      dos_pos_map_size = inp_map_idx ? inp_map_idx * 2 : 1000;
                      dos_pos_map = xrealloc((char *)dos_pos_map,
                                             dos_pos_map_size *
                                             sizeof(struct dos_map));
                    }

                  if (!inp_map_idx)
                    {
                      /* Add sentinel entry.  */
                      dos_pos_map[inp_map_idx].pos = 0;
                      dos_pos_map[inp_map_idx++].add = 0;

                      /* Initialize first real entry.  */
                      dos_pos_map[inp_map_idx].add = 0;
                    }

                  /* Put the new entry.  If the stripped CR characters
                     precede a Newline (the usual case), pretend that
                     they were found *after* the Newline.  This makes
                     displayed byte offsets more reasonable in some
                     cases, and fits better the intuitive notion that
                     the line ends *before* the CR, not *after* it.  */
                  inp_map_idx++;
                  dos_pos_map[inp_map_idx-1].pos =
                    (*buf == '\n' ? destp + 1 : destp ) - bufbeg + totalcc;
                  dos_pos_map[inp_map_idx].add = dos_stripped_crs;
                  dos_pos_map_used = inp_map_idx;

                  /* The following will be updated on the next pass.  */
                  dos_pos_map[inp_map_idx].pos = destp - bufbeg + totalcc + 1;
                }
            }
        }

      return chars_left;
    }

  return buflen;
}

/* Convert internal byte count into external.  */
static inline off_t
dossified_pos (off_t byteno)
{
  off_t pos_lo;
  off_t pos_hi;

  if (dos_file_type != DOS_TEXT || dos_report_unix_offset)
    return byteno;

  /* Optimization: usually the file will be scanned sequentially.
     So in most cases, this byte position will be found in the
     table near the previous one, as recorded in `out_map_idx'.  */
  pos_lo = dos_pos_map[out_map_idx-1].pos;
  pos_hi = dos_pos_map[out_map_idx].pos;

  /* If the initial guess failed, search up or down, as
     appropriate, beginning with the previous place.  */
  if (byteno >= pos_hi)
    {
      out_map_idx++;
      while (out_map_idx < dos_pos_map_used &&
             byteno >= dos_pos_map[out_map_idx].pos)
        out_map_idx++;
    }

  else if (byteno < pos_lo)
    {
      out_map_idx--;
      while (out_map_idx > 1 && byteno < dos_pos_map[out_map_idx-1].pos)
        out_map_idx--;
    }

  return byteno + dos_pos_map[out_map_idx].add;
}

#ifdef __DJGPP__
/*  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 <stdarg.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' */

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;

  /* 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 (!color_option || 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_grep_startup (void)
{
  __FSEXT_set_function (STDOUT_FILENO, msdos_screen_write);
  isatty_stdout = isatty (STDOUT_FILENO);
  atexit (restore_blink_bit);
}
#endif  /* __DJGPP__ */
