/*---------------------------------------------------------------------------*/
/* our private stdlib functions                                              */
/*---------------------------------------------------------------------------*/

#include <platform.h>

NAMESPACE_BEGIN

// ivsnprintf_ is the printf base function,
// it doesn't place a terminating '\0' in the ouput buffer

// IPxxx specific format changes and/or extensions are marked by #if IPxx

#define IPxxx 1
static unsigned url_2_str_ (char *url, unsigned cnt, char *buf, unsigned len);
static unsigned str_2_url_ (char *str, unsigned cnt, char *buf, unsigned len);
static unsigned wstr_2_url_ (word *wstr, unsigned cnt, char *buf, unsigned len);

// null pointer names

static word wNULL[]  = { '(','N','U','L','L',')',0 };
static char cNULL[]  = "(NULL)";

// hex conversions

static char xlower[] = "0123456789abcdef";
static char xupper[] = "0123456789ABCDEF";

static byte ascii_upper_case[256] =
{
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
    0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
    0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
    0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
    0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
    0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
    0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
    0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
    0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
    0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
};

// Size of conversion buffer

#define BUF 40

// Macros for converting digits to letters and vice versa

#define to_digit(c)     ((c) - '0')
#define to_char(n)      ((char)((n) + '0'))

// Flags used during conversion.
// Because of (sizeof(int) == sizeof(long))
// the LONGINT flag is not used here

#define QUADINT         0x01            // long long integer
#define LONGINT         0x02            // long integer
#define SHORTINT        0x04            // short integer
#define SIGNED          0x08            // signed
#define ALT             0x08            // alternate form
#define LADJUST         0x10            // left adjustment
#define ZEROPAD         0x20            // zero (as opposed to blank) pad
#define HEXPREFIX       0x40            // add 0x or 0X prefix

extern "C" int ivsnprintf_ (char *out, int len, char const *fmt0, va_list ap)
{
    const char *fmt;   // format string
    int ch;            // character from fmt
    int n;             // handy integer (short term usage)
    char *cp;          // handy char pointer (short term usage)
    const char *fmark;          // for remembering a place in fmt
    int flags;         // flags as above
    int ret;                    // return value accumulator
    int width;                  // width from format (%8d), or 0
    int prec;                   // precision from format (%.3d), or -1
    char sign;                  // sign prefix (' ', '+', '-', or \0)
    enum { OCT, DEC, HEX } base;// base for [diouxX] conversion
    int dprec;                  // a copy of prec if [diouxX], 0 otherwise
    int dpad;                   // extra 0 padding needed for integers
    int fieldsz;                // field size expanded by sign, dpad etc
    int size = 0;               // size of converted field or string
    char buf[BUF];              // space for %c, %[diouxX], %[eEfgG]
    char ox[2];                 // space for 0x hex-prefix
#if IPxxx
    ulong64 id[2];              // special 'string'
    struct tm * tm;             // time conversions
    word * wstr;                // wide char strings
#endif /* IPxxx */

#define PRINT(_p_,_l_)  { int cnt = _l_; if (cnt > 0) { if (ret + cnt > len) cnt = len - ret; memcpy (out, _p_, cnt); out += cnt; ret += cnt; if (ret >= len) goto done; } }
#define PAD_SP(_n_)     { int cnt = _n_; if (cnt > 0) { if (ret + cnt > len) cnt = len - ret; memset (out, ' ', cnt); out += cnt; ret += cnt; if (ret >= len) goto done; } }
#define PAD_0(_n_)      { int cnt = _n_; if (cnt > 0) { if (ret + cnt > len) cnt = len - ret; memset (out, '0', cnt); out += cnt; ret += cnt; if (ret >= len) goto done; } }

    fmt = fmt0;
    ret = 0;

    // Scan the format for conversions (`%' character).

    for (;;) {
        for (fmark = fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++)
            /* void */;
        PRINT(fmark, (int)(fmt - fmark));

        if (ch == '\0')
            goto done;
        fmt++;          // skip over '%'

        flags = 0;
        dprec = 0;
        width = 0;
        prec = -1;
        sign = '\0';

rflag:      ch = *fmt++;
reswitch:   switch (ch) {
        case ' ':
                // ``If the space and + flags both appear, the space
                // flag will be ignored.''
                //      -- ANSI X3J11
                if (!sign)
                    sign = ' ';
                goto rflag;
        case '#':
                flags |= ALT;
                goto rflag;
        case '*':
                // ``A negative field width argument is taken as a
                // - flag followed by a positive field width.''
                //      -- ANSI X3J11
                // They don't exclude field widths read from args.
                if ((width = va_arg(ap, int)) >= 0)
                    goto rflag;
                width = -width;
                /* FALLTHROUGH */
        case '-':
                flags |= LADJUST;
                flags &= ~ZEROPAD; // '-' disables '0'
                goto rflag;
        case '+':
                sign = '+';
                goto rflag;
        case '.':
                if ((ch = *fmt++) == '*') {
                    n = va_arg(ap, int);
                    prec = n < 0 ? -1 : n;
                    goto rflag;
                }
                n = 0;
                while (ch >= '0' && ch <= '9') {
                    n = 10 * n + to_digit(ch);
                    ch = *fmt++;
                }
                prec = n < 0 ? -1 : n;
                goto reswitch;
        case '0':
                // ``Note that 0 is taken as a flag, not as the
                //  beginning of a field width.''
                //      -- ANSI X3J11
                if (!(flags & LADJUST))
                    flags |= ZEROPAD; // '-' disables '0'
                goto rflag;
        case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
                n = 0;
                do {
                    n = 10 * n + to_digit(ch);
                    ch = *fmt++;
                } while (ch >= '0' && ch <= '9');
                width = n;
                goto reswitch;
        case 'h':
                flags |= SHORTINT;
                goto rflag;
        case 'l':   // LONGINT
#if QUADINT
                if (*fmt == 'l') {
                    fmt++;
                    flags |= QUADINT;
                }
#endif /* QUADINT */
                goto rflag;
        case 'c':
                *(cp = buf) = va_arg(ap, int);
                size = 1;
                sign = '\0';
                break;
#if !IPxxx
        case 'D':   // LONGINT
        case 'd':
#endif /* !IPxxx */
        case 'i':
                flags |= SIGNED;
                base = DEC;
                goto number;
#if !IPxxx
        case 'n':
                if (flags & SHORTINT)
                    *va_arg(ap, short *) = ret;
                else
                    *va_arg(ap, int *) = ret;
                continue;       /* no output */

        case 'O':   // LONGINT
        case 'o':
                base = OCT;
                goto nosign;
#endif /* !IPxxx */
        case 'p':
                // ``The argument shall be a pointer to void.  The
                // value of the pointer is converted to a sequence
                // of printable characters, in an implementation-
                // defined manner.''
                //      -- ANSI X3J11
                base = HEX;
#if !IPxxx
                flags |= HEXPREFIX;
#endif /* !IPxxx */
#if QUADINT
                if (sizeof(void *) > 4)
                    flags |= QUADINT;
#endif
                ch = 'x';
                goto nosign;
        case 's':
                if ((cp = va_arg(ap, char *)) == NULL)
                    cp = cNULL;
                if (prec >= 0) {
                    // can't use strlen; can only look for the
                    // NUL in the first `prec' characters, and
                    // strlen() will go further.
                    char *p = (char*)memchr(cp, 0, prec);
                    if (p != NULL) {
                        size = (int)(p - cp);
                        if (size > prec)
                            size = prec;
                    } else
                            size = prec;
                } else
                    size = (int)(strlen(cp));
                sign = '\0';
                break;
#if !IPxxx
        case 'U':   // LONGINT
#endif /* !IPxxx */
        case 'u':
                base = DEC;
                goto nosign;
        case 'X':
        case 'x':
                base = HEX;

                // unsigned conversions
nosign:         sign = '\0';
                // ``... diouXx conversions ... if a precision is
                // specified, the 0 flag will be ignored.''
                //      -- ANSI X3J11
number:         if ((dprec = prec) >= 0)
                    flags &= ~ZEROPAD;
                cp = buf + BUF;
#if QUADINT
                // don't know about efficiency of 64 bit operations,
                // thus it's better to do this numbers separately.
                if (flags & QUADINT) {
                    ulong64 _ulong64 = va_arg(ap, ulong64);
                    if (flags & SIGNED && (long64)_ulong64 < 0) {
                        _ulong64 = (ulong64)-((long64)_ulong64);
                        sign = '-';
                    }
                    if (_ulong64 != 0 || prec != 0) {
                        char *xdigs;
                        switch (base) {
#if !IPxxx
                        case OCT:
                            do {
                                *--cp = to_char(_ulong64 & 7);
                                _ulong64 >>= 3;
                            } while (_ulong64);
                            if (flags & ALT && *cp != '0')
                                *--cp = '0';
                            break;
#endif /* !IPxxx */
                        case DEC:
                            while (_ulong64 >= 10) {
                                *--cp = to_char(_ulong64 % 10);
                                _ulong64 /= 10;
                            }
                            *--cp = to_char(_ulong64);
                            break;

                        case HEX:
                            if ((flags & ALT) && _ulong64 != 0)
                                flags |= HEXPREFIX;
                            xdigs = (ch == 'X')? xupper : xlower;
                            do {
                                *--cp = xdigs[_ulong64 & 15];
                                _ulong64 >>= 4;
                            } while (_ulong64);
                            break;

                        default:
                            break;
                        }
                    }
                }
                else
#endif /* QUADINT */
                {
                    unsigned _ulong;
                    if (flags & SHORTINT) {
                        if (flags & SIGNED)
                            _ulong = (int)(short)va_arg(ap, int);
                        else
                            _ulong = (unsigned)(unsigned short)va_arg(ap, int);
                    }
                    else {
                        _ulong = va_arg(ap, unsigned);
                    }
                    if ((flags & SIGNED) && (int)_ulong < 0) {
                        _ulong = (unsigned)-((int)_ulong);
                        sign = '-';
                    }

                    // ``The result of converting a zero value with an
                    // explicit precision of zero is no characters.''
                    //      -- ANSI X3J11
                    if (_ulong != 0 || prec != 0) {
                        char *xdigs; // digits for [xX] conversion
                        // unsigned mod is hard, and unsigned mod
                        // by a constant is easier than that by
                        // a variable; hence this switch.
                        switch (base) {
#if !IPxxx
                        case OCT:
                            do {
                                *--cp = to_char(_ulong & 7);
                                _ulong >>= 3;
                            } while (_ulong);
                            // handle octal leading 0
                            if (flags & ALT && *cp != '0')
                                *--cp = '0';
                            break;
#endif /* !IPxxx */
                        case DEC:
                            // many numbers are 1 digit
                            while (_ulong >= 10) {
                                *--cp = to_char(_ulong % 10);
                                _ulong /= 10;
                            }
                            *--cp = to_char(_ulong);
                            break;

                        case HEX:
                            // leading 0x/X only if non-zero
                            if ((flags & ALT) && _ulong != 0)
                                flags |= HEXPREFIX;
                            if (ch == 'X')
                                xdigs = xupper;
                            else // ch == 'x' || ch == 'p'
                                xdigs = xlower;
                            do {
                                *--cp = xdigs[_ulong & 15];
                                _ulong >>= 4;
                            } while (_ulong);
                            break;

                        default:
                            break;
                        }
                    }
                }
                size = (int)(buf + BUF - cp);
                break;
#if IPxxx /* { */
        case 'A':   // IP4 address
            {   IP4addr *addr = va_arg(ap, IP4addr*);
                size = isnprintf (buf, BUF, "%i.%i.%i.%i",
                                  addr->b[0], addr->b[1], addr->b[2], addr->b[3]);
                cp = buf;
                goto fixsize;
            }
        case 'a':
            {
                dword * d = va_arg(ap, dword *);
                int off;
                if(d) {
                    IPaddr tmp; tmp.d[0] = d[0]; tmp.d[1] = d[1]; tmp.d[2] = d[2]; tmp.d[3] = d[3];
                    IPaddr * ip6addr = &tmp;
                    if(is_ip4(*ip6addr)) {
                        size = isnprintf (buf, BUF, "%i.%i.%i.%i",
                                          ip6addr->b[12], ip6addr->b[13], ip6addr->b[14], ip6addr->b[15]);
                        cp = buf;
                        goto fixsize;
                    }
                    else {
                        off = 0;
                        if (flags & ALT) buf[off++] = '[';
                        int count = 0;
                        int ofs = 8;
                        int next_ofs = -1;
                        for (int i = 0; i < 8; i++) {
                            if (!ip6addr->w[i]) {
                                if (next_ofs < 0) next_ofs = i;
                            }
                            else {
                                if (next_ofs >= 0) {
                                    if (i - next_ofs > count && i - next_ofs > 1) {
                                        count = i - next_ofs;
                                        ofs = next_ofs;
                                    }
                                    next_ofs = -1;
                                }
                            }
                        }
                        if (next_ofs >= 0 && 8 - next_ofs > count) {
                            count = 8 - next_ofs;
                            ofs = next_ofs;
                        }
                        for (int i = 0; i < ofs; i++) {
                            off += isnprintf(&buf[off], BUF - off, "%x%s", READ_WORD_NET(&ip6addr->b[i * 2]), i == 7 ? "" : (i < ofs - 1 ? ":" : "::"));
                        }
                        if (ofs == 0) off += isnprintf(&buf[off], BUF - off, "::");
                        for (int i = ofs + count; i < 8; i++) {
                            off += isnprintf(&buf[off], BUF - off, "%x%s", READ_WORD_NET(&ip6addr->b[i * 2]), i == 7 ? "" : ":");
                        }
                        if (flags & ALT) buf[off++] = ']';
                    }
                }
                else off = isprintf(&buf[0], "NULL");
                size = off;
                cp = buf;
                goto fixsize;
            }
            break;

        case 'd':   // date and time
        case 'D':   // date and time
                tm = va_arg(ap, struct tm *);
                if (ch == 'd')
                    size = isnprintf (buf, BUF, "%02i.%02i.%04i-%02i:%02i:%02i",
                                      tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900,
                                      tm->tm_hour, tm->tm_min, tm->tm_sec);
                else
                    size = isnprintf (buf, BUF, "%04i%02i%02i-%02i%02i%02i",
                                      tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
                                      tm->tm_hour, tm->tm_min, tm->tm_sec);
                cp = buf;
                goto fixsize;

        case 'e':   // URL decode
        case 'E':   // URL encode
                // width limits output, prec limits input.
                if ((cp = va_arg(ap, char *)) == NULL)
                    cp = cNULL;
                if (width <= 0 && prec < 0)
                    width = prec = 1024; // if both are undefined 1024 is used as limit (historic behaviour)
                else if (width <= 0)
                    width = 3 * prec;
                else if (prec < 0)
                    prec = 3 * width;
                if (width > len - ret)
                    width = len - ret;
                if (ch == 'e')
                    n = (int)url_2_str_ (cp, (unsigned)prec, out, (unsigned)width);
                else
                    n = (int)str_2_url_ (cp, (unsigned)prec, out, (unsigned)width);
                out += n; ret += n;
                continue;

        case 'U':   // URL encode wide chars
                if ((wstr = va_arg(ap, word *)) == NULL)
                    wstr = wNULL;
                if (width <= 0 && prec < 0)
                    width = prec = 1024; // if both are undefined 1024 is used as limit (historic behaviour)
                else if (width <= 0)
                    width = 3 * prec;
                else if (prec < 0)
                    prec = 3 * width;
                if (width > len - ret)
                    width = len - ret;
                n = (int)wstr_2_url_ (wstr, (unsigned)prec, out, (unsigned)width);
                out += n; ret += n;
                continue;

        case 'H': // hex dump
                if (flags & ALT) {
                    // 65 0d 0a 43  6f 6e 74 65  6e 74 2d 74  79 70 65 3a   e..Content-type:
                    // <------------------50----------------------------><3><----16-------->
                    // width limits output (default = 69), prec limits input (default = 16).
                    if (!width || (unsigned)width > 69) width = 69;
                    if ((unsigned)prec > 16) prec = 16;
                    if (ret + width > len)
                        goto done;  // out of space
                    if (prec) {
                        cp = va_arg(ap, char *);
                        for (size = 0; size < 16; size++) {
                            n = size * 3 + size / 4;
                            if (size < prec) {
                                ch = *cp++;
                                out[n + 0] = xlower[(ch >> 4) & 0x0f];
                                out[n + 1] = xlower[ch & 0x0f];
                                if (ch < 0x20 || ch > 0x7f) ch = '.';
                            } else {
                                out[n + 0] = ' ';
                                out[n + 1] = ' ';
                                ch = ' ';
                            }
                            out[n + 2] = ' ';
                            if (size & 3) out[n + 3] = ' ';
                            out[53 + size] = ch;
                        }
                        out[52] = ' ';
                        out += 69;
                        ret += 69;
                    }
                } else {
                    // plain unformatted hex dump, width limits output, prec limits input.
                    // if neither width nor prec is specified the value is skipped.
                    n = width / 2;
                    if (prec >= 0)
                        n = prec;
                    if (ret + 2 * n > len)
                        goto done;  // out of space
                    cp = va_arg(ap, char *);
                    ret += 2 * n;
                    while (n--) {
                        ch = *cp++;
                        *out++ = xlower[(ch >> 4) & 0x0f];
                        *out++ = xlower[ch & 0x0f];
                    }
                }
                continue;

        case 'I':   // interface ID
                id[0] = va_arg(ap, ulong64);
                id[1] = 0;
                cp = id[0]? (char *)&id[0] : (char *)"*";
                size = (int)(strlen (cp));
                goto fixsize;

        case 'm':   // ethernet address
                cp = va_arg(ap, char *);
                size = isnprintf (buf, BUF, "%02x-%02x-%02x-%02x-%02x-%02x",
                                  (byte)cp[0], (byte)cp[1], (byte)cp[2], (byte)cp[3], (byte)cp[4], (byte)cp[5]);
                cp = buf;
        fixsize:
                if (prec >= 0 && size > prec)
                    size = prec;
                break;

        case 'n':   // e164 number ie with numbering plan info
                cp = va_arg(ap, char *);
                size = 0;
                if (cp && (n = cp[0] & 0xff)) {
                    char *p = buf;
                    switch((cp[1] & 0x70)) {
                    case 0x10:
                        *p++ = 'I';
                        break;
                    case 0x20:
                        *p++ = 'N';
                        break;
                    case 0x30:
                        *p++ = 'X';
                        break;
                    case 0x40:
                        *p++ = 'S';
                        break;
                    case 0x60:
                        *p++ = 'A';
                        break;
                    }
                    n--;
                    if (n > 0 && !(cp[1] & 0x80))
                    {
                        if ((cp[2] & 0x60) == 0x20)
                            *p++ = 'R';
                        cp++; n--;
                    }
                    size = (int)(p - buf);
                    PRINT(buf, size);
                    PRINT(cp + 2, n);
                    size += n;
                }
                PAD_SP(width - size);
                continue;

        case 'N':   // wide chars (UNUSED)
        case 'S':   // wide chars */
                // width limits output, prec limits input.
                if ((wstr = va_arg(ap, word *)) == NULL)
                    wstr = wNULL;
                if (width <= 0)
                    width = 1024;
                if (prec < 0)
                    prec = 1024;
                if (width > len - ret)
                    width = len - ret;
                while (prec-- > 0 && (ch = (word)(*wstr++))) {
                    if(ch<128) {
                        if (width<1) break;
                        width-=1; ret+=1; *out++ = ch;
                    } else if (ch<2048) {
                        if (width<2) break;
                        width-=2; ret+=2; *out++ = (char)(0xc0 | (ch >> 6)); *out++ = (char)(0x80 | (ch & 0x3f));
                    } else {
                        if (width<3) break;
                        width-=3; ret+=3; *out++ = (char)(0xe0 | (ch >> 12)); *out++ = (char)(0x80 | ((ch >> 6) & 0x3f));   *out++ = (char)(0x80 | (ch & 0x3f));
                    }
                }
                continue;

        case 'o':   // object identifier
            {   dword b[128]; unsigned long _ulong;
                n = (int)(va_arg(ap, class objectIdentifier *))->get(b);
                for (int i = 0; i < n; i++) {
                    for (cp = buf + BUF, _ulong = b[i]; _ulong >= 10 ; ) {
                        *--cp = to_char(_ulong % 10);
                        _ulong /= 10;
                    }
                    *--cp = to_char(_ulong);
                    if (i) *--cp = '.';
                    PRINT(cp, (int)(buf + BUF - cp));
                }
                continue;
            }
        case 't':   // indentation
                n = va_arg(ap, int);
                PAD_SP(n);
                continue;

#endif /* } IPxxx */
        default:    // "%?" prints ?, unless ? is NUL
                if (ch == '\0')
                    goto done;
                // pretend it was %c with argument ch
                cp = buf;
                *cp = ch;
                size = 1;
                sign = '\0';
                break;
        }

        // All reasonable formats wind up here.  At this point,
        // `cp' points to a string which (if not flags&LADJUST)
        // should be padded out to `width' places.  If
        // flags&ZEROPAD, it should first be prefixed by any
        // sign or other prefix; otherwise, it should be blank
        // padded before the prefix is emitted.  After any
        // left-hand padding and prefixing, emit zeroes
        // required by a decimal [diouxX] precision, then print
        // the string proper; finally, if LADJUST, pad with blanks.

        // compute actual size, so we know how much to pad.

        fieldsz = size;
        dpad = dprec - size;
        if (dpad < 0)
            dpad = 0;

        if (sign)
            fieldsz++;
        else if (flags & HEXPREFIX)
            fieldsz += 2;
        fieldsz += dpad;

        // right-adjusting blank padding
        if ((flags & (LADJUST|ZEROPAD)) == 0)
            PAD_SP(width - fieldsz);

        // prefix
        if (sign) {
            PRINT(&sign, 1);
        } else if (flags & HEXPREFIX) {
            ox[0] = '0';
            ox[1] = ch;
            PRINT(ox, 2);
        }

        // right-adjusting zero padding
        if ((flags & (LADJUST|ZEROPAD)) == ZEROPAD)
            PAD_0(width - fieldsz);

        // leading zeroes from decimal precision
        PAD_0(dpad);

        // the string or number proper
        PRINT(cp, size);

        // left-adjusting padding (always blank)
        if (flags & LADJUST)
            PAD_SP(width - fieldsz);

        // no need to adjust ret here,
        // it's adjusted whenever the buffer is written
    }
done:
    return ret;
}

extern "C" int ivsnprintf (char * buffer, int length, const char * format, va_list ap)
{
    if (length == 0) return 0;
    int ret = ivsnprintf_ (buffer, length - 1, format, ap);
    buffer[ret] = 0;
    return (ret);
}

extern "C" int isnprintf (char * buffer, int length, const char * format, ...)
{
    if (length == 0) return 0;
    va_list ap;
    va_start (ap, format);
    int ret = ivsnprintf_ ((char *)buffer, length - 1, format, ap);
    va_end (ap);
    buffer[ret] = 0;
    return (ret);
}

#if IPxxx

static unsigned xchar_2_nibble (unsigned c)
{
    if (c >= '0' && c <= '9') return (c - (unsigned)'0');
    if (c >= 'a' && c <='f') return ((c - (unsigned)'a') + 10);
    if (c >= 'A' && c <='F') return ((c - (unsigned)'A') + 10);
    return 0;
}

static unsigned url_2_str_ (char *url, unsigned cnt, char *buf, unsigned len)
{
    byte *u; char *p; unsigned c = 0;

    for (u = (byte *)url, p = buf; cnt-- && (c = *u++); )
    {
        if (c == '+')
        {
            c =' ';
        }
        else if (c == '%')
        {
            if (cnt >= 1 && u[0] == '%' )
            {
                u++;
                cnt--;
            }
            else if (cnt >= 2 )
            {
                c = (xchar_2_nibble (u[0]) << 4) | xchar_2_nibble (u[1]);
                u += 2;
                cnt -= 2;
            }
        }

        if (c)
        {
            if (len--)
                *p++ = (char)c;
            else
                break;
        }
    }
    return ((unsigned)(p - buf));
}

static unsigned str_2_url_ (char *str, unsigned cnt, char *buf, unsigned len)
{
    byte *s; char *p; unsigned c = 0;

    for (s = (byte *)str, p = buf; cnt-- && (c = *s++) && len--; *p++ = (char)c)
    {
        switch (c)
        {
        case ' ':
            c = '+';
            continue;
        case '*': case '.': case '-': case '_':
            continue;
        default:
            if ( (c>='0' && c<='9') || (c>='a' && c<='z') || (c>='A' && c<='Z') )
                continue;
            if (len >= 2)
            {
                len -= 2;
                *p++ = '%';
                *p++ = xupper[(c >> 4) & 0x0f];
                c = (unsigned)xupper[c & 0x0f];
                continue;
            }
        }
        break;  /* out of space */
    }
    return ((unsigned)(p - buf));
}

static unsigned wstr_2_url_ (word *wstr, unsigned cnt, char *buf, unsigned len)
{
    word *s; char *p; unsigned c = 0;

    for (s = wstr, p = buf; cnt-- && (c = *s++) && len--; *p++ = (char)c)
    {
        switch (c)
        {
        case ' ':
            c = '+';
            continue;
        case '*': case '.': case '-': case '_':
            continue;
        default:
            if ( (c>='0' && c<='9') || (c>='a' && c<='z') || (c>='A' && c<='Z') )
                continue;
            if (len >= 2)
            {
                len -= 2;
                *p++ = '%';
                *p++ = xupper[(c >> 4) & 0x0f];
                c = (unsigned)xupper[c & 0x0f];
                continue;
            }
        }
        break;  /* out of space */
    }
    return ((unsigned)(p - buf));
}
#endif /* IPxxx */

extern "C" int isprintf (char * b, const char * format, ...)
{
    va_list ap;
    va_start(ap,format);
    int len = ivsnprintf_ (b, 32767, format, ap);
    va_end(ap);
    b[len] = 0;
    return (len);
}

extern  "C" int ivsprintf (char * b, const char * format, va_list ap)
{
    return (ivsnprintf_ (b, 32767, format, ap));
}

extern "C" bool strmatchi(const char * ref, const char * s, char ** ptr)
{
    int i;

    for(i=0; ref[i]; i++) {
        if(ref[i]!=s[i]) {
            if(s[i]>='A' && s[i]<='Z') {
                if(ref[i] != s[i] + ('a'-'A')) return false;
            }
            else {
                return false;
            }
        }
    }
    if(ptr) {
        *ptr = (char *)&s[i];
        return true;
    }
    if(s[i]==0) return true;
    return false;
}

int _strcasecmp(const char * s1, const char * s2)
{
    for( ; *s1 && ascii_upper_case[(byte)*s1]==ascii_upper_case[(byte)*s2]; s1++,s2++);
    return ascii_upper_case[(byte)*s1]-ascii_upper_case[(byte)*s2];
}

extern "C" void amemcpy(dword * dst, const dword * src, int len)
{
    for(len = (len+3)>>2; len>=4; len-=4, src+=4, dst+=4) {
        dword d1 = src[0];
        dword d2 = src[1];
        dword d3 = src[2];
        dword d4 = src[3];

        dst[0] = d1;
        dst[1] = d2;
        dst[2] = d3;
        dst[3] = d4;
    }
    while(len--) *dst++ = *src++;
}

extern "C" void amemclr(dword * dst, int len)
{
    for(len = (len+3)>>2; len>=4; len-=4, dst+=4) {
        dst[0] = 0;
        dst[1] = 0;
        dst[2] = 0;
        dst[3] = 0;
    }
    while(len--) *dst++ = 0;
}

#ifndef PLATFORM_NO_PACKET
extern "C" int packet2args(class packet * data, char * buf, int buf_size, int & argc, char * argv[], bool comment, bool escape)
{
    int max_argc = argc;
    int i;
    int comment_on = 0;
    int disable_on = 0;
    bool escape_on  = false;

    if(data) {
        buf_size = data->look_head(buf,buf_size-1);
    }
    buf[buf_size] = 0;

    for(argc=0,argv[0]=0,i=0; buf[i] && buf[i]!='\n'; i++) {
        if(argc<max_argc && !argv[argc]) {
            if(buf[i]!=' ' && buf[i]!='\t' && buf[i]!='\r') {
                argv[argc] = &buf[i];
            }
        }
        else {
            if( (!escape_on && buf[i]==' ') || (!escape_on && buf[i]=='\t') || buf[i]=='\r') {
                bool read = true;
                escape_on=false;

                buf[i] = 0;

                if(argc<max_argc) {
#if 0
                    if(comment) {
                        if(disable_on || comment_on) read = false;

                        switch(argv[argc][0]) {
                        case '[':
                            disable_on++;
                            break;
                        case ']':
                            if(disable_on) disable_on--;
                            break;
                        case '{':
                            comment_on++;
                            break;
                        case '}':
                            if(comment_on) comment_on--;
                            break;
                        }

                        if(disable_on || comment_on) read = false;
                    }
#endif
                    if(read) {
                        argc++;
                    }
                    if(argc<max_argc) argv[argc] = 0;
                }
            }
            else
                if(escape && buf[i]=='"') escape_on=!escape_on;
        }
    }
    if(buf[i]=='\n' || buf[i]==0) {
        i++;
    }
    if(argc<max_argc && argv[argc] && !(disable_on || comment_on)) argc++;
    return i;
}

extern "C" class packet * args2packet(int argc, char * argv[])
{
    int i;
    class packet * data = new packet();

    for(i=0; i<argc; i++) {
        data->put_tail(argv[i],strlen(argv[i]));
        data->put_tail((char *)" ",1);
    }
    data->rem_tail(1);
    return data;
}
#endif

static time_t dist_tm(struct tm * t1, struct tm *t2)
{
    time_t distance = 0;
    unsigned long int v1, v2;
    int diff_flag = 0;

    v1 = v2 = 0;

    #define doit(x, secs)                  \
        v1 += t1->x * secs;                \
        v2 += t2->x * secs;                \
        if (!diff_flag)                    \
        {                                  \
            if (t1->x < t2->x)             \
                diff_flag = -1;            \
            else if (t1->x > t2->x)        \
                diff_flag = 1;             \
        }

    doit (tm_year, 31536000);   // Okay, not all years have 365 days
    doit (tm_mon, 2592000);     // Okay, not all months have 30 days
    doit (tm_mday, 86400);
    doit (tm_hour, 3600);
    doit (tm_min, 60);
    doit (tm_sec, 1);

    #undef doit

        // We should also make sure that the sign of DISTANCE is correct -- if
        // DIFF_FLAG is positive, the distance should be positive and vice versa

    distance = (v1 > v2) ? (v1 - v2) : (v2 - v1);
    if(diff_flag < 0) distance = -distance;

        // We need this DIFF_FLAG business because it is forseeable that the
        // distance may be zero when, in actuality, the two structures are
        // different. This is usually the case when the dates are 366 days apart
        // and one of the years is a leap year

    if(distance == 0 && diff_flag) distance = 86400 * diff_flag;

    return distance;
}

extern "C" time_t tm2time_t(struct tm * tm)
{
    int times_through_search = 0;
    struct tm our_tm;           // our working space
    struct tm *me = &our_tm;    // a pointer to the above

        // copy the struct tm that was passed in by the caller
    *me = *tm;

    if(me->tm_year < 0) return 0;

    {
        struct tm * guess_tm;
        time_t guess = 0;
        time_t distance = 0;
        time_t last_distance = 0;

        do {
            guess += distance;

            times_through_search++;
            if(times_through_search>20) return 0;

            guess_tm = gmtime(&guess);

                // How far is our guess from the desired struct tm?
            distance = guess_tm ? dist_tm(me, guess_tm) : -distance;

                // Handle periods of time where a period of time is skipped.
                // For example, 2:15 3 April 1994 does not exist, because DST
                // is in effect.  The distance function will alternately
                // return values of 3600 and -3600, because it doesn't know
                // that the requested time doesn't exist.  In these situations
                // (even if the skip is not exactly an hour) the distances
                // returned will be the same, but alternating in sign.  We
                // want the later time, so check to see that the distance is
                // oscillating and we've chosen the correct of the two
                // possibilities.
                //
                // Useful: 3 Apr 94 765356300, 30 Oct 94 783496000

            if((distance == -last_distance) && (distance < last_distance))
                return 0;

            last_distance = distance;

        } while (distance != 0);

        return guess;
    }
}

extern "C" dword fix_log2(dword n) //  log base 2 as 8.24 fixed point number
{
    static const word table[256] = {  // log2(1.0) ... log2(2.0) in 1/256 intervals
    0x0000,0x0171,0x02e0,0x044e,0x05ba,0x0725,0x088e,0x09f7,0x0b5d,0x0cc3,0x0e27,0x0f8a,0x10eb,0x124b,0x13aa,0x1508,
    0x1664,0x17bf,0x1919,0x1a71,0x1bc8,0x1d1e,0x1e73,0x1fc6,0x2119,0x226a,0x23ba,0x2508,0x2656,0x27a2,0x28ed,0x2a37,
    0x2b80,0x2cc8,0x2e0f,0x2f54,0x3098,0x31dc,0x331e,0x345f,0x359f,0x36de,0x381b,0x3958,0x3a94,0x3bce,0x3d08,0x3e41,
    0x3f78,0x40af,0x41e4,0x4319,0x444c,0x457f,0x46b0,0x47e1,0x4910,0x4a3f,0x4b6c,0x4c99,0x4dc5,0x4eef,0x5019,0x5142,
    0x526a,0x5391,0x54b7,0x55dc,0x5700,0x5824,0x5946,0x5a68,0x5b89,0x5ca8,0x5dc7,0x5ee5,0x6003,0x611f,0x623a,0x6355,
    0x646f,0x6588,0x66a0,0x67b7,0x68ce,0x69e4,0x6af8,0x6c0c,0x6d20,0x6e32,0x6f44,0x7055,0x7165,0x7274,0x7383,0x7490,
    0x759d,0x76aa,0x77b5,0x78c0,0x79ca,0x7ad3,0x7bdb,0x7ce3,0x7dea,0x7ef0,0x7ff6,0x80fb,0x81ff,0x8302,0x8405,0x8507,
    0x8608,0x8709,0x8809,0x8908,0x8a06,0x8b04,0x8c01,0x8cfe,0x8dfa,0x8ef5,0x8fef,0x90e9,0x91e2,0x92db,0x93d2,0x94ca,
    0x95c0,0x96b6,0x97ab,0x98a0,0x9994,0x9a87,0x9b7a,0x9c6c,0x9d5e,0x9e4f,0x9f3f,0xa02e,0xa11e,0xa20c,0xa2fa,0xa3e7,
    0xa4d4,0xa5c0,0xa6ab,0xa796,0xa881,0xa96a,0xaa53,0xab3c,0xac24,0xad0c,0xadf2,0xaed9,0xafbe,0xb0a4,0xb188,0xb26c,
    0xb350,0xb433,0xb515,0xb5f7,0xb6d9,0xb7ba,0xb89a,0xb97a,0xba59,0xbb38,0xbc16,0xbcf4,0xbdd1,0xbead,0xbf8a,0xc065,
    0xc140,0xc21b,0xc2f5,0xc3cf,0xc4a8,0xc580,0xc658,0xc730,0xc807,0xc8de,0xc9b4,0xca8a,0xcb5f,0xcc34,0xcd08,0xcddc,
    0xceaf,0xcf82,0xd054,0xd126,0xd1f7,0xd2c8,0xd399,0xd469,0xd538,0xd607,0xd6d6,0xd7a4,0xd872,0xd93f,0xda0c,0xdad9,
    0xdba5,0xdc70,0xdd3b,0xde06,0xded0,0xdf9a,0xe063,0xe12c,0xe1f5,0xe2bd,0xe385,0xe44c,0xe513,0xe5d9,0xe69f,0xe765,
    0xe82a,0xe8ef,0xe9b3,0xea77,0xeb3b,0xebfe,0xecc1,0xed83,0xee45,0xef06,0xefc8,0xf088,0xf149,0xf209,0xf2c8,0xf387,
    0xf446,0xf505,0xf5c3,0xf680,0xf73e,0xf7fb,0xf8b7,0xf973,0xfa2f,0xfaea,0xfba5,0xfc60,0xfd1a,0xfdd4,0xfe8e,0xff47,
    };

    if (!n) return 0;

    dword n_int = 31;
    while ((int)n > 0) {
        n <<= 1;
        n_int--;
    }

    dword index = (n >> 23) & 0x000000ff;
    dword min = table[index];
    dword max = table[(index + 1) & 0x000000ff];

    return  (n_int << 24)
          + (min << 8)
          + (((n & 0x007fffff) * ((max - min) & 0x0000ffff)) >> 15);
}

extern "C" dword fix_log10(dword n)    //  log base 10 as 8.24 fixed point number
{
    return n? (dword)(((ulong64)fix_log2(n) * 646456993LL/*log10(2)*/) >> 31) : 0;
}

NAMESPACE_END
