/*
 * MSDOS-specific functions for `ls'.
 *
 * Written by Eli Zaretskii <eliz@is.elta.co.il>
 *
 * Copied from Fileutils 3.16 DJGPP port for use in the Fileutils 4.0
 * and 4.1 DJGPP ports by Richard Dawe <rich@phekda.freeserve.co.uk>.
 * Modifications are indicated by '(richdawe) ...' comments.
 */

#ifdef MSDOS

#include <string.h>

/*  dos_mode_string -- convert MS-DOS file attribute bits into a character
    string.  The characters stored in STR are:

    0   'r'  if file is read-only, '-' otherwise.
    1   'h'  if file is hidden, '-' otherwise.
    2   's'  if file has its system bit set, '-' otherwise.
    3   'v'  if file is a volume label, '-' otherwise.
    4   'd'  if file is a directory, '-' otherwise.
    5   'm'  if file was modified since last backup, '-' otherwise.
    
    For instance, for a write-protected, hidden file the function
    will return a string "rh----".  */

void
dos_mode_string (bits, str)
    int bits;
    char *str;
{
  unsigned mask = 1 << 5;
  
  strcpy(str, "rhsvdm");          /* assume all bits set */
  for (str += 5; mask; mask >>= 1, --str)
    if ((bits & mask) == 0)
      *str = '-';                 /* put hyphen instead of unused bit */
}

#ifdef __DJGPP__

/* What follows won't work with anything but DJGPP.  */

#include <sys/stat.h>
#include <dirent.h>

static int colorization_required;

/* Ls is a heavy user of `stat' whose full emulation on MS-DOS is
   sometimes very expensive.  Let's punish the user as little as we
   can by requesting the fastest possible version of `stat'.

   The default bits, suitable for just displaying the names of the files
   are defined on `djstart.c'; below are the bits required by various
   Ls options. */
void set_stat_bits_for_ls (int need_time, int need_size,
			   int need_long_format, int msdos_long_format,
			   int indicate_type, int use_color, int show_hidden)
{
#ifndef _POSIX_SOURCE
  if (need_time)
    _djstat_flags &= ~_STAT_ROOT_TIME;
  if (need_size)
    _djstat_flags &= ~_STAT_DIRSIZE;
  if (need_long_format)
    {
      _djstat_flags &= ~(_STAT_ROOT_TIME
			 | _STAT_DIRSIZE);
      if (msdos_long_format)
	_djstat_flags &= ~_STAT_EXEC_EXT;
      else
	_djstat_flags &= ~(_STAT_EXEC_MAGIC
			   | _STAT_EXEC_EXT);
    }
  if (indicate_type)
    _djstat_flags &= ~(_STAT_EXEC_EXT
		       | _STAT_EXEC_MAGIC);
  if (use_color)
    _djstat_flags &= ~_STAT_EXEC_EXT;

  /* (richdawe) Default to skipping hidden files. */
  __opendir_flags &= ~__OPENDIR_FIND_HIDDEN;
  __opendir_flags |= __OPENDIR_NO_HIDDEN;

  /* Find hidden files only if user specified -a.  */
  if (show_hidden)
    {
      /* (richdawe) Eli advised me that setting __OPENDIR_FIND_HIDDEN
       * will not work, because of a bug in DJGPP 2.03's libc. We also need
       * to clear __OPENDIR_NO_HIDDEN. */
      __opendir_flags |= __OPENDIR_FIND_HIDDEN;
      __opendir_flags &= ~__OPENDIR_NO_HIDDEN;

      /* Find volume labels only if user specified both -a and -g.  */
      if (msdos_long_format)
	__opendir_flags |= __OPENDIR_FIND_LABEL;
    }

#endif /* _POSIX_SOURCE */

  /* Our screen redirector (below) wants to know if
     colorization was actually required.  */
  if (use_color)
    colorization_required = 1;
  else
    colorization_required = 0;
}

/*  Screen write redirector.  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.

    Warning: this function relies on the fact that `ls' disables the use
    of TAB characters when colorization is required, and therefore it
    does NOT expand TABs!  It also doesn't check for NULL characters in
    the buffer, and might end the output prematurely if there are NULLs.  */

#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>

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

/* 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 ();
    }
}

#define ESC '\033'

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;
  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 (!colorization_required || 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;
      cbuf = (char *)xrealloc (cbuf, cbuf_len);
    }
  memcpy (cbuf, buf, buflen);
  cbuf[buflen] = '\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;

      /* (richdawe) Handle the null escape sequence (ESC-[m), which ls uses
       * to restore the original colour. */
      if ((p[1] == '[') && (p[2] == 'm'))
	{
	  textattr (norm_attr);
	  p += 3;
	  anchor = p_next = p;
	  continue;
	}
      
      if (p[1] == '[')	/* "Esc-[" sequence */
	{
	  /* 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 */
	      cputs (anchor);
	      *p = ESC;		/* restore the ESC character */
	      anchor = p;
	    }
	  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);

	      /* Sanity checks:

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

		 If any of the above is violated, we just ignore the bogon. */
	      if (q == p || code > 47 || 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;
		  default:
		    p_next = q;	/* ignore unknown codes */
		    break;
		}
	      p = q;
	    }
	  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++;
    }

  /* Output what's left in the buffer.  */
  cputs (anchor);
  *retval = buflen;
  return 1;
}

/* This is called before `main' to install our STDOUT redirector.  */

static void __attribute__((constructor))
djgpp_ls_startup (void)
{
  __FSEXT_set_function (STDOUT_FILENO, msdos_screen_write);
  isatty_stdout = isatty (STDOUT_FILENO);
  atexit (restore_blink_bit);
}

#endif  /* __DJGPP__ */
#endif  /* MSDOS */
