/*! \file rprintf.c \brief printf routine and associated routines. */
//*****************************************************************************
//
// File Name	: 'rprintf.c'
// Title		: printf routine and associated routines
// Author		: Pascal Stang - Copyright (C) 2000-2002
// Created		: 2000.12.26
// Revised		: 2003.5.1
// Version		: 1.0
// Target MCU	: Atmel AVR series and other targets
// Editor Tabs	: 4
//
// NOTE: This code is currently below version 1.0, and therefore is considered
// to be lacking in some functionality or documentation, or may not be fully
// tested.  Nonetheless, you can expect most functions to work.
//
// This code is distributed under the GNU Public License
//		which can be found at http://www.gnu.org/licenses/gpl.txt
//
//*****************************************************************************

#include <avr/pgmspace.h>
//#include <string-avr.h>
//#include <stdlib.h>
#include <stdarg.h>
#include "conf/global.h"
#include "rprintf.h"

#ifndef TRUE
	#define TRUE	-1
	#define FALSE	0
#endif

#define INF     32766	// maximum field size to print
#define READMEMBYTE(a,char_ptr)	((a)?(pgm_read_byte(char_ptr)):(*char_ptr))

#ifdef RPRINTF_COMPLEX
	static unsigned char buf[128];
#endif

// use this to store hex conversion in RAM
//static char HexChars[] = "0123456789ABCDEF";
// use this to store hex conversion in program memory
//static prog_char HexChars[] = "0123456789ABCDEF";
static char __attribute__ ((progmem)) HexChars[] = "0123456789ABCDEF";

#define hexchar(x)	pgm_read_byte( HexChars+((x)&0x0f) )
//#define hexchar(x)	((((x)&0x0F)>9)?((x)+'A'-10):((x)+'0'))

// function pointer to single character output routine
static void (*rputchar)(unsigned char c);

// *** rprintf initialization ***
// you must call this function once and supply the character output
// routine before using other functions in this library
void rprintfInit(void (*putchar_func)(unsigned char c))
{
	rputchar = putchar_func;
}

// *** rprintfChar ***
// send a character/byte to the current output device
void rprintfChar(unsigned char c)
{
	// do LF -> CR/LF translation
	if(c == '\n')
		rputchar('\r');
	// send character
	rputchar(c);
}

// *** rprintfStr ***
// prints a null-terminated string stored in RAM
void rprintfStr(char str[])
{
	// send a string stored in RAM
	// check to make sure we have a good pointer
	if (!str) return;

	// print the string until a null-terminator
	while (*str)
		rprintfChar(*str++);
}

// *** rprintfStrLen ***
// prints a section of a string stored in RAM
// begins printing at position indicated by <start>
// prints number of characters indicated by <len>
void rprintfStrLen(char str[], unsigned int start, unsigned int len)
{
	register int i=0;

	// check to make sure we have a good pointer
	if (!str) return;
	// spin through characters up to requested start
	// keep going as long as there's no null
	while((i++<start) && (*str++));
//	for(i=0; i<start; i++)
//	{
//		// keep steping through string as long as there's no null
//		if(*str) str++;
//	}

	// then print exactly len characters
	for(i=0; i<len; i++)
	{
		// print data out of the string as long as we haven't reached a null yet
		// at the null, start printing spaces
		if(*str)
			rprintfChar(*str++);
		else
			rprintfChar(' ');
	}

}

// *** rprintfProgStr ***
// prints a null-terminated string stored in program ROM
void rprintfProgStr(const prog_char str[])
{
	// print a string stored in program memory
	register char c;

	// check to make sure we have a good pointer
	if (!str) return;
	
	// print the string until the null-terminator
	while((c = pgm_read_byte(str++)))
		rprintfChar(c);
}

// *** rprintfCRLF ***
// prints carriage return and line feed
void rprintfCRLF(void)
{
	// print CR/LF
	//rprintfChar('\r');
	// LF -> CR/LF translation built-in to rprintfChar()
	rprintfChar('\n');
}

// *** rprintfu04 ***
// prints an unsigned 4-bit number in hex (1 digit)
void rprintfu04(unsigned char data)
{
	// print 4-bit hex value
//	char Character = data&0x0f;
//	if (Character>9)
//		Character+='A'-10;
//	else
//		Character+='0';
	rprintfChar(hexchar(data));
}

// *** rprintfu08 ***
// prints an unsigned 8-bit number in hex (2 digits)
void rprintfu08(unsigned char data)
{
	// print 8-bit hex value
	rprintfu04(data>>4);
	rprintfu04(data);
}

// *** rprintfu16 ***
// prints an unsigned 16-bit number in hex (4 digits)
void rprintfu16(unsigned short data)
{
	// print 16-bit hex value
	rprintfu08(data>>8);
	rprintfu08(data);
}

// *** rprintfu32 ***
// prints an unsigned 32-bit number in hex (8 digits)
void rprintfu32(unsigned long data)
{
	// print 32-bit hex value
	rprintfu16(data>>16);
	rprintfu16(data);
}

// *** rprintfNum ***
// special printf for numbers only
// see formatting information below
//	Print the number "n" in the given "base"
//	using exactly "numDigits"
//	print +/- if signed flag "isSigned" is TRUE
//	use the character specified in "padchar" to pad extra characters
//
//	Examples:
//	uartPrintfNum(10, 6,  TRUE, ' ',   1234);  -->  " +1234"
//	uartPrintfNum(10, 6, FALSE, '0',   1234);  -->  "001234"
//	uartPrintfNum(16, 6, FALSE, '.', 0x5AA5);  -->  "..5AA5"
void rprintfNum(char base, char numDigits, char isSigned, char padchar, long n)
{
	// define a global HexChars or use line below
	//static char HexChars[16] = "0123456789ABCDEF";
	char *p, buf[32];
	unsigned long x;
	unsigned char count;

	// prepare negative number
	if( isSigned && (n < 0) )
	{
		x = -n;
	}
	else
	{
	 	x = n;
	}

	// setup little string buffer
	count = (numDigits-1)-(isSigned?1:0);
  	p = buf + sizeof (buf);
  	*--p = '\0';
	
	// force calculation of first digit
	// (to prevent zero from not printing at all!!!)
	*--p = hexchar(x%base); x /= base;
	// calculate remaining digits
	while(count--)
	{
		if(x != 0)
		{
			// calculate next digit
			*--p = hexchar(x%base); x /= base;
		}
		else
		{
			// no more digits left, pad out to desired length
			*--p = padchar;
		}
	}

	// apply signed notation if requested
	if( isSigned )
	{
		if(n < 0)
		{
   			*--p = '-';
		}
		else if(n > 0)
		{
	   		*--p = '+';
		}
		else
		{
	   		*--p = ' ';
		}
	}

	// print the string right-justified
	count = numDigits;
	while(count--)
	{
		rprintfChar(*p++);
	}
}

#ifdef RPRINTF_FLOAT
// *** rprintfFloat ***
// floating-point print
void rprintfFloat(char numDigits, char shit, double x)
{
	unsigned char firstplace = FALSE;
	unsigned char negative;
	unsigned char i, digit;
	double place = 1.0;
	
	// save sign
	negative = (x<0);
	// convert to absolute value
	x = (x>0)?(x):(-x);
	
	// find starting digit place
	for(i=0; i<15; i++)
	{
		if((x/place) < 10.0)
			break;
		else
			place *= 10.0;
	}
	// print polarity character
	if(negative)
		rprintfChar('-');
	else
		rprintfChar(shit);

	// print digits
	for(i=0; i<numDigits; i++)
	{
		digit = (x/place);

		if(digit | firstplace | (place == 1.0))
		{
			firstplace = TRUE;
			rprintfChar(digit+0x30);
		}
		else
			rprintfChar(' ');
		
		if(place == 1.0)
		{
			rprintfChar('.');
		}
		
		x -= (digit*place);
		place /= 10.0;
	}
}
#endif

#ifdef RPRINTF_SIMPLE
// *** rprintf1RamRom ***
// called by rprintf() - does a simple printf (supports %d, %x, %c)
// Supports:
// %d - decimal
// %x - hex
// %c - character
int rprintf1RamRom(unsigned char stringInRom, const char *format, ...)
{
	// simple printf routine
	// define a global HexChars or use line below
	//static char HexChars[16] = "0123456789ABCDEF";
	char format_flag;
	unsigned int u_val, div_val, base;
	va_list ap;

	va_start(ap, format);
	for (;;)
	{
		while ((format_flag = READMEMBYTE(stringInRom,format++) ) != '%')
		{	// Until '%' or '\0'
			if (!format_flag)
			{
				va_end(ap);
				return(0);
			}
			rprintfChar(format_flag);
		}

		switch (format_flag = READMEMBYTE(stringInRom,format++) )
		{
			case 'c': format_flag = va_arg(ap,int);
			default:  rprintfChar(format_flag); continue;
			case 'd': base = 10; div_val = 10000; goto CONVERSION_LOOP;
//			case 'x': base = 16; div_val = 0x10;
			case 'x': base = 16; div_val = 0x1000;

			CONVERSION_LOOP:
			u_val = va_arg(ap,int);
			if (format_flag == 'd')
			{
				if (((int)u_val) < 0)
				{
					u_val = - u_val;
					rprintfChar('-');
				}
				while (div_val > 1 && div_val > u_val) div_val /= 10;
			}
			do
			{
				//rprintfChar(pgm_read_byte(HexChars+(u_val/div_val)));
				rprintfu04(u_val/div_val);
				u_val %= div_val;
				div_val /= base;
			} while (div_val);
		}
	}
	va_end(ap);
}
#endif


#ifdef RPRINTF_COMPLEX
// *** rprintf2RamRom ***
// called by rprintf() - does a more powerful printf (supports %d, %u, %o, %x, %c, %s)
// Supports:
// %d - decimal
// %u - unsigned decimal
// %o - octal
// %x - hex
// %c - character
// %s - strings
// and the width,precision,padding modifiers
// **this printf does not support floating point numbers
int rprintf2RamRom(unsigned char stringInRom, const char *sfmt, ...)
{
	register unsigned char *f, *bp;
	register long l;
	register unsigned long u;
	register int i;
	register int fmt;
	register unsigned char pad = ' ';
	int flush_left = 0, f_width = 0, prec = INF, hash = 0, do_long = 0;
	int sign = 0;

	va_list ap;
	va_start(ap, sfmt);

	f = (unsigned char *) sfmt;

	for (; READMEMBYTE(stringInRom,f); f++)
	{
		if (READMEMBYTE(stringInRom,f) != '%')
		{	// not a format character
			// then just output the char
			rprintfChar(READMEMBYTE(stringInRom,f));
		}
		else 
		{
			f++;						// if we have a "%" then skip it
			if (READMEMBYTE(stringInRom,f) == '-')
			{
				flush_left = 1;	// minus: flush left
				f++;
			}
            if (READMEMBYTE(stringInRom,f) == '0'
				 || READMEMBYTE(stringInRom,f) == '.')
				{
					// padding with 0 rather than blank
					pad = '0';
					f++;
            }
            if (READMEMBYTE(stringInRom,f) == '*')
				{	// field width
					f_width = va_arg(ap, int);
					f++;
            }
            else if (Isdigit(READMEMBYTE(stringInRom,f)))
				{
					f_width = atoiRamRom(stringInRom, (char *) f);
					while (Isdigit(READMEMBYTE(stringInRom,f)))
						f++;        // skip the digits
            }
            if (READMEMBYTE(stringInRom,f) == '.')
				{	// precision
					f++;
					if (READMEMBYTE(stringInRom,f) == '*')
					{
						prec = va_arg(ap, int);
						f++;
					}
					else if (Isdigit(READMEMBYTE(stringInRom,f)))
					{
						prec = atoiRamRom(stringInRom, (char *) f);
						while (Isdigit(READMEMBYTE(stringInRom,f)))
							f++;    // skip the digits
					}
				}
            if (READMEMBYTE(stringInRom,f) == '#')
				{	// alternate form
					hash = 1;
					f++;
            }
            if (READMEMBYTE(stringInRom,f) == 'l')
				{	// long format
					do_long = 1;
					f++;
            }

				fmt = READMEMBYTE(stringInRom,f);
				bp = buf;
				switch (fmt) {		// do the formatting
				case 'd':			// 'd' signed decimal
					if (do_long)
						l = va_arg(ap, long);
					else
						l = (long) (va_arg(ap, int));
					if (l < 0)
					{
						sign = 1;
						l = -l;
					}
					do	{
						*bp++ = l % 10 + '0';
					} while ((l /= 10) > 0);
					if (sign)
						*bp++ = '-';
					f_width = f_width - (bp - buf);
					if (!flush_left)
						while (f_width-- > 0)
							rprintfChar(pad);
					for (bp--; bp >= buf; bp--)
						rprintfChar(*bp);
					if (flush_left)
						while (f_width-- > 0)
							rprintfChar(' ');
					break;
            case 'o':			// 'o' octal number
            case 'x':			// 'x' hex number
            case 'u':			// 'u' unsigned decimal
					if (do_long)
						u = va_arg(ap, unsigned long);
					else
						u = (unsigned long) (va_arg(ap, unsigned));
					if (fmt == 'u')
					{	// unsigned decimal
						do {
							*bp++ = u % 10 + '0';
						} while ((u /= 10) > 0);
					}
					else if (fmt == 'o')
					{  // octal
						do {
							*bp++ = u % 8 + '0';
						} while ((u /= 8) > 0);
						if (hash)
							*bp++ = '0';
					}
					else if (fmt == 'x')
					{	// hex
						do {
							i = u % 16;
							if (i < 10)
								*bp++ = i + '0';
							else
								*bp++ = i - 10 + 'a';
						} while ((u /= 16) > 0);
						if (hash)
						{
							*bp++ = 'x';
							*bp++ = '0';
						}
					}
					i = f_width - (bp - buf);
					if (!flush_left)
						while (i-- > 0)
							rprintfChar(pad);
					for (bp--; bp >= buf; bp--)
						rprintfChar((int) (*bp));
					if (flush_left)
						while (i-- > 0)
							rprintfChar(' ');
					break;
            case 'c':			// 'c' character
					i = va_arg(ap, int);
					rprintfChar((int) (i));
					break;
            case 's':			// 's' string
					bp = va_arg(ap, unsigned char *);
					if (!bp)
						bp = (unsigned char *) "(nil)";
					f_width = f_width - strlen((char *) bp);
					if (!flush_left)
						while (f_width-- > 0)
							rprintfChar(pad);
					for (i = 0; *bp && i < prec; i++)
					{
						rprintfChar(*bp);
						bp++;
					}
					if (flush_left)
						while (f_width-- > 0)
							rprintfChar(' ');
					break;
            case '%':			// '%' character
					rprintfChar('%');
					break;
			}
			flush_left = 0, f_width = 0, prec = INF, hash = 0, do_long = 0;
			sign = 0;
			pad = ' ';
		}
	}

	va_end(ap);
	return 0;
}

unsigned char Isdigit(char c)
{
	if((c >= 0x30) && (c <= 0x39))
		return TRUE;
	else
		return FALSE;
}

int atoiRamRom(unsigned char stringInRom, char *str)
{
	int num = 0;;

	while(Isdigit(READMEMBYTE(stringInRom,str)))
	{
		num *= 10;
		num += ((READMEMBYTE(stringInRom,str++)) - 0x30);
	}
	return num;
}

#endif

