/*-------------------------------------------------------------------------
  vprintf.c - formatted output conversion

             Written By - Martijn van Balen aed@iae.nl (1999)
	     Added %f By - johan.knol@iduna.nl (2000)

   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 program; if not, write to the Free Software
   Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

   In other words, you are welcome to use, share and improve this program.
   You are forbidden to forbid anyone else to use, share and improve
   what you give them.   Help stamp out software-hoarding!
-------------------------------------------------------------------------*/

/* this module uses some global variables instead function parameters, so: */
#ifdef SDCC_STACK_AUTO
#warning "this module cannot yet be use as a reentrant one"
#endif

#if defined(__ds390)
#define USE_FLOATS 1
#endif

#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>

#define PTR value.p

#ifdef SDCC_ds390
#define NULL_STRING "<NULL>"
#define NULL_STRING_LENGTH 6
#endif

/****************************************************************************/

//typedef char * ptr_t;
#define ptr_t char *

#ifdef toupper
#undef toupper
#endif

//#define toupper(c) ((c)&=~0x20)
#define toupper(c) ((c)&=0xDF)

typedef union
{
  unsigned char  byte[5];
  long           l;
  unsigned long  ul;
  float          f;
  char           *p;
} value_t;


static code char memory_id[] = "IXCP-";

static ptr_t output_ptr;
static bit   output_to_string;
static bit   lower_case;
static bit   lsd;

/* this one NEEDS to be in data */
static data value_t value;

static unsigned char radix;

// this makes the whole dammed thing nonreentrent
static int charsOutputted;

/****************************************************************************/

static void output_char( char c ) reentrant
{
  if (output_to_string)
  {
    *output_ptr++ = c;
  }
  else
  {
    putchar( c );
  }
  charsOutputted++;
}

/*--------------------------------------------------------------------------*/

static void output_digit( unsigned char n ) reentrant
{
  output_char( n <= 9 ? '0'+n : 
	       (lower_case ? n+(char)('a'-10) : n+(char)('A'-10)) );
}

/*--------------------------------------------------------------------------*/

static void output_2digits( unsigned char b ) reentrant
{
  output_digit( b>>4 );
  output_digit( b&0x0F );
}
        
/*--------------------------------------------------------------------------*/

static void calculate_digit( void )
{
  unsigned char i;

  for( i = 32; i != 0; i-- )
  {
_asm
  clr  c
  mov  a,_value+0  
  rlc  a
  mov  _value+0,a
  mov  a,_value+1
  rlc  a
  mov  _value+1,a
  mov  a,_value+2
  rlc  a
  mov  _value+2,a
  mov  a,_value+3
  rlc  a
  mov  _value+3,a
  mov  a,_value+4
  rlc  a
  mov  _value+4,a
_endasm;

    if (radix <= value.byte[4] )
    {
      value.byte[4] -= radix;
      value.byte[0]++;
    }
  }
}

#if USE_FLOATS

/* This is a very inefficient but direct approach, since we have no math
   library yet (e.g. log()).
   It does most of the modifiers, but has some restrictions. E.g. the 
   abs(float) shouldn't be bigger than an unsigned long (that's 
   about 4294967295), but still makes it usefull for most real-life
   applications.
*/

#define DEFAULT_FLOAT_PRECISION 6

static void output_float (float f, unsigned char reqWidth, 
			  signed char reqDecimals,
			  bit left, bit zero, bit sign, bit space)
{
  char negative=0;
  unsigned long integerPart;
  float decimalPart;
  char fpBuffer[128];
  char fpBI=0, fpBD;
  unsigned char minWidth, i;

  // save the sign
  if (f<0) {
    negative=1;
    f=-f;
  }

  if (f>0x00ffffff) {
    // this part is from Frank van der Hulst
    signed char exp;
    
    for (exp = 0; f >= 10.0; exp++) f /=10.0;
    for (       ; f < 1.0;   exp--) f *=10.0;

    if (negative) {
      putchar ('-');
    } else {
      if (sign) {
	putchar ('+');
      }
    }
    output_float(f, 0, reqDecimals, 0, 0, 0, 0);
    putchar ('e');
    if (exp<0) {
      putchar ('-');
      exp = -exp;
    }
    putchar ('0'+exp/10);
    putchar ('0'+exp%10);
    return;
  }

  // split the float
  integerPart=f;
  decimalPart=f-integerPart;

  // fill the buffer with the integerPart (in reversed order!)
  while (integerPart) {
    fpBuffer[fpBI++]='0' + integerPart%10;
    integerPart /= 10;
  }
  if (!fpBI) {
    // we need at least a 0
    fpBuffer[fpBI++]='0';
  }

  // display some decimals as default
  if (reqDecimals==-1)
    reqDecimals=DEFAULT_FLOAT_PRECISION;
  
  // fill buffer with the decimalPart (in normal order)
  fpBD=fpBI;
  if (i=reqDecimals /* that's an assignment */) {
    do {
      decimalPart *= 10.0;
      // truncate the float
      integerPart=decimalPart;
      fpBuffer[fpBD++]='0' + integerPart;
      decimalPart-=integerPart;
    } while (--i);
  }
  
  minWidth=fpBI; // we need at least these
  minWidth+=reqDecimals?reqDecimals+1:0; // maybe these
  if (negative || sign || space)
    minWidth++; // and maybe even this :)
  
  if (!left && reqWidth>i) {
    if (zero) {
      if (negative) output_char('-');
      else if (sign) output_char('+');
      else if (space) output_char(' ');
      while (reqWidth-->minWidth)
	output_char ('0');
    } else {
      while (reqWidth-->minWidth)
	output_char (' ');
      if (negative) output_char('-');
      else if (sign) output_char('+');
      else if (space) output_char (' ');
    }
  } else {
    if (negative) output_char('-');
    else if (sign) output_char('+');
    else if (space) output_char(' ');
  }

  // output the integer part
  i=fpBI-1;
  do {
    output_char (fpBuffer[i]);
  } while (i--);
  
  // ouput the decimal part
  if (reqDecimals) {
    output_char ('.');
    i=fpBI;
    while (reqDecimals--)
      output_char (fpBuffer[i++]);
  }

  if (left && reqWidth>minWidth) {
    while (reqWidth-->minWidth)
      output_char(' ');
  }
}
#endif

/*--------------------------------------------------------------------------*/

int vsprintf (const char *buf, const char *format, va_list ap)
{
  static bit            left_justify;
  static bit            zero_padding;
  static bit            prefix_sign;
  static bit            prefix_space;
  static bit            signed_argument;
  static bit            char_argument;
  static bit            long_argument;
  static bit            float_argument;

  unsigned char  width;
  signed char decimals;
  unsigned char  length;
  char           c;

  // reset output chars
  charsOutputted=0;

  output_ptr = buf;
  if ( !buf )
  {
    output_to_string = 0;
  }
  else
  {
    output_to_string = 1;
  }

#ifdef SDCC_ds390
  if (format==0) {
    format=NULL_STRING;
  }
#endif
 
  while( c=*format++ )
  {
    if ( c=='%' )
    {
      left_justify    = 0;
      zero_padding    = 0;
      prefix_sign     = 0;
      prefix_space    = 0;
      signed_argument = 0;
      radix           = 0;
      char_argument   = 0;
      long_argument   = 0;
      float_argument  = 0;
      width           = 0;
      decimals        = -1;

get_conversion_spec:

      c = *format++;

      if (c=='%') {
	output_char(c);
	continue;
      }

      if (isdigit(c)) {
	if (decimals==-1) {
	  width = 10*width + (c - '0');
	  if (width == 0) {
	    /* first character of width is a zero */
	    zero_padding = 1;
	  }
	} else {
	  decimals = 10*decimals + (c-'0');
	}
	goto get_conversion_spec;
      }

      if (c=='.') {
	if (decimals=-1) decimals=0;
	else 
	  ; // duplicate, ignore
	goto get_conversion_spec;
      }

      lower_case = islower(c);
      if (lower_case)
      {
	c = toupper(c);
      }

      switch( c )
      {
      case '-':
	left_justify = 1;
	goto get_conversion_spec;
      case '+':
	prefix_sign = 1;
	goto get_conversion_spec;
      case ' ':
	prefix_space = 1;
	goto get_conversion_spec;
      case 'B':
	char_argument = 1;
	goto get_conversion_spec;
      case 'L':
	long_argument = 1;
	goto get_conversion_spec;

      case 'C':
       	output_char( va_arg(ap,int) );
	break;

      case 'S':
	PTR = va_arg(ap,ptr_t);

#ifdef SDCC_ds390
	if (PTR==0) {
	  PTR=NULL_STRING;
	  length=NULL_STRING_LENGTH;
	} else {
	  length = strlen(PTR);
	}
#else
	length = strlen(PTR);
#endif
	if ( ( !left_justify ) && (length < width) )
	{
	  width -= length;
	  while( width-- != 0 )
	  {
	    output_char( ' ' );
	  }
	}

        while ( *PTR )
	  output_char( *PTR++ );

	if ( left_justify && (length < width))
	{
	  width -= length;
	  while( width-- != 0 )
	  {
	    output_char( ' ' );
	  }
	}
	break;

      case 'P':
	PTR = va_arg(ap,ptr_t);

#ifdef SDCC_ds390
	output_char(memory_id[(value.byte[3] > 3) ? 4 : value.byte[3]] );
	output_char(':');
	output_char('0');
	output_char('x');
	output_2digits(value.byte[2]);
	output_2digits(value.byte[1]);
	output_2digits(value.byte[0]);
#else
	output_char( memory_id[(value.byte[2] > 3) ? 4 : value.byte[2]] );
	output_char(':');
	output_char('0');
	output_char('x');
	if ((value.byte[2] != 0x00 /* DSEG */) &&
	    (value.byte[2] != 0x03 /* SSEG */))
	  output_2digits( value.byte[1] );
	output_2digits( value.byte[0] );
#endif
	break;

      case 'D':
      case 'I':
	signed_argument = 1;
	radix = 10;
	break;

      case 'O':
	radix = 8;
	break;

      case 'U':
	radix = 10;
	break;

      case 'X':
	radix = 16;
	break;

      case 'F':
	float_argument=1;
	break;
	
      default:
	// nothing special, just output the character
	output_char( c );
	break;
      }

      if (float_argument) {
	value.f=va_arg(ap,float);
#if !USE_FLOATS
	PTR="<NO FLOAT>";
	while (c=*PTR++)
	  output_char (c);
	// treat as long hex
	//radix=16;
	//long_argument=1;
	//zero_padding=1;
	//width=8;
#else
	// ignore b and l conversion spec for now
	output_float(value.f, width, decimals, left_justify, zero_padding, 
		     prefix_sign, prefix_space);
#endif
      } else if (radix != 0)
      {
	// Apperently we have to output an integral type
        // with radix "radix"

        // store value in byte[0] (LSB) ... byte[3] (MSB)
	if (char_argument)
	{
	  value.l = va_arg(ap,char);
	  if (!signed_argument)
	  {
	    value.byte[1] = 0x00;
	    value.byte[2] = 0x00;
	    value.byte[3] = 0x00;
	  }
	}
	else if (long_argument)
	{
	  value.l = va_arg(ap,long);
	}
	else // must be int
	{
	  value.l = va_arg(ap,int);
	  if (!signed_argument)
	  {
	    value.byte[2] = 0x00;
	    value.byte[3] = 0x00;
	  }
	}

        if ( signed_argument )
        {
          if (value.l < 0)
            value.l = -value.l;
          else
            signed_argument = 0;
        }

	length=0;
        lsd = 1;

	do {
          value.byte[4] = 0;
	  calculate_digit();

_asm
  jb   _lsd,1$
  pop  b                ; b = <lsd>
  mov  a,_value+4       ; a = <msd>
  swap a
  orl  b,a              ; b = <msd><lsd>
  push b
  sjmp 2$
1$:
  mov  a,_value+4       ; a = <lsd>
  push acc
2$:
_endasm;

	  length++;
          lsd = ~lsd;
	} while( (value.byte[0] != 0) || (value.byte[1] != 0) ||
		 (value.byte[2] != 0) || (value.byte[3] != 0) );

	if (width == 0)
	{
	  // default width. We set it to 1 to output
          // at least one character is case the value itself
          // is zero (i.e. length==0)
	  width=1;
	}

	/* prepend spaces if needed */
	if (!zero_padding && !left_justify)
	{
	  while ( width > length+1 )
	  {
	    output_char( ' ' );
	    width--;
	  }
	}

        if (signed_argument) // this now means the original value was negative
        {
          output_char( '-' );
	  // adjust width to compensate for this character
	  width--;
        }
        else if (length != 0)
	{
	  // value > 0
	  if (prefix_sign)
	  {
	    output_char( '+' );
	    // adjust width to compensate for this character
	    width--;
	  }
	  else if (prefix_space)
	  {
	    output_char( ' ' );
	    // adjust width to compensate for this character
            width--;
	  }
	}

	/* prepend zeroes/spaces if needed */
	if (!left_justify)
	  while ( width-- > length )
	  {
	    output_char( zero_padding ? '0' : ' ' );
	  }
	else
	{
	  /* spaces are appended after the digits */
	  if (width > length)
	    width -= length;
	  else
	    width = 0;
	}

	/* output the digits */
	while( length-- )
	{
          lsd = ~lsd;

_asm
  jb   _lsd,3$
  pop  acc              ; a = <msd><lsd>
  nop                   ; to disable the "optimizer"
  push acc
  swap a
  anl  a,#0x0F          ; a = <msd>
  sjmp 4$
3$:
  pop  acc
  anl  a,#0x0F          ; a = <lsd>
4$:
  mov  _value+4,a
_endasm;

	  output_digit( value.byte[4] );
	}
	if (left_justify)
	  while (width-- > 0)
	    output_char(' ');
      }
    }
    else
    {
      // nothing special, just output the character
      output_char( c );
    }
  }

  // Copy \0 to the end of buf
  // Modified by JB 17/12/99
  if (output_to_string) {
    output_char(0);
    return charsOutputted-1;
  } else {
    return charsOutputted;
  }
}

/*--------------------------------------------------------------------------*/

int vprintf (const char *format, va_list ap)
{
  return vsprintf( 0, format, ap );
}
