/*
        This file is part of the cFos project,
        Copyright (c) 1993 by Martin Winkler, Christoph Lders

        This file derives from EMX 0.8f, Copyright (c) 1990-1993 by
        Eberhard Mattes.  We made some slight changes to exclude the
        floating point parts of printf() and changed the way printf()
        outputs it's data; unfortunately we had to exclude the `long long'
        stuff, too.


        You are allowed to use, copy, distribute, publish, redistribute
        and/or modify this program under the terms of the 'cFos public
        licence and warranty'.

        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 COPYING.CF for the 'cFos public license and warranty'.
*/

#define WANT_FLOAT 0
#define WANT_LLONG 0

/* #include <sys/emx.h> */
/* #include <stdio.h> */
/* #include <stdlib.h> */
#include <stdarg.h>
#include <string.h>
#if WANT_FLOAT
# include <limits.h>
# include <math.h>
#endif

#include "vars.h"

#define _itoa itoa

#define MAX_FRACT 20        /* 64 * log 2 */
#define MAX_MANT  309       /* extended format isn't used */
#define DEFAULT_PREC 6

#define BEGIN do {
#define END   } while (0)

#ifdef ORIGINAL_STYLE
#define PUTC(c) BEGIN \
                 if (putc (c, stream) == EOF) \
                   return (-1); \
                 ++count; \
                END
#else
#define PUTC(c) BEGIN \
                 if (deststr != NULL) *(deststr++) = c; \
                 else printme (c, cport); \
                 ++count; \
                END
#endif


char *itoa (int, char*, int);
char *ltoa (long, char*, int);
#if WANT_FLOAT
static void conv_real (char *dst, double x, int prec, int fixed, int force_dp,
                       int *prec_out, int *exp_out);
#endif


/* ultoa.c (emx+gcc) -- Copyright (c) 1990-1993 by Eberhard Mattes */
/* printf.c (emx+gcc) -- Copyright (c) 1990-1993 by Eberhard Mattes */
/* output.c (emx+gcc) -- Copyright (c) 1990-1993 by Eberhard Mattes */


static char *_ultoa (unsigned long value, char *string, int radix)
{
  char *dst;
  char digits[32];
  int i, n;
  
  dst = string;
  if (radix < 2 || radix > 36)
    {
      *dst = 0;
      return (string);
    }
  i = 0;
  do
    {
      n = (int) (value % radix);
      digits[i++] = (n < 10 ? (char)n+'0' : (char)n-10+'a');
      value /= radix;
    } while (value != 0);
  while (i > 0)
    *dst++ = digits[--i];
  *dst = 0;
  return (string);
}



int _output (int (*printme)(int, int), int cport, char *deststr, const char *format, char *arg_ptr)
{
  char just, sign, blank, hash, size, number, integer, pad, cont;
  char *s, tmp[66], prefix[8], *pfx_ptr, *pfx_start, tbuf[40];
  int width, prec, count, *ptr, i, pn, zn, sn, tzn;
  long n;
#if WANT_FLOAT
  long long lln;
  int neg, fix;
  unsigned long long ulln;
  double dn;
  char dbuf[MAX_MANT+MAX_FRACT+10];
  ind de;
#endif

  count = 0;
  while (*format != 0)
    if (*format != '%' || format[1] == '%')
      {
        PUTC (*format);
        if (*format == '%') ++format;
        ++format;
      }
    else
      {
        just = sign = blank = hash = FALSE;
        width = 0; prec = -1; size = 0; tzn = 0; pad = ' ';
        cont = TRUE;
        while (cont)
          {
            ++format;
            switch (*format)
              {
              case '-': just = TRUE; break;
              case '+': sign = TRUE; break;
              case '0': pad = '0'; break;
              case ' ': blank = TRUE; break;
              case '#': hash = TRUE; break;
              default: cont = FALSE; break;
              }
          }
        if (*format == '*')
          {
            ++format;
            width = va_arg (arg_ptr, int);
            if (width < 0)
              {
                width = -width; just = TRUE;
              }
          }
        else
          while (*format >= '0' && *format <= '9')
            {
              width = width * 10 + (*format - '0');
              ++format;
            }
        if (*format == '.')
          {
            pad = ' ';
            ++format;
            if (*format == '*')
              {
                ++format;
                prec = va_arg (arg_ptr, int);
                if (prec < 0) prec = -1;
              }
            else
              {
                prec = 0;
                while (*format >= '0' && *format <= '9')
                  {
                    prec = prec * 10 + (*format - '0');
                    ++format;
                  }
              }
          }
        if (*format == 'h' || *format == 'l' || *format == 'L')
          {
            size = *format++;
#if WANT_LLONG
            if (size == 'l' && *format == 'l')
              {
                size = 'L'; ++format;
              }
#endif
          }
        s = NULL; number = TRUE; integer = TRUE; sn = 0;
        pfx_ptr = pfx_start = prefix+1;
        tbuf[0] = 0;
        switch (*format)
          {
          case 'n':
            ptr = va_arg (arg_ptr, int *);
            *ptr = count;
            goto next;
          case 'c':
            number = FALSE;
            tmp[0] = (char)va_arg (arg_ptr, int);
            s = tmp; sn = 1;
            break;
          case 'u':
#if WANT_LLONG
            if (size == 'L')
              {
                ulln = va_arg (arg_ptr, unsigned long long);
                s = _ulltoa (ulln, tmp, 10);
              }
            else
#endif
              {
                if (size == 'l') {
                   n = va_arg (arg_ptr, long);
                } else {
                   n = va_arg (arg_ptr, int);
                   n = (unsigned short)n;
                }
                s = _ultoa (n & 0xffff, tmp, 10);
              }
            sn = strlen (s);
            break;
          case 'd':
          case 'i':
#if WANT_LLONG
            if (size == 'L')
              {
                lln = va_arg (arg_ptr, long long);
                s = _lltoa (lln, tmp, 10);
              }
            else
#endif
              {
                if (size == 'l') {
                   n = va_arg (arg_ptr, long);
                } else {
                   n = va_arg (arg_ptr, int);
                   n = (short)n;
                }
                s = ltoa (n, tmp, 10);
              }
            sn = strlen (s);
            break;
          case 'p':
          case 'x':
          case 'X':
#if WANT_LLONG
            if (size == 'L')
              {
                ulln = va_arg (arg_ptr, unsigned long long);
                s = _ulltoa (ulln, tmp, 16);
                n = (ulln != 0);
              }
            else
#endif
              {
                if (size == 'l') {
                   n = va_arg (arg_ptr, long);
                } else {
                   n = va_arg (arg_ptr, int);
                   n = (unsigned short)n;
                }
                s = ltoa (n, tmp, 16);
              }
            sn = strlen (s);
            if (*format == 'X')
              strupr (tmp);
            if (n != 0 && hash)
              {
                *pfx_ptr++ = '0';
                *pfx_ptr++ = 'x';
              }
            break;
#ifdef ORIGINAL_STYLE
          case 'o':
#if WANT_LLONG
            if (size == 'L')
              {
                ulln = va_arg (arg_ptr, unsigned long long);
                s = _ulltoa (ulln, tmp, 8);
                n = (ulln != 0);
              }
            else
#endif
              {
                n = va_arg (arg_ptr, int);
                if (size == 'h') n = (unsigned short)n;
                s = ltoa (n, tmp, 8);
              }
            sn = strlen (s);
            if (n != 0 && hash)
              *pfx_ptr++ = '0';
            break;
#endif
#if WANT_FLOAT
          case 'g':
          case 'G':
          case 'e':
          case 'E':
          case 'f':
            if (prec == -1) prec = DEFAULT_PREC;
            dn = va_arg (arg_ptr, double);
            tzn = prec;
            if (prec > MAX_FRACT) prec = MAX_FRACT;
            switch (_fxam (dn))
              {
              case FX_P_NAN:
              case FX_N_NAN:      s = "#NAN"; break;
              case FX_P_INFINITY: s = "#INF"; break;
              case FX_N_INFINITY: s = "-#INF"; break;
              case FX_P_EMPTY:    s = "#EMP"; break;
              case FX_N_EMPTY:    s = "-#EMP"; break;
              case FX_P_DENORMAL: s = "#DEN"; break;
              case FX_N_DENORMAL: s = "-#DEN"; break;
              default:
                s = NULL;
                break;
              }
            if (s != NULL)
              {
                sn = strlen (s); prec = tzn = 0;
                break;
              }
            neg = (dn < 0.0);
            if (neg) dn = -dn;
            switch (*format)
              {
              case 'f':
                conv_real (dbuf, dn, prec, TRUE, hash, &i, &de);
                tzn -= prec - i;
                break;
              case 'g':
              case 'G':
                if (dn == 0.0)
                  {
                    dbuf[1] = '0';
                    dbuf[2] = 0;
                    if (!hash) tzn = 0;
                    break;
                  }
                conv_real (dbuf, dn, prec+1, FALSE, FALSE, &tzn, &de);
                fix = !(de < -4 || de > prec);
                if (fix)
                  {
                    conv_real (dbuf, dn, prec, TRUE, hash, &i, &de);
                    tzn -= prec - i;
                  }
                if (!hash)
                  {
                    i = strlen (dbuf+1);
                    while (i > 1 && dbuf[i] == '0') --i;
                    if (i > 1 && dbuf[i] == '.') --i;
                    dbuf[i+1] = 0;
                    tzn = 0;
                  }
                if (!fix) goto fmt_e;
                break;
              case 'e':
              case 'E':
                if (dn == 0.0)
                  {
                    s = dbuf + 1;
                    *s++ = '0';
                    if (prec != 0 || hash) *s++ = '.';
                    *s = 0; de = 0;
                  }
                else
                  conv_real (dbuf, dn, prec+1, FALSE, FALSE, &tzn, &de);
fmt_e:
                tbuf[0] = (*format >= 'A' && *format <= 'Z' ? 'E' : 'e');
                tbuf[1] = (de < 0 ? '-' : '+');
                if (de < 0) de = -de;
                tbuf[2] = (char)((de / 100) % 10) + '0';
                tbuf[3] = (char)((de / 10) % 10) + '0';
                tbuf[4] = (char)(de % 10) + '0';
                tbuf[5] = 0;
                break;
              }
            if (neg)
              {
                dbuf[0] = '-';
                s = dbuf;
              }
            else
              s = dbuf + 1;
            sn = strlen (s); prec = 0;
            break;
#endif
          case 's':
            number = FALSE;
            s = va_arg (arg_ptr, char *);
            if (s == NULL) s = "(null)";
            sn = strlen (s);
            if (prec > 0 && prec < sn)
              sn = prec;
            break;
          default:
            if (size != 0)
              --format;
            PUTC (*format);
            break;
          }
        if (s != NULL)
          {
            if (number && sn > 0)
              {
                if (*s == '-')
                  {
                    ++s; --sn;
                    *--pfx_start = '-';
                  }
                else if (sign)
                  *--pfx_start = '+';
                else if (blank)
                  *--pfx_start = ' ';
              }
            zn = 0;
            if (number && integer && prec > 0)
              {
                zn = prec - sn;
                if (zn < 0) zn = 0;
              }
            pn = (int) (pfx_ptr - pfx_start);
            if (tzn < 0) tzn = 0;
            n = pn + zn + sn + tzn + strlen (tbuf);
            if (number && pad == '0' && n < width && !just)
              {
                zn += width - n;
                n = width;
              }
            if (n < width && !just)
              for (i = n; i < width; ++i)
                PUTC (pad);
            for (i = 0; i < pn; ++i)
              {
                PUTC (*pfx_start); ++pfx_start;
              }
            for (i = 0; i < zn; ++i)
              PUTC ('0');
            for (i = 0; i < sn; ++i)
              {
                PUTC (*s); ++s;
              }
            for (i = 0; i < tzn; ++i)
              PUTC ('0');
            for (i = 0; tbuf[i] != 0; ++i)
              PUTC (tbuf[i]);
            if (n < width && just)
              for (i = n; i < width; ++i)
                PUTC (' ');
          }
next:
        ++format;
      }
  return (count);
}


#if WANT_FLOAT
/* Now comes the hard part.  Convert X to a string of digits.  Store the
   resulting string to DST.  DST[0] is reserved for the sign character
   (and used internally for rounding) -- it is not set by this
   function: the string starts at DST+1.  PREC is the precision, ie,
   the number of decimal digits after the decimal point.  FIXED is
   non-zero for fixed-point formats.  If FORCE_DP is non-zero, the
   result will always contain a decimal point.  The resulting number of
   decimal digits after the decimal point is returned in *PREC_OUT.
   The exponent to be applied to the string is returned in *EXP_OUT
   (used only if FIXED is 0).

   Algorithm:

   - Convert the integer part of X into a string of digits by repeated
     division by 10.  After this step, I contains the number of digits
     (integer part), the digit string is stored in TMP (I characters),
     EXP contains the number of digits (integer part) - 1.
     LEADING_ZERO is set FALSE if a fixed-point format is used (FIXED
     non-zero) or if the integer part is non-zero.

   - Copy the digit string (integer part) to DST.  A decimal point is
     added for fixed-point formats if FORCE_DP is non-zero or if PREC
     is positive.  For exponential formats, a decimal point is not
     added -- it will be inserted later between the first and the
     second digits.  PREC is adjusted to indicate the number of digits
     remaining to be computed.

   - The fractional part is converted into a string of digits until it
     is zero or the precision PREC is exhausted.  This is done by
     repeated multiplication by 10.  Leading zeros are suppressed (see
     note about LEADING_ZERO above) adjusting EXP appropriately.

   - If the remaining fractional part is non-zero, rounding is
     applied.  This is done by computing the next digit and comparing
     it to 5: If it is greater than or equal to 5, the "number" (digit
     string) is "incremented by 1" -- ignoring the decimal point.  This
     operation may overflow into the first character of the
     destination string which is reserved for this reason (and for
     storing the sign character).  On overflow the string is moved by
     one character to free the first character.

   - For exponential formats, the decimal point is inserted after the
     first digit of the string.

  This algorithm is somewhat inaccurate.  Letting the coprocessor
  convert the number to BCD should improve accuracy.  Another approach
  is to do all the computations using 80-bit floating point numbers. */

static void conv_real (char *dst, double x, int prec, int fixed, int force_dp,
                       int *prec_out, int *exp_out)
{
  double df, dt, di;
  char tmp[MAX_MANT+MAX_FRACT+10], c, *start;
  int i, exp, leading_zero;

  start = dst++;
  df = modf (x, &di);
  i = 0; exp = -1; leading_zero = !fixed;
  while (di != 0.0)
    {
      dt = modf (di / 10.0, &di);
      tmp[i++] = (char)(dt * 10.0 + 0.125) + '0';
      ++exp; leading_zero = FALSE;
    }
  if (fixed)
    {
      if (i == 0) *dst++ = '0';
      while (i != 0)
        *dst++ = tmp[--i];
      if (prec > 0 || force_dp) *dst++ = '.';
    }
  else
    while (i != 0 && prec > 0)
      {
        *dst++ = tmp[--i];
        --prec;
      }
  while (df != 0.0 && prec > 0)
    {
      df = modf (df * 10.0, &dt);
      c = (char)dt + '0';
      if (c != '0' || !leading_zero)
        {
          *dst++ = c; leading_zero = FALSE;
          --prec;
        }
      else
        --exp;
    }
  *dst = 0;
  if (df != 0.0)
    {
      df = modf (df * 10.0, &dt);
      c = (char)dt + '0';
      if (c >= '5')
        {
          *start = '0';
          while (dst != start)
            {
              --dst;
              if (*dst == '.') continue;
              if (*dst != '9')
                {
                  ++*dst; break;
                }
              *dst = '0';
            }
          if (dst == start)
            {
              i = strlen (start);
              memmove (start+1, start, i+1);
              if (!fixed)
                {
                  start[i] = 0; ++exp;
                }
            }
        }
    }
  if (!fixed)
    {
      memmove (start+3, start+2, strlen (start+1));
      start[2] = '.';
    }
  *prec_out = prec;
  *exp_out = exp;
}
#endif



void myputchar (int c)
{
   if (c == 10)
      mybdos (2, 13, 0);
   mybdos (2, c, 0);
}



int printf (const char *format, ...)
{
  va_list arg_ptr;
  int result;

  va_start (arg_ptr, format);
  result = _output ((int (*) (int, int)) myputchar, 0, NULL, format, arg_ptr);
  va_end (arg_ptr);
  return (result);
}


/* this is an implementation of sprintf WITHOUT terminating zero !! */

int mysprintf (char *dest, const char *format, ...)
{
  va_list arg_ptr;
  int result;

  va_start (arg_ptr, format);
  result = _output ((int (*) (int, int)) NULL, 0, dest, format, arg_ptr);
  va_end (arg_ptr);
  return (result);
}


/* --- eof printf.c */
