/*!The Treasure Box Library
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Copyright (C) 2009-present, TBOOX Open Source Group.
 *
 * @author      ruki
 * @file        vsnprintf.c
 * @ingroup     libc
 *
 */

/* //////////////////////////////////////////////////////////////////////////////////////
 * includes
 */
#include "prefix.h"
#include "stdio.h"
#include "../../math/math.h"
#include "../../libm/libm.h"
#include "../../utils/utils.h"
#include "../string/string.h"
#include "printf_object.h"

/* //////////////////////////////////////////////////////////////////////////////////////
 * types
 */

// the printf type
typedef enum __tb_printf_type_t
{
    TB_PRINTF_TYPE_NONE             = 0
,   TB_PRINTF_TYPE_INT              = 1
,   TB_PRINTF_TYPE_CHAR             = 2
,   TB_PRINTF_TYPE_CHAR_PERCENT     = 3
,   TB_PRINTF_TYPE_FLOAT            = 4
,   TB_PRINTF_TYPE_DOUBLE           = 5
,   TB_PRINTF_TYPE_STRING           = 6
,   TB_PRINTF_TYPE_WIDTH            = 7
,   TB_PRINTF_TYPE_PRECISION        = 8
,   TB_PRINTF_TYPE_OBJECT           = 9
,   TB_PRINTF_TYPE_INVALID          = 10

}tb_printf_type_t;

// the printf extra info
typedef enum __tb_printf_extra_t
{
    TB_PRINTF_EXTRA_NONE            = 0
,   TB_PRINTF_EXTRA_SIGNED          = 1     // signed integer for %d %i
,   TB_PRINTF_EXTRA_UPPER           = 2     // upper case for %X %B
,   TB_PRINTF_EXTRA_PERCENT         = 4     // percent char: %
,   TB_PRINTF_EXTRA_EXP             = 8     // exponent form: [-]d.ddd e[+/-]ddd

}tb_printf_extra_t;

// printf length qualifier
typedef enum __tb_printf_qual_t
{
    TB_PRINTF_QUAL_NONE             = 0
,   TB_PRINTF_QUAL_H                = 1
,   TB_PRINTF_QUAL_L                = 2
,   TB_PRINTF_QUAL_LL               = 3
,   TB_PRINTF_QUAL_I8               = 4
,   TB_PRINTF_QUAL_I16              = 5
,   TB_PRINTF_QUAL_I32              = 6
,   TB_PRINTF_QUAL_I64              = 7

}tb_printf_qual_t;

// printf flag type
typedef enum __tb_printf_flag_t
{
    TB_PRINTF_FLAG_NONE             = 0
,   TB_PRINTF_FLAG_PLUS             = 1     // +: denote the sign '+' or '-' of a number
,   TB_PRINTF_FLAG_LEFT             = 2     // -: left-justified
,   TB_PRINTF_FLAG_ZERO             = 4     // 0: fill 0 instead of spaces
,   TB_PRINTF_FLAG_PFIX             = 8     // #: add prefix

}tb_printf_flag_t;

// printf entry
typedef struct __tb_printf_entry_t
{
    // format type
    tb_uint8_t          type;

    // extra info
    tb_uint8_t          extra;

    // flag
    tb_uint8_t          flags;

    // qualifier
    tb_uint8_t          qual;

    // field width
    tb_int_t            width;

    // precision
    tb_int_t            precision;

    // base: 2 8 10 16
    tb_int_t            base;

    // the object name
    tb_char_t           object[TB_PRINTF_OBJECT_NAME_MAXN];

}tb_printf_entry_t;

/* //////////////////////////////////////////////////////////////////////////////////////
 * implementation
 */
static tb_int_t tb_skip_atoi(tb_char_t const** s)
{
    tb_int_t i = 0;
    while (tb_isdigit(**s)) i = i * 10 + *((*s)++) - '0';
    return i;
}
static tb_char_t* tb_printf_object(tb_char_t* pb, tb_char_t* pe, tb_printf_entry_t e, tb_cpointer_t object)
{
#ifndef TB_CONFIG_MICRO_ENABLE
    // find the object func
    tb_printf_object_func_t func = tb_printf_object_find(e.object);
    if (func)
    {
        // printf it
        tb_long_t size = func(object, pb, pe - pb);
        if (size >= 0) pb += size;
        else
        {
            // invalid
            if (pb < pe) *pb++ = 'i';
            if (pb < pe) *pb++ = 'n';
            if (pb < pe) *pb++ = 'v';
            if (pb < pe) *pb++ = 'a';
            if (pb < pe) *pb++ = 'l';
            if (pb < pe) *pb++ = 'i';
            if (pb < pe) *pb++ = 'd';
        }
    }
    else
    {
        // null
        if (pb < pe) *pb++ = 'n';
        if (pb < pe) *pb++ = 'u';
        if (pb < pe) *pb++ = 'l';
        if (pb < pe) *pb++ = 'l';
    }
#else
    if (pb < pe) *pb++ = '%';
    if (pb < pe) *pb++ = '{';
#endif

    // ok?
    return pb;
}
static tb_char_t* tb_printf_string(tb_char_t* pb, tb_char_t* pe, tb_printf_entry_t e, tb_char_t const* s)
{
    // done
    if (s)
    {
        // get length
        tb_long_t n = tb_strnlen(s, e.precision);

        // fill space at left side, e.g. "   abcd"
        if (!(e.flags & TB_PRINTF_FLAG_LEFT))
        {
            while (n < e.width--)
                if (pb < pe) *pb++ = ' ';
        }

        // copy string
        tb_int_t i = 0;
        for (i = 0; i < n; ++i)
            if (pb < pe) *pb++ = *s++;

        // fill space at right side, e.g. "abcd    "
        while (n < e.width--)
            if (pb < pe) *pb++ = ' ';
    }
    else
    {
        // null
        if (pb < pe) *pb++ = 'n';
        if (pb < pe) *pb++ = 'u';
        if (pb < pe) *pb++ = 'l';
        if (pb < pe) *pb++ = 'l';
    }

    return pb;
}
static tb_char_t* tb_printf_int64(tb_char_t* pb, tb_char_t* pe, tb_printf_entry_t e, tb_uint64_t num)
{
    // digits table
    static tb_char_t const* digits_table = "0123456789ABCDEF";

    // max: 64-bit binary decimal
    tb_char_t   digits[64] = {0};
    tb_int_t    digit_i = 0;

    // lowercase mask, e.g. 'F' | 0x20 => 'f'
    tb_int_t lomask = (e.extra & TB_PRINTF_EXTRA_UPPER)? 0x0 : 0x20;

    // sign: + -
    tb_char_t sign = 0;
    if (e.extra & TB_PRINTF_EXTRA_SIGNED)
    {
        if ((tb_int64_t)num < 0)
        {
            sign = '-';
            --e.width;

            num = (tb_uint64_t)(-(tb_int64_t)num);
        }
        else if (e.flags & TB_PRINTF_FLAG_PLUS)
        {
            sign = '+';
            --e.width;
        }
    }

    // convert num => digits string in reverse order
    if (num == 0) digits[digit_i++] = '0';
    else
    {
#if 0
        do
        {
            digits[digit_i++] = digits_table[num % e.base] | lomask;
            num /= e.base;
        }
        while (num);
#else
        if (e.base != 10)
        {
            tb_int_t shift_bits = 4;
            if (e.base == 8) shift_bits--;
            else if (e.base == 2) shift_bits -= 3;
            do
            {
                digits[digit_i++] = digits_table[(tb_uint8_t)num & (e.base - 1)] | lomask;
                num >>= shift_bits;
            }
            while (num);
        }
        else
        {
            do
            {
                digits[digit_i++] = digits_table[num % e.base] | lomask;
                num /= e.base;
            }
            while (num);
        }
#endif
    }

    // adjust precision
    if (digit_i > e.precision)
        e.precision = digit_i;

    // fill spaces at left side, e.g. "   0x0"
    e.width -= e.precision;
    if (!(e.flags & (TB_PRINTF_FLAG_LEFT + TB_PRINTF_FLAG_ZERO)))
    {
        while (--e.width >= 0)
            if (pb < pe) *pb++ = ' ';
    }

    // append sign: + / -
    if (sign && (pb < pe)) *pb++ = sign;

    // append prefix: 0x..., 0X..., 0b..., 0B...
    if (e.flags & TB_PRINTF_FLAG_PFIX)
    {
        switch (e.base)
        {
        case 16:
            {
                if (pb + 1 < pe)
                {
                    *pb++ = '0';
                    *pb++ = 'X' | lomask;
                    e.width -= 2;
                }
                break;
            }
        case 8:
            {
                if (pb < pe)
                {
                    *pb++ = '0';
                    --e.width;
                }
                break;
            }
        case 2:
            {
                if (pb + 1 < pe)
                {
                    *pb++ = '0';
                    *pb++ = 'B' | lomask;
                    e.width -= 2;
                }
                break;
            }
        default:
            break;
        }
    }

    // fill 0 or spaces, e.g. "0x   ff"
    if (!(e.flags & TB_PRINTF_FLAG_LEFT))
    {
        tb_char_t c = (e.flags & TB_PRINTF_FLAG_ZERO)? '0' : ' ';
        while (--e.width >= 0)
            if (pb < pe) *pb++ = c;
    }

    // fill 0 if precision is larger, e.g. "0x000ff"
    while (digit_i <= --e.precision)
        if (pb < pe) *pb++ = '0';

    // append digits
    while (--digit_i >= 0)
        if (pb < pe) *pb++ = digits[digit_i];

    // trailing space padding for left-justified flags, e.g. "0xff   "
    while (--e.width >= 0)
        if (pb < pe) *pb++ = ' ';

    return pb;
}
static tb_char_t* tb_printf_int32(tb_char_t* pb, tb_char_t* pe, tb_printf_entry_t e, tb_uint32_t num)
{
    // digits table
    static tb_char_t const* digits_table = "0123456789ABCDEF";

    // max: 64-bit binary decimal
    tb_char_t   digits[64] = {0};
    tb_int_t    digit_i = 0;

    // lowercase mask, e.g. 'F' | 0x20 => 'f'
    tb_int_t lomask = (e.extra & TB_PRINTF_EXTRA_UPPER)? 0x0 : 0x20;

    // sign: + -
    tb_char_t sign = 0;
    if (e.extra & TB_PRINTF_EXTRA_SIGNED)
    {
        if ((tb_int32_t)num < 0)
        {
            sign = '-';
            --e.width;
            num = (tb_uint32_t)(-(tb_int32_t)num);
        }
        else if (e.flags & TB_PRINTF_FLAG_PLUS)
        {
            sign = '+';
            --e.width;
        }
    }

    // convert num => digits string in reverse order
    if (num == 0) digits[digit_i++] = '0';
    else
    {
#if 0
        do
        {
            digits[digit_i++] = digits_table[num % e.base] | lomask;
            num /= e.base;
        }
        while (num);
#else
        if (e.base != 10)
        {
            tb_int_t shift_bits = 4;
            if (e.base == 8) shift_bits--;
            else if (e.base == 2) shift_bits -= 3;
            do
            {
                digits[digit_i++] = digits_table[(tb_uint8_t)num & (e.base - 1)] | lomask;
                num >>= shift_bits;
            }
            while (num);
        }
        else
        {
            do
            {
                digits[digit_i++] = digits_table[num % e.base] | lomask;
                num /= e.base;
            }
            while (num);
        }
#endif
    }

    // adjust precision
    if (digit_i > e.precision)
        e.precision = digit_i;

    // fill spaces at left side, e.g. "   0x0"
    e.width -= e.precision;
    if (!(e.flags & (TB_PRINTF_FLAG_LEFT + TB_PRINTF_FLAG_ZERO)))
    {
        while (--e.width >= 0)
            if (pb < pe) *pb++ = ' ';
    }

    // append sign: + / -
    if (sign && (pb < pe)) *pb++ = sign;

    // append prefix: 0x..., 0X..., 0b..., 0B...
    if (e.flags & TB_PRINTF_FLAG_PFIX)
    {
        switch (e.base)
        {
        case 16:
            {
                if (pb + 1 < pe)
                {
                    *pb++ = '0';
                    *pb++ = 'X' | lomask;
                    e.width -= 2;
                }
                break;
            }
        case 8:
            {
                if (pb < pe)
                {
                    *pb++ = '0';
                    --e.width;
                }
                break;
            }
        case 2:
            {
                if (pb + 1 < pe)
                {
                    *pb++ = '0';
                    *pb++ = 'B' | lomask;
                    e.width -= 2;
                }
                break;
            }
        default:
            break;
        }
    }

    // fill 0 or spaces, e.g. "0x   ff"
    if (!(e.flags & TB_PRINTF_FLAG_LEFT))
    {
        tb_char_t c = (e.flags & TB_PRINTF_FLAG_ZERO)? '0' : ' ';
        while (--e.width >= 0)
            if (pb < pe) *pb++ = c;
    }

    // fill 0 if precision is larger, e.g. "0x000ff"
    while (digit_i <= --e.precision)
        if (pb < pe) *pb++ = '0';

    // append digits
    while (--digit_i >= 0)
        if (pb < pe) *pb++ = digits[digit_i];

    // trailing space padding for left-justified flags, e.g. "0xff   "
    while (--e.width >= 0)
        if (pb < pe) *pb++ = ' ';

    return pb;
}
#ifdef TB_CONFIG_TYPE_HAVE_FLOAT
static tb_char_t* tb_printf_float(tb_char_t* pb, tb_char_t* pe, tb_printf_entry_t e, tb_float_t num)
{
    // digits
    tb_char_t   ints[32] = {0};
    tb_char_t   decs[32] = {0};
    tb_int_t    ints_i = 0, decs_i = 0;

    // for inf nan
    if (tb_isinff(num))
    {
        if (pb < pe && num < 0) *pb++ = '-';
        if (pb < pe) *pb++ = (e.extra & TB_PRINTF_EXTRA_UPPER)? 'I' : 'i';
        if (pb < pe) *pb++ = (e.extra & TB_PRINTF_EXTRA_UPPER)? 'N' : 'n';
        if (pb < pe) *pb++ = (e.extra & TB_PRINTF_EXTRA_UPPER)? 'F' : 'f';
        return pb;
    }
    else if (tb_isnanf(num))
    {
        if (pb < pe) *pb++ = (e.extra & TB_PRINTF_EXTRA_UPPER)? 'N' : 'n';
        if (pb < pe) *pb++ = (e.extra & TB_PRINTF_EXTRA_UPPER)? 'A' : 'a';
        if (pb < pe) *pb++ = (e.extra & TB_PRINTF_EXTRA_UPPER)? 'N' : 'n';
        return pb;
    }

    // sign: + -
    tb_char_t sign = 0;
    if (e.extra & TB_PRINTF_EXTRA_SIGNED)
    {
        if (num < 0)
        {
            sign = '-';
            --e.width;
        }
        else if (e.flags & TB_PRINTF_FLAG_PLUS)
        {
            sign = '+';
            --e.width;
        }
    }

    // adjust sign
    if (num < 0) num = -num;

    // default precision: 6
    if (e.precision <= 0) e.precision = 6;

    // round? i.dddddddd5 => i.ddddddde
    tb_uint32_t p = 1;
    tb_size_t   n = e.precision;
    while (n--) p *= 10;
    if (((tb_uint32_t)(num * p * 10) % 10) > 4)
        num += 1.0f / (tb_float_t)p;

    // get integer & decimal
    tb_int32_t integer = (tb_int32_t)num;
    tb_float_t decimal = num - integer;

    // convert integer => digits string in reverse order
    if (integer == 0) ints[ints_i++] = '0';
    else
    {
        if (integer < 0) integer = -integer;
        do
        {
            ints[ints_i++] = (integer % 10) + '0';
            integer /= 10;
        }
        while (integer);
    }

    // convert decimal => digits string in positive order
    if (decimal == 0) decs[decs_i++] = '0';
    else
    {
        tb_long_t d = (tb_long_t)(decimal * 10);
        do
        {
            decs[decs_i++] = (tb_char_t)(d + (tb_long_t)'0');
            decimal = decimal * 10 - d;
            d = (tb_long_t)(decimal * 10);
        }
        while (decs_i < e.precision);
    }

    // adjust precision
    if (decs_i > e.precision)
        decs_i = e.precision;

    // fill spaces at left side, e.g. "   0.31415926"
    e.width -= (tb_int_t)(ints_i + 1 + e.precision);
    if (!(e.flags & (TB_PRINTF_FLAG_LEFT + TB_PRINTF_FLAG_ZERO)))
    {
        while (--e.width >= 0)
            if (pb < pe) *pb++ = ' ';
    }

    // append sign: + / -
    if (sign && (pb < pe)) *pb++ = sign;

    // fill 0 or spaces, e.g. "00003.1415926"
    if (!(e.flags & TB_PRINTF_FLAG_LEFT))
    {
        tb_char_t c = (e.flags & TB_PRINTF_FLAG_ZERO)? '0' : ' ';
        while (--e.width >= 0)
            if (pb < pe) *pb++ = c;
    }

    // append integer
    while (--ints_i >= 0)
        if (pb < pe) *pb++ = ints[ints_i];

    // append .
    if (pb < pe) *pb++ = '.';

    // append decimal
    tb_int_t decs_n = decs_i;
    while (--decs_i >= 0)
        if (pb < pe) *pb++ = decs[decs_n - decs_i - 1];

    // fill 0 if precision is larger, e.g. "0.3140000"
    while (decs_n <= --e.precision)
        if (pb < pe) *pb++ = '0';

    // trailing space padding for left-justified flags, e.g. "0.31415926   "
    while (--e.width >= 0)
        if (pb < pe) *pb++ = ' ';

    return pb;
}
static tb_char_t* tb_printf_double(tb_char_t* pb, tb_char_t* pe, tb_printf_entry_t e, tb_double_t num)
{
    // digits
    tb_char_t   ints[64] = {0};
    tb_char_t   decs[64] = {0};
    tb_int_t    ints_i = 0, decs_i = 0;

    // for inf nan
    if (tb_isinf(num))
    {
        if (pb < pe && num < 0) *pb++ = '-';
        if (pb < pe) *pb++ = (e.extra & TB_PRINTF_EXTRA_UPPER)? 'I' : 'i';
        if (pb < pe) *pb++ = (e.extra & TB_PRINTF_EXTRA_UPPER)? 'N' : 'n';
        if (pb < pe) *pb++ = (e.extra & TB_PRINTF_EXTRA_UPPER)? 'F' : 'f';
        return pb;
    }
    else if (tb_isnan(num))
    {
        if (pb < pe) *pb++ = (e.extra & TB_PRINTF_EXTRA_UPPER)? 'N' : 'n';
        if (pb < pe) *pb++ = (e.extra & TB_PRINTF_EXTRA_UPPER)? 'A' : 'a';
        if (pb < pe) *pb++ = (e.extra & TB_PRINTF_EXTRA_UPPER)? 'N' : 'n';
        return pb;
    }


    // sign: + -
    tb_char_t sign = 0;
    if (e.extra & TB_PRINTF_EXTRA_SIGNED)
    {
        if (num < 0)
        {
            sign = '-';
            --e.width;
        }
        else if (e.flags & TB_PRINTF_FLAG_PLUS)
        {
            sign = '+';
            --e.width;
        }
    }

    // adjust sign
    if (num < 0) num = -num;

    // default precision: 6
    if (e.precision <= 0) e.precision = 6;

    // round? i.dddddddd5 => i.ddddddde
    tb_uint64_t p = 1;
    tb_size_t   n = e.precision;
    while (n--) p *= 10;
    if (((tb_uint64_t)(num * p * 10) % 10) > 4)
        num += 1. / (tb_double_t)p;

    // get integer & decimal
    tb_int64_t integer = (tb_int64_t)num;
    tb_double_t decimal = num - integer;

    // convert integer => digits string in reverse order
    if (integer == 0) ints[ints_i++] = '0';
    else
    {
        if (integer < 0) integer = -integer;
        do
        {
            ints[ints_i++] = (integer % 10) + '0';
            integer /= 10;
        }
        while (integer);
    }

    // convert decimal => digits string in positive order
    if (decimal == 0) decs[decs_i++] = '0';
    else
    {
        tb_long_t d = (tb_long_t)(decimal * 10);
        do
        {
            decs[decs_i++] = (tb_char_t)(d + (tb_long_t)'0');
            decimal = decimal * 10 - d;
            d = (tb_long_t)(decimal * 10);
        }
        while (decs_i < e.precision);
    }

    // adjust precision
    if (decs_i > e.precision)
        decs_i = e.precision;

    // fill spaces at left side, e.g. "   0.31415926"
    e.width -= (tb_int_t)(ints_i + 1 + e.precision);
    if (!(e.flags & (TB_PRINTF_FLAG_LEFT + TB_PRINTF_FLAG_ZERO)))
    {
        while (--e.width >= 0)
            if (pb < pe) *pb++ = ' ';
    }

    // append sign: + / -
    if (sign && (pb < pe)) *pb++ = sign;

    // fill 0 or spaces, e.g. "00003.1415926"
    if (!(e.flags & TB_PRINTF_FLAG_LEFT))
    {
        tb_char_t c = (e.flags & TB_PRINTF_FLAG_ZERO)? '0' : ' ';
        while (--e.width >= 0)
            if (pb < pe) *pb++ = c;
    }

    // append integer
    while (--ints_i >= 0)
        if (pb < pe) *pb++ = ints[ints_i];

    // append .
    if (pb < pe) *pb++ = '.';

    // append decimal
    tb_int_t decs_n = decs_i;
    while (--decs_i >= 0)
        if (pb < pe) *pb++ = decs[decs_n - decs_i - 1];

    // fill 0 if precision is larger, e.g. "0.3140000"
    while (decs_n <= --e.precision)
        if (pb < pe) *pb++ = '0';

    // trailing space padding for left-justified flags, e.g. "0.31415926   "
    while (--e.width >= 0)
        if (pb < pe) *pb++ = ' ';

    return pb;
}
#endif
// get a printf format entry
static tb_int_t tb_printf_entry(tb_char_t const* fmt, tb_printf_entry_t* e)
{
    tb_char_t const* p = fmt;

    // get field width for *
    if (e->type == TB_PRINTF_TYPE_WIDTH)
    {
        if (e->width < 0)
        {
            e->width = -e->width;
            e->flags |= TB_PRINTF_FLAG_LEFT;
        }
        e->type = TB_PRINTF_TYPE_NONE;
        goto get_precision;
    }

    // get precision for *
    if (e->type == TB_PRINTF_TYPE_PRECISION)
    {
        if (e->precision < 0) e->precision = 0;
        e->type = TB_PRINTF_TYPE_NONE;
        goto get_qualifier;
    }

    // default type
    e->type = TB_PRINTF_TYPE_NONE;

    // goto %
    for (; *p; ++p)
        if (*p == '%') break;

    // return non-format string
    if (p != fmt || !*p)
        return (tb_int_t)(p - fmt);

    // skip %
    ++p;

    // get flags
    e->flags = TB_PRINTF_FLAG_NONE;
    while (1)
    {
        tb_bool_t is_found = tb_true;
        switch (*p)
        {
        case '+': e->flags |= TB_PRINTF_FLAG_PLUS; break;
        case '-': e->flags |= TB_PRINTF_FLAG_LEFT; break;
        case '0': e->flags |= TB_PRINTF_FLAG_ZERO; break;
        case '#': e->flags |= TB_PRINTF_FLAG_PFIX; break;
        default: is_found = tb_false; break;
        }
        if (is_found == tb_false) break;
        else ++p;
    }

    // get field width
    e->width = -1;
    if (tb_isdigit(*p)) e->width = tb_skip_atoi(&p);
    else if (*p == '*')
    {
        // it's the next argument
        e->type = TB_PRINTF_TYPE_WIDTH;
        return (tb_int_t)(++p - fmt);
    }

get_precision:
    // get precision
    e->precision = -1;
    if (*p == '.')
    {
        ++p;
        if (tb_isdigit(*p))
        {
            e->precision = tb_skip_atoi(&p);
            if (e->precision < 0) e->precision = 0;
        }
        else if (*p == '*')
        {
            // it's the next argument
            e->type = TB_PRINTF_TYPE_PRECISION;
            return (tb_int_t)(++p - fmt);
        }
    }

get_qualifier:
    // get length qualifier
    e->qual = TB_PRINTF_QUAL_NONE;
    switch (*p)
    {
        // short & long => int
    case 'h':
        e->qual = TB_PRINTF_QUAL_H;
        ++p;
        break;
    case 'l':
        e->qual = TB_PRINTF_QUAL_L;
        ++p;
        if (*p == 'l')
        {
            e->qual = TB_PRINTF_QUAL_LL;
            ++p;
        }
        break;
    case 'I':
        {
            ++p;
            tb_int_t n = tb_skip_atoi(&p);
            switch (n)
            {
            case 8: e->qual = TB_PRINTF_QUAL_I8; break;
            case 16: e->qual = TB_PRINTF_QUAL_I16; break;
            case 32: e->qual = TB_PRINTF_QUAL_I32; break;
            case 64: e->qual = TB_PRINTF_QUAL_I64; break;
            default: break;
            }
            break;
        }
    case 'z':
        switch (sizeof(tb_size_t))
        {
        case 1: e->qual = TB_PRINTF_QUAL_I8; break;
        case 2: e->qual = TB_PRINTF_QUAL_I16; break;
        case 4: e->qual = TB_PRINTF_QUAL_I32; break;
        case 8: e->qual = TB_PRINTF_QUAL_I64; break;
        default: break;
        }
        ++p;
        break;
    default:
        e->qual = TB_PRINTF_QUAL_NONE;
        break;
    }

    // get base & type
    e->base = -1;
    e->type = TB_PRINTF_TYPE_INVALID;
    e->extra = TB_PRINTF_EXTRA_NONE;
    switch (*p)
    {
    case 's':
        e->type = TB_PRINTF_TYPE_STRING;
        return (tb_int_t)(++p - fmt);
    case '%':
        e->extra |= TB_PRINTF_EXTRA_PERCENT;
    case 'c':
        e->type = TB_PRINTF_TYPE_CHAR;
        return (tb_int_t)(++p - fmt);
    case 'd':
    case 'i':
        e->extra |= TB_PRINTF_EXTRA_SIGNED;
    case 'u':
        e->base = 10;
        e->type = TB_PRINTF_TYPE_INT;
        break;
    case 'X':
        e->extra |= TB_PRINTF_EXTRA_UPPER;
    case 'x':
        e->base = 16;
        e->type = TB_PRINTF_TYPE_INT;
        break;
    case 'P':
        e->extra |= TB_PRINTF_EXTRA_UPPER;
    case 'p':
        e->base = 16;
        e->type = TB_PRINTF_TYPE_INT;
        e->flags |= TB_PRINTF_FLAG_PFIX;
#if TB_CPU_BITSIZE == 64
        e->qual = TB_PRINTF_QUAL_I64;
#endif
        break;
    case 'o':
        e->base = 8;
        e->type = TB_PRINTF_TYPE_INT;
        break;
    case 'B':
        e->extra |= TB_PRINTF_EXTRA_UPPER;
    case 'b':
        e->base = 2;
        e->type = TB_PRINTF_TYPE_INT;
        break;
#ifdef TB_CONFIG_TYPE_HAVE_FLOAT
    case 'F':
        e->extra |= TB_PRINTF_EXTRA_UPPER;
    case 'f':
        e->type = TB_PRINTF_TYPE_FLOAT;
        e->extra |= TB_PRINTF_EXTRA_SIGNED;
        break;
    case 'E':
        e->extra |= TB_PRINTF_EXTRA_UPPER;
    case 'e':
        e->type = TB_PRINTF_TYPE_FLOAT;
        e->extra |= TB_PRINTF_EXTRA_SIGNED;
        e->extra |= TB_PRINTF_EXTRA_EXP;
        break;
#endif
    case '{':
        {
#ifdef TB_CONFIG_MICRO_ENABLE
            e->type = TB_PRINTF_TYPE_OBJECT;
#else
            // get the object name
            ++p;
            tb_size_t indx = 0;
            tb_size_t maxn = tb_arrayn(e->object);
            while (*p && *p != '}' && indx < maxn - 1) e->object[indx++] = *p++;
            e->object[indx] = '\0';

            // save the object type
            e->type = *p == '}'? TB_PRINTF_TYPE_OBJECT : TB_PRINTF_TYPE_INVALID;
#endif
        }
        break;
    default:
        e->type = TB_PRINTF_TYPE_INVALID;
        return (tb_int_t)(p - fmt);
    }

    return (tb_int_t)(++p - fmt);
}

/* //////////////////////////////////////////////////////////////////////////////////////
 * implementation
 */
tb_long_t tb_vsnprintf(tb_char_t* s, tb_size_t n, tb_char_t const* fmt, tb_va_list_t args)
{
    // check
    if (!n || !s || !fmt) return 0;

    // init start and end pointer
    tb_char_t* pb = s;
    tb_char_t* pe = s + n - 1;

#if 0
    // pe must be larger than pb
    if (pe < pb)
    {
        pe = ((tb_char_t*)-1);
        n = (tb_size_t)(pe - pb);
    }
#endif

    // parse format
    tb_printf_entry_t e = {0};
    tb_int_t en = 0;
    while (*fmt)
    {
        tb_char_t const* ofmt = fmt;

        // get an entry
        en = tb_printf_entry(fmt, &e);
        fmt += en;

        switch (e.type)
        {
            // copy it if none type
        case TB_PRINTF_TYPE_NONE:
            {
                tb_int_t copy_n = en;
                if (pb < pe)
                {
                    if (copy_n > pe - pb) copy_n = (tb_int_t)(pe - pb);
                    tb_memcpy(pb, ofmt, copy_n);
                    pb += copy_n;
                }
                break;
            }
            // get a character for %c
        case TB_PRINTF_TYPE_CHAR:
            {
                // char: %
                if (e.extra & TB_PRINTF_EXTRA_PERCENT)
                {
                    if (pb < pe) *pb++ = '%';
                }
                // char: %c
                else
                {
                    // fill space at left side, e.g. "   a"
                    if (!(e.flags & TB_PRINTF_FLAG_LEFT))
                    {
                        while (--e.width > 0)
                        {
                            if (pb < pe) *pb++ = ' ';
                        }
                    }

                    if (pb < pe) *pb++ = (tb_char_t)tb_va_arg(args, tb_int_t);

                    // fill space at right side, e.g. "a   "
                    while (--e.width > 0)
                    {
                        if (pb < pe) *pb++ = ' ';
                    }
                }
                break;
            }
            // get field width for *
        case TB_PRINTF_TYPE_WIDTH:
            e.width = tb_va_arg(args, tb_int_t);
            break;
            // get precision for *
        case TB_PRINTF_TYPE_PRECISION:
            e.precision = tb_va_arg(args, tb_int_t);
            break;
            // get string for %s
        case TB_PRINTF_TYPE_STRING:
            {
                pb = tb_printf_string(pb, pe, e, tb_va_arg(args, tb_char_t const*));
                break;
            }
            // get an integer for %d %u %x ...
        case TB_PRINTF_TYPE_INT:
            {
                if (    e.qual == TB_PRINTF_QUAL_I64
#if TB_CPU_BIT64
                    ||  e.qual == TB_PRINTF_QUAL_L
#endif
                    ||  e.qual == TB_PRINTF_QUAL_LL)
                    pb = tb_printf_int64(pb, pe, e, tb_va_arg(args, tb_uint64_t));
                else
                {
                    tb_uint32_t num = 0;
                    if (e.extra & TB_PRINTF_EXTRA_SIGNED)
                    {
                        switch (e.qual)
                        {
                        case TB_PRINTF_QUAL_I8:     num = (tb_int8_t)tb_va_arg(args, tb_int_t); break;
                        case TB_PRINTF_QUAL_I16:    num = (tb_int16_t)tb_va_arg(args, tb_int_t); break;
                        case TB_PRINTF_QUAL_I32:    num = (tb_int32_t)tb_va_arg(args, tb_int32_t); break;
                        default:                    num = tb_va_arg(args, tb_int_t); break;
                        }
                    }
                    else
                    {
                        switch (e.qual)
                        {
                        case TB_PRINTF_QUAL_I8:     num = (tb_uint8_t)tb_va_arg(args, tb_uint_t); break;
                        case TB_PRINTF_QUAL_I16:    num = (tb_uint16_t)tb_va_arg(args, tb_uint_t); break;
                        case TB_PRINTF_QUAL_I32:    num = tb_va_arg(args, tb_uint32_t); break;
                        default:                    num = tb_va_arg(args, tb_uint_t); break;
                        }
                    }
                    pb = tb_printf_int32(pb, pe, e, num);
                }
                break;
            }
#ifdef TB_CONFIG_TYPE_HAVE_FLOAT
        case TB_PRINTF_TYPE_FLOAT:
            {
                // double?
                if (e.qual == TB_PRINTF_QUAL_L)
                {
                    tb_double_t num = tb_va_arg(args, tb_double_t);
                    pb = tb_printf_double(pb, pe, e, num);
                }
                // float?
                else
                {
                    tb_float_t num = (tb_float_t)tb_va_arg(args, tb_double_t);
                    pb = tb_printf_float(pb, pe, e, num);
                }
                break;
            }
#endif
            // get object for %{object_name}
        case TB_PRINTF_TYPE_OBJECT:
            {
                pb = tb_printf_object(pb, pe, e, tb_va_arg(args, tb_cpointer_t));
                break;
            }
        case TB_PRINTF_TYPE_INVALID:
            {
                if (pb < pe) *pb++ = '%';
                break;
            }
        default:
            break;
        }
    }

    // end
    if (pb < s + n) *pb = '\0';

    // the trailing null byte doesn't count towards the total
    return (pb - s);
}

