/*
This program is distributed under the terms of the 'MIT license'. The text
of this licence follows...

Copyright (c) 2007 J.D.Medhurst (a.k.a. Tixy)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

/**
@file

@brief String formatting utilities header file.
*/

#ifndef STRINGF_H
#define STRINGF_H

/**
@addtogroup utils_stringf Utils - String formatting

Extensible classes for producing formated C strings.

This also contains implementations of the C standard library functions:
#vsprintf, #sprintf, #vsnprintf and #snprintf

@version 2009-30-12
	- Correct spelling of 'Conversion' in APIs and made some tweaks to docs.
@{
*/

#include <stdarg.h> // for va_list

/**
@brief Class for generating formatted strings in a similar way to the
C standard library function sprintf.

The produced text is passed to the pure virtual #Out(const char*,size_t) and
#Out(char,size_t) methods which are responsible for storing or outputing the
string. For an example see #StringBufferFormatter.

Supported conversion specifiers are:
- diouxX - Integers. May have optional length modifier.
- c - Character argument. Any length modifier is ignored.
- s - String argument. Any length modifier is ignored.
- p - Pointer argument.
- n - Number of characters written so far. May have optional length modifier.

Valid length modifiers are:
- hh - char
- h - short
- l - long
- ll - long long
- j - intmax_t
- z - size_t
- t - ptrdiff_t

The default implementation correctly parses the following conversion specifiers
(but produces no output)

- aAeEfgG - Floating point number with optional 'L' length modifier.
*/
class StringFormatter
	{
public:
	/**
	Produce a formatted string.

	@param formatString	A string specifying the format of the string. This is
						specified in the same way as the C standard library
						sprintf function.
	@param args			The arguments for the formatted string.

	@return The size of the resulting string.
	*/
	size_t VFormat(const char* formatString, va_list args);

	/**
	Produce a formatted string.

	@param formatString	A string specifying the format of the string. This is
						specified in the same way as the C standard library
						sprintf function.
	@param ...			The arguments for the formatted string.

	@return The size of the resulting string.
	*/
	size_t Format(const char* formatString, ...);

	/**
	Produce a single line of a hex dump. Each line represents the contents of up
	to 16 bytes.

    The format of the text is like:
	<pre>
	12345678  61 62 63 64  65 66 67 68  69 6a 6b 6c  6d 6e 6f 70  abcdefghijklmnop
	</pre>

	@param data				Address of data to be dumped.
	@param size				Size of data to be dumped. If >16 then only 16 bytes are dumped.
	@param addressOffset	Value to add to \a data for the address value to display in the dump.

	@return The number of bytes remaining to dump.
			I.e. \a size-16 if \a size was greater 16, else zero.
	*/
	size_t HexDumpLine(const void* data, size_t size, ptrdiff_t addressOffset=0);

protected:
	/**
	Called to 'output' generated text.

	@param text		Pointer to text.
	@param textSize	Size of text.
	*/
	virtual void Out(const char* text, size_t textSize) =0;

	/**
	Called to 'output' a repeated single character.

	@param character	The character to output.
	@param repeatCount	Number of times \a character should be output.
	*/
	virtual void Out(char character, size_t repeatCount) =0;

protected:
	/**
	Flags indicating the format of a single conversion specification. See StringFormatter::ConversionSpec.
	*/
	enum Flags
		{
		FlagMinus			= 1<<0,		///< Format flag '-' is present.
		FlagPlus			= 1<<1,		///< Format flag '+' is present.
		FlagSpace			= 1<<2,		///< Format flag ' ' is present.
		FlagHash			= 1<<3,		///< Format flag '#' is present.
		FlagZero			= 1<<4,		///< Format flag '0' is present.

		LengthMod_hh		= 1<<8,		///< Format length modifier 'hh' is present. I.e. argument is a char.
		LengthMod_h			= 1<<9,		///< Format length modifier 'h' is present. I.e. argument is a short.
		LengthMod_l			= 1<<10,	///< Format length modifier 'l' is present. I.e. argument is a long.
		LengthMod_ll		= 1<<11,	///< Format length modifier 'll' is present. I.e. argument is a long long.
		LengthMod_L			= 1<<12,	///< Format length modifier 'L' is present. I.e. argument is a double.
		LengthMod_unknown	= 1<<13,	///< Used to indicate that the argument size is unkown.

		SignedInt			= 1<<15		///< Used to indicate that an integer argument is signed.
		};

	/**
	@brief Information about a single conversion specification.
	*/
	class ConversionSpec
		{
	public:
		/**
		@param args	The argument list for the formatted string.
		*/
		inline ConversionSpec(va_list args)
			: Args(args)
			{}

		/**
		Decode the conversion specification \a format, and update this class members accordingly.

		@param format	Pointer to conversion specification.

		@return Address of the character immediately after the decoded format specification in \a format.
		*/
		const char* Decode(const char* format);

		/**
		Get an integer argument, of a specified size, from the argument list.

		@param flags	Bitmask of values from #Flags. Size of argument is detemined by
						one of: #LengthMod_ll, #LengthMod_l, #LengthMod_h or #LengthMod_hh;
						if none are present, 'int' is assumed.
						If #SignedInt bit is set, the integer argument is sign extended.

		@return The argument as type long long.
		*/
		longlong GetIntArg(unsigned flags);

		/**
		Get an integer argument from the argument list.

		@return The argument.
		*/
		inline int GetIntArg()
			{
			return va_arg(Args,int);
			}

		/**
		Get an pointer argument from the argument list.

		@return The argument.
		*/
		inline void* GetPointerArg()
			{
			return va_arg(Args,void*);
			}

	private:
		/**
		Read an decimal integer value from \a format, or if the first character
		is a '*', get an integer from Args.

		@param		format	Pointer to the text to read.
		@param[out] val		The value of the integer read, or zero if \a format
							did not point to either a decimal number or a '*'.

		@return Address of character after the decoded decimal number (or after the '*').
		*/
		const char* ReadInt(const char* format, int& val);

	public:
		va_list		Args;					///< The argument list of the formatted string.
		uint16_t	Flags;					///< Bitmask of flags from #Flags
		char		ConversionSpecifier;	///< The character representing the format, e.g. 'd' if the format is "%d".
		int			FieldWidth;				///< The field width, or zero if none specified
		int			Precision;				///< The specified precision, or -1 if none specified.
		};

	/**
	Called when an conversion specification is found which is not recognised.

	The implementation of this method should produce text corresponding to the
	specified format, or handle it as an error in an implementation specific manner.

	The default implementation of this method just calls #DefaultUnkownFormat.
	If you wish to provide your own implementation, define the macro
	STRINGFORMATTER_UNKOWNFORMAT_DEFINED when compiling stringf.cpp, this will
	omit the implementation defined in that file.

	@param[in,out] dstEnd	End of buffer where any generated text is stored.
							This is initialise to the end of a buffer of #FormatTextBufferSize
							bytes which this function can optionally make use of.
	@param spec				The decoded conversion specification which is to be handed.

	@return The start of the generated text.
			(The address immediately after the text is returned in \a dstEnd.)
	*/
	virtual char* UnkownFormat(char*& dstEnd, ConversionSpec& spec);

	/**
	Default implemenation for #UnkownFormat.

	This returns an empty sting and also discards any float type argument from \a args if
	the conversion specifier is any of 'a', 'A', 'e', 'E', 'f', 'F', 'g' or 'G'.

	@param[in,out] dstEnd	End of buffer where any generated text is stored. This is left unchanged.
	@param spec				The decoded conversion specification which is to be handed.

	@return /a dstEnd.
	*/
	char* DefaultUnkownFormat(char*& dstEnd, ConversionSpec& spec);

public:
	/**
	Convert an unsigned integer into a hexadecimal numeric string.

	The number is left padded with zeros if it contains less that \a precision digits,
	and is optionally prefixed by "0x".

	@param val			The value to convert.
	@param dst			The address of the character immediately after where the string is to be stored.
						I.e. this specifies where the string will end, not where it starts.
	@param precision	Minimum number of digits to store. A -ve value signifies 'use as many as required'.
	@param x			This must be either 'x' or 'X'. If 'x', all alphabetic characters are coverted
						in lower case, if 'X', they are coverted in upper case,
	@param prefix		If true, the converted value is prefixed with "0x".

	@return Pointer to the first character in the produced string.
	*/
	static char* PushHex(ulonglong val, char* dst, int precision=-1, int x='x', int prefix=false);

	/**
	Convert an unsigned integer into a octal numeric string.

	The number is left padded with zeros if it contains less that \a precision digits.

	@param val			The value to convert.
	@param dst			The address of the character immediately after where the string is to be stored.
						I.e. this specifies where the string will end, not where it starts.
	@param precision	Minimum number of digits to store. A -ve value signifies 'use as many as required'.
	@param prefix		If true, the converted value is prefixed "0" if it doesn't already start with one.

	@return Pointer to the first character in the produced string.
	*/
	static char* PushOctal(ulonglong val, char* dst, int precision=-1, int prefix=false);

	/**
	Convert an unsigned integer into a decimal numeric string.

	The number is left padded with zeros if it contains less that \a precision digits.

	@param val			The value to convert.
	@param dst			The address of the character immediately after where the string is to be stored.
						I.e. this specifies where the string will end, not where it starts.
	@param precision	Minimum number of digits to store. A -ve value signifies 'use as many as required'.

	@return Pointer to the first character in the produced string.
	*/
	static char* PushDecimal(ulonglong val, char* dst, int precision=-1);

#if !defined(_MSC_VER)
	/**
	Size of internal buffer used for the storing the generated text for a single conversion
	specification. This sets a limit on the precision value for integers.
	*/
	static const size_t FormatTextBufferSize = 130;

	/**
	Size of buffer required for #HexDumpLine.
	*/
	static const size_t HexDumpLineSize =
		sizeof(void*)*2		// digits in address
		+3*16				// 16 hex bytes with leading space
		+16/sizeof(void*)	// extra leading spaces used to group bytes
		+2					// two spaces
		+16					// 16 ascii characters
		+1+1;				// '\n' + NUL

#else
	// old versions of MSVC don't like static const members, so use enums...
	enum { FormatTextBufferSize = 130 };
	enum { HexDumpLineSize = sizeof(void*)*2+3*16+16/sizeof(void*)+2+16+1+1 };

#endif

	friend class StringFormatter::ConversionSpec;
	};


/**
@brief A StringFormatter which stores the output text in a specified buffer.

Example useage:

@code
	char buffer[100];
	StringBufferFormatter formatter(buffer,sizeof(buffer));
	formatter.Format("Part %d,",1);
	formatter.Format("and append a %s part","second");
	char* end = formatter.End(); // appends NUL (if room) and resets formatter for new string
	size_t stringLength = end-buffer;
@endcode

Important note: the produced string will only have a terminating null character
if the produced text has a size less than the size of the buffer.
*/
class StringBufferFormatter : public StringFormatter
	{
public:
	/**
	@param buffer	Address of the buffer to store the generated text in.
	@param size		Size of buffer.
	*/
	StringBufferFormatter(char* buffer, size_t size);

	// Implement output methods for StringFormatter...
	virtual void Out(const char* text, size_t textSize);
	virtual void Out(char character, size_t repeatCount);

	/**
	Terminate text generation.

	This appends a null character after the end of the generated text (if there
	is space in the buffer) and sets the buffer state to be empty.

	@return End of generated text. This is the address of the terminating null
			character, or the address immediately after the end of the buffer
			if the buffer was full.
	*/
	char* End();

protected:
	char* BufferStart;	///< Start of buffer used to store text.
	char* BufferEnd;	///< End of buffer used to store text. (The address immediately after the last byte.)
	char* BufferPtr;	///< Address to store the next byte of text to be 'output'.
	};


/**
Generate a formatted C string.

@param str		String where output is to be written.
@param format	Format string.
@param ap		Arguments for formatting.

@return Number of bytes written to \a str, excluding the terminating null byte.
*/
extern "C" int vsprintf(char* str, const char* format, va_list ap);

/**
Generate a formatted C string.

@param str		String where output is to be written.
@param format	Format string.
@param ...		Arguments for formatting.

@return Number of bytes written to \a str, excluding the terminating null byte.
*/
extern "C" int sprintf(char* str, const char* format, ...);

/**
Generate a formatted C string with a limited size.

@param str		String where output is to be written.
@param size		Size of the buffer refered to by \a str.
@param format	Format string.
@param ap		Arguments for formatting.

@return Number of bytes that would have been written to \a str, excluding the
		terminating null byte if \a size was large enough.
*/
extern "C" int vsnprintf(char* str, size_t size, const char* format, va_list ap);

/**
Generate a formatted C string with a limited size.

@param str		String where output is to be written.
@param size		Size of the buffer refered to by \a str.
@param format	Format string.
@param ...		Arguments for formatting.

@return Number of bytes that would have been written to \a str, excluding the
		terminating null byte if \a size was large enough.
*/
extern "C" int snprintf(char* str, size_t size, const char* format, ...);


/** @} */ // End of group

#endif // STRINGF_H
