/**
 @file format.cpp

 @author Morgan McGuire, graphics3d.com

 @created 2000-09-09
 @edited  2006-08-14
*/

#include "G3D/format.h"
#include "G3D/platform.h"
#include "G3D/System.h"

#ifdef _MSC_VER
    // disable: "C++ exception handler used"
#   pragma warning (push)
#   pragma warning (disable : 4530)
#endif // _MSC_VER

// If your platform does not have vsnprintf, you can find a
// implementation at http://www.ijs.si/software/snprintf/

namespace G3D {

std::string __cdecl format(const char* fmt,...) {
    va_list argList;
    va_start(argList,fmt);
    std::string result = vformat(fmt, argList);
    va_end(argList);

    return result;
}

#if defined(_MSC_VER) && (_MSC_VER >= 1300)
// Both MSVC seems to use the non-standard vsnprintf
// so we are using vscprintf to determine buffer size, however
// only MSVC7 and up headers include vscprintf for some reason.
std::string vformat(const char *fmt, va_list argPtr) {
    // We draw the line at a 1MB string.
    const int maxSize = 1000000;

    // If the string is less than 161 characters,
    // allocate it on the stack because this saves
    // the malloc/free time.
    const int bufSize = 161;
    char stackBuffer[bufSize];

    // MSVC does not support va_copy
    int actualSize = _vscprintf(fmt, argPtr) + 1;

    if (actualSize > bufSize) {

        // Now use the heap.
        char* heapBuffer = NULL;

        if (actualSize < maxSize) {

            heapBuffer = (char*)System::malloc(maxSize + 1);
            _vsnprintf(heapBuffer, maxSize, fmt, argPtr);
            heapBuffer[maxSize] = '\0';
        } else {
            heapBuffer = (char*)System::malloc(actualSize);
            vsprintf(heapBuffer, fmt, argPtr);            
        }

        std::string formattedString(heapBuffer);
        System::free(heapBuffer);
        return formattedString;
    } else {

        vsprintf(stackBuffer, fmt, argPtr);
        return std::string(stackBuffer);
    }
}

#elif defined(_MSC_VER) && (_MSC_VER < 1300)

std::string vformat(const char *fmt, va_list argPtr) {
    // We draw the line at a 1MB string.
    const int maxSize = 1000000;

    // If the string is less than 161 characters,
    // allocate it on the stack because this saves
    // the malloc/free time.
    const int bufSize = 161;
    char stackBuffer[bufSize];

    // MSVC6 doesn't support va_copy, however it also seems to compile
    // correctly if we just pass our argument list along.  Note that 
    // this whole code block is only compiled if we're on MSVC6 anyway
    int actualWritten = _vsnprintf(stackBuffer, bufSize, fmt, argPtr);

    // Not a big enough buffer, bufSize characters written
    if (actualWritten == -1) {

        int heapSize = 512;
        double powSize = 1.0;
        char* heapBuffer = (char*)System::malloc(heapSize);
        
        while ((_vsnprintf(heapBuffer, heapSize, fmt, argPtr) == -1) &&
            (heapSize  < maxSize)) {

            heapSize = iCeil(heapSize * ::pow((double)2.0, powSize++));
            heapBuffer = (char*)System::realloc(heapBuffer, heapSize);
        }

        heapBuffer[heapSize-1] = '\0';

        std::string heapString(heapBuffer);
        System::free(heapBuffer);

        return heapString;
    } else {

        return std::string(stackBuffer);
    }
}

#else

// glibc 2.1 has been updated to the C99 standard
std::string vformat(const char* fmt, va_list argPtr) {
    // If the string is less than 161 characters,
    // allocate it on the stack because this saves
    // the malloc/free time.  The number 161 is chosen
    // to support two lines of text on an 80 character
    // console (plus the null terminator).
    const int bufSize = 161;
    char stackBuffer[bufSize];

    va_list argPtrCopy;
    va_copy(argPtrCopy, argPtr);
    int numChars = vsnprintf(stackBuffer, bufSize, fmt, argPtrCopy);
    va_end(argPtrCopy);

    if (numChars >= bufSize) {
      // We didn't allocate a big enough string.
      char* heapBuffer = (char*)System::malloc((numChars + 1) * sizeof(char));

      debugAssert(heapBuffer);
      int numChars2 = vsnprintf(heapBuffer, numChars + 1, fmt, argPtr);
      debugAssert(numChars2 == numChars);
      (void)numChars2;

      std::string result(heapBuffer);
      
      System::free(heapBuffer);

      return result;

    } else {

      return std::string(stackBuffer);

    }
}

#endif

} // namespace

#ifdef _MSC_VER
#   pragma warning (pop)
#endif
