/*********************************************************************
 *
 * Copyright (C) 2002-2004  Karlsruhe University
 *
 * File path:     generic/printk.cc
 * Description:   Implementation of printf
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 ********************************************************************/
#include <stdarg.h>	/* for va_list, ... comes with gcc */
#include <l4/lib/printk.h>
/* FIXME: LICENSE LICENCE */
typedef unsigned int word_t;

extern void putc(const char c);
extern int print_tid (word_t val, word_t width, word_t precision, int adjleft);


/* convert nibble to lowercase hex char */
#define hexchars(x) (((x) < 10) ? ('0' + (x)) : ('a' + ((x) - 10)))

/**
 *	Print hexadecimal value
 *
 *	@param val		value to print
 *	@param width		width in caracters
 *	@param precision	minimum number of digits to apprear
 *	@param adjleft		left adjust the value
 *	@param nullpad		pad with leading zeros (when right padding)
 *
 *	Prints a hexadecimal value with leading zeroes of given width
 *	using putc(), or if adjleft argument is given, print
 *	hexadecimal value with space padding to the right.
 *
 *	@returns the number of charaters printed (should be same as width).
 */
int print_hex64(u64 val, int width, int precision, int adjleft, int nullpad)
{
    int i, n = 0;
    int nwidth = 0;
    u32 high, low;

    high = val >> 32;
    low = (u32)val;

    // Find width of hexnumber
    if (high) {
	while ((high >> (4 * nwidth)) && ((unsigned) nwidth <  2 * sizeof (u32)))
	    nwidth++;
	nwidth += 32;
    } else {
	while ((low >> (4 * nwidth)) && ((unsigned) nwidth <  2 * sizeof (u32)))
	    nwidth++;
    }

    if (nwidth == 0)
	nwidth = 1;

    // May need to increase number of printed digits
    if (precision > nwidth)
	nwidth = precision;

    // May need to increase number of printed characters
    if (width == 0 && width < nwidth)
	width = nwidth;

    // Print number with padding
    if (high)
    {
	if (!adjleft)
	    for (i = width - nwidth; i > 0; i--, n++)
		putc (nullpad ? '0' : ' ');
	for (i = 4 * (nwidth - 33); i >= 0; i -= 4, n++)
	    putc (hexchars ((high >> i) & 0xF));
	if (adjleft)
	    for (i = width - nwidth; i > 0; i--, n++)
		putc (' ');
	width -= 32;
	nwidth -= 32;
	nullpad = 1;
    }
    if (! adjleft)
	for (i = width - nwidth; i > 0; i--, n++)
	    putc (nullpad ? '0' : ' ');
    for (i = 4 * (nwidth - 1); i >= 0; i -= 4, n++)
	putc (hexchars ((low >> i) & 0xF));
    if (adjleft)
	for (i = width - nwidth; i > 0; i--, n++)
	    putc (' ');

    return n;
}

int print_hex_3arg(const word_t val, int width, int precision)
{
    long i, n = 0;
    long nwidth = 0;
    int adjleft = 0;
    int nullpad = 0;

    // Find width of hexnumber
    while ((val >> (4 * nwidth)) && (word_t) nwidth <  2 * sizeof (word_t))
	nwidth++;

    if (nwidth == 0)
	nwidth = 1;

    // May need to increase number of printed digits
    if (precision > nwidth)
	nwidth = precision;

    // May need to increase number of printed characters
    if (width == 0 && width < nwidth)
	width = nwidth;

    // Print number with padding
    if (! adjleft)
	for (i = width - nwidth; i > 0; i--, n++)
	    putc (nullpad ? '0' : ' ');
    for (i = 4 * (nwidth - 1); i >= 0; i -= 4, n++)
	putc (hexchars ((val >> i) & 0xF));
    if (adjleft)
	for (i = width - nwidth; i > 0; i--, n++)
	    putc (' ');

    return n;
}

int print_hex_5arg(const word_t val, int width,
		   int precision, int adjleft, int nullpad)
{
    long i, n = 0;
    long nwidth = 0;

    // Find width of hexnumber
    while ((val >> (4 * nwidth)) && (word_t) nwidth <  2 * sizeof (word_t))
	nwidth++;

    if (nwidth == 0)
	nwidth = 1;

    // May need to increase number of printed digits
    if (precision > nwidth)
	nwidth = precision;

    // May need to increase number of printed characters
    if (width == 0 && width < nwidth)
	width = nwidth;

    // Print number with padding
    if (! adjleft)
	for (i = width - nwidth; i > 0; i--, n++)
	    putc (nullpad ? '0' : ' ');
    for (i = 4 * (nwidth - 1); i >= 0; i -= 4, n++)
	putc (hexchars ((val >> i) & 0xF));
    if (adjleft)
	for (i = width - nwidth; i > 0; i--, n++)
	    putc (' ');

    return n;
}
/**
 *	Print a string
 *
 *	@param s	zero-terminated string to print
 *	@param width	minimum width of printed string
 *
 *	Prints the zero-terminated string using putc().  The printed
 *	string will be right padded with space to so that it will be
 *	at least WIDTH characters wide.
 *
 *      @returns the number of charaters printed.
 */
int print_string_3arg(const char * s, const int width, const int precision)
{
    int n = 0;

    for (;;)
    {
	if (*s == 0)
	    break;

	putc(*s++);
	n++;
	if (precision && n >= precision)
	    break;
    }

    while (n < width) { putc(' '); n++; }

    return n;
}

int print_string_1arg(const char * s)
{
	int n = 0;
	int width = 0;
	int precision = 0;

	for (;;) {
		if (*s == 0)
			break;

		putc(*s++);
		n++;
		if (precision && n >= precision)
			break;
	}

	while (n < width) { 
		putc(' '); 
		n++; 
	}

	return n;
}


/**
 *	Print hexadecimal value with a separator
 *
 *	@param val	value to print
 *	@param bits	number of lower-most bits before which to
 *                      place the separator
 *      @param sep      the separator to print
 *
 *	@returns the number of charaters printed.
 */
int print_hex_sep(const word_t val, const int bits, const char *sep)
{
    int n = 0;

    n = print_hex_3arg(val >> bits, 0, 0);
    n += print_string_1arg(sep);
    n += print_hex_3arg(val & ((1 << bits) - 1), 0, 0);

    return n;
}


/**
 *	Print decimal value
 *
 *	@param val	value to print
 *	@param width	width of field
 *      @param pad      character used for padding value up to width
 *
 *	Prints a value as a decimal in the given WIDTH with leading
 *	whitespaces.
 *
 *	@returns the number of characters printed (may be more than WIDTH)
 */
int print_dec(const word_t val, int width)
{
    word_t divisor;
    int digits;
    /* estimate number of spaces and digits */
    for (divisor = 1, digits = 1; val/divisor >= 10; divisor *= 10, digits++);

    /* print spaces */
    for ( ; digits < width; digits++ )
	putc(' ');

    /* print digits */
    do {
	putc(((val/divisor) % 10) + '0');
    } while (divisor /= 10);

    /* report number of digits printed */
    return digits;
}

/**
 *	Does the real printk work
 *
 *	@param format_p		pointer to format string
 *	@param args		list of arguments, variable length
 *
 *	Prints the given arguments as specified by the format string.
 *	Implements a subset of the well-known printf plus some L4-specifics.
 *
 *	@returns the number of characters printed
 */
int do_printk(char* format_p, va_list args)
{
    const char* format = format_p;
    int n = 0;
    int i = 0;
    int width = 8;
    int precision = 0;
    int adjleft = 0, nullpad = 0;

#define arg(x) va_arg(args, x)

    /* sanity check */
    if (format == '\0')
    {
	return 0;
    }

    while (*format)
    {
	switch (*(format))
	{
	case '%':
	    width = precision = 0;
	    adjleft = nullpad = 0;
	reentry:
	    switch (*(++format))
	    {
		/* modifiers */
	    case '.':
		for (format++; *format >= '0' && *format <= '9'; format++)
		    precision = precision * 10 + (*format) - '0';
		if (*format == 'w')
		{
		    // Set precision to printsize of a hex word
		    precision = sizeof (word_t) * 2;
		    format++;
		}
		format--;
		goto reentry;
	    case '0':
		nullpad = (width == 0);
	    case '1'...'9':
		width = width*10 + (*format)-'0';
		goto reentry;
	    case 'w':
		// Set width to printsize of a hex word
		width = sizeof (word_t) * 2;
		goto reentry;
	    case '-':
		adjleft = 0;
		goto reentry;
	    case 'l':
		goto reentry;
		break;
	    case 'c':
		putc(arg(int));
		n++;
		break;
	    case 'm':	/* microseconds */
	    {
		n += print_hex64(arg(u64), width, precision,
			       adjleft, nullpad);
		break;
	    }
	    case 'd':
	    {
		long val = arg(long);
		if (val < 0)
		{
		    putc('-');
		    val = -val;
		}
		n += print_dec(val, width);
		break;
	    }
	    case 'u':
		n += print_dec(arg(long), width);
		break;
	    case 'p':
		precision = sizeof (word_t) * 2;
	    case 'x':
		n += print_hex_5arg(arg(long), width, precision, adjleft, nullpad);
		break;
	    case 's':
	    {
		char* s = arg(char*);
		if (s)
		    n += print_string_3arg(s, width, precision);
		else
		    n += print_string_3arg("(null)", width, precision);
	    }
	    break;

	    case 't':
	    case 'T':
	    	// Do nothing for now.
		//n += print_tid (arg (word_t), width, precision, adjleft);
		break;

	    case '%':
		putc('%');
		n++;
		format++;
		continue;
	    default:
		n += print_string_1arg("?");
		break;
	    };
	    i++;
	    break;
	default:
	    putc(*format);
	    n++;
	    break;
	}
	format++;
    }

    return n;
}

/**
 *	Flexible print function
 *
 *	@param format	string containing formatting and parameter type
 *			information
 *	@param ...	variable list of parameters
 *
 *	@returns the number of characters printed
 */
int printk(char *format, ...)
{
    va_list args;
    int i;

    va_start(args, format);
    i = do_printk(format, args);
    va_end(args);
    return i;
};


