/////////////////////////////////////////////////////////////////////////////
// Name:        src/common/wxprintf.cpp
// Purpose:     wxWidgets wxPrintf() implementation
// Author:      Ove Kaven
// Modified by: Ron Lee, Francesco Montorsi
// Created:     09/04/99
// Copyright:   (c) wxWidgets copyright
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

// ===========================================================================
// headers, declarations, constants
// ===========================================================================

// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"


#ifndef WX_PRECOMP
    #include "wx/string.h"
    #include "wx/utils.h"     // for wxMin and wxMax
    #include "wx/log.h"
#endif

#include "wx/private/wxprintf.h"

#include <errno.h>

// ============================================================================
// printf() implementation
// ============================================================================

// special test mode: define all functions below even if we don't really need
// them to be able to test them
#ifdef wxTEST_PRINTF
    #undef wxCRT_VsnprintfW
    #undef wxCRT_VsnprintfA
#endif

// ----------------------------------------------------------------------------
// implement [v]snprintf() if the system doesn't provide a safe one
// or if the system's one does not support positional parameters
// (very useful for i18n purposes)
// ----------------------------------------------------------------------------

// ----------------------------------------------------------------------------
// common code for both ANSI and Unicode versions
// ----------------------------------------------------------------------------

#if !defined(wxCRT_VsnprintfW) || !defined(wxCRT_VsnprintfA)


// Copy chars from source to dest converting '%%' to '%'. Takes at most maxIn
// chars from source and write at most outMax chars to dest, returns the
// number of chars actually written. Does not treat null specially.
template<typename CharType>
static int wxCopyStrWithPercents(
        size_t maxOut,
        CharType *dest,
        size_t maxIn,
        const CharType *source)
{
    size_t written = 0;

    if (maxIn == 0)
        return 0;

    size_t i;
    for ( i = 0; i < maxIn-1 && written < maxOut; source++, i++)
    {
        dest[written++] = *source;
        if (*(source+1) == wxT('%'))
        {
            // skip this additional '%' character
            source++;
            i++;
        }
    }

    if (i < maxIn && written < maxOut)
        // copy last character inconditionally
        dest[written++] = *source;

    return written;
}

template<typename CharType>
static int wxDoVsnprintf(CharType *buf, size_t lenMax,
                         const CharType *format, va_list argptr)
{
    // useful for debugging, to understand if we are really using this function
    // rather than the system implementation
#if 0
    wprintf(L"Using wxCRT_VsnprintfW\n");
#endif

    // If the output buffer is of size 0 we can't write anything to it, not
    // even the terminating NUL, so just bail out: continuing would underflow
    // lenCur below and access buf[(size_t)-1], writing out of bounds.
    if ( lenMax == 0 )
        return -1;

    wxPrintfConvSpecParser<CharType> parser(format);

    wxPrintfArg argdata[wxMAX_SVNPRINTF_ARGUMENTS];

    size_t i;

    // number of characters in the buffer so far, must be less than lenMax
    size_t lenCur = 0;

    if (parser.posarg_present && parser.nonposarg_present)
    {
        buf[0] = 0;
        // Indicate to the caller that it's an unrecoverable error and not just
        // due to the buffer being too small.
        errno = EINVAL;
        return -1;      // format strings with both positional and
    }                   // non-positional conversion specifier are unsupported !!

    // on platforms where va_list is an array type, it is necessary to make a
    // copy to be able to pass it to LoadArg as a reference.
    bool ok = true;
    va_list ap;
    wxVaCopy(ap, argptr);

    // now load arguments from stack
    for (i=0; i < parser.nargs && ok; i++)
    {
        // !pspec[i] means that the user forgot a positional parameter (e.g. %$1s %$3s);
        // LoadArg == false means that wxPrintfConvSpec::Parse failed to set the
        // conversion specifier 'type' to a valid value...
        ok = parser.pspec[i] && parser.pspec[i]->LoadArg(&argdata[i], ap);
    }

    va_end(ap);

    // something failed while loading arguments from the variable list...
    // (e.g. the user repeated twice the same positional argument)
    if (!ok)
    {
        buf[0] = 0;
        errno = EINVAL;
        return -1;
    }

    // finally, process each conversion specifier with its own argument
    const CharType *toparse = format;
    for (i=0; i < parser.nspecs; i++)
    {
        wxPrintfConvSpec<CharType>& spec = parser.specs[i];

        // skip any asterisks, they're processed as part of the conversion they
        // apply to
        if ( spec.m_type == wxPAT_STAR )
            continue;

        // copy in the output buffer the portion of the format string between
        // last specifier and the current one
        size_t tocopy = ( spec.m_pArgPos - toparse );

        lenCur += wxCopyStrWithPercents(lenMax - lenCur, buf + lenCur,
                                        tocopy, toparse);
        if (lenCur == lenMax)
        {
            buf[lenMax - 1] = 0;
            return -1;      // not enough space in the output buffer !
        }

        // process this specifier directly in the output buffer
        int n = spec.Process(buf+lenCur, lenMax - lenCur,
                                      &argdata[spec.m_pos], lenCur);
        if (n == -1)
        {
            buf[lenMax-1] = wxT('\0');  // be sure to always NUL-terminate the string
            return -1;      // not enough space in the output buffer !
        }
        lenCur += n;

        // the +1 is because wxPrintfConvSpec::m_pArgEnd points to the last character
        // of the format specifier, but we are not interested to it...
        toparse = spec.m_pArgEnd + 1;
    }

    // copy portion of the format string after last specifier
    // NOTE: toparse is pointing to the character just after the last processed
    //       conversion specifier
    // NOTE2: the +1 is because we want to copy also the '\0'
    size_t tocopy = wxStrlen(format) + 1  - ( toparse - format ) ;

    lenCur += wxCopyStrWithPercents(lenMax - lenCur, buf + lenCur,
                                    tocopy, toparse) - 1;
    if (buf[lenCur])
    {
        buf[lenCur] = 0;
        return -1;     // not enough space in the output buffer !
    }

    // Don't do:
    //      wxASSERT(lenCur == wxStrlen(buf));
    // in fact if we embedded NULs in the output buffer (using %c with a '\0')
    // such check would fail

    return lenCur;
}

#endif // !defined(wxCRT_VsnprintfW) || !defined(wxCRT_VsnprintfA)

// ----------------------------------------------------------------------------
// wxCRT_VsnprintfW
// ----------------------------------------------------------------------------

#if !defined(wxCRT_VsnprintfW)

#if !wxUSE_WXVSNPRINTFW
    #error "wxUSE_WXVSNPRINTFW must be 1 if our wxCRT_VsnprintfW is used"
#endif

int wxCRT_VsnprintfW(wchar_t *buf, size_t len,
                     const wchar_t *format, va_list argptr)
{
    return wxDoVsnprintf(buf, len, format, argptr);
}

#else    // wxCRT_VsnprintfW is defined

#if wxUSE_WXVSNPRINTFW
    #error "wxUSE_WXVSNPRINTFW must be 0 if our wxCRT_VsnprintfW is not used"
#endif

#endif // !wxCRT_VsnprintfW

// ----------------------------------------------------------------------------
// wxCRT_VsnprintfA
// ----------------------------------------------------------------------------

#ifndef wxCRT_VsnprintfA

#if !wxUSE_WXVSNPRINTFA
    #error "wxUSE_WXVSNPRINTFA must be 1 if our wxCRT_VsnprintfA is used"
#endif

int wxCRT_VsnprintfA(char *buf, size_t len,
                     const char *format, va_list argptr)
{
    return wxDoVsnprintf(buf, len, format, argptr);
}

#else    // wxCRT_VsnprintfA is defined

#if wxUSE_WXVSNPRINTFA
    #error "wxUSE_WXVSNPRINTFA must be 0 if our wxCRT_VsnprintfA is not used"
#endif

#endif // !wxCRT_VsnprintfA
