/*
 * $Id: hbprintf.c 12552 2009-09-19 11:53:05Z druzus $
 */

/*
 * Harbour Project source code:
 * hb_sprintf() function.
 *
 * Copyright 2008 Przemyslaw Czerpak <druzus / at / priv.onet.pl>
 * www - http://www.harbour-project.org
 *
 * 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 2, 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 software; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307 USA (or visit the web site http://www.gnu.org/).
 *
 * As a special exception, the Harbour Project gives permission for
 * additional uses of the text contained in its release of Harbour.
 *
 * The exception is that, if you link the Harbour libraries with other
 * files to produce an executable, this does not by itself cause the
 * resulting executable to be covered by the GNU General Public License.
 * Your use of that executable is in no way restricted on account of
 * linking the Harbour library code into it.
 *
 * This exception does not however invalidate any other reasons why
 * the executable file might be covered by the GNU General Public License.
 *
 * This exception applies only to the code released by the Harbour
 * Project under the name Harbour.  If you copy code from other
 * Harbour Project or Free Software Foundation releases into a copy of
 * Harbour, as the General Public License permits, the exception does
 * not apply to the code that you add in this way.  To avoid misleading
 * anyone as to the status of such modified files, you must delete
 * this exception notice from them.
 *
 * If you write modifications of your own for Harbour, it is your choice
 * whether to permit this exception to apply to your modifications.
 * If you do not wish that, delete this exception notice.
 *
 */

/*
patterm format:
   '%' [<flags>*] [<field width>] [.<precision>] [<length modifier>]
       <conversion specifier>
 */

/*
The folowwing conversions are not explicitly supported:
      A, a
      E, e
      G, g
   These are (long) double conversions. If necessary they can be easy added.
   Now they are simply redirected to F, f conversions.

      C (or Lc)
      S (or Ls)
   These are wide character conversions and needs locale settings.

double conversion if not necessary can be disabled to not create unnencessary
overhead and/or references to math library by
   #define __NO_DOUBLE__
It can be also greatly optimized anyhow it will increase dependences list and
reduce portability.
Internally 'long double' is used for all calculations. If some platforms do
not support it then it can be eliminated by
   #define __NO_LONGDOUBLE__

If positional parameters are not necessary then support for them can be
disabled by
   #define __NO_ARGPOS__
In such case this code neither allocates memory nor extensively use stack
as memory buffer. All conversions are done "on the fly". If memory
allocations or stack size is not a problem then some parts can be easy
optimized.
*/

/* #define __NO_DOUBLE__ */
/* #define __NO_LONGDOUBLE__ */
/* #define __NO_LONGLONG__ */
/* #define __NO_ARGPOS__ */

#ifndef _GNU_SOURCE
#  define _GNU_SOURCE
#endif

#ifndef __EXTENSIONS__
#  define __EXTENSIONS__
#endif

/* workaround for some missing C99 math macros in SunOS GCC
 * used in C++ mode
 */
#if !defined( __C99FEATURES__ ) && defined( __GNUC__ ) && defined( __sun__ )
#  define __C99FEATURES__
#endif

#include <stddef.h>

#include "hbapi.h"
#include "hbmath.h"

#if ! defined( HB_USE_CRTL_SNPRINTF )

#if defined( __BORLANDC__ ) || defined( __WATCOMC__ ) || defined( _MSC_VER )
#  include <float.h>
#elif defined( __DJGPP__ )
   /* _LIB_VERSION_TYPE _LIB_VERSION = _XOPEN_; */
#elif defined( HB_OS_SUNOS )
#  include <ieeefp.h>    /* for finite() */
#endif

#if defined( HB_LONG_DOUBLE_OFF ) && !defined( __NO_LONGDOUBLE__ )
#  define __NO_LONGDOUBLE__
#endif

#if defined( HB_LONG_LONG_OFF ) && !defined( __NO_LONGLONG__ )
#  define __NO_LONGLONG__
#endif

#ifndef va_copy
#  ifdef __va_copy
#     define va_copy( dst, src )    __va_copy( dst, src )
#  else
#     define va_copy( dst, src )    ( (dst) = (src) )
#  endif
#endif

/* few macros for some platform dependent floating point functions/macros */

#define _HB_NUM_NAN     1
#define _HB_NUM_PINF    2
#define _HB_NUM_NINF    4

#if defined( __BORLANDC__ ) && 0
   /* do not use Borland C _fpclass[l]() function.
    * it switches internal logic used for floating point calculation
    * in this compiler reducing the precision to 'float' type.
    */
#  ifdef __NO_LONGDOUBLE__
#     define hb_fpclassify( d )     _fpclass( d )
#  else
#     define hb_fpclassify( d )     _fpclassl( d )
#  endif
#endif

#if ( defined( __BORLANDC__ ) && __BORLANDC__ < 1410 ) || \
    ( defined( __WATCOMC__ ) && __WATCOMC__ < 1270 ) || \
    ( defined( __DJGPP__ ) && \
      ( __DJGPP__ < 2 || ( __DJGPP__ == 2 && __DJGPP_MINOR__ <= 3 ) ) ) || \
    ( defined( _MSC_VER ) && \
      !( defined( __LCC__ ) || defined( __POCC__ ) || defined( __XCC__ ) ) )
   /* TODO: add other C compilers which does not support [u]intmax_t
    *       definitions (check C compiler version number).
    *       If compiler supports stdint.h then it should be added
    *       to hbdefs.h.
    */
#  define intmax_t      _x_longlong
#  define uintmax_t     _x_ulonglong
#endif

/* signbit() needs _GNU_SOURCE defined. If it's not available on given
 * platform then it can be replaced by 'value < 0' but in such case
 * -0.0 will be shown as 0.0
 */
#if defined( __GNUC__ ) && defined( _ISOC99_SOURCE )
   /* use C99 macros */
#  define hb_signbit( d )     signbit( d )

#elif defined( __BORLANDC__ ) && defined( hb_fpclassify )

#  define hb_signbit( d )     ( ( hb_fpclassify( d ) & ( _FPCLASS_NINF | _FPCLASS_NZ ) ) != 0 )

#elif 0 /* TODO: add other C compilers here (check their version number) */
#else

#  define hb_signbit( d )     ( d < 0 )

#endif


#if defined( __GNUC__ ) && ( defined( _ISOC99_SOURCE ) || defined( __MINGW32__ ) )

   /* use C99 macros */
#  define HB_NUMTYPE( v, d )  do { \
                                 v = ( isfinite( d ) ? 0 : \
                                       ( isnan( d ) ? _HB_NUM_NAN : \
                                         ( isinf( d ) < 0 ? _HB_NUM_NINF : \
                                           _HB_NUM_PINF ) ) ); \
                              } while( 0 )

#elif ( defined( __GNUC__ ) || \
        defined( __SUNPRO_C ) || defined( __SUNPRO_CC ) ) && \
      ( defined( _BSD_SOURCE ) || defined( _SVID_SOURCE ) || \
        defined( _XOPEN_SOURCE ) )

   /* use BSD floating point functions */
#  if defined( __NO_LONGDOUBLE__ ) || defined( HB_OS_SUNOS )
#     define HB_NUMTYPE( v, d )  do { \
                                    v = ( finite( d ) ? 0 : \
                                          ( isnan( d ) ? _HB_NUM_NAN : \
                                            ( isinf( d ) < 0 ? _HB_NUM_NINF : \
                                              _HB_NUM_PINF ) ) ); \
                                 } while( 0 )
#  else
#     define HB_NUMTYPE( v, d )  do { \
                                    v = ( finitel( d ) ? 0 : \
                                          ( isnanl( d ) ? _HB_NUM_NAN : \
                                            ( isinfl( d ) < 0 ? _HB_NUM_NINF : \
                                              _HB_NUM_PINF ) ) ); \
                                 } while( 0 )
#  endif

#elif defined( __BORLANDC__ ) && defined( hb_fpclassify )

#  define HB_NUMTYPE( v, d )  do { \
                                 int t = hb_fpclassify( d ); \
                                 v = ( ( t & ( _FPCLASS_UNSUP | _FPCLASS_SNAN | _FPCLASS_QNAN ) ) ? _HB_NUM_NAN : \
                                       ( ( t & _FPCLASS_NINF ) ? _HB_NUM_NINF : \
                                         ( ( t & _FPCLASS_PINF ) ? _HB_NUM_PINF : 0 ) ) ); \
                              } while( 0 )
#elif 0 /* TODO: add other C compilers here (check their version number) */
#else

#  if defined( __RSXNT__ ) || defined( __EMX__ ) || \
      defined( __XCC__ ) || defined( __POCC__ ) || \
      defined( __MINGW32__ ) || defined( HB_OS_HPUX )
#     define hb_isfinite( d )       isfinite( d )
#  elif !defined( __NO_LONGDOUBLE__ ) && defined( __BORLANDC__ )
#     define hb_isfinite( d )       _finitel( d )
#  elif defined( _MSC_VER )
#     define hb_isfinite( d )       _finite( ( double ) d )
#  elif defined( __BORLANDC__ ) || defined( __WATCOMC__ )
#     define hb_isfinite( d )       _finite( d )
#  elif defined( __GNUC__ ) || defined( __DJGPP__ ) || defined( __LCC__ )
#     define hb_isfinite( d )       finite( d )
#  else
#     define hb_isfinite( d )       FALSE
#  endif

#  define HB_NUMTYPE( v, d )  do { \
                                 v = hb_isfinite( d ) ? 0 : _HB_NUM_NAN ; \
                              } while( 0 )

#endif


#define _ARGBUF_SIZE    16
#define _ARGBUF_ALLOC   16

#define _F_ALTERNATE    0x01  /* only for: o xX aA eE fF gG  */
#define _F_ZEROPADED    0x02  /* only for: d i o u xX aA eE fF gG */
#define _F_LEFTADJUSTED 0x04  /* clears _F_ZEROPADED */
#define _F_SPACE        0x08  /* only for signed num conversions: d i fF aA eE gG */
#define _F_SIGN         0x10  /* only for signed num conversions: d i fF aA eE gG, clears _F_SPACE */

#define _L_UNDEF_       0
#define _L_CHAR_        1
#define _L_SHORT_       2
#define _L_LONG_        3
#define _L_LONGLONG_    4
#define _L_INTMAX_      5
#define _L_SIZE_        6
#define _L_PTRDIFF_     7
#define _L_DOUBLE_      8
#define _L_LONGDOUBLE_  9

#ifndef __NO_DOUBLE__
#  ifdef __NO_LONGDOUBLE__
#     define _x_long_dbl      double
#     define _MODFD( x, p )   modf( x, p )
#  else
#     define _x_long_dbl      long double
#     if defined( __WATCOMC__ ) || defined( __MINGW32CE__ ) || defined( __CYGWIN__ ) || \
         defined( HB_OS_BEOS ) || defined( __OpenBSD__ ) || defined( __NetBSD__ ) || \
         ( defined( HB_OS_WIN_CE ) && defined( __POCC__ ) )
#        define _HB_WRAP_MODFL_
#        define _MODFD( x, p )   _hb_modfl( x, p )
#     else
#        define _MODFD( x, p )   modfl( x, p )
#     endif
#  endif
#  define _x_double           double
#else
#  define _x_long_dbl         int
#  define _x_double           int
#endif
#ifndef __NO_LONGLONG__
#  define _x_longlong         LONGLONG
#  define _x_ulonglong        ULONGLONG
#else
#  define _x_longlong         long
#  define _x_ulonglong        unsigned long
#endif
#define _x_int                int
#define _x_uint               unsigned int
#define _x_ulong              unsigned long
#define _x_long               long
#define _x_intmax_t           intmax_t
#define _x_uintmax_t          uintmax_t
#define _x_size_t             size_t
#define _x_ptrdiff_t          ptrdiff_t
#define _x_ptr                void *
#define _x_str                char *
#define _x_intptr             int *

#define v_x_int         1
#define v_x_uint        2
#define v_x_long        3
#define v_x_ulong       4
#define v_x_longlong    5
#define v_x_ulonglong   6
#define v_x_intmax_t    7
#define v_x_uintmax_t   8
#define v_x_size_t      9
#define v_x_ptrdiff_t   10
#define v_x_ptr         11
#define v_x_str         12
#define v_x_intptr      13
#define v_x_double      14
#define v_x_long_dbl    15

typedef union {
   _x_int         as_x_int;
   _x_uint        as_x_uint;
   _x_long        as_x_long;
   _x_ulong       as_x_ulong;
   _x_longlong    as_x_longlong;
   _x_ulonglong   as_x_ulonglong;
   _x_intmax_t    as_x_intmax_t;
   _x_uintmax_t   as_x_uintmax_t;
   _x_size_t      as_x_size_t;
   _x_ptrdiff_t   as_x_ptrdiff_t;
   _x_ptr         as_x_ptr;
   _x_str         as_x_str;
   _x_intptr      as_x_intptr;
   _x_double      as_x_double;
   _x_long_dbl    as_x_long_dbl;
} x_type;

typedef struct {
   int      id;
   x_type   value;
} v_param;

typedef struct {
   int         maxarg;
   int         size;
   BOOL        repeat;
   v_param *   arglst;
} v_paramlst;

#ifdef __NO_ARGPOS__

/* this version does not support positional parameters (f.e. '%1$d')
 * they can be added but it will force allocating additional
 * memory block in heap or stack and making second pass for
 * format decoding/coping.
 * so we will use only this simple macro which ignores parameter
 * positions.
 */
#define va_arg_n( va, type, n )     va_arg( va, type )

/* on some systems where each parameter allocates memory with
 * the same size in function stack frame this simple macro can
 * be used.
 */

/*
#define va_arg_n( va, type, n )     \
   ( { \
      type result; \
      if( n == 0 ) \
         result = va_arg( va, type ); \
      else \
      { \
         int count = (n); \
         va_list ap; \
         va_start( ap, format ); \
         do \
            result = va_arg( ap, type ); \
         while( --count > 0 ); \
         va_end( ap ); \
      } \
      result; \
   } )
*/

#else

#define va_arg_n( va, type, n )     \
               ( n == 0 ? va_arg( va, type ) : \
                          va_arg_get( n, &params, v##type )->value.as##type )

static v_param * va_arg_get( int iArg, v_paramlst * plst, int iType )
{
   if( plst->maxarg == 0 )
   {
      plst->repeat = TRUE;
      memset( plst->arglst, 0, plst->size * sizeof( v_param ) );
   }
   if( plst->repeat )
   {
      if( iArg > plst->maxarg )
         plst->maxarg = iArg;
      if( iArg > plst->size )
      {
         int prev_size = plst->size;

         plst->size = iArg + _ARGBUF_ALLOC;
         if( prev_size == _ARGBUF_SIZE )
            plst->arglst = ( v_param * ) memcpy( hb_xgrab( plst->size * sizeof( v_param ) ),
                                                 plst->arglst, _ARGBUF_SIZE * sizeof( v_param ) );
         else
            plst->arglst = ( v_param * ) hb_xrealloc( plst->arglst, plst->size * sizeof( v_param ) );
         memset( &plst->arglst[ prev_size ], 0, ( plst->size - prev_size ) *
                                                  sizeof( v_param ) );
      }
      plst->arglst[ iArg - 1 ].id = iType;
   }
   return &plst->arglst[ iArg - 1 ];
}

static void va_arg_fill( v_paramlst * plst, va_list va )
{
   int iArg;
   for( iArg = 0; iArg < plst->maxarg; ++iArg )
   {
      switch( plst->arglst[ iArg ].id )
      {
         case v_x_uint:
             plst->arglst[ iArg ].value.as_x_uint = va_arg( va, _x_uint );
             break;
         case v_x_long:
             plst->arglst[ iArg ].value.as_x_long = va_arg( va, _x_long );
             break;
         case v_x_ulong:
             plst->arglst[ iArg ].value.as_x_ulong = va_arg( va, _x_ulong );
             break;
         case v_x_longlong:
             plst->arglst[ iArg ].value.as_x_longlong = va_arg( va, _x_longlong );
             break;
         case v_x_ulonglong:
             plst->arglst[ iArg ].value.as_x_ulonglong = va_arg( va, _x_ulonglong );
             break;
         case v_x_intmax_t:
             plst->arglst[ iArg ].value.as_x_intmax_t = va_arg( va, _x_intmax_t );
             break;
         case v_x_uintmax_t:
             plst->arglst[ iArg ].value.as_x_uintmax_t = va_arg( va, _x_uintmax_t );
             break;
         case v_x_size_t:
             plst->arglst[ iArg ].value.as_x_size_t = va_arg( va, _x_size_t );
             break;
         case v_x_ptrdiff_t:
             plst->arglst[ iArg ].value.as_x_ptrdiff_t = va_arg( va, _x_ptrdiff_t );
             break;
         case v_x_ptr:
             plst->arglst[ iArg ].value.as_x_ptr = va_arg( va, _x_ptr );
             break;
         case v_x_str:
             plst->arglst[ iArg ].value.as_x_str = va_arg( va, _x_str );
             break;
         case v_x_intptr:
             plst->arglst[ iArg ].value.as_x_intptr = va_arg( va, _x_intptr );
             break;
         case v_x_double:
             plst->arglst[ iArg ].value.as_x_double = va_arg( va, _x_double );
             break;
         case v_x_long_dbl:
             plst->arglst[ iArg ].value.as_x_long_dbl = va_arg( va, _x_long_dbl );
             break;
         default:
             plst->arglst[ iArg ].value.as_x_int = va_arg( va, _x_int );
             break;
      }
   }
}
#endif

#ifdef _HB_WRAP_MODFL_
_x_long_dbl _hb_modfl( _x_long_dbl x, _x_long_dbl * p )
{
   _x_double pd;
   _x_long_dbl r = modf( x, &pd );
   *p = pd;
   return r;
}
#endif

static char get_decimal( char c, const char **format, int *result )
{
   *result = c - '0';
   while( ( c = *(*format)++ ) >= '0' && c <= '9' )
      *result = *result * 10 + ( c - '0' );

   return c;
}

static size_t put_octal( char *buffer, size_t bufsize, size_t size,
                         uintmax_t value, int flags, int width, int precision )
{
   uintmax_t v = value;
   int nums = 0, n;
   char c;

   while( v )
   {
      ++nums;
      v >>= 3;
   }
   if( precision > nums )
      nums = precision;
   else if( flags & _F_ALTERNATE )
      ++nums;
   else if( nums == 0 && precision != 0 )
      ++nums;
   width -= nums;

   if( ( flags & _F_LEFTADJUSTED ) == 0 )
   {
      while( width > 0 )
      {
         if( size < bufsize )
            buffer[ size ] = ( flags & _F_ZEROPADED ) ? '0' : ' ';
         ++size;
         --width;
      }
   }
   if( nums )
   {
      n = nums;
      do
      {
         c = ( char ) ( value & 0x7 ) + '0';
         value >>= 3;
         --n;
         if( size + n < bufsize )
            buffer[ size + n ] = c;
      }
      while( n );
      size += nums;
   }
   while( width > 0 )
   {
      if( size < bufsize )
         buffer[ size ] = ' ';
      ++size;
      --width;
   }

   return size;
}

static size_t put_dec( char *buffer, size_t bufsize, size_t size,
                       uintmax_t value, int flags, int width, int precision,
                       int sign )
{
   uintmax_t v = value;
   int nums = 0, n;
   char c;

   while( v )
   {
      ++nums;
      v /= 10;
   }
   if( precision > nums )
      nums = precision;
   else if( nums == 0 && precision != 0 )
      ++nums;
   if( ( flags & ( _F_SPACE | _F_SIGN ) ) || sign )
      width--;
   if( ( flags & ( _F_LEFTADJUSTED | _F_ZEROPADED ) ) == _F_ZEROPADED &&
       width > nums )
      nums = width;
   width -= nums;

   if( ( flags & _F_LEFTADJUSTED ) == 0 )
   {
      while( width > 0 )
      {
         if( size < bufsize )
            buffer[ size ] = ' ';
         ++size;
         --width;
      }
   }
   if( ( flags & ( _F_SPACE | _F_SIGN ) ) || sign )
   {
      if( size < bufsize )
         buffer[ size ] = sign ? '-' : ( flags & _F_SIGN ? '+' : ' ' );
      ++size;
   }
   if( nums )
   {
      n = nums;
      do
      {
         c = ( char ) ( value % 10 ) + '0';
         value /= 10;
         --n;
         if( size + n < bufsize )
            buffer[ size + n ] = c;
      }
      while( n );
      size += nums;
   }
   while( width > 0 )
   {
      if( size < bufsize )
         buffer[ size ] = ' ';
      ++size;
      --width;
   }

   return size;
}

#ifndef __NO_DOUBLE__
static size_t put_dbl( char *buffer, size_t bufsize, size_t size,
                       _x_long_dbl value, int flags, int width, int precision )
{
   _x_long_dbl dInt, dFract;
   int sign, nums = 0, n;
   char c;

   if( precision < 0 )
      precision = 6;
   sign = hb_signbit( value );
   if( sign )
      value = - value;

   /* Round the number to given precision.
    * powl() is of course faster when precision is big but it is libm
    * (-lm link switch) function so we will make small trick here and
    * calculate it in a loop
    */
#if 0
   value += _POWD( 10, -precision ) / 2;
#else
   n = precision;
   dFract = 1;
   while( --n >= 0 )
      dFract /= 10;
   value += dFract / 2;
#endif

   dFract = _MODFD( value, &dInt );
   width -= precision;
   if( ( flags & ( _F_SPACE | _F_SIGN ) ) || sign )
      width--;
   if( precision > 0 || ( flags & _F_ALTERNATE ) )
      width--;
   value = dInt;
   do
   {
      ++nums;
      _MODFD( value / 10 + 0.01, &value );
   }
   while( value >= 1 );
   width -= nums;
   c = ( ( flags & ( _F_SPACE | _F_SIGN ) ) || sign ) ?
       ( buffer[ size ] = sign ? '-' : ( flags & _F_SIGN ? '+' : ' ' ) ) : 0;
   if( ( flags & _F_LEFTADJUSTED ) == 0 && width > 0 )
   {
      if( c && ( flags & _F_ZEROPADED ) )
      {
         if( size < bufsize )
            buffer[ size ] = c;
         ++size;
         c = 0;
      }
      do
      {
         if( size < bufsize )
            buffer[ size ] = ( flags & _F_ZEROPADED ) ? '0' : ' ';
         ++size;
      }
      while( --width > 0 );
   }
   if( c )
   {
      if( size < bufsize )
         buffer[ size ] = sign ? '-' : ( flags & _F_SIGN ? '+' : ' ' );
      ++size;
   }

   n = nums;
   do
   {
      value = _MODFD( dInt / 10 + 0.01, &dInt ) * 10;
      c = '0' + ( char ) ( value + 0.01 );
      --n;
      if( size + n < bufsize )
         buffer[ size + n ] = c;
   }
   while( n );
   size += nums;

   if( precision > 0 || ( flags & _F_ALTERNATE ) )
   {
      if( size < bufsize )
         buffer[ size ] = '.';
      ++size;
      while( precision > 0 )
      {
         dFract = _MODFD( dFract * 10, &dInt );
         c = '0' + ( char ) ( dInt + 0.01 );
         if( size < bufsize )
            buffer[ size ] = c;
         ++size;
         --precision;
      }
   }
   while( width > 0 )
   {
      if( size < bufsize )
         buffer[ size ] = ' ';
      ++size;
      --width;
   }

   return size;
}
#endif

static size_t put_hex( char *buffer, size_t bufsize, size_t size,
                       uintmax_t value, int flags, int width, int precision,
                       int upper )
{
   uintmax_t v = value;
   int nums = 0, n;
   char c;

   while( v )
   {
      ++nums;
      v >>= 4;
   }
   if( precision > nums )
      nums = precision;
   else if( nums == 0 && precision != 0 )
      ++nums;
   if( ( flags & _F_ALTERNATE ) && value )
      width -= 2;
   width -= nums;

   if( ( flags & ( _F_LEFTADJUSTED | _F_ZEROPADED ) ) == 0 )
   {
      while( width > 0 )
      {
         if( size < bufsize )
            buffer[ size ] = ' ';
         ++size;
         --width;
      }
   }
   if( ( flags & _F_ALTERNATE ) && value )
   {
      if( size < bufsize )
         buffer[ size ] = '0';
      ++size;
      if( size < bufsize )
         buffer[ size ] = upper ? 'X' : 'x';
      ++size;
   }
   if( ( flags & _F_LEFTADJUSTED ) == 0 )
   {
      while( width > 0 )
      {
         if( size < bufsize )
            buffer[ size ] = ( flags & _F_ZEROPADED ) ? '0' : ' ';
         ++size;
         --width;
      }
   }
   if( nums )
   {
      n = nums;
      do
      {
         c = ( char ) ( value & 0x0f ) + '0';
         if( c > '9' )
            c += upper ? 'A' - '9' - 1 : 'a' - '9' - 1;
         value >>= 4;
         --n;
         if( size + n < bufsize )
            buffer[ size + n ] = c;
      }
      while( n );
      size += nums;
   }
   while( width > 0 )
   {
      if( size < bufsize )
         buffer[ size ] = ' ';
      ++size;
      --width;
   }

   return size;
}

static size_t put_str( char *buffer, size_t bufsize, size_t size,
                       const char * str, int flags, int width, int precision )
{
   if( !str )
      str = "(null)";
   if( precision < 0 )
      precision = ( int ) strlen( str );
   else if( precision > 0 )
      precision = ( int ) hb_strnlen( str, precision );

   width -= precision;
   if( ( flags & _F_LEFTADJUSTED ) == 0 ) while( width > 0 )
   {
      if( size < bufsize )
         buffer[ size ] = ' ';
      ++size;
      --width;
   }
   while( precision > 0 )
   {
      if( size < bufsize )
         buffer[ size ] = *str++;
      ++size;
      --precision;
   }
   while( width > 0 )
   {
      if( size < bufsize )
         buffer[ size ] = ' ';
      ++size;
      --width;
   }

   return size;
}

int hb_vsnprintf( char * buffer, size_t bufsize, const char * format, va_list ap )
{
   va_list args;
   size_t size;
   char c;
#ifndef __NO_ARGPOS__
   const char * fmt_start = format;
   v_param argbuf[ _ARGBUF_SIZE ];
   v_paramlst params;

   params.size = _ARGBUF_SIZE;
   params.maxarg = 0;
   params.arglst = argbuf;
#endif


#ifndef __NO_ARGPOS__
   do
   {
      params.repeat = FALSE;
      if( params.maxarg > 0 )
      {
         va_copy( args, ap );
         va_arg_fill( &params, args );
         va_end( args );
      }
      format = fmt_start;
#endif
      va_copy( args, ap );
      size = 0;

      do
      {
         c = *format++;
         if( c == '%' )
         {
            const char * pattern = format;

            c = *format++;
            if( c != 0 && c != '%' )
            {
               /* decode pattern */
               v_param argval;
               int param = 0, flags = 0, width = -1, precision = -1, length,
                   value, stop = 0;

               /* parameter position */
               if( c >= '0' && c <= '9' )
               {
                  c = get_decimal( c, &format, &value );
                  if( c == '$' )
                  {
                     param = value;
                     c = *format++;
                  }
                  else
                  {
                     format = pattern;
                     c = *format++;
                  }
               }

               /* flags */
               while( !stop ) switch( c )
               {
                  case '#':
                     flags |= _F_ALTERNATE;
                     c = *format++;
                     break;
                  case '0':
                     flags |= _F_ZEROPADED;
                     c = *format++;
                     break;
                  case '-':
                     flags |= _F_LEFTADJUSTED;
                     c = *format++;
                     break;
                  case ' ':
                     flags |= _F_SPACE;
                     c = *format++;
                     break;
                  case '+':
                     flags |= _F_SIGN;
                     c = *format++;
                     break;
#ifdef _SUSV2_COMPAT_
                  case '\'':  /* group with locale thousands' grouping characters */
                     c = *format++;
                     break;
#endif
                  default:
                     stop = 1;
                     break;
               }

               /* field width */
               if( c == '*' )
               {
                  c = *format++;
                  if( c >= '0' && c <= '9' )
                  {
                     c = get_decimal( c, &format, &value );
                     if( c == '$' )
                     {
                        width = va_arg_n( args, _x_int, value );
                        c = *format++;
                     }
                     /* else error, wrong format */
                  }
                  else
                     width = va_arg_n( args, _x_int, 0 );
               }
               else if( c >= '0' && c <= '9' )
                  c = get_decimal( c, &format, &width );

               /* precision */
               if( c == '.' )
               {
                  precision = 0;
                  c = *format++;
                  if( c == '*' )
                  {
                     c = *format++;
                     if( c >= '0' && c <= '9' )
                     {
                        c = get_decimal( c, &format, &value );
                        if( c == '$' )
                        {
                           precision = va_arg_n( args, _x_int, value );
                           c = *format++;
                        }
                        /* else error, wrong format */
                     }
                     else
                        precision = va_arg_n( args, _x_int, 0 );
                  }
                  else if( c >= '0' && c <= '9' )
                     c = get_decimal( c, &format, &precision );
               }

               /* length modifier */
               switch( c )
               {
                  case 'h':
                     c = *format++;
                     if( c == 'h' )
                     {
                        length = _L_CHAR_;
                        c = *format++;
                     }
                     else
                        length = _L_SHORT_;
                     break;
                  case 'l':
                     c = *format++;
                     if( c == 'l' )
                     {
                        length = _L_LONGLONG_;
                        c = *format++;
                     }
                     else
                        length = _L_LONG_;
                     break;
                  case 'L':
                     length = _L_LONGDOUBLE_;
                     c = *format++;
                     break;
                  case 'j':
                     length = _L_INTMAX_;
                     c = *format++;
                     break;
                  case 'z':
                     length = _L_SIZE_;
                     c = *format++;
                     break;
                  case 't':
                     length = _L_PTRDIFF_;
                     c = *format++;
                     break;
                  case 'I':   /* MS-Windows extension */
                     if( format[ 0 ] == '6' && format[ 1 ] == '4' )
                     {
                        length = _L_LONGLONG_;
                        format += 2;
                        c = *format++;
                        break;
                     }
                     else if( format[ 0 ] == '1' && format[ 1 ] == '6' )
                     {
                        length = _L_SHORT_;
                        format += 2;
                        c = *format++;
                        break;
                     }
                     else if( format[ 0 ] == '3' && format[ 1 ] == '2' )
                     {
                        format += 2;
                        c = *format++;
                     }
                     /* no break; */
                  default:
                     length = _L_UNDEF_;
                     break;
               }

               /* conversion specifier */

               switch( c )
               {
#ifndef __NO_DOUBLE__
                  case 'a':
                  case 'A':
                  case 'e':
                  case 'E':
                  case 'g':
                  case 'G':
                     /* redirect above conversion to 'f' or 'F' type to keep
                      * valid parameters order
                      */
                     c = ( c == 'a' || c == 'e' || c == 'g' ) ? 'f' : 'F';
                     /* no break; */
                  case 'f':   /* double decimal notation */
                  case 'F':   /* double decimal notation */
                     if( length == _L_LONGDOUBLE_ )
                        argval.value.as_x_long_dbl = va_arg_n( args, _x_long_dbl, param );
                     else
                        argval.value.as_x_long_dbl = va_arg_n( args, _x_double, param );
                     HB_NUMTYPE( value, argval.value.as_x_long_dbl );
                     if( value & _HB_NUM_NAN )
                        size = put_str( buffer, bufsize, size,
                                        c == 'f' ?
                                        ( flags & _F_SIGN ? "+nan": "nan" ) :
                                        ( flags & _F_SIGN ? "+NAN": "NAN" ) ,
                                        flags, width, -1 );
                     else if( value & _HB_NUM_PINF )
                        size = put_str( buffer, bufsize, size,
                                        c == 'f' ?
                                        ( flags & _F_SIGN ? "+inf": "inf" ) :
                                        ( flags & _F_SIGN ? "+INF": "INF" ),
                                        flags, width, -1 );
                     else if( value & _HB_NUM_NINF )
                        size = put_str( buffer, bufsize, size,
                                        c == 'f' ? "-inf" : "-INF",
                                        flags, width, -1 );
                     else
                        size = put_dbl( buffer, bufsize, size, argval.value.as_x_long_dbl,
                                        flags, width, precision );
                     continue;
#endif
                  case 'd':
                  case 'i':   /* signed int decimal conversion */
                     if( length == _L_CHAR_ )
                        argval.value.as_x_intmax_t = ( unsigned char ) va_arg_n( args, _x_int, param );
                     else if( length == _L_SHORT_ )
                        argval.value.as_x_intmax_t = ( unsigned short ) va_arg_n( args, _x_int, param );
                     else if( length == _L_LONG_ )
                        argval.value.as_x_intmax_t = va_arg_n( args, _x_long, param );
                     else if( length == _L_LONGLONG_ )
                        argval.value.as_x_intmax_t = va_arg_n( args, _x_longlong, param );
                     else if( length == _L_INTMAX_ )
                        argval.value.as_x_intmax_t = va_arg_n( args, _x_intmax_t, param );
                     else if( length == _L_SIZE_ )
                        argval.value.as_x_intmax_t = va_arg_n( args, _x_size_t, param );
                     else if( length == _L_PTRDIFF_ )
                        argval.value.as_x_intmax_t = va_arg_n( args, _x_ptrdiff_t, param );
                     else
                        argval.value.as_x_intmax_t = va_arg_n( args, _x_int, param );
                     value = argval.value.as_x_intmax_t < 0;
                     argval.value.as_x_uintmax_t = value ? -argval.value.as_x_intmax_t :
                                                            argval.value.as_x_intmax_t;
                     size = put_dec( buffer, bufsize, size, argval.value.as_x_uintmax_t,
                                     flags, width, precision, value );
                     continue;
                  case 'o':   /* unsigned int octal conversion */
                  case 'u':   /* unsigned int decimal conversion */
                  case 'x':   /* unsigned int hexadecimal conversion */
                  case 'X':   /* unsigned int hexadecimal conversion */
                     if( length == _L_CHAR_ )
                        argval.value.as_x_uintmax_t = ( unsigned char ) va_arg_n( args, _x_int, param );
                     else if( length == _L_SHORT_ )
                        argval.value.as_x_uintmax_t = ( unsigned short ) va_arg_n( args, _x_int, param );
                     else if( length == _L_LONG_ )
                        argval.value.as_x_uintmax_t = va_arg_n( args, _x_ulong, param );
                     else if( length == _L_LONGLONG_ )
                        argval.value.as_x_uintmax_t = va_arg_n( args, _x_ulonglong, param );
                     else if( length == _L_INTMAX_ )
                        argval.value.as_x_uintmax_t = va_arg_n( args, _x_uintmax_t, param );
                     else if( length == _L_SIZE_ )
                        argval.value.as_x_uintmax_t = va_arg_n( args, _x_size_t, param );
                     else if( length == _L_PTRDIFF_ )
                        argval.value.as_x_uintmax_t = va_arg_n( args, _x_ptrdiff_t, param );
                     else
                        argval.value.as_x_uintmax_t = va_arg_n( args, _x_uint, param );

                     if( c == 'o' )
                        size = put_octal( buffer, bufsize, size, argval.value.as_x_uintmax_t,
                                          flags, width, precision );
                     else if( c == 'u' )
                        size = put_dec( buffer, bufsize, size, argval.value.as_x_uintmax_t,
                                        flags & ~( _F_SPACE | _F_SIGN ),
                                        width, precision, 0 );
                     else
                        size = put_hex( buffer, bufsize, size, argval.value.as_x_uintmax_t,
                                        flags, width, precision, c == 'X' );
                     continue;
                  case 'p':   /* void * pointer */
                     argval.value.as_x_ptr = va_arg_n( args, _x_ptr, param );
                     if( argval.value.as_x_ptr )
                        size = put_hex( buffer, bufsize, size, ( HB_PTRUINT ) argval.value.as_x_ptr,
                                        flags | _F_ALTERNATE, width, precision, 0 );
                     else
                        size = put_str( buffer, bufsize, size, "(nil)",
                                        flags, width, -1 );
                     continue;
                  case 'c':   /* signed int casted to unsigned char */
                     if( ( flags & _F_LEFTADJUSTED ) == 0 ) while( --width > 0 )
                     {
                        if( size < bufsize )
                           buffer[ size ] = ' ';
                        ++size;
                     }
                     c = ( unsigned char ) va_arg_n( args, _x_int, param );
                     if( size < bufsize )
                        buffer[ size ] = c;
                     ++size;
                     while( --width > 0 )
                     {
                        if( size < bufsize )
                           buffer[ size ] = ' ';
                        ++size;
                     }
                     continue;
                  case 's':   /* const char * */
                     argval.value.as_x_str = va_arg_n( args, _x_str, param );
                     size = put_str( buffer, bufsize, size, argval.value.as_x_str,
                                     flags, width, precision );
                     continue;
                  case 'n':   /* store current result size in int * arg */
                     /* This is very danger feature in *printf() functions
                      * family very often used by hackers to create buffer
                      * overflows. It can also cause unintentional memory
                      * corruption by programmers typo in pattern so if it's
                      * not strictly necessary it's good to disable it.
                      */
                     *( va_arg_n( args, _x_intptr, param ) ) = ( int ) size;
                     continue;
                  case '%':   /* store % consuming arguments % */
                     break;
                  default:    /* error, wrong format, store pattern */
                     format = pattern;
                     c = '%';
                     break;
               }
            }
         }

         /* ISO C99 defines that when size is 0 and buffer is NULL we should
          * return number of characters that would have been written in case
          * the output string has been large enough without trailing 0.
          * Many implementations always returns number of characters necessary
          * to hold the string even if the above condition is not true so the
          * returned value can be used to check if buffer was large enough
          * and if not to allocate bigger buffer. Let's do the same.
          */
         if( size < bufsize )
            buffer[ size ] = c;
         ++size;
      }
      while( c != 0 );

      va_end( args );

#ifndef __NO_ARGPOS__
   }
   while( params.repeat );

   if( params.arglst != argbuf )
      hb_xfree( params.arglst );
#endif

   /* always set trailing \0 !!! */
   if( bufsize )
      buffer[ bufsize - 1 ] = 0;

   return ( int ) ( size - 1 );
}

#else /* defined( HB_USE_CRTL_SNPRINTF ) */

#undef _HB_SNPRINTF_ADD_EOS

/* NOTE: The full size of the buffer is expected as nSize. [vszakats] */
int hb_vsnprintf( char * buffer, size_t nSize, const char * format, va_list arglist )
{
   ULONG result;

#if defined( __DJGPP__ ) && ( __DJGPP__ < 2 || ( __DJGPP__ == 2 && __DJGPP_MINOR__ <= 3 ) )
   /* Use vsprintf() for DJGPP <= 2.03.
      This is a temporary hack, should implement a C99 snprintf() ourselves. */
   result = vsprintf( buffer, format, arglist );
   #define _HB_SNPRINTF_ADD_EOS
#elif defined( _MSC_VER ) && _MSC_VER >= 1400
   result = _vsnprintf_s( buffer, nSize, _TRUNCATE, format, arglist );
#elif ( defined( _MSC_VER ) || defined( __DMC__ ) ) && !defined( __XCC__ )
   result = _vsnprintf( buffer, nSize, format, arglist );
   #define _HB_SNPRINTF_ADD_EOS
#elif defined( __WATCOMC__ ) && __WATCOMC__ < 1200
   result = _vbprintf( buffer, nSize, format, arglist );
#else
   result = vsnprintf( buffer, nSize, format, arglist );
#endif

#ifdef _HB_SNPRINTF_ADD_EOS
   if( buffer && nSize )
      buffer[ nSize - 1 ] = '\0';
#endif

   return result;
}

#endif /* HB_USE_CRTL_SNPRINTF */

int hb_snprintf( char * buffer, size_t bufsize, const char * format, ... )
{
   va_list ap;
   int iResult;

   va_start( ap, format );
   iResult = hb_vsnprintf( buffer, bufsize, format, ap );
   va_end( ap );

   return iResult;
}
