/* Copyright (C) 1991, 1992 Free Software Foundation, Inc.
This file is part of the GNU C Library.

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

The GNU C Library 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
Library General Public License for more details.

You should have received a copy of the GNU Library General Public
License along with the GNU C Library; see the file COPYING.LIB.  If
not, write to the Free Software Foundation, Inc., 675 Mass Ave,
Cambridge, MA 02139, USA.  */

#include <ansidecl.h>
#include <localeinfo.h>
#include <ctype.h>
#include <errno.h>
#include <float.h>
#include <limits.h>
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <printf.h>


/* If it's an unbuffered stream that we provided
   temporary buffering for, remove that buffering.  */
#define	RETURN(x)							      \
  do									      \
    {									      \
      done = (x);							      \
      goto do_return;							      \
    } while (0)

#define	outchar(x)							      \
  do									      \
    {									      \
      register CONST int outc = (x);					      \
      if (putc(outc, s) == EOF)						      \
	RETURN(-1);							      \
      else								      \
	++done;								      \
    } while (0)

/* Cast the next arg, of type ARGTYPE, into CASTTYPE, and put it in VAR.  */
#define	castarg(var, argtype, casttype) \
  var = (casttype) va_arg(args, argtype)
/* Get the next arg, of type TYPE, and put it in VAR.  */
#define	nextarg(var, type)	castarg(var, type, type)

static printf_function printf_unknown;

extern printf_function **__printf_function_table;

#ifdef	__GNUC__
#define	HAVE_LONGLONG
#define	LONGLONG	long long
#else
#define	LONGLONG	long
#endif


int
DEFUN(vfprintf, (s, format, args),
      register FILE *s AND CONST char *format AND va_list args)
{
  /* Lower-case digits.  */
  static CONST char lower_digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";
  /* Upper-case digits.  */
  static CONST char upper_digits[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  /* Base-36 digits for numbers.  */
  CONST char *digits = lower_digits;

  /* Pointer into the format string.  */
  register CONST char *f;

  /* Number of characters written.  */
  register size_t done = 0;

  /* Nonzero we're providing buffering.  */
  char our_buffer;
  /* Temporary buffer for unbuffered streams.  */
  char temporary_buffer[BUFSIZ];

  if (!__validfp(s) || !s->__mode.__write || format == NULL)
    {
      errno = EINVAL;
      return -1;
    }

  if (!s->__seen)
    {
      if (__flshfp(s, EOF) == EOF)
	return EOF;
    }

  our_buffer = s->__buffer == NULL;
  if (our_buffer)
    {
      /* If it's an unbuffered stream, buffer it
	 at least inside this function call.  */
      s->__bufp = s->__buffer = temporary_buffer;
      s->__bufsize = sizeof(temporary_buffer);
      s->__put_limit = s->__buffer + s->__bufsize;
      s->__get_limit = s->__buffer;
    }

  /* Reset multibyte characters to their initial state.  */
  (void) mblen((char *) NULL, 0);

  f = format;
  while (*f != '\0')
    {
      /* Type modifiers.  */
      char is_short, is_long, is_long_double;
#ifdef	HAVE_LONGLONG
      /* We use the `L' modifier for `long long int'.  */
#define	is_longlong	is_long_double
#else
#define	is_longlong	0
#endif
      /* Format spec modifiers.  */
      char space, showsign, left, alt;

      /* Padding character: ' ' or '0'.  */
      char pad;
      /* Width of a field.  */
      register int width;
      /* Precision of a field.  */
      int prec;

      /* Decimal integer is negative.  */
      char is_neg;

      /* Current character of the format.  */
      char fc;

      /* Base of a number to be written.  */
      int base;
      /* Integral values to be written.  */
      unsigned LONGLONG int num;
      LONGLONG int signed_num;

      /* Auxiliary function to do output.  */
      printf_function *function;

      if (!isascii(*f))
	{
	  /* Non-ASCII, may be a multibyte.  */
	  int len = mblen(f, strlen(f));
	  if (len > 0)
	    {
	      while (len-- > 0)
		outchar(*f++);
	      continue;
	    }
	}

      if (*f != '%')
	{
	  /* This isn't a format spec, so write
	     everything out until the next one.  */
	  CONST char *next = strchr(f + 1, '%');
	  if (next == NULL)
	    next = strchr(f + 1, '\0');
	  if (next - f > 20)
	    {
	      size_t written = fwrite((PTR) f, 1, next - f, s);
	      done += written;
	      if (written != next - f)
		break;
	      f += written;
	    }
	  else
	    while (f < next)
	      outchar(*f++);
	  continue;
	}

      ++f;

      /* Check for "%%".  Note that although the ANSI standard lists
	 '%' as a conversion specifier, it says "The complete format
	 specification shall be `%%'," so we can avoid all the width
	 and precision processing.  */
      if (*f == '%')
	{
	  ++f;
	  outchar('%');
	  continue;
	}

      /* Check for spec modifiers.  */
      space = showsign = left = alt = 0;
      pad = ' ';
      while (*f == ' ' || *f == '+' || *f == '-' || *f == '#' || *f == '0')
	switch (*f++)
	  {
	  case ' ':
	    /* Output a space in place of a sign, when there is no sign.  */
	    space = 1;
	    break;
	  case '+':
	    /* Always output + or - for numbers.  */
	    showsign = 1;
	    break;
	  case '-':
	    /* Left-justify things.  */
	    left = 1;
	    break;
	  case '#':
	    /* Use the "alternate form":
	       Hex has 0x or 0X, FP always has a decimal point.  */
	    alt = 1;
	    break;
	  case '0':
	    /* Pad with 0s.  */
	    pad = '0';
	    break;
	  }
      if (left)
	pad = ' ';

      /* Get the field width.  */
      width = 0;
      if (*f == '*')
	{
	  /* The field width is given in an argument.
	     A negative field width indicates left justification.  */
	  nextarg(width, int);
	  if (width < 0)
	    {
	      width = - width;
	      left = 1;
	    }
	  ++f;
	}
      else
	while (isdigit(*f))
	  {
	    width *= 10;
	    width += *f++ - '0';
	  }

      /* Get the precision.  */
      /* -1 means none given; 0 means explicit 0.  */
      prec = -1;
      if (*f == '.')
	{
	  ++f;
	  if (*f == '*')
	    {
	      /* The precision is given in an argument.  */
	      nextarg(prec, int);
	      /* Avoid idiocy.  */
	      if (prec < 0)
		prec = -1;
	      ++f;
	    }
	  else if (isdigit(*f))
	    {
	      prec = 0;
	      while (*f != '\0' && isdigit(*f))
		{
		  prec *= 10;
		  prec += *f++ - '0';
		}
	    }
	}

      /* Check for type modifiers.  */
      is_short = is_long = is_long_double = 0;
      while (*f == 'h' || *f == 'l' || *f == 'L')
	switch (*f++)
	  {
	  case 'h':
	    /* int's are short int's.  */
	    is_short = 1;
	    break;
	  case 'l':
#ifdef	HAVE_LONGLONG
	    if (is_long)
	      /* A double `l' is equivalent to an `L'.  */
	      is_longlong = 1;
	    else
#endif
	      /* int's are long int's.  */
	      is_long = 1;
	    break;
	  case 'L':
	    /* double's are long double's, and int's are long long int's.  */
	    is_long_double = 1;
	    break;
	  }

      /* Format specification.  */
      fc = *f++;
      function = (__printf_function_table == NULL ? NULL :
		  __printf_function_table[fc]);
      if (function == NULL)
	switch (fc)
	  {
	  case 'i':
	  case 'd':
	    /* Decimal integer.  */
	    base = 10;
	    if (is_longlong)
	      nextarg(signed_num, LONGLONG int);
	    else if (is_long)
	      nextarg(signed_num, long int);
	    else if (!is_short)
	      castarg(signed_num, int, long int);
	    else
	      castarg(signed_num, int, short int);

	    is_neg = signed_num < 0;
	    num = is_neg ? (- signed_num) : signed_num;
	    goto number;

	  case 'Z':
	    /* `size_t' value.  */
#ifdef	HAVE_LONGLONG
	    if (sizeof(size_t) > sizeof(unsigned long long int))
	      __libc_fatal("`size_t' is bigger than any known type!");
	    else
	      is_longlong = sizeof(size_t) > sizeof(unsigned long int);
#endif
	    is_long = sizeof(size_t) > sizeof(unsigned int);

	    /* Fall through, to print a `size_t' as a decimal integer.  */

	  case 'u':
	    /* Decimal unsigned integer.  */
	    base = 10;
	    goto unsigned_number;

	  case 'o':
	    /* Octal unsigned integer.  */
	    base = 8;
	    goto unsigned_number;

	  case 'X':
	    /* Hexadecimal unsigned integer.  */
	  case 'x':
	    /* Hex with lower-case digits.  */

	    digits = fc == 'X' ? upper_digits : lower_digits;

	    base = 16;

	  unsigned_number:;
	    /* Unsigned number of base BASE.  */

	    if (is_longlong)
	      castarg(num, LONGLONG int, unsigned LONGLONG int);
	    else if (is_long)
	      castarg(num, long int, unsigned long int);
	    else if (!is_short)
	      castarg(num, int, unsigned int);
	    else
	      castarg(num, int, unsigned short int);

	    is_neg = 0;

	  number:;
	    /* Number of base BASE.  */
	    {
	      char work[BUFSIZ];
	      char *CONST workend = &work[sizeof(work) - 1];
	      register char *w;

	      /* Supply a default precision if none was given.  */
	      if (prec == -1)
		prec = 1;

	      /* Put the number in WORK.  */
	      w = workend;
	      while (num > 0)
		{
		  *w-- = digits[num % base];
		  num /= base;
		}
	      width -= workend - w;
	      prec -= workend - w;

	      if (alt && base == 8 && prec <= 0)
		{
		  *w-- = '0';
		  --width;
		}

	      if (prec > 0)
		{
		  width -= prec;
		  while (prec-- > 0)
		    *w-- = '0';
		}

	      if (alt && base == 16)
		width -= 2;

	      if (is_neg || showsign || space)
		--width;

	      if (!left && pad == ' ')
		while (width-- > 0)
		  outchar(' ');

	      if (is_neg)
		outchar('-');
	      else if (showsign)
		outchar('+');
	      else if (space)
		outchar(' ');

	      if (!left && pad == '0')
		while (width-- > 0)
		  outchar('0');

	      if (alt && base == 16)
		{
		  outchar ('0');
		  outchar (fc);
		}

	      /* Write the number.  */
	      while (++w <= workend)
		outchar(*w);

	      if (left)
		while (width-- > 0)
		  outchar(' ');
	    }
	    break;

	  case 'e':
	  case 'E':
	  case 'f':
	  case 'g':
	  case 'G':
	    {
	      /* Floating-point number.  */
	      extern printf_function __printf_fp;
	      function = __printf_fp;
	      goto use_function;
	    }

	  case 'c':
	    /* Character.  */
	    nextarg(num, int);
	    if (!left)
	      while (--width > 0)
		outchar(' ');
	    outchar((unsigned char) num);
	    if (left)
	      while (--width > 0)
		outchar(' ');
	    break;

	  case 's':
	    /* String.  */
	    {
	      static CONST char null[] = "(null)";
	      CONST char *str;
	      size_t len;

	      nextarg(str, CONST char *);
	      if (str == NULL)
		/* Write "(null)" if there's space.  */
		if (prec == -1 || prec >= (int) sizeof(null) - 1)
		  {
		    str = null;
		    len = sizeof(null) - 1;
		  }
		else
		  {
		    str = "";
		    len = 0;
		  }
	      else
		len = strlen(str);

	      if (prec != -1 && (size_t) prec < len)
		len = prec;
	      width -= len;

	      if (!left)
		while (width-- > 0)
		  outchar(' ');
	      if (len < 20)
		while (len-- > 0)
		  outchar(*str++);
	      else
		if (fwrite(str, 1, len, s) != len)
		  RETURN(-1);
		else
		  done += len;
	      if (left)
		while (width-- > 0)
		  outchar(' ');
	    }
	    break;

	  case 'p':
	    /* Generic pointer.  */
	    {
	      CONST PTR ptr;
	      nextarg(ptr, CONST PTR);
	      if (ptr != NULL)
		{
		  /* If the pointer is not NULL, write it as a %#x spec.  */
		  base = 16;
		  fc = 'x';
		  alt = 1;
		  num = (unsigned LONGLONG int) (unsigned long int) ptr;
		  is_neg = 0;
		  goto number;
		}
	      else
		{
		  /* Write "(nil)" for a nil pointer.  */
		  static CONST char nil[] = "(nil)";
		  register CONST char *p;

		  width -= sizeof(nil) - 1;
		  if (left)
		    while (width-- > 0)
		      outchar(' ');
		  for (p = nil; *p != '\0'; ++p)
		    outchar(*p);
		  if (!left)
		    while (width-- > 0)
		      outchar(' ');
		}
	    }
	    break;

	  case 'n':
	    /* Answer the count of characters written.  */
	    if (is_longlong)
	      {
		LONGLONG int *p;
		nextarg(p, LONGLONG int *);
		*p = done;
	      }
	    else if (is_long)
	      {
		long int *p;
		nextarg(p, long int *);
		*p = done;
	      }
	    else if (!is_short)
	      {
		int *p;
		nextarg(p, int *);
		*p = done;
	      }
	    else
	      {
		short int *p;
		nextarg(p, short int *);
		*p = done;
	      }
	    break;

	  default:
	    /* Unrecognized format specifier.  */
	    function = printf_unknown;
	    goto use_function;
	  }
      else
      use_function:
	{
	  int function_done;
	  struct printf_info info;

	  info.prec = prec;
	  info.width = width;
	  info.spec = fc;
	  info.is_long_double = is_long_double;
	  info.is_short = is_short;
	  info.is_long = is_long;
	  info.alt = alt;
	  info.space = space;
	  info.left = left;
	  info.showsign = showsign;
	  info.pad = pad;

	  function_done = (*function)(s, &info, &args);
	  if (function_done < 0)
	    RETURN(-1);

	  done += function_done;
	}
    }

 do_return:;
  if (our_buffer)
    {
      if (fflush(s) == EOF)
	return -1;
      s->__buffer = s->__bufp = s->__get_limit = s->__put_limit = NULL;
      s->__bufsize = 0;
    }
  return done;
}


#undef	RETURN
#define	RETURN	return

static int
DEFUN(printf_unknown, (s, type, info, arg),
      FILE *s AND CONST struct printf_info *info AND va_list *arg)
{
  int done = 0;
  char work[BUFSIZ];
  char *CONST workend = &work[sizeof(work) - 1];
  register char *w;
  register int prec = info->prec, width = info->width;

  outchar('%');

  if (info->alt)
    outchar('#');
  if (info->showsign)
    outchar('+');
  else if (info->space)
    outchar(' ');
  if (info->left)
    outchar('-');
  if (info->pad == '0')
    outchar('0');

  w = workend;
  while (width > 0)
    {
      *w-- = '0' + (width % 10);
      width /= 10;
    }
  while (++w <= workend)
    outchar(*w);

  if (info->prec != -1)
    {
      outchar('.');
      w = workend;
      while (prec > 0)
	{
	  *w-- = '0' + (prec % 10);
	  prec /= 10;
	}
      while (++w <= workend)
	outchar(*w);
    }

  outchar(info->spec);

  return done;
}
