/*  This file is part of Whitix.
 *
 *  Whitix 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 of the License, or
 *  (at your option) any later version.
 *
 *  Whitix 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 Whitix; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

/* Finally I've got round to coding a industry-strength vsprintf, although some ideas are from Linux. 
There aren't a thousands ways to code a vsprintf y'know.Phew, coded this all quickly. Should be very useful */

/* TODO: Edit this to make it cleaner */

#include <init.h>
#include <module.h>
#include <string.h>
#include <typedefs.h>

#define SIGN 1
#define ZEROPAD 2
#define PLUS 4
#define SPACE 8
#define LEFT 16
#define SPECIAL 32
#define SMALL 64

#define is_digit(c) ((c) >= '0' && (c) <= '9')
#define do_div(n,base) ({ \
int __res; \
__asm__("divl %4":"=a" (n),"=d" (__res):"0" (n),"1" (0),"r" (base)); \
__res; })

int skip_atoi(const char** s)
{
	int i=0;
	while (is_digit(**s))
		i=i*10+*((*s)++)-'0';
	return i;
}

char* number(char* str,int num,int base,int size,int precision,int type)
{
	char c,sign,tmp[36];
	const char* digits="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
	int i=0;
	
	if (type & SMALL) digits="0123456789abcdefghijklmnopqrstuvwxyz";
	if (type & LEFT) type &= ~ZEROPAD;
	if (base < 2 || base > 36)
		return NULL;
	c=(type & ZEROPAD) ? '0' : ' ';
	if (type & SIGN && num < 0)
	{
		sign='-';
		num=-num;
	}else
		sign=(type & PLUS) ? '+' : ((type & SPACE) ? ' ' : 0);
	if (sign) size--;
	if (type & SPECIAL)
	{
		if (base == 16) size-=2;
		else if (base == 8) size--;
	}
		
	if (num == 0)
		tmp[i++]='0';
	else while (num != 0)
	{
		tmp[i++]=digits[do_div(num,base)];
	}

	if (i>precision) precision=i;
	size-=precision;
	if (!(type & (ZEROPAD+LEFT)))
		while (size-- > 0)
			*str++=' ';
			
	if (sign)
		*str++=sign;
	
	if (type & SPECIAL)
	{
		if (base == 8)
			*str++='0';
		else if (base == 16)
		{
			*str++='0';
			*str++=digits[33];
		}
	}
		
	if (!(type & LEFT))
		while (size-- > 0)
			*str++=c;
			
	while (i < precision--)
		*str++='0';
	while (i-- > 0)
		*str++=tmp[i];
	while (size-- > 0)
		*str++=' ';
	return str;
}

int vsprintf(char* buf,const char* fmt,va_list args)
{
	char* str=buf;
	int fieldWidth,precision,qualifier,flags;

	for (; *fmt; fmt++)
	{
		fieldWidth=precision=qualifier=-1;
		flags=0;

		if (LIKELY(*fmt != '%'))
		{
			*str++=*fmt;
			continue;
		}
		
		repeat:
		++fmt;
		switch (*fmt)
		{
			case '-': flags |= LEFT; goto repeat;
			case '+': flags |= PLUS; goto repeat;
			case ' ': flags |= SPACE; goto repeat;
			case '#': flags |= SPECIAL; goto repeat;
			case '0': flags |= ZEROPAD; goto repeat;
		}
		
		/* Deal with the field width */
		if (is_digit(*fmt))
			fieldWidth=skip_atoi(&fmt);
		else if (*fmt == '*')
		{
			fieldWidth=va_arg(args,int);
			if (fieldWidth < 0)
			{
				fieldWidth=-fieldWidth;
				flags |= LEFT;
			}
		}
		
		if (*fmt == '.')
		{
			++fmt;
			if (is_digit(*fmt))
				precision=skip_atoi(&fmt);
			else if (*fmt == '*')
			{
				precision=va_arg(args,int);
			}
			if (precision < 0)
				precision=0;
		}
		
		qualifier=-1;
		if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L')
		{
			qualifier=*fmt;
			++fmt;
		}
		
		switch (*fmt)
		{
		case 'c':
			if (!(flags & LEFT))
				while (--fieldWidth > 0)
					*str++=' ';
			*str++=(unsigned char)va_arg(args,int);
			while (--fieldWidth > 0)
				*str++=' ';			
			break;

		case 's':
		{
			char* s=va_arg(args,char*);
			if (!s)
				s="<NULL>";
			int len=strlen(s);
			if (precision < 0)
				precision=len;
			else if (len > precision)
				len=precision;
				
			if (!(flags & LEFT))
				while (len < fieldWidth--)
					*str++=' ';
			int i;
			for (i=0; i<len; i++)
				*str++=*s++;
				
			while (len < fieldWidth--)
				*str++=' ';
			break;
		}
		
		case 'o':
			str=number(str,va_arg(args,unsigned long),8,fieldWidth,
				precision,flags);
			break;
			
		case 'p':
			if (fieldWidth == -1)
			{
				fieldWidth=8;
				flags |= ZEROPAD;
			}
			str=number(str,(unsigned long)va_arg(args,void*),16,fieldWidth,
				precision,flags);
			break;
			
		case 'x':
			flags |= SMALL;
		case 'X':
			str=number(str,va_arg(args,unsigned long),16,fieldWidth,
				precision,flags);
			break;
		case 'd':
		case 'i':
			flags |= SIGN;
		case 'u':
			str=number(str,va_arg(args,unsigned long),10,fieldWidth,
				precision,flags);
			break;
		default:
			if (*fmt != '%')
				*str++ = '%';
			if (*fmt)
				*str++ = *fmt;
			else
				--fmt;
			break;
		}
	}
	*str='\0';
	return (str-buf);
}

SYMBOL_EXPORT(vsprintf);

int sprintf(char* buf,const char* fmt,...)
{
	int i;
	va_list args;

	va_start(args,fmt);
	i=vsprintf(buf,fmt,args);
	va_end(args);
	return i;
}

SYMBOL_EXPORT(sprintf);
