#include <stdlib.h>
#include <string.h>
#include <printf.h> /* fnptr. Also include stdarg.h for va_list, va_arg() */
#include <string.h>

/*****************************************************************************
	name:   dowPrintf
	action: minimal subfunction for w?printf, calls function
		Fn with arg Ptr for each character to be output
	returns:total number of characters output

	%[flag][width][.prec][mod][conv]
	flag:   -   left justify, pad right w/ blanks   DONE
		0   pad left w/ 0 for numerics      xxx
		+   always print sign, + or -       xxx
		' ' (blank)                 xxx
		#   (???)                   xxx
	width:      (field width)               DONE
	prec:       (precision)             xxx
	conv:   d,i decimal int             DONE
		o   octal                   DONE
		x,X hex                 DONE
		f,e,g,E,G float                 xxx
		c   char                    DONE
		s   string                  DONE
		p   ptr                 DONE
	mod:    N   near ptr                xxx
		u   decimal unsigned            DONE
		F   far ptr                 xxx
		h   short int               DONE
		l   long int                DONE
		L   long double             xxx
*****************************************************************************/
/* flags used in processing format string */
#define     VPR_LJ  0x01    /* left justify */
#define     VPR_CA  0x02    /* use A-F instead of a-f for hex */
#define     VPR_SG  0x04    /* signed numeric conversion (%d vs. %u) */
#define     VPR_32  0x08    /* long (32-bit) numeric conversion */
#define     VPR_16  0x10    /* short (16-bit) numeric conversion */
#define     VPR_WS  0x20    /* VPR_SG set and Num was < 0 */
#define     VPR_NC  0x40    /* %c or %s is char, not wchar_t */
#define     VPR_PAD 0x80

/* largest number handled is 2^32-1, lowest radix handled is 8.
2^32-1 in base 8 has 11 digits (add one for trailing NUL) */
#define     VPR_BUFLEN  12

/* Some idiot passed NULL to %s... */
#define		VPR_NULL	L"(null)"

//! Implements the core functionality of the ...printf() functions.
/*!
 *	This function is used by wprintf(), swprintf(), etc. to process a string
 *		and output it to a file or string. The function provided will be called
 *		to handle the string formed.
 *	\param	fn	The function to be called periodically for blocks of 
 *		characters.
 *	\param	Ptr	The string to be processed. Conforms to normal printf() 
 *		specifications.
 *	\param	Args	A va_list of the arguments that control the output.
 *	\return	The total number of characters passed to fn.
 */
int dowprintf(WPRINTFUNC Fn, void* Ptr, const wchar_t* Fmt, va_list Args)
{
	unsigned short Count, GivenWd, State, Flags, ActualWd, Temp;
	wchar_t *Where, Buf[VPR_BUFLEN];
	char* Narrow;
	unsigned long Radix;
	long Num;
	wchar_t ch[2] = { 0, 0 };

	State = Flags = Count = GivenWd = 0;
	/* begin scanning format specifier list */
	for(; *Fmt; Fmt++)
	{
		switch(State)
		{
		case 0: /* AWAITING % */
			if(*Fmt != '%')         /* not %... */
			{
				ch[0] = *Fmt;
				Fn(Ptr, ch, 1);      /* ...just echo it */
				Count++;
				break;
			}
			// found %, get next char and advance state to check if next char is a flag */
			else
			{
				State++;
				Fmt++;
			}/* FALL THROUGH */

		case 1:     /* AWAITING FLAGS (-) */
			if(*Fmt == '-')
			{
				if(Flags & VPR_LJ)  /* %-- is illegal */
					State = Flags = GivenWd=0;
				else
					Flags |= VPR_LJ;
				break;
			}
			else if (*Fmt == '0')
			{
				if(Flags & VPR_PAD)  /* %00 is illegal */
					State=Flags=GivenWd=0;
				else
					Flags |= VPR_PAD;
				break;
			}
			/* not a flag char: advance state to check if it's field width */
			else
				State++;    /* FALL THROUGH */

		case 2:     /* AWAITING FIELD WIDTH (<number>) */
			if(*Fmt >= '0' && *Fmt <= '9')
			{
				GivenWd=10 * GivenWd + (*Fmt - '0');
				break;
			}
			/* not field width: advance state to check if it's a modifier */
			else
				State++;    /* FALL THROUGH */

		case 3:     /* AWAITING MODIFIER CHARS (lh) */
			if(*Fmt == 'l')
			{
				Flags |= VPR_32;
				break;
			}

			if(*Fmt == 'h')
			{
				Flags |= VPR_16;
				break;
			}
			/* not modifier: advance state to check if it's a conversion char */
			else
				State++;    /* FALL THROUGH */

		case 4:     /* AWAITING CONVERSION CHARS (Xxpndiuocs) */
			Where = Buf + countof(Buf) - 1;
			Narrow = (char*) Where;
			*Where = '\0';
			switch(*Fmt)
			{
			case '%':
				ch[0] = '%';
				Fn(Ptr, ch, 1);
				break;
			case 'X':
				Flags |= VPR_CA;/* FALL THROUGH */

			case 'x':
			case 'p':
			case 'n':
				Radix = 16;
				goto DO_NUM;

			case 'd':
			case 'i':
				Flags |= VPR_SG;/* FALL THROUGH */

			case 'u':
				Radix = 10;
				goto DO_NUM;

			case 'o':
				Radix = 8;

DO_NUM:
				/* load the value to be printed. l=long=32 bits: */
				if(Flags & VPR_32)
					Num = va_arg(Args, unsigned long);
				else if(Flags & VPR_16)
				{
					// h=short=16 bits (signed or unsigned)
					if(Flags & VPR_SG)
						Num = va_arg(Args, short);
					else
						Num = va_arg(Args, unsigned short);
				}
				else
				{
					/* no h nor l: sizeof(int) bits (signed or unsigned) */
					if(Flags & VPR_SG)
						Num = va_arg(Args, int);
					else
						Num = va_arg(Args, unsigned int);
				}

				/* take care of sign */
				if(Flags & VPR_SG)
				{
					if((long)Num < 0)
					{
						Flags |= VPR_WS;
						Num=-Num;
					}
				}

				/* convert binary to octal/decimal/hex ASCII */
				Temp = 0;
				do
				{
					Temp = (unsigned short) (Num % Radix);
					Where--;
					if (Temp < 10)
						*Where = Temp + '0';
					else if (Flags & VPR_CA)
						*Where = Temp - 10 + 'A';
					else
						*Where = Temp - 10 + 'a';
					Num /= Radix;
				} while(Num != 0);

				/* sign, again */
				if(Flags & VPR_WS)
					*--Where='-';
				goto EMIT;
			case 'c':
				Where--;
				*Where = (wchar_t)va_arg(Args, wchar_t);
				ActualWd=1;
				goto EMIT2;
			case 'C':
				Where--;
				*Where = (char)va_arg(Args, char);
				ActualWd=1;
				goto EMIT2;
			case 'S':
				Narrow = va_arg(Args, char*);
				if (Narrow == NULL)
				{
					Where = VPR_NULL;
					goto EMIT;
				}
				else
				{
					Flags |= VPR_NC;
					ActualWd = (unsigned short) strlen(Narrow);
				}
				goto EMIT2;
			case 's':
				Where = va_arg(Args, wchar_t*);
				if (Where == NULL)
					Where = VPR_NULL;

EMIT:           
				if (Flags & VPR_NC)
					ActualWd = (unsigned short) strlen(Narrow);
				else
					ActualWd = (unsigned short) wcslen(Where);
EMIT2:          if (Flags & VPR_PAD)
				{
					for(; GivenWd > ActualWd; GivenWd--)
					{
						ch[0] = '0';
						Fn(Ptr, ch, 1);
						Count++; 
					}
				}
				else if((Flags & VPR_LJ) == 0)
				{
					// pad on left with spaces (for right justify)
					for(; GivenWd > ActualWd; GivenWd--)
					{
						ch[0] = ' ';
						Fn(Ptr, ch, 1);
						Count++; 
					}
				}
				// emit string/char/converted number from Buf

				if (Flags & VPR_NC)
				{
					while (ActualWd > 0)
					{
						Temp = (unsigned short) 
							mbstowcs(Buf, Narrow, min(countof(Buf) - 1, ActualWd));
						Buf[Temp] = '\0';
						Fn(Ptr, Buf, Temp);
						ActualWd -= Temp;
						Narrow += Temp;
						Count += Temp;
					}
				}
				else
				{
					Fn(Ptr, Where, ActualWd);
					Count += ActualWd;
				}
				// pad on right with spaces (for left justify)
				if(GivenWd < ActualWd)
					GivenWd=0;
				else
					GivenWd -= ActualWd;
				for(; GivenWd; GivenWd--)
				{
					ch[0] = ' ';
					Fn(Ptr, ch, 1);
					Count++;
				}   // FALL THROUGH
			default:    // FALL THROUGH
				break;
			}
		default:
			State=Flags=GivenWd=0;
			break;
		}
	}
	return(Count);
}
