//*****************************************************************************
//
//! @file am_util_stdio.c
//!
//! @brief A few printf-style functions for use with Ambiq products
//!
//! Functions for performing printf-style operations without dynamic memory
//! allocation.
//!
//! For further information about this module concerning its history, uses,
//! and limitations, please see the Ambiq Micro KB article "Q&A: What does
//! the AmbiqSuite SDK am_util_stdio_printf() function do?" at:
//!
//! https://support.ambiqmicro.com/hc/en-us/articles/360040441631
//!
//! @addtogroup stdio STDIO - Ambiq's Implementation
//! @ingroup utils
//! @{
//
//*****************************************************************************

//*****************************************************************************
//
// Copyright (c) 2024, Ambiq Micro, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// Third party software included in this distribution is subject to the
// additional license terms as defined in the /docs/licenses directory.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// This is part of revision ambiqvos_r4.5-fdfa8cf6a4 of the AmbiqSuite Development Package.
//
//*****************************************************************************

#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>
#include "am_util_stdio.h"

//*****************************************************************************
//
// Global Variables
//
//*****************************************************************************

// function pointer for printf
am_util_stdio_print_char_t g_pfnCharPrint;

// buffer for printf
#ifdef AM_PART_APOLLO5_API
static char g_prfbuf[AM_PRINTF_BUFSIZE] __attribute__((aligned(4096)));
#else
static char g_prfbuf[AM_PRINTF_BUFSIZE];
#endif

// Flag to do conversion of '\n' to '\n\r' in sprintf()
static bool g_bTxtXlate = false;

//*****************************************************************************
//
// Sets the interface for printf calls.
//
//*****************************************************************************
void
am_util_stdio_printf_init(am_util_stdio_print_char_t pfnCharPrint)
{
    g_pfnCharPrint = pfnCharPrint;
}

//*****************************************************************************
//
// Converts strings to 32-bit unsigned integers.
//
//*****************************************************************************
uint32_t
am_util_stdio_strtoul(const char *str, char **endptr, int base)
{
    char *pos;
    uint32_t ui32BaseVal;
    uint32_t ui32RetVal;

    //
    // Prepare a pointer to start advancing through the string.
    //
    pos = (char *)str;

    //
    // Determine what base we are using. Default to '16', but change to other
    // values as specified by the user. If the user didn't specify anything,
    // try to guess the base value from looking at the first few characters of
    // the input
    //
    ui32BaseVal = 16;

    //
    // Switch to octal for a leading zero
    //
    if (*pos == '0')
    {
        ui32BaseVal = 8;
        pos++;

        //
        // Switch back to hex for a leading '0x'
        //
        if (*pos == 'x')
        {
            ui32BaseVal = 16;
            pos++;
        }
    }

    //
    // No matter what, if the user specified a base value, use that as the real
    // base value.
    //
    if (base)
    {
        ui32BaseVal = base;
    }

    //
    // Start accumulating the converted integer value
    //
    ui32RetVal = 0;

    //
    // Loop through the digits, one character at a time. End the loop if the
    // number is out of range
    //
    while ((*pos >= 'a' && *pos <= 'f' && ui32BaseVal == 16) ||
           (*pos >= 'A' && *pos <= 'F' && ui32BaseVal == 16) ||
           (*pos >= '0' && *pos <= '9'))
    {
        //
        // Make sure to stop if we hit a NULL byte.
        //
        if (*pos == 0)
        {
            break;
        }

        //
        // Multiply by the base value to move up one 'digit'
        //
        ui32RetVal *= ui32BaseVal;

        //
        // Add the value of the next character.
        //
        if (*pos >= '0' && *pos <= '9')
        {
            ui32RetVal += *pos - '0';
        }
        else if (*pos >= 'A' && *pos <= 'F')
        {
            ui32RetVal += (*pos - 'A') + 10;
        }
        else
        {
            ui32RetVal += (*pos - 'a') + 10;
        }

        //
        // Grab the next character.
        //
        pos++;
    }

    //
    // If we get here, hopefully it means that we have parsed a number
    // correctly. The 'pos' pointer should already be pointing at the character
    // right after the last valid number, so set the enptr appropriately, and
    // return the calculated numerical value of the string.
    //
    if (endptr)
    {
        *endptr = pos;
    }

    return ui32RetVal;
}

//*****************************************************************************
//
//  Divide an unsigned 32-bit value by 10.
//
//  Note: Adapted from Ch10 of Hackers Delight (hackersdelight.org).
//
//*****************************************************************************
static uint64_t
divu64_10(uint64_t ui64Val)
{
    uint64_t q64, r64;
    uint32_t q32, r32, ui32Val;

    //
    // If a 32-bit value, use the more optimal 32-bit routine.
    //
    if ( ui64Val >> 32 )
    {
        q64 = (ui64Val>>1) + (ui64Val>>2);
        q64 += (q64 >> 4);
        q64 += (q64 >> 8);
        q64 += (q64 >> 16);
        q64 += (q64 >> 32);
        q64 >>= 3;
        r64 = ui64Val - q64*10;
        return q64 + ((r64 + 6) >> 4);
    }
    else
    {
        ui32Val = (uint32_t)(ui64Val & 0xffffffff);
        q32 = (ui32Val>>1) + (ui32Val>>2);
        q32 += (q32 >> 4);
        q32 += (q32 >> 8);
        q32 += (q32 >> 16);
        q32 >>= 3;
        r32 = ui32Val - q32*10;
        return (uint64_t)(q32 + ((r32 + 6) >> 4));
    }
}

//*****************************************************************************
//
// Return the number of decimal digits in an uint64_t.
//
// example: 10000 return 5, 123 returns 3.
//
//*****************************************************************************
static int
ndigits_in_u64(uint64_t ui64Val)
{
    int iNDigits = ui64Val ? 0 : 1;

    while ( ui64Val )
    {
        //
        // ui32Val /= 10;
        //
        ui64Val = divu64_10(ui64Val);
        ++iNDigits;
    }

    return iNDigits;
}

//*****************************************************************************
//
// Return the number of decimal digits in a 64-bit integer.
//
// Note: Does not include the '-' sign.
//
// example: -3 returns 1, 3 returns 1, 15 returns 2, -15 returns 2, ...
//
//*****************************************************************************
static int
ndigits_in_i64(int64_t i64Val)
{
    if ( i64Val < 0 )
    {
        //
        // Get absolute value
        //
        i64Val = -i64Val;
    }

    return ndigits_in_u64((uint64_t) i64Val);
}

//*****************************************************************************
//
// Return the number of hex digits in an uint64_t.
//
//*****************************************************************************
static int
ndigits_in_hex(uint64_t ui64Val)
{
    int iDigits = ui64Val ? 0 : 1;

    while ( ui64Val )
    {
        ui64Val >>= 4;
        ++iDigits;
    }

    return iDigits;
}

//*****************************************************************************
//
// Converts a string representing a decimal value to an int32_t.
//
// Returns the int32_t integer value.
//
// Note: If a count of the number of chars is desired, then provide
// pui32CharCnt.
//
//*****************************************************************************
static uint32_t
decstr_to_int(const char *pcStr, uint32_t *pui32CharCnt)
{
    bool bNeg = false;
    uint32_t ui32Val = 0, uCnt = 0;

    if ( *pcStr == '-')
    {
        bNeg = true;
        pcStr++;
        uCnt++;
    }

    while ( *pcStr >= '0'  &&  *pcStr <= '9' )
    {
        ++uCnt;

        //
        // Multiply accumulated value by 10.
        //
        ui32Val *= 10;

        //
        // Add in the new low digit.
        //
        ui32Val += (*pcStr - '0');
        pcStr++;
    }

    if ( pui32CharCnt )
    {
        *pui32CharCnt = uCnt;
    }

    return bNeg ? -ui32Val : ui32Val;
}

//*****************************************************************************
//
// Converts ui64Val to a string.
// Note: pcBuf[] must be sized for a minimum of 21 characters.
//
// Returns the number of decimal digits in the string.
//
// NOTE: If pcBuf is NULL, will compute a return ui64Val only (no chars
// written).
//
//*****************************************************************************
static int
uint64_to_str(uint64_t ui64Val, char *pcBuf)
{
    char tbuf[25];
    int ix = 0, iNumDig = 0;
    unsigned uMod;
    uint64_t u64Tmp;

    do
    {
        //
        // Divide by 10
        //
        u64Tmp = divu64_10(ui64Val);

        //
        // Get modulus
        //
        uMod = ui64Val - (u64Tmp * 10);

        tbuf[ix++] = uMod + '0';
        ui64Val = u64Tmp;
    } while ( ui64Val );

    //
    // Save the total number of digits
    //
    iNumDig = ix;

    //
    // Now, reverse the buffer when saving to the caller's buffer.
    //
    if ( pcBuf )
    {
        while ( ix-- )
        {
            *pcBuf++ = tbuf[ix];
        }

        //
        // Terminate the caller's buffer
        //
        *pcBuf = 0x00;
    }

    return iNumDig;
}

//*****************************************************************************
//
// Converts ui64Val to a hex string.  Alpha chars are lower case.
// Input:
//  ui64Val = Value to be converted.
//  pcBuf[] must be sized for a minimum of 17 characters.
//
// Returns the number of hex digits required for ui64Val (does not
//  include the terminating NULL char in the string).
//
// NOTE: If pcBuf is NULL, will compute a return value only (no chars
// written).
//
//*****************************************************************************
static int
uint64_to_hexstr(uint64_t ui64Val, char *pcBuf, bool bLower)
{
    int iNumDig, ix = 0;
    char cCh, tbuf[20];

    if ( ui64Val == 0 )
    {
        tbuf[ix++] = '0';   // Print a '0'
    }

    while ( ui64Val )
    {
        cCh = ui64Val & 0xf;

        //
        // Alpha character
        //
        if ( cCh > 9 )
        {
            cCh += bLower ? 0x27 : 0x7;
        }

        tbuf[ix++] = cCh + '0';
        ui64Val >>= 4;
    }

    //
    // Save the total number of digits
    //
    iNumDig = ix;

    //
    // Now, reverse the buffer when saving to the callers buffer.
    //
    if (pcBuf)
    {
        while (ix--)
        {
            *pcBuf++ = tbuf[ix];
        }

        //
        // Terminate the caller's buffer
        //
        *pcBuf = 0;
    }

    return iNumDig;
}

//*****************************************************************************
//
// Return length of the given string.
//
//*****************************************************************************
static uint32_t
simple_strlen(char *pcBuf)
{
    uint32_t ui32RetVal = 0;
    if ( !pcBuf )
    {
        return ui32RetVal;
    }

    while ( *pcBuf++ )
    {
        ui32RetVal++;
    }
    return ui32RetVal;
}

//*****************************************************************************
//
// Pad a string buffer with pad characters.
//
//*****************************************************************************
static int32_t
padbuffer(char *pcBuf, uint8_t cPadChar, int32_t i32NumChars)
{
    int32_t i32Cnt = 0;

    if ( i32NumChars <= 0 )
    {
        return i32Cnt;
    }

    while ( i32NumChars-- )
    {
        if ( pcBuf )
        {
            *pcBuf++ = cPadChar;
        }
        i32Cnt++;
    }

    return i32Cnt;
}

//*****************************************************************************
//
// Text mode translates linefeed (\n) characters to carriage return/
// linefeed (CR/LF) combinations in printf() and sprintf() functions.
//
//*****************************************************************************
bool
am_util_stdio_textmode_set(bool bSetTextTranslationMode)
{
    bool bRet = g_bTxtXlate;

    //
    // true=cvt LF chars to CR/LF
    //
    g_bTxtXlate = bSetTextTranslationMode;

    //
    // return previous mode.
    //
    return bRet;
}

//*****************************************************************************
//
//  Float to ASCII text. A basic implementation for providing support for
//  single-precision %f.
//
//  param
//      fValue     = Float value to be converted.
//      pcBuf      = Buffer to place string AND input of buffer size.
//      iPrecision = Desired number of decimal places.
//      IMPORTANT: On entry, the first 32-bit word of pcBuf must
//                 contain the size (in bytes) of the buffer!
//                 The recommended size is at least 16 bytes.
//
//  This function performs a basic translation of a floating point single
//  precision value to a string.
//
//  return Number of chars printed to the buffer.
//
//*****************************************************************************
#define AM_FTOA_ERR_VAL_TOO_SMALL   -1
#define AM_FTOA_ERR_VAL_TOO_LARGE   -2
#define AM_FTOA_ERR_BUFSIZE         -3

typedef union
{
    int32_t I32;
    float F;
} i32fl_t;

static int ftoa(float fValue, char *pcBuf, int iPrecision)
{
    i32fl_t unFloatValue;
    int iExp2, iBufSize;
    int32_t i32Significand, i32IntPart, i32FracPart;
    char *pcBufInitial, *pcBuftmp;

    iBufSize = *(uint32_t*)pcBuf;
    if (iBufSize < 4)
    {
        return AM_FTOA_ERR_BUFSIZE;
    }

    if (fValue == 0.0f)
    {
        // "0.0"
        *(uint32_t*)pcBuf = 0x00 << 24 | ('0' << 16) | ('.' << 8) | ('0' << 0);
        return 3;
    }

    pcBufInitial = pcBuf;

    unFloatValue.F = fValue;

    iExp2 = ((unFloatValue.I32 >> 23) & 0x000000FF) - 127;
    i32Significand = (unFloatValue.I32 & 0x00FFFFFF) | 0x00800000;
    i32FracPart = 0;
    i32IntPart = 0;

    if (iExp2 >= 31)
    {
        return AM_FTOA_ERR_VAL_TOO_LARGE;
    }
    else if (iExp2 < -23)
    {
        return AM_FTOA_ERR_VAL_TOO_SMALL;
    }
    else if (iExp2 >= 23)
    {
        i32IntPart = i32Significand << (iExp2 - 23);
    }
    else if (iExp2 >= 0)
    {
        i32IntPart = i32Significand >> (23 - iExp2);
        i32FracPart = (i32Significand << (iExp2 + 1)) & 0x00FFFFFF;
    }
    else // if (iExp2 < 0)
    {
        i32FracPart = (i32Significand & 0x00FFFFFF) >> -(iExp2 + 1);
    }

    if (unFloatValue.I32 < 0)
    {
        *pcBuf++ = '-';
    }

    if (i32IntPart == 0)
    {
        *pcBuf++ = '0';
    }
    else
    {
        if (i32IntPart > 0)
        {
            uint64_to_str(i32IntPart, pcBuf);
        }
        else
        {
            *pcBuf++ = '-';
            uint64_to_str(-i32IntPart, pcBuf);
        }
        while (*pcBuf)    // Get to end of new string
        {
            pcBuf++;
        }
    }

    //
    // Now, begin the fractional part
    //
    *pcBuf++ = '.';

    if (i32FracPart == 0)
    {
        *pcBuf++ = '0';
    }
    else
    {
        int jx, iMax;

        iMax = iBufSize - (pcBuf - pcBufInitial) - 1;
        iMax = (iMax > iPrecision) ? iPrecision : iMax;

        for (jx = 0; jx < iMax; jx++)
        {
            i32FracPart *= 10;
            *pcBuf++ = (i32FracPart >> 24) + '0';
            i32FracPart &= 0x00FFFFFF;
        }

        //
        // Per the printf spec, the number of digits printed to the right of the
        // decimal point (i.e. iPrecision) should be rounded.
        // Some examples:
        // Value        iPrecision          Formatted value
        // 1.36399      Unspecified (6)     1.363990
        // 1.36399      3                   1.364
        // 1.36399      4                   1.3640
        // 1.36399      5                   1.36399
        // 1.363994     Unspecified (6)     1.363994
        // 1.363994     3                   1.364
        // 1.363994     4                   1.3640
        // 1.363994     5                   1.36399
        // 1.363995     Unspecified (6)     1.363995
        // 1.363995     3                   1.364
        // 1.363995     4                   1.3640
        // 1.363995     5                   1.36400
        // 1.996        Unspecified (6)     1.996000
        // 1.996        2                   2.00
        // 1.996        3                   1.996
        // 1.996        4                   1.9960
        //
        // To determine whether to round up, we'll look at what the next
        // decimal value would have been.
        //
        if ( ((i32FracPart * 10) >> 24) >= 5 )
        {
            //
            // Yes, we need to round up.
            // Go back through the string and make adjustments as necessary.
            //
            pcBuftmp = pcBuf - 1;
            while ( pcBuftmp >= pcBufInitial )
            {
                if ( *pcBuftmp == '.' )
                {
                }
                else if ( *pcBuftmp == '9' )
                {
                    *pcBuftmp = '0';
                }
                else
                {
                    *pcBuftmp += 1;
                    break;
                }
                pcBuftmp--;
            }
        }
    }

    //
    // Terminate the string and we're done
    //
    *pcBuf = 0x00;

    return (pcBuf - pcBufInitial);
} // ftoa()

//******************************************************************************
//
// Format data into string. (va_list implementation)
//
//******************************************************************************
uint32_t
am_util_stdio_vsprintf(char *pcBuf, const char *pcFmt, va_list pArgs)
{
    char *pcStr;
    uint64_t ui64Val;
    int64_t i64Val;
    uint32_t ui32NumChars, ui32CharCnt = 0;
    int iWidth, iVal, iPrecision;
    uint8_t ui8CharSpecifier, ui8PadChar;
    bool bLower, bLongLong, bNeg;
    uint32_t ui32strlen = 0;

    while ( *pcFmt != 0x0 )
    {
        iPrecision = 6;             // printf() default precision for %f is 6

        if ( *pcFmt != '%' )
        {
            //
            // Accumulate the string portion of the format specification.
            //
            if ( pcBuf )
            {
                // If '\n', convert to '\r\n'
                if ( *pcFmt == '\n'  &&  g_bTxtXlate )
                {
                    *pcBuf++ = '\r';
                    ++ui32CharCnt;
                }
                *pcBuf++ = *pcFmt;
            }

            ++pcFmt;
            ++ui32CharCnt;
            continue;
        }

        //
        // Handle the specifier.
        //
        ++pcFmt;
        bLower = bLongLong = false;

        //
        // Default to space as ui8PadChar
        //
        ui8PadChar = ' ';

        if ( *pcFmt == '0' )
        {
            ui8PadChar = '0';
            ++pcFmt;
        }

        //
        // Width specifier
        //
        iWidth = decstr_to_int(pcFmt, &ui32NumChars);
        pcFmt += ui32NumChars;

        //
        // For now, only support a negative width specifier for %s
        //
        if ( ( *pcFmt != 's' )  &&  ( iWidth < 0 ) )
        {
            iWidth = -iWidth;
        }

        //
        // Check for precision specifier
        //
        if (*pcFmt == '.')
        {
            ++pcFmt;
            iPrecision = decstr_to_int(pcFmt, &ui32NumChars);
            pcFmt += ui32NumChars;
        }

        //
        // Check for the long or long long length field sub-specifiers, 'l' or
        // 'll', which must be a modifier for either 'd', 'i', 'u', 'x', or 'X'
        // (or even 'o', which is not currently supported). Other sub-specifiers
        // like 'hh','h', etc. are not currently handled.
        // Note - 'l' is used in Coremark, a primary reason it's supported here.
        //
        if ( *pcFmt == 'l' )
        {
            pcFmt++;
            if ( *pcFmt == 'l' )    // "ll" (long long)
            {
                pcFmt++;
                bLongLong = true;
            }
        }

        switch ( *pcFmt )
        {
            case 'c':
                ui8CharSpecifier = va_arg(pArgs, uint32_t);

                if ( pcBuf )
                {
                    *pcBuf++ = ui8CharSpecifier;
                }

                ++ui32CharCnt;
                break;

            case 's':
                pcStr = va_arg(pArgs, char *);

                //
                // For %s, we support the width specifier. If iWidth is negative
                // the string is left-aligned (padding on the right).  Otherwise
                // the string is padded at the beginning with spaces.
                //
                ui32strlen = simple_strlen(pcStr);
                if ( iWidth > 0 )
                {
                    // Pad the beginning of the string (right-aligned).
                    if ( ui32strlen < iWidth )
                    {
                        // String needs some padding.
                        iWidth -= ui32strlen;
                        iWidth = padbuffer(pcBuf, ui8PadChar, iWidth);
                        pcBuf += pcBuf ? iWidth : 0;
                        ui32CharCnt += iWidth;
                        iWidth = 0;
                    }
                }

                while (*pcStr != 0x0)
                {
                    if ( pcBuf )
                    {
                        *pcBuf++ = *pcStr;
                    }

                    ++pcStr;
                    ++ui32CharCnt;
                }

                if ( iWidth )
                {
                    iWidth = -iWidth;

                    // Pad the end of the string (left-aligned).
                    if ( ui32strlen < iWidth )
                    {
                        // String needs some padding.
                        iWidth -= ui32strlen;
                        iWidth = padbuffer(pcBuf, ui8PadChar, iWidth);
                        pcBuf += pcBuf ? iWidth : 0;
                        ui32CharCnt += iWidth;
                        iWidth = 0;
                    }
                }
                break;

            case 'x':
                bLower = true;
            case 'X':
                ui64Val = bLongLong ? va_arg(pArgs, uint64_t) :
                                      va_arg(pArgs, uint32_t);

                if ( iWidth )
                {
                    //
                    // Compute # of leading chars
                    //
                    iWidth -= ndigits_in_hex(ui64Val);

                    iWidth = padbuffer(pcBuf, ui8PadChar, iWidth);
                    pcBuf += pcBuf ? iWidth : 0;
                    ui32CharCnt += iWidth;
                    iWidth = 0;
                }

                iVal = uint64_to_hexstr(ui64Val, pcBuf, bLower);

                if ( pcBuf )
                {
                    pcBuf += iVal;
                }

                ui32CharCnt += iVal;
                break;

            case 'u':
                ui64Val = bLongLong ? va_arg(pArgs, uint64_t) :
                                      va_arg(pArgs, uint32_t);

                if ( iWidth )
                {
                    //
                    // We need to pad the beginning of the value.
                    // Compute # of leading chars
                    //
                    iWidth -= ndigits_in_u64(ui64Val);

                    iWidth = padbuffer(pcBuf, ui8PadChar, iWidth);
                    pcBuf += pcBuf ? iWidth : 0;
                    ui32CharCnt += iWidth;
                    iWidth = 0;
                }

                iVal = uint64_to_str(ui64Val, pcBuf);

                if ( pcBuf )
                {
                    pcBuf += iVal;
                }

                ui32CharCnt += iVal;
                break;

            case 'd':
            case 'i':
                //
                // Output for a negative number, for example, -5:
                //   %d:-5
                //  %5d:   -5
                // %05d:-0005
                //
                i64Val = bLongLong ? va_arg(pArgs, int64_t) :
                                     va_arg(pArgs, int32_t);

                //
                // Get absolute value
                //
                if ( i64Val < 0 )
                {
                    ui64Val = -i64Val;          // Get absolute value
                    bNeg = true;
                }
                else
                {
                    ui64Val = i64Val;
                    bNeg = false;
                }

                if ( iWidth )
                {
                    //
                    // We need to pad the beginning of the value.
                    // Compute # of leading chars
                    //
                    iWidth -= ndigits_in_i64(ui64Val);

                    if ( bNeg )
                    {
                        --iWidth;

                        //
                        // Allow for the negative sign
                        //
                        if ( ui8PadChar == '0' )
                        {
                            //
                            // Print the neg sign BEFORE the leading zeros
                            //
                            if ( pcBuf )
                            {
                                *pcBuf++ = '-';
                            }

                            ++ui32CharCnt;
                        }
                    }

                    iWidth = padbuffer(pcBuf, ui8PadChar, iWidth);
                    pcBuf += pcBuf ? iWidth : 0;
                    ui32CharCnt += iWidth;
                    iWidth = 0;

                    if ( bNeg  &&  (ui8PadChar == ' ') )
                    {
                        //
                        // Print the neg sign AFTER the leading blanks
                        //
                        if ( pcBuf )
                        {
                            *pcBuf++ = '-';
                        }

                        ++ui32CharCnt;
                    }
                }
                else
                {
                    if ( bNeg )
                    {
                        if ( pcBuf )
                        {
                            *pcBuf++ = '-';
                        }
                        ++ui32CharCnt;
                    }
                }

                iVal = uint64_to_str(ui64Val, pcBuf);

                if ( pcBuf )
                {
                    pcBuf += iVal;
                }

                ui32CharCnt += iVal;
                break;

            case 'f':
            case 'F':
                if ( pcBuf )
                {
                    float fValue = va_arg(pArgs, double);

                    //
                    // pcBuf is an input (size of buffer) and also an output of ftoa()
                    //
                    *(uint32_t*)pcBuf = 20;

                    iVal = ftoa(fValue, pcBuf, iPrecision);
                    if ( iVal < 0 )
                    {
                        uint32_t u32PrntErrVal;
                        if ( iVal == AM_FTOA_ERR_VAL_TOO_SMALL )
                        {
                            u32PrntErrVal = (0x00 << 24) | ('0' << 16) |
                                            ('.' << 8)   | ('0' << 0);  // "0.0"
                        }
                        else if ( iVal == AM_FTOA_ERR_VAL_TOO_LARGE )
                        {
                            u32PrntErrVal = (0x00 << 24) | ('#' << 16) |
                                            ('.' << 8)   | ('#' << 0);  // "#.#"
                        }
                        else
                        {
                            u32PrntErrVal = (0x00 << 24) | ('?' << 16) |
                                            ('.' << 8)   | ('?' << 0);  // "?.?"
                        }
                        *(uint32_t*)pcBuf = u32PrntErrVal;
                        iVal = 3;
                    }
                    ui32CharCnt += iVal;
                    pcBuf += iVal;
                }
                break;

            //
            // Invalid specifier character
            // For non-handled specifiers, we'll just print the character.
            // e.g. this will allow the normal printing of a '%' using
            // "%%".
            //
            default:
                if ( pcBuf )
                {
                    *pcBuf++ = *pcFmt;
                }

                ++ui32CharCnt;
                break;

        } // switch ()

        //
        // Bump the format specification to the next character
        //
        ++pcFmt;

    } // while ()

    //
    // Terminate the string
    //
    if ( pcBuf )
    {
        *pcBuf = 0x0;
    }

    return (ui32CharCnt);
}

//******************************************************************************
//
// Format data into string.
//
//******************************************************************************
uint32_t
am_util_stdio_sprintf(char *pcBuf, const char *pcFmt, ...)
{
    uint32_t ui32CharCnt;

    va_list pArgs;
    va_start(pArgs, pcFmt);
    ui32CharCnt = am_util_stdio_vsprintf(pcBuf, pcFmt, pArgs);
    va_end(pArgs);

    return ui32CharCnt;
}

//*****************************************************************************
//
// A lite version of printf()
//
//*****************************************************************************
uint32_t
am_util_stdio_printf(const char *pcFmt, ...)
{
    uint32_t ui32NumChars;

    if (!g_pfnCharPrint)
    {
        return 0;
    }

    //
    // Convert to the desired string.
    //
    va_list pArgs;
    va_start(pArgs, pcFmt);
    ui32NumChars = am_util_stdio_vsprintf(g_prfbuf, pcFmt, pArgs);
    va_end(pArgs);

    //
    // This is where we print the buffer to the configured interface.
    //
    g_pfnCharPrint(g_prfbuf);

    //
    // return the number of characters printed.
    //
    return ui32NumChars;
}

uint32_t
am_util_stdio_vsnprintf(char *pcBuf, uint32_t n, const char *pcFmt,
                        va_list pArgs)
{
    uint32_t ui32NumChars, i;

    if (n >= AM_PRINTF_BUFSIZE)
    {
        return 0;
    }

    ui32NumChars = am_util_stdio_vsprintf(g_prfbuf, pcFmt, pArgs);

    if (ui32NumChars >= n)
    {
        return 0;
    }

    for (i = 0; i < ui32NumChars; i++)
    {
        pcBuf[i] = g_prfbuf[i];
    }

    return ui32NumChars;
}

uint32_t
am_util_stdio_snprintf(char *pcBuf, uint32_t n, const char *pcFmt, ...)
{
    uint32_t ui32CharCnt;

    va_list pArgs;
    va_start(pArgs, pcFmt);
    ui32CharCnt = am_util_stdio_vsnprintf(pcBuf, n, pcFmt, pArgs);
    va_end(pArgs);

    return ui32CharCnt;
}

uint32_t
am_util_stdio_vprintf(const char *pcFmt, va_list pArgs)
{
    uint32_t ui32NumChars;

    if (!g_pfnCharPrint)
    {
        return 0;
    }

    ui32NumChars = am_util_stdio_vsprintf(g_prfbuf, pcFmt, pArgs);

    //
    // This is where we print the buffer to the configured interface.
    //
    g_pfnCharPrint(g_prfbuf);

    //
    // return the number of characters printed.
    //
    return ui32NumChars;
}

//*****************************************************************************
//
// This function clears a standard terminal screen.
//
//*****************************************************************************
void
am_util_stdio_terminal_clear(void)
{
    //
    // Simulate a clear terminal by printing a series of linefeeds.
    //
    am_util_stdio_printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
}

//*****************************************************************************
//
// End Doxygen group.
//! @}
//
//*****************************************************************************

