/* output.c (emx+gcc) -- Copyright (c) 1990-1994 by Eberhard Mattes */

#include <sys/emx.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <setjmp.h>
#include <float.h>
#include <limits.h>

#define DEFAULT_PREC    6

#define BEGIN do {
#define END   } while (0)

#define PUTC(V,C) BEGIN \
                    if (putc (C, (V)->stream) == EOF) \
                      longjmp ((V)->jerror, 1); \
                    ++(V)->count; \
                  END

#define STRLEN(S) (((S) == NULL) ? 0 : strlen (S))

/* This structure holds the local variables of _output() which are
   passed to the various functions called by _output(). */

typedef struct
{
  FILE *stream;                 /* Where output should go */
  char minus;                   /* Non-zero if `-' flag present */
  char plus;                    /* Non-zero if `+' flag present */
  char blank;                   /* Non-zero if ` ' flag present */
  char hash;                    /* Non-zero if `#' flag present */
  char pad;                     /* Pad character (' ' or '0')  */
  int width;                    /* Field width (or 0 if none specified) */
  int prec;                     /* Precision (or -1 if none specified) */
  int lim;                      /* Number of significant digits */
  int count;                    /* Number of characters printed */
  jmp_buf jerror;               /* Jump there on output error */
} olocal;


/* Print the first N characters of the string S. */

static void out_str (olocal *v, const char *s, int n)
{
  if (n >= 16)
    {
      if (fwrite (s, 1, n, v->stream) != n)
        longjmp (v->jerror, 1);
      v->count += n;
    }
  else
    while (n > 0)
      {
        PUTC (v, *s);
        ++s; --n;
      }
}


/* Print the character C N times. */

static void out_pad (olocal *v, char c, int n)
{
  char buf[256];

  if (n > sizeof (buf))
    {
      int i;

      /* Very big padding -- do 256 characters at a time. */
      memset (buf, c, sizeof (buf));
      while (n > 0)
        {
          i = sizeof (buf);
          if (i > n)
            i = n;
          out_str (v, buf, i);
          n -= i;
        }
    }
  else if (n >= 16)
    {
      memset (buf, c, n);
      out_str (v, buf, n);
    }
  else
    while (n > 0)
      {
        PUTC (v, c);
        --n;
      }
}


/* Perform formatting for the "%c" and "%s" formats.  The meaning of
   the '0' flag is not defined by ANSI for "%c" and "%s"; we always
   use blanks for padding. */

static void cvt_str (olocal *v, const char *str, int str_len)
{
  if (str == NULL)
    {
      /* Print "(null)" for the NULL pointer if the precision is big
         enough or not specified. */

      if (v->prec < 0 || v->prec >= 6)
        str = "(null)";
      else
        str = "";
    }
  if (str_len == -1)
    str_len = strlen (str);

  /* Limit the number of characters printed by the precision. */

  if (v->prec >= 0 && v->prec < str_len)
    str_len = v->prec;

  /* Print the string, using blanks for padding. */

  if (str_len < v->width && !v->minus)
    out_pad (v, ' ', v->width - str_len);
  out_str (v, str, str_len);
  if (str_len < v->width && v->minus)
    out_pad (v, ' ', v->width - str_len);
}


/* Print and pad a number (which has already been converted into
   strings).  PFX is "0" for non-zero octal numbers, "0x" or "0X" for
   non-zero hexadecimal numbers, "0." for floating point numbers in
   [0.0,1.0) unless using exponential format, the significant digits
   for large floating point numbers in the "%f" format, or NULL.
   Insert LPAD0 zeros between PFX and STR.  STR is the number.  Insert
   RPAD0 zeros between STR and XPS.  XPS is the exponent (unless it is
   NULL).  IS_SIGNED is non-zero when printing a signed number. IS_NEG
   is non-zero for negative numbers (the strings don't contain a
   sign). */

static void cvt_number (olocal *v, const char *pfx, const char *str,
                        const char *xps, int lpad0, int rpad0,
                        int is_signed, int is_neg)
{
  int sign, pfx_len, str_len, xps_len, min_len;

  if (!is_signed)               /* No sign for %u, %o and %x */
    sign = EOF;
  else if (is_neg)
    sign = '-';
  else if (v->plus)             /* '+' overrides ' ' */
    sign = '+';
  else if (v->blank)
    sign = ' ';
  else
    sign = EOF;

  pfx_len = STRLEN (pfx);
  str_len = strlen (str);
  xps_len = STRLEN (xps);

  if (lpad0 < 0)
    lpad0 = 0;
  if (rpad0 < 0)
    rpad0 = 0;

  /* Compute the minimum length required for printing the number. */

  min_len = lpad0 + pfx_len + str_len + rpad0 + xps_len;
  if (sign != EOF)
    ++min_len;

  /* If padding with zeros is requested, increase LPAD0 to pad the
     number on the left with zeros.  Note that the `-' flag
     (left-justify) turns off padding with zeros. */

  if (v->pad == '0' && min_len < v->width)
    {
      lpad0 += v->width - min_len;
      min_len = v->width;
    }

  /* Pad on the left with blanks. */

  if (min_len < v->width && !v->minus)
    out_pad (v, ' ', v->width - min_len);

  /* Print the number. */

  if (sign != EOF)
    PUTC (v, sign);
  out_str (v, pfx, pfx_len);
  out_pad (v, '0', lpad0);
  out_str (v, str, str_len);
  out_pad (v, '0', rpad0);
  out_str (v, xps, xps_len);

  /* Pad on the right with blanks. */

  if (min_len < v->width && v->minus)
    out_pad (v, ' ', v->width - min_len);
}


/* Print and pad an integer (which has already been converted into the
   string STR).  PFX is "0" for non-zero octal numbers, "0x" or "0X"
   for hexadecimal numbers, or NULL.  ZERO is non-zero if the number
   is zero.  IS_NEG is non-zero if the number is negative (the string
   STR doesn't contain a sign). */

static void cvt_integer (olocal *v, const char *pfx, const char *str,
                         int zero, int is_signed, int is_neg)
{
  int lpad0;

  if (zero && v->prec == 0)     /* ANSI */
    return;

  if (v->prec >= 0)             /* Ignore `0' if `-' is given for an integer */
    v->pad = ' ';

  lpad0 = v->prec - strlen (str);
  cvt_number (v, pfx, str, NULL, lpad0, 0, is_signed, is_neg);
}


static void cvt_hex (olocal *v, char *str, char x, int zero)
{
  if (x == 'X')
    strupr (str);
  cvt_integer (v, ((!zero && v->hash) ? "0x" : NULL),
               str, zero, FALSE, FALSE);
}


static void cvt_hex_32 (olocal *v, unsigned n, char x)
{
  char buf[9];

  _ltoa (n, buf, 16);
  cvt_hex (v, buf, x, n == 0);
}


static void cvt_hex_64 (olocal *v, unsigned long long n, char x)
{
  char buf[17];

  _ulltoa (n, buf, 16);
  cvt_hex (v, buf, x, n == 0);
}


static void cvt_oct (olocal *v, char *str, int zero)
{
  cvt_integer (v, ((!zero && v->hash) ? "0" : NULL),
               str, zero, FALSE, FALSE);
}


static void cvt_oct_32 (olocal *v, unsigned n)
{
  char buf[12];

  _ltoa (n, buf, 8);
  cvt_oct (v, buf, n == 0);
}


static void cvt_oct_64 (olocal *v, unsigned long long n)
{
  char buf[23];

  _ulltoa (n, buf, 8);
  cvt_oct (v, buf, n == 0);
}


static void cvt_dec_32 (olocal *v, unsigned n, int is_signed, int is_neg)
{
  char buf[11];

  _ultoa (n, buf, 10);
  cvt_integer (v, NULL, buf, n == 0, is_signed, is_neg);
}


static void cvt_dec_64 (olocal *v, unsigned long long n, int is_signed,
                        int is_neg)
{
  char buf[21];

  _ulltoa (n, buf, 10);
  cvt_integer (v, NULL, buf, n == 0, is_signed, is_neg);
}


/* Print a floating point number (which has been turned into the digit
   string DIGITS and the exponent XP) for the "%f" format.  If IS_NEG
   is non-zero, the number is negative. */

static void cvt_fixed_digits (olocal *v, char *digits, int xp, int is_neg,
                              int is_auto)
{
  int lpad0, rpad0, len, frac;

  /* We have to handle 9 cases (the examples are for DIG=4):

         | Fmt | Number    || digits | xp | pfx  |lpad0|rpad0| Output
     ----+-----+-----------++--------+----+------+-----+-----+---------
     (1) | .3f | 0.0001234 || "1234" | -4 | "0." | 0   | 3   | 0.000
     (2) | .7f | 0.001234  || "1234" | -3 | "0." | 2   | 1   | 0.0012340
     (3) | .3f | 0.001234  || "1234" | -3 | "0." | 2   | 0   | 0.001
     (4) | .3f | 0.1234    || "1234" | -1 | "0." | 0   | 0   | 0.123
     (5) | .3f | 1.234     || "1234" |  0 | N/A  | N/A | 0   | 1.234
     (6) | .1f | 12.34     || "1234" |  1 | N/A  | N/A | 0   | 12.3
     (7) | .3f | 123.4     || "1234" |  2 | N/A  | N/A | 2   | 123.400
     (8) | .3f | 1234.0    || "1234" |  3 | N/A  | N/A | 3   | 1234.000
     (9) | .3f | 123456.0  || "1235" |  5 |"1235"| 2   | 3   | 123500.000

   */


  if (xp < 0)
    {
      /* Cases (1) through (4).  We print "0." followed by

           min (-xp - 1, v->prec)

         zeros and the string of digit. */

      lpad0 = -xp - 1;          /* This is for cases (2) through (3) */
      if (v->prec < lpad0)
        lpad0 = v->prec;        /* This is for case (1) */

      /* Compute the number of zeros to append. */

      rpad0 = v->prec - (lpad0 + DIG); /* This is for case (2) */
      if (rpad0 < 0)
        rpad0 = 0;              /* This is for cases (1), (3) and (4) */

      /* Truncate the string of digits according to the precision for
         cases (1), (3) and (4). */

      len = v->prec - lpad0;
      if (len >= 0 && len < DIG)
        digits[len] = 0;

      if (is_auto)
        {
          rpad0 = 0;
          _cvt_remove_zeros (digits, 0);
          if (digits[0] == 0)
            lpad0 = 0;
        }

      cvt_number (v, ((v->hash || digits[0] != 0 || lpad0 != 0) ? "0." : "0"),
                  digits, NULL, lpad0, rpad0, TRUE, is_neg);
    }
  else if (xp < DIG)
    {
      /* Cases (5) through (8). */

      /* Compute the number of zeros to append and truncate the string
         of digits. */

      frac = DIG - xp - 1;      /* Number of decimals (frac >= 0) */
      rpad0 = v->prec - frac;   /* For cases (5), (7) and (8) */
      if (rpad0 < 0)
        {
          /* Case (6) */
          digits[DIG + rpad0] = 0;
          rpad0 = 0;
        }

      if (is_auto)
        {
          _cvt_remove_zeros (digits, xp + 1);
          rpad0 = 0;
        }

      /* Insert the decimal point. */
      if (v->hash || digits[xp + 1] != 0 || rpad0 != 0)
        {
          memmove (digits + xp + 2, digits + xp + 1, DIG - xp);
          digits[xp + 1] = '.';
        }
      cvt_number (v, NULL, digits, NULL, 0, rpad0, TRUE, is_neg);
    }
  else
    {
      /* Case (9). */

      lpad0 = xp - DIG + 1;
      rpad0 = (is_auto ? 0 : v->prec);

      cvt_number (v, digits, ((v->hash || rpad0 != 0) ? "." : ""),
                  NULL, lpad0, rpad0, TRUE, is_neg);
    }
}


/* Print a floating point number (which has been turned into the digit
   string DIGITS and the exponent XP) for the "%e" and "%g" formats.
   If IS_NEG is non-zero, the number is negative.  XP_CHAR is 'e' or
   'E'.  If IS_AUTO is non-zero, we should omit trailing zeros for
   the "%g" format. */

static void cvt_exp_digits (olocal *v, char *digits, int xp, int is_neg,
                            char xp_char, int is_auto)
{
  int i, rpad0;
  char xps_buf[10];

  xps_buf[0] = xp_char;
  xps_buf[1] = '+';
  if (xp < 0)
    {
      xps_buf[1] = '-';
      xp = -xp;
    }
  i = 2;
  if (xp >= 1000)
    xps_buf[i++] = (char)((xp / 1000) % 10) + '0';
  if (xp >= 100)
    xps_buf[i++] = (char)((xp / 100) % 10) + '0';
  xps_buf[i++] = (char)((xp / 10) % 10) + '0';
  xps_buf[i++] = (char)(xp % 10) + '0';
  xps_buf[i] = 0;

  /* Insert decimal point. */

  if (v->prec == 0 && !v->hash)
    {
      digits[1] = 0;
      rpad0 = 0;
    }
  else
    {
      memmove (digits + 2, digits + 1, DIG);
      digits[1] = '.';
      if (v->prec >= DIG)
        rpad0 = 1 + v->prec - DIG;
      else
        {
          rpad0 = 0;
          digits[2 + v->prec] = 0;
        }
      if (is_auto)
        {
          _cvt_remove_zeros (digits, 2);
          if (digits[2] == 0)
            digits[1] = 0;
          rpad0 = 0;
        }
    }
  cvt_number (v, NULL, digits, xps_buf, 0, rpad0, TRUE, is_neg);
}


/* Perform formatting for the "%f" format. */

static void cvt_fixed (olocal *v, DOUBLE x, int is_neg)
{

  if (x == 0.0)
    cvt_number (v, NULL, ((v->hash || v->prec > 0) ? "0." : "0"),
                NULL, 0, v->prec, TRUE, is_neg);
  else
    {
      char digits[DIG+2];
      int xp;

      xp = _cvt_float_decimal (x, digits);
      _cvt_round (digits, &xp, xp + 1 + v->prec, v->lim);
      cvt_fixed_digits (v, digits, xp, is_neg, FALSE);
    }
}


/* Perform formatting for the "%e" format.  XP_CHAR is 'e' or 'E'. */

static void cvt_exp (olocal *v, DOUBLE x, int is_neg, char xp_char)
{
  if (x == 0.0)
    {
      static char xps_buf[] = "e+00";

      xps_buf[0] = xp_char;
      cvt_number (v, NULL, ((v->hash || v->prec > 0) ? "0." : "0"),
                  xps_buf, 0, v->prec, TRUE, FALSE);
    }
  else
    {
      char digits[DIG+2];
      int xp;

      xp = _cvt_float_decimal (x, digits);
      _cvt_round (digits, &xp, v->prec + 1, v->lim);
      cvt_exp_digits (v, digits, xp, is_neg, xp_char, FALSE);
    }
}


/* Perform formatting for the "%g" format.  XP_CHAR is 'e' or 'E'. */

static void cvt_auto (olocal *v, DOUBLE x, int is_neg, char xp_char)
{
  /* A precision of zero is treated as a precision of 1.  Note that
     the precision defines the number of significant digits, not the
     number of decimals! */

  if (v->prec == 0)
    v->prec = 1;

  /* 0.0 is treated specially as _cvt_float_decimal etc. cannot handle
     that case. */

  if (x == 0.0)
    cvt_number (v, NULL, (v->hash ? "0." : "0"), NULL,
                0, (v->hash ? v->prec : 0), TRUE, FALSE);
  else
    {
      char digits[DIG+2];
      int xp;

      /* Convert the number to decimal and round it according to the
         precision (number of significant digits). */

      xp = _cvt_float_decimal (x, digits);
      _cvt_round (digits, &xp, v->prec, v->lim);

      /* If the exponent (of the "%e" format) is less than -4 or
         greater than or equal to the precision, use the "%e"
         format.  Otherwise, use the "%f" format. */

      if (xp < -4 || xp >= v->prec)
        {
          /* Adjust the precision to indicate the number of
             decimals. */

          --v->prec;

          /* Treat "%#g" like "%e" (except for the precision).  "%g"
             (without `#') removes trailing zeros. */

          cvt_exp_digits (v, digits, xp, is_neg, xp_char, !v->hash);
        }
      else
        {
          /* Compute the number of decimals from the exponent and the
             precision.  We must not remove trailing zeros from the
             integer part of the number! */

          v->prec -= xp + 1;
          if (v->prec < 0)
            v->prec = 0;

          cvt_fixed_digits (v, digits, xp, is_neg, !v->hash);
        }
    }
}


/* Print the floating point number X.  LIM is the number of
   significant digits of X (depending on the type of X), FMT is the
   format character from the format string. */

static void cvt_float (olocal *v, DOUBLE x, int lim, char fmt)
{
  const char *s;
  int is_neg;

  s = _cvt_nan (FXAM (x));
  if (s != NULL)
    {
      v->prec = -1;             /* TODO: Is this correct? */
      cvt_str (v, s, -1);       /* TODO: Is this correct? */
      return;
    }

  v->lim = lim;
  if (v->prec < 0)
    v->prec = DEFAULT_PREC;

  is_neg = (x < 0.0);
  if (is_neg)
    x = -x;

  switch (fmt)
    {
    case 'f':
      cvt_fixed (v, x, is_neg);
      break;

    case 'g':
      cvt_auto (v, x, is_neg, 'e');
      break;

    case 'G':
      cvt_auto (v, x, is_neg, 'E');
      break;

    case 'e':
      cvt_exp (v, x, is_neg, 'e');
      break;

    case 'E':
      cvt_exp (v, x, is_neg, 'E');
      break;

    default:
      abort ();
    }
}
  

/* This is the working horse for printf() and friends. */

int _output (FILE *stream, const char *format, char *arg_ptr)
{
  olocal v;
  char size, cont;

  /* Initialize local variables. */

  v.stream = stream;
  v.count = 0;

  /* Return -1 on output error. */

  if (setjmp (v.jerror) != 0)
    return (-1);

  /* Interpret the string. */

  while (*format != 0)
    if (*format != '%')
      {
        PUTC (&v, *format);
        ++format;
      }
    else if (format[1] == '%')
      {
        PUTC (&v, '%');
        format += 2;
      }
    else
      {
        v.minus = v.plus = v.blank = v.hash = FALSE;
        v.width = 0; v.prec = -1; size = 0; v.pad = ' ';
        cont = TRUE;
        do
          {
            ++format;
            switch (*format)
              {
              case '-':
                v.minus = TRUE;
                break;
              case '+':
                v.plus = TRUE;
                break;
              case '0':
                v.pad = '0';
                break;
              case ' ':
                v.blank = TRUE;
                break;
              case '#':
                v.hash = TRUE;
                break;
              default: 
                cont = FALSE;
                break;
              }
          } while (cont);

        /* `-' overrides `0' */

        if (v.minus)
          v.pad = ' ';

        /* Field width */

        if (*format == '*')
          {
            ++format;
            v.width = va_arg (arg_ptr, int);
            if (v.width < 0)
              {
                v.width = -v.width;
                v.minus = TRUE;
              }
          }
        else
          while (*format >= '0' && *format <= '9')
            {
              v.width = v.width * 10 + (*format - '0');
              ++format;
            }

        /* Precision */

        if (*format == '.')
          {
            ++format;
            if (*format == '*')
              {
                ++format;
                v.prec = va_arg (arg_ptr, int);
                if (v.prec < 0)
                  v.prec = -1;  /* We don't need this */
              }
            else
              {
                v.prec = 0;
                while (*format >= '0' && *format <= '9')
                  {
                    v.prec = v.prec * 10 + (*format - '0');
                    ++format;
                  }
              }
          }

        /* Size */

        if (*format == 'h' || *format == 'l' || *format == 'L')
          {
            size = *format++;
            if (size == 'l' && *format == 'l')
              {
                size = 'L'; ++format;
              }
          }

        /* Format */

        switch (*format)
          {
          case 0:
            return (v.count);

          case 'n':
            if (size == 'L')
              {
                long long *ptr = va_arg (arg_ptr, long long *);
                *ptr = v.count;
              }
            else if (size == 'h')
              {
                short *ptr = va_arg (arg_ptr, short *);
                *ptr = v.count;
              }
            else
              {
                int *ptr = va_arg (arg_ptr, int *);
                *ptr = v.count;
              }
            break;

          case 'c':
            {
              char c;

              c = (char)va_arg (arg_ptr, int);
              v.prec = 1;
              cvt_str (&v, &c, 1);
            }
            break;

          case 's':
            cvt_str (&v, va_arg (arg_ptr, const char *), -1);
            break;

          case 'd':
          case 'i':
            if (size == 'L')
              {
                long long n = va_arg (arg_ptr, long long);
                if (n < 0)
                  cvt_dec_64 (&v, -n, TRUE, TRUE);
                else
                  cvt_dec_64 (&v, n, TRUE, FALSE);
              }
            else
              {
                int n = va_arg (arg_ptr, int);
                if (size == 'h')
                  n = (short)n;
                if (n < 0)
                  cvt_dec_32 (&v, -n, TRUE, TRUE);
                else
                  cvt_dec_32 (&v, n, TRUE, FALSE);
              }
            break;

          case 'u':
            if (size == 'L')
              cvt_dec_64 (&v, va_arg (arg_ptr, unsigned long long),
                          FALSE, FALSE);
            else
              {
                unsigned n = va_arg (arg_ptr, unsigned);
                if (size == 'h')
                  n = (unsigned short)n;
                cvt_dec_32 (&v, n, FALSE, FALSE);
              }
            break;

          case 'p':
            v.hash = TRUE;
            cvt_hex_32 (&v, va_arg (arg_ptr, unsigned), 'x');
            break;

          case 'x':
          case 'X':
            if (size == 'L')
              cvt_hex_64 (&v, va_arg (arg_ptr, unsigned long long), *format);
            else
              {
                unsigned n = va_arg (arg_ptr, unsigned);
                if (size == 'h')
                  n = (unsigned short)n;
                cvt_hex_32 (&v, n, *format);
              }
            break;

          case 'o':
            if (size == 'L')
              cvt_oct_64 (&v, va_arg (arg_ptr, unsigned long long));
            else
              {
                unsigned n = va_arg (arg_ptr, unsigned);
                if (size == 'h')
                  n = (unsigned short)n;
                cvt_oct_32 (&v, n);
              }
            break;

          case 'g':
          case 'G':
          case 'e':
          case 'E':
          case 'f':
            if (size == 'L')
              {
                DOUBLE x = va_arg (arg_ptr, long double);
                cvt_float (&v, x, DIG, *format);
              }
            else
              {
                double x = va_arg (arg_ptr, double);
                cvt_float (&v, x, DBL_DIG, *format);
              }
            break;

          default:
            /* TODO: print the last letter only or all the characters? */
            out_str (&v, format, 1);
            break;
          }
        ++format;
      }

  /* Return the number of characters printed. */

  return (v.count);
}
