/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: hxstrfmt.cpp,v 1.7.28.3 2004/07/09 01:45:59 hubbe Exp $
 * 
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */

#include "hxstring.h"
#include "hxassert.h"

#include "hlxclib/string.h"
#include "safestring.h"
#include "hlxclib/stdarg.h"
#include "hxassert.h"

static const int AlternateFlag   = 0x01;
static const int ZeroPadFlag     = 0x02;
static const int LeftJustifyFlag = 0x04;
static const int AddSpaceFlag    = 0x08;
static const int AddSignFlag     = 0x10;

static const int MaxWidthSize = 12;
static const int WidthParam = -2;
static const int WidthError = -3;

static const int MaxPrecisionSize = 12;
static const int NoPrecision    = -1;
static const int PrecisionParam = -2;
static const int PrecisionError = -3;

static const int NoLength = 0;
static const int ShortLength = 1;
static const int LongLength = 2;

static const int MaxConversionSize = 32;
static const int MaxFormatSize = 11;

static int GetFlags(const char*& pCur)
{
    int ret = 0;

    for (; *pCur && strchr("#0- +", *pCur); pCur++)
    {
	switch(*pCur) {
	case '#':
	    ret |= AlternateFlag;
	    break;

	case '0':
	    ret |= ZeroPadFlag;
	    break;

	case '-':
	    ret |= LeftJustifyFlag;
	    break;

	case ' ':
	    ret |= AddSpaceFlag;
	    break;
	    
	case '+':
	    ret |= AddSignFlag;
	    break;
	};
    }

    return ret;
}

static int GetWidth(const char*& pCur)
{
    int ret = 1;

    if (*pCur)
    {
	if (*pCur == '*')
	{
	    // The width is specified as a parameter
	    pCur++;
	    ret = WidthParam;
	}
	else
	{
	    if (strchr("123456789", *pCur))
	    {
		int i = 0;
		char widthBuf[MaxWidthSize]; /* Flawfinder: ignore */
		
		widthBuf[i++] = *pCur++;

		for(; (i < MaxWidthSize) && 
			*pCur && (strchr("0123456789", *pCur)); i++)
		    widthBuf[i] = *pCur++;

		if (i != MaxWidthSize)
		{
		    widthBuf[i] = '\0';

		    char* pEnd = 0;
		    long int tmp = strtol(widthBuf, &pEnd, 10);
		    
		    if (widthBuf[0] && !*pEnd)
			ret = (int)tmp;
		}
		else
		    ret = WidthError;
	    }
	}
    }

    return ret;
}

static int GetPrecision(const char*& pCur)
{
    int ret = NoPrecision;

    if (*pCur == '.')
    {
	pCur++;

	if (*pCur == '*')
	{
	    // The width is specified as a parameter
	    pCur++;
	    ret = PrecisionParam;
	}
	else
	{
	    int i = 0;
	    char precisionBuf[MaxPrecisionSize]; /* Flawfinder: ignore */
	    
	    for(; (i < MaxPrecisionSize) && 
		    *pCur && (strchr("0123456789", *pCur)); i++)
		precisionBuf[i] = *pCur++;
	    
	    if (i != MaxPrecisionSize)
	    {
		precisionBuf[i] = '\0';
	    
		if (strlen(precisionBuf))
		{
		    char* pEnd = 0;
		    long int tmp = strtol(precisionBuf, &pEnd, 10);
		    
		    if (precisionBuf[0] && !*pEnd)
			ret = (int)tmp;
		}
		else
		    ret = 0;
	    }
	    else
		ret = PrecisionError;
	}
    }

    return ret;
}

static int GetLength(const char*& pCur)
{
    int ret = NoLength;

    switch(*pCur) {
    case 'l':
	ret = LongLength;
	pCur++;
	break;
    case 'h':
	ret = ShortLength;
	pCur++;
	break;
    };

    return ret;
}

static void ConstructFormat(char* fmt, char type, int flags, int length,
			    int precision)
{
    int i = 0;
    fmt[i++] = '%';

    if (flags & AlternateFlag)
	fmt[i++] = '#';

    if (flags & LeftJustifyFlag)
	fmt[i++] = '-';

    if (flags & AddSpaceFlag)
	fmt[i++] = ' ';

    if (flags & AddSignFlag)
	fmt[i++] = '+';

    if (flags & ZeroPadFlag)
	fmt[i++] = '0';

    fmt[i++] = '*';

    if (precision != NoPrecision)
    {
	fmt[i++] = '.';
	fmt[i++] = '*';
    }

    if (length == ShortLength)
	fmt[i++] = 'h';
    
    if (length == LongLength)
	fmt[i++] = 'l';


    fmt[i++] = type;
    fmt[i] = '\0';

    HX_ASSERT(i < MaxFormatSize);
}

// This could be handled with a single template function, but for now
// we can't use templates. :( 
// I'll use a macro instead.

#define CONVERT_FUNC_DEF(funcName, convertType)                    \
static int funcName(const char* fmt,                               \
                    int width, int precision,                      \
                    convertType value)                             \
{                                                                  \
    int ret = 0;                                                   \
    int bufSize = width + MaxConversionSize;                       \
                                                                   \
    if (precision != NoPrecision)                                  \
        bufSize += precision;                                      \
    char* pBuf = new char[bufSize];                                \
    if (precision == NoPrecision)                                  \
	ret = SafeSprintf(pBuf, bufSize, fmt, width, value);       \
    else                                                           \
	ret = SafeSprintf(pBuf, bufSize, fmt, width, precision, value); \
    HX_ASSERT(ret < bufSize);                                      \
    delete [] pBuf;                                                \
    return ret;                                                    \
}

CONVERT_FUNC_DEF(ConvertInt, int)
CONVERT_FUNC_DEF(ConvertShort, short int)
CONVERT_FUNC_DEF(ConvertLong, long int)
CONVERT_FUNC_DEF(ConvertUInt, unsigned int)
CONVERT_FUNC_DEF(ConvertUShort, unsigned short int)
CONVERT_FUNC_DEF(ConvertULong, unsigned long int)
CONVERT_FUNC_DEF(ConvertDouble, double)
CONVERT_FUNC_DEF(ConvertChar, char)
CONVERT_FUNC_DEF(ConvertWChar, wchar_t)
CONVERT_FUNC_DEF(ConvertPtr, void*)

static bool ParseFormat(const char*& pCur, int& charCount, va_list& args)
{
    bool ret = true;

    const char* pTmp = pCur;

    int flags = GetFlags(pTmp);
    int width = 1;
    int precision = NoPrecision;
    int convertSize = 0;

    if ((width = GetWidth(pTmp)) == WidthError)
    {
	HX_ASSERT(!fprintf(stderr, "Width field too long '%s'\n", pCur));
	ret = false;
    }
    else if ((precision = GetPrecision(pTmp)) == PrecisionError)
    {
	HX_ASSERT(!fprintf(stderr, "Precision field too long '%s'\n", pCur));
	ret = false;
    }
    else
    {
	int length = GetLength(pTmp);
	char type = *pTmp++;

	if (width == WidthParam)
	{
	    width = va_arg(args, int);
	    if (width < 0)
	    {
		width = -width;
		flags |= LeftJustifyFlag;
	    }
	}

	if (precision == PrecisionParam)
	{
	    precision = va_arg(args, int);

	    if (precision < 0)
		precision = 0;
	}

	switch (type) {
	case 's':
	{
	    const char* pVal = va_arg(args, const char*);
	    if (length == LongLength)
	    {
		HX_ASSERT(!"Wide characters not supported");

		// Make up something large and hope that it's big enough
		convertSize = 512;
	    }
	    else
	    {
		if (precision >= 0)
		{
		    for(; (convertSize < precision) && 
			    pVal[convertSize]; convertSize++);
		}
		else
		    convertSize = strlen(pVal);
	    }
	}break;

	case 'd':
	case 'i':
	{
	    char fmt[MaxFormatSize]; /* Flawfinder: ignore */

	    ConstructFormat(fmt, type, flags, length, precision);

	    if (length == LongLength)
	    {
		long int val = va_arg(args, long int);
		convertSize = ConvertLong(fmt, width, precision, val);
	    }
	    else if (length == ShortLength)
	    {
		short int val = va_arg(args, int);
		convertSize = ConvertShort(fmt, width, precision, val);
	    }
	    else
	    {
		int val = va_arg(args, int);
		convertSize = ConvertInt(fmt, width, precision, val);;
	    }
	}break;

	case 'u':
	case 'o':
	case 'x':
	case 'X':
	{
	    char fmt[MaxFormatSize]; /* Flawfinder: ignore */

	    ConstructFormat(fmt, type, flags, length, precision);

	    if (length == LongLength)
	    {
		unsigned long int val = va_arg(args, unsigned long int);
		convertSize = ConvertULong(fmt, width, precision, val);
	    }
	    else if (length == ShortLength)
	    {
		unsigned short int val = va_arg(args, unsigned int);
		convertSize = ConvertUShort(fmt, width, precision, val);
	    }
	    else
	    {
		unsigned int val = va_arg(args, unsigned int);
		convertSize = ConvertUInt(fmt, width, precision, val);
	    }
	}break;

	case 'e':
	case 'E':
	case 'f':
	case 'g':
	case 'G':
	{
	    char fmt[MaxFormatSize]; /* Flawfinder: ignore */

	    ConstructFormat(fmt, type, flags, length, precision);

	    double val = va_arg(args, double);
	    convertSize = ConvertDouble(fmt, width, precision, val);
	}break;

	case 'c':
	{
	    char fmt[MaxFormatSize]; /* Flawfinder: ignore */

	    ConstructFormat(fmt, type, flags, length, precision);

	    if (length == LongLength)
	    {
		wchar_t val = va_arg(args, int);
		convertSize = ConvertWChar(fmt, width, precision, val);
	    }
	    else
	    {
		char val = va_arg(args, int);
		convertSize = ConvertChar(fmt, width, precision, val);
	    }
	}break;

	case 'p':
	{
	    char fmt[MaxFormatSize]; /* Flawfinder: ignore */

	    ConstructFormat(fmt, type, flags, length, precision);

	    void* val = va_arg(args, void*);
	    convertSize = ConvertPtr(fmt, width, precision, val);
	}break;

	case '%':
	    convertSize = 1;
	    break;

	default:
	{
	    HX_ASSERT(!"Unknown format type");
	    // Make up something large and hope that it's big enough
	    convertSize = 512;
	}break;
	};
	
    }

    if (ret)
    {
	charCount += (convertSize > width) ? convertSize : width;

	pCur = pTmp;
    }

    return ret;
}

static int GuessSize(const char* pFormat, va_list& args)
{
    int ret = 1;

    const char* pCur = pFormat;

    while(*pCur && ret != -1)
    {
	switch(*pCur) {
	case '%':
	    pCur++;

	    // Handle format characters
	    if (!ParseFormat(pCur, ret, args))
		ret = -1;
	    break;
	default:
	    ret++;
	    pCur++;
	    break;
	};
    }

    return ret;
}

// This fudge factor is added to the guess to protect us
// from guessing wrong
static const int FormatFudgeFactor = 128;

void CHXString::Format(const char* pFmt, ...)
{
    va_list args;
    va_start(args, pFmt);

    // Guess the size
    int estimatedSize = GuessSize(pFmt, args);

    va_end(args);

    va_start(args, pFmt);

    if (m_pRep)
	m_pRep->Resize(estimatedSize + FormatFudgeFactor);
    else
	m_pRep = new CHXStringRep(estimatedSize + FormatFudgeFactor);

    int actualSize = vsnprintf(m_pRep->GetBuffer(), m_pRep->GetBufferSize(),
                               pFmt, args);

    HX_ASSERT(actualSize < estimatedSize);

    m_pRep->SetStringSize(actualSize);

    FreeExtra();

    va_end(args);

}

