/**************************************************************************
 *
 * char.cpp - helpers for user-defined character type and its traits
 *
 * $Id$
 *
 ***************************************************************************
 *
 * Licensed to the Apache Software  Foundation (ASF) under one or more
 * contributor  license agreements.  See  the NOTICE  file distributed
 * with  this  work  for  additional information  regarding  copyright
 * ownership.   The ASF  licenses this  file to  you 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.
 * 
 **************************************************************************/

// expand _TEST_EXPORT macros
#define _RWSTD_TEST_SRC

#include <rw_char.h>
#include <rw_printf.h>   // for rw_snprintfa()

#include <ctype.h>       // for isdigit()
#include <stdarg.h>      // for va_arg(), va_list, ...
#include <stdlib.h>      // for strtol()
#include <string.h>      // for memcpy(), strlen(), ...

/**************************************************************************/
// for convenience
typedef unsigned char UChar;


void UserTraits<char>::
assign (char_type &dst, const char_type &src)
{
    ++n_calls_ [MemFun::assign];
    Base::assign (dst, src);
}


bool UserTraits<char>::
eq (const char_type &ch1, const char_type &ch2)
{
    ++n_calls_ [MemFun::eq];
    return Base::eq (ch1, ch2);
}


bool UserTraits<char>::
lt (const char_type &ch1, const char_type &ch2)
{
    ++n_calls_ [MemFun::lt];
    return Base::lt (ch1, ch2);
}


int UserTraits<char>::
compare (const char_type *s1, const char_type *s2, size_t n)
{
    ++n_calls_ [MemFun::compare];
    return Base::compare (s1, s2, n);
}

        
size_t UserTraits<char>::
length (const char_type *s)
{
    ++n_calls_ [MemFun::length];
    return Base::length (s);
}
 

const UserTraits<char>::char_type*
UserTraits<char>::
find (const char_type *s, size_t n, const char_type &ch)
{
    ++n_calls_ [MemFun::find];
    return Base::find (s, n, ch);
}


UserTraits<char>::char_type*
UserTraits<char>::
copy (char_type *dst, const char_type *src, size_t n)
{
    ++n_calls_ [MemFun::copy];
    return Base::copy (dst, src, n);
}


UserTraits<char>::char_type*
UserTraits<char>::
move (char_type *dst, const char_type *src, size_t n)
{
    ++n_calls_ [MemFun::move];
    return Base::move (dst, src, n);
}


UserTraits<char>::char_type*
UserTraits<char>::
assign (char_type *s, size_t n, char_type ch)
{
    ++n_calls_ [MemFun::assign];
    return Base::assign (s, n, ch);
}


UserTraits<char>::int_type
UserTraits<char>::
not_eof (const int_type &i)
{
    ++n_calls_ [MemFun::not_eof];
    return eof () == i ? ~i : i;
}


UserTraits<char>::char_type
UserTraits<char>::
to_char_type (const int_type &i)
{
    ++n_calls_ [MemFun::to_char_type];
    return Base::to_char_type (i);
}

      
UserTraits<char>::int_type
UserTraits<char>::
to_int_type (const char_type &ch)
{
    ++n_calls_ [MemFun::to_int_type];
    return Base::to_int_type (ch);
}


bool UserTraits<char>::
eq_int_type (const int_type &i1, const int_type &i2)
{
    ++n_calls_ [MemFun::eq_int_type];
    return Base::eq_int_type (i1, i2);
}


UserTraits<char>::int_type
UserTraits<char>::
eof ()
{
    return eof_;
}


size_t UserTraits<char>::
n_calls_ [UserTraits<char>::MemFun::n_funs];


UserTraits<char>::int_type
UserTraits<char>::
eof_ = std::char_traits<char>::eof ();


#ifndef _RWSTD_NO_WCHAR_T

void UserTraits<wchar_t>::
assign (char_type &dst, const char_type &src)
{
    ++n_calls_ [MemFun::assign];
    Base::assign (dst, src);
}


bool UserTraits<wchar_t>::
eq (const char_type &ch1, const char_type &ch2)
{
    ++n_calls_ [MemFun::eq];
    return Base::eq (ch1, ch2);
}


bool UserTraits<wchar_t>::
lt (const char_type &ch1, const char_type &ch2)
{
    ++n_calls_ [MemFun::lt];
    return Base::lt (ch1, ch2);
}


int UserTraits<wchar_t>::
compare (const char_type *s1, const char_type *s2, size_t n)
{
    ++n_calls_ [MemFun::compare];
    return Base::compare (s1, s2, n);
}

        
size_t UserTraits<wchar_t>::
length (const char_type *s)
{
    ++n_calls_ [MemFun::length];
    return Base::length (s);
}
 

const UserTraits<wchar_t>::char_type*
UserTraits<wchar_t>::
find (const char_type *s, size_t n, const char_type &ch)
{
    ++n_calls_ [MemFun::find];
    return Base::find (s, n, ch);
}


UserTraits<wchar_t>::char_type*
UserTraits<wchar_t>::
copy (char_type *dst, const char_type *src, size_t n)
{
    ++n_calls_ [MemFun::copy];
    return Base::copy (dst, src, n);
}


UserTraits<wchar_t>::char_type*
UserTraits<wchar_t>::
move (char_type *dst, const char_type *src, size_t n)
{
    ++n_calls_ [MemFun::move];
    return Base::move (dst, src, n);
}


UserTraits<wchar_t>::char_type*
UserTraits<wchar_t>::
assign (char_type *s, size_t n, char_type ch)
{
    ++n_calls_ [MemFun::assign];
    return Base::assign (s, n, ch);
}


UserTraits<wchar_t>::int_type
UserTraits<wchar_t>::
not_eof (const int_type &i)
{
    ++n_calls_ [MemFun::not_eof];
    return eof () == i ? ~i : i;
}


UserTraits<wchar_t>::char_type
UserTraits<wchar_t>::
to_char_type (const int_type &i)
{
    ++n_calls_ [MemFun::to_char_type];
    return Base::to_char_type (i);
}

      
UserTraits<wchar_t>::int_type
UserTraits<wchar_t>::
to_int_type (const char_type &ch)
{
    ++n_calls_ [MemFun::to_int_type];
    return Base::to_int_type (ch);
}


bool UserTraits<wchar_t>::
eq_int_type (const int_type &i1, const int_type &i2)
{
    ++n_calls_ [MemFun::eq_int_type];
    return Base::eq_int_type (i1, i2);
}


UserTraits<wchar_t>::int_type
UserTraits<wchar_t>::
eof ()
{
    return eof_;
}


size_t UserTraits<wchar_t>::
n_calls_ [UserTraits<wchar_t>::MemFun::n_funs];


UserTraits<wchar_t>::int_type
UserTraits<wchar_t>::
eof_ = std::char_traits<wchar_t>::eof ();

#endif   // _RWSTD_NO_WCHAR_T


size_t
UserTraits<UserChar>::
n_calls_ [UserTraits<UserChar>::MemFun::n_funs];


void
UserTraits<UserChar>::
assign (char_type &c1, const char_type &c2)
{
    ++n_calls_ [MemFun::assign];

    c1.f = c2.f;
    c1.c = c2.c;
}


bool
UserTraits<UserChar>::
eq (const char_type &c1, const char_type &c2)
{
    ++n_calls_ [MemFun::eq];

    return c1.f == c2.f && c1.c == c2.c;
}


bool
UserTraits<UserChar>::
lt (const char_type &c1, const char_type &c2)
{
    ++n_calls_ [MemFun::lt];

    return c1.f < c2.f || (c1.f == c2.f && c1.c < c2.c);
}


int UserTraits<UserChar>::
compare (const char_type *s1, const char_type *s2, size_t n)
{
    RW_ASSERT (0 == n || (s1 && s2));

    ++n_calls_ [MemFun::compare];

    for (size_t i = 0; i != n; ++i) {
        if (s1 [i].f != s2 [i].f || s1 [i].c != s2 [i].c) {
            if (   s1 [i].f < s2 [i].f
                || (s1 [i].f == s2 [i].f && s1 [i].c < s2 [i].c))
                return -1;

            return 1;
        }
    }

    return 0;
}
        

size_t UserTraits<UserChar>::
length (const char_type *s)
{
    RW_ASSERT (0 != s);

    ++n_calls_ [MemFun::length];

    size_t len = 0;

    for (; s [len].f || s [len].c; ++len);

    return len;
}
 

const UserTraits<UserChar>::char_type*
UserTraits<UserChar>::
find (const char_type *s, size_t n, const char_type &c)
{
    RW_ASSERT (0 == n || s);

    ++n_calls_ [MemFun::find];

    for (; n--; ++s) {
        if (s->f == c.f && s->c == c.c)
            return s;
    }

    return 0;
}


UserTraits<UserChar>::char_type*
UserTraits<UserChar>::
copy (char_type *dst, const char_type *src, size_t n)
{
    RW_ASSERT (0 == n || (dst && src));

    ++n_calls_ [MemFun::copy];

    for (size_t i = 0; i != n; i++)
        dst [i] = src [i];

    return dst;
}


UserTraits<UserChar>::char_type*
UserTraits<UserChar>::
move (char_type *dst, const char_type *src, size_t n)
{
    RW_ASSERT (0 == n || (dst && src));

    ++n_calls_ [MemFun::move];

    if (dst < src) {
        for (size_t i = 0; i != n; ++i)
            dst [i] = src [i];
    }
    else {
        while (n--)
            dst [n] = src [n];
    }

    return dst;
}


UserTraits<UserChar>::char_type*
UserTraits<UserChar>::
assign (char_type *s, size_t n, char_type c)
{
    RW_ASSERT (0 == n || s);

    ++n_calls_ [MemFun::assign2];

    for (; n--; s [n] = c);

    return s;
}


UserTraits<UserChar>::int_type
UserTraits<UserChar>::
not_eof (const int_type &i)
{
    ++n_calls_ [MemFun::not_eof];

    if (i.equal (int_type::eof ())) {
        const char_type c = { 0, 0 };

        return int_type::from_char (c);
    }

    return i;
}


UserTraits<UserChar>::char_type
UserTraits<UserChar>::
to_char_type (const int_type &i)
{
    ++n_calls_ [MemFun::to_char_type];

    return i.to_char ();
}
      

UserTraits<UserChar>::int_type
UserTraits<UserChar>::
to_int_type (const char_type &c)
{
    ++n_calls_ [MemFun::to_int_type];

    return int_type::from_char (c);
}


bool
UserTraits<UserChar>::
eq_int_type (const int_type &i1, const int_type &i2)
{
    ++n_calls_ [MemFun::eq_int_type];

    return i1.equal (i2);
}


UserTraits<UserChar>::int_type
UserTraits<UserChar>::
eof ()
{
    ++n_calls_ [MemFun::eof];

    return int_type::eof ();
}

/**************************************************************************/

_TEST_EXPORT
char*
rw_widen (char *dst, const char *src, size_t len /* = SIZE_MAX */)
{
    // compute the length of src if not specified
    if (_RWSTD_SIZE_MAX == len)
        len = src ? strlen (src) : 0;

    if (len) {
        RW_ASSERT (0 != dst);

        if (src) {
            // copy src into dst
            memcpy (dst, src, len);
            dst [len] = '\0';
        }
        else {
            // set dst to all NUL
            memset (dst, 0, len);
        }
    }
    else if (dst)
        *dst = '\0';

    return dst;
}

/**************************************************************************/

static unsigned long
_rw_get_char (const char *src, const char** end, size_t *count)
{
    RW_ASSERT (0 != src);
    RW_ASSERT (0 != end);
    RW_ASSERT (0 != count);

    if (0 == *count) {
        *end = src;
        return 0;
    }

    unsigned long ch = UChar (*src++);

    if (0 == ch && _RWSTD_SIZE_MAX == *count) {
        // stop at the terminating NUL
        *end   = src;
        *count = 1;
        return ch;
    }

    if ('<' == char (ch) && 'U' == src [0] && isxdigit (src [1])) {
        // this looks like the beginning of a <Unnn...>
        // sequence encoding a Unicode character, look
        // for a sequence of digits followed by the
        // closing '>'

        char *tmp_end;
        const unsigned long val = strtoul (src + 1, &tmp_end, 16);
        if ('>' == *tmp_end) {
            ch  = val;
            src = tmp_end + 1;
        }
    }

    if ('@' == src [0] && isdigit (src [1])) {
        // <char>@<count> denotes a repeat directive representing
        // <count> consecutive occurrences of the character <char>

        char* tmp_end;
        *count = strtoul (src + 1, &tmp_end, 10);
        src    = tmp_end;
    }
    else
        *count = 1;

    *end = src;

    return ch;
}


static void*
_rw_expand (void *dst, size_t elemsize,
            const char *src, size_t src_len /* = SIZE_MAX */,
            size_t *dst_len /* = 0 */)
{
    // create a typedef for "genericity"
#ifndef _RWSTD_NO_WCHAR_T
    typedef wchar_t WChar;
#else   // if defined (_RWSTD_NO_WCHAR_T)
    // dummy, will never be used
    typedef int WChar;
#endif   // _RWSTD_NO_WCHAR_T

    RW_ASSERT (   sizeof (char) == elemsize
               || sizeof (WChar) == elemsize
               || sizeof (UserChar) == elemsize);

    if (0 == src) {
        src     = "";
        src_len = 0;
    }

    // save the original value of dst and avoid deallocating
    // it when a large buffer is needed
    const void* const dst_save = dst;

    // when both dst and dst_size are non-null use *dst_len
    // as the size of the destination buffer
    size_t bufsize = dst ? dst_len ? *dst_len : _RWSTD_SIZE_MAX : 0;
    size_t buflen  = 0;

    if (_RWSTD_SIZE_MAX == src_len)
        src_len = strlen (src);

    void *pnext = dst;

    for (const char *psrc = src; ; ) {

        size_t count = src_len;
        const char *end = 0;
        const unsigned long ch = _rw_get_char (psrc, &end, &count);

        const size_t nchars = size_t (end - psrc);
        if (nchars <= src_len)
            src_len -= nchars;
        else
            src_len = 0;

        psrc = end;

        if (bufsize - buflen <= count) {
            // increase the size of the buffer
            bufsize = (bufsize + count) * 2;
            if (bufsize < 128)
                bufsize = 128;

            // allocate larger buffer
            void* tmp;

            switch (elemsize) {
            case sizeof (WChar):    tmp = new WChar [bufsize]; break;
            case sizeof (UserChar): tmp = new UserChar [bufsize]; break;
            default:                tmp = new char [bufsize]; break;
            }

            // copy old buffer into new one
            memcpy (tmp, dst, buflen * elemsize);

            // dispose of previously allocated buffer
            if (dst != dst_save) {
                switch (elemsize) {
                case sizeof (WChar):    delete[] (WChar*)dst; break;
                case sizeof (UserChar): delete[] (UserChar*)dst; break;
                default:                delete[] (char*)dst; break;
                }
            }

            dst = tmp;

            pnext = (char*)dst + buflen * elemsize;
        }

        if (sizeof (WChar) == elemsize) {
            for (size_t i = 0; i != count; ++i)
                ((WChar*)pnext)[i] = WChar (ch);
        }
        else if (sizeof (UserChar) == elemsize) {
            for (size_t i = 0; i != count; ++i) {
                ((UserChar*)pnext)[i].f = 0;
                ((UserChar*)pnext)[i].c = UChar (ch);
            }
        }
        else if (ch < 0x80U) {
            memset (pnext, UChar (ch), count);
        }
        else {
#if 1
            // narrow the wide character to char
            memset (pnext, UChar (ch), count);

            pnext   = (char*)pnext + count * elemsize;
            buflen += count;
            
#else   // disabled

            // FIXME: enable UCS to UTF-8 conversion
            // (need to allocate sufficient storage above)

            const char* const pnext_start = pnext;

            // count the number of UTF-8 bytes
            size_t nbytes = 0;

            for (size_t i = 0; i != count; ++i) {
                if (ch < 0x800U) {
                    *pnext++ = UChar (0xc0U | (ch >> 6));
                    *pnext++ = UChar (0x80U | (ch & 0x3fU));
                    nbytes  += 2;
                }
                else if (ch < 0x10000U) {
                    *pnext++ = UChar (0xe0U | (ch >> 12));
                    *pnext++ = UChar (0x80U | (ch >>  6 & 0x3fU));
                    *pnext++ = UChar (0x80U | (ch       & 0x3fU));
                    nbytes  += 3;
                }
                else if (ch < 0x200000U) {
                    *pnext++ = UChar (0xf0U | (ch >> 18));
                    *pnext++ = UChar (0x80U | (ch >> 12 & 0x3fU));
                    *pnext++ = UChar (0x80U | (ch >>  6 & 0x3fU));
                    *pnext++ = UChar (0x80U | (ch       & 0x3fU));
                    nbytes  += 4;
                }
                else if (ch < 0x4000000U) {
                    *pnext++ = UChar (0xf8U | (ch >> 24));
                    *pnext++ = UChar (0x80U | (ch >> 18 & 0x3fU));
                    *pnext++ = UChar (0x80U | (ch >> 12 & 0x3fU));
                    *pnext++ = UChar (0x80U | (ch >>  6 & 0x3fU));
                    *pnext++ = UChar (0x80U | (ch       & 0x3fU));
                    nbytes  += 5;
                }
                else {
                    *pnext++ = UChar (0xfcU | (ch >> 30));
                    *pnext++ = UChar (0x80U | (ch >> 24 & 0x3fU));
                    *pnext++ = UChar (0x80U | (ch >> 18 & 0x3fU));
                    *pnext++ = UChar (0x80U | (ch >> 12 & 0x3fU));
                    *pnext++ = UChar (0x80U | (ch >>  6 & 0x3fU));
                    *pnext++ = UChar (0x80U | (ch       & 0x3fU));
                    nbytes  += 6;
                }
            }

#endif   // 0/1

        }

        pnext   = (char*)pnext + count * elemsize;
        buflen += count;

        if (0 == src_len) {
            memset (pnext, 0, elemsize);
            break;
        }
    }

    if (dst_len)
        *dst_len = buflen;

    return dst;
}

/**************************************************************************/

_TEST_EXPORT
char*
rw_expand (char *dst, const char *src, size_t src_len /* = SIZE_MAX */,
           size_t *dst_len /* = 0 */)
{
    void* const result = _rw_expand (dst, sizeof *dst, src, src_len, dst_len);

    return _RWSTD_STATIC_CAST (char*, result);
}


_TEST_EXPORT
char*
rw_narrow (char *dst, const char *src, size_t len /* = SIZE_MAX */)
{
    return rw_widen (dst, src, len);
}


_TEST_EXPORT
size_t
rw_match (const char *s1, const char *s2, size_t len /* = SIZE_MAX */)
{
    // the length of the initial subsequence of s1 and s2
    // consisting solely of characters that compare equal
    size_t count = 0;

    if (0 == s1) {
        s1 = s2;
        s2 = 0;
    }

    const char* p1 = s1;

    if (0 == s2) {
        // when one of the strings is null, compute the length
        // of the other string when all directives are expanded
        if (0 == s1 || 0 == *s1)
            return 0;

        do {
            size_t n = _RWSTD_SIZE_MAX == len ? len : len - count;

            _rw_get_char (p1, &p1, &n);

            count += n;
        } while (p1 && *p1);

        return count;
    }
    
    const char* p2 = s2;

    size_t n1 = 0;
    size_t n2 = 0;

    for (unsigned long ch1, ch2; count < len; ) {

        while (0 == n1) {
            n1  = _RWSTD_SIZE_MAX == len ? len : len - count;
            ch1 = _rw_get_char (p1, &p1, &n1);
        }

        while (0 == n2) {
            n2  = _RWSTD_SIZE_MAX == len ? len : len - count;
            ch2 = _rw_get_char (p2, &p2, &n2);
        }

        if (ch1 != ch2)
            break;

        if (n1 < n2) {
            // the repeat count specified by the first directive
            // is less than the repeat count given by the second
            count += n1;
            n2    -= n1;
            n1     = 0;

        }
        else if (n2 <= n1) {
            // the repeat count specified by the second directive
            // is less than or equal than that given by the first
            count += n2;
            n1    -= n2;
            n2     = 0;
        }

        if (_RWSTD_SIZE_MAX == len && 0 == ch1)
            break;
    }

    return len < count ? len : count;
}


#ifndef _RWSTD_NO_WCHAR_T

_TEST_EXPORT
wchar_t*
rw_widen (wchar_t *dst, const char *src, size_t len /* = SIZE_MAX */)
{
    // compute the length of src if not specified
    if (_RWSTD_SIZE_MAX == len)
        len = src ? strlen (src) : 0;

    // if len is non-zero dst must be non-0 as well
    RW_ASSERT (0 == len || 0 != dst);

    if (dst) {
        if (src) {
            // widen src into dst one element at a time
            for (size_t i = 0; ; ++i) {
                if (i == len) {
                    dst [i] = L'\0';
                    break;
                }

                dst [i] = wchar_t (UChar (src [i]));
            }
        }
        else {
            // set dst to all NUL
            memset (dst, 0, len * sizeof *dst);
        }
    }

    return dst;
}


_TEST_EXPORT
wchar_t*
rw_expand (wchar_t *dst, const char *src, size_t src_len /* = SIZE_MAX */,
           size_t *dst_len /* = 0 */)
{
    void* const result = _rw_expand (dst, sizeof *dst, src, src_len, dst_len);

    return _RWSTD_STATIC_CAST (wchar_t*, result);
}


_TEST_EXPORT
char*
rw_narrow (char *dst, const wchar_t *src, size_t len /* = SIZE_MAX */)
{
    // compute the length of src if not specified
    if (_RWSTD_SIZE_MAX == len) {
        if (src) {
            for (len = 0; src [len]; ++len);
        }
        else
            len = 0;
    }

    // if len is non-zero dst must be non-0 as well
    RW_ASSERT (0 == len || 0 != dst);

    if (dst) {
        if (src) {
            // narrow src into dst one element at a time
            for (size_t i = 0; ; ++i) {
                if (i == len) {
                    dst [i] = '\0';
                    break;
                }

                dst [i] = char (UChar (src [i]));
            }
        }
        else {
            // set dst to all NUL
            memset (dst, 0, len);
        }
    }

    return dst;
}


_TEST_EXPORT
size_t
rw_match (const char *s1, const wchar_t *s2, size_t len /* = SIZE_MAX */)
{
    if (0 == s1) {
        if (s2) {
            // return the length of s2
            for (len = 0; s2 [len]; ++len);
            return len;
        }

        return 0;
    }

    if (0 == s2)
        return rw_match (s1, (char*)0, len);

    const char*    p1 = s1;
    const wchar_t* p2 = s2;

    // the length of the initial subsequence of s1 and s2
    // consisting solely of characters that compare equal
    size_t count = 0;

    size_t n1 = 0;
    size_t n2 = 0;

    for (unsigned long ch1, ch2; count < len; ) {

        while (0 == n1) {
            n1  = _RWSTD_SIZE_MAX == len ? len : len - count;
            ch1 = _rw_get_char (p1, &p1, &n1);
        }

        ch2 = _RWSTD_STATIC_CAST (unsigned long, *p2++);
        n2  = 1;

        if (ch1 != ch2)
            break;

        if (n1 < n2) {
            // the repeat count specified by the first directive
            // is less than the repeat count given by the second
            count += n1;
            n2    -= n1;
            n1     = 0;

        }
        else if (n2 <= n1) {
            // the repeat count specified by the second directive
            // is less than or equal than that given by the first
            count += n2;
            n1    -= n2;
            n2     = 0;
        }

        if (_RWSTD_SIZE_MAX == len && L'\0' == ch1)
            break;
    }

    return len < count ? len : count;
}

#endif   // _RWSTD_NO_WCHAR_T


_TEST_EXPORT
UserChar*
rw_widen (UserChar *dst, const char *src, size_t len /* = SIZE_MAX */)
{
    // compute the length of src if not specified
    if (_RWSTD_SIZE_MAX == len)
        len = src ? strlen (src) : 0;

    // if len is non-zero dst must be non-0 as well
    RW_ASSERT (0 == len || 0 != dst);

    if (dst) {
        if (src) {
            // widen src into dst one element at a time
            for (size_t i = 0; ; ++i) {
                if (i == len) {
                    dst [i] = UserChar::eos ();
                    break;
                }

                dst [i].f = 0;
                dst [i].c = UChar (src [i]);
            }
        }
        else {
            // set dst to all NUL
            memset (dst, 0, len * sizeof *dst);
        }
    }

    return dst;
}


_TEST_EXPORT
UserChar*
rw_expand (UserChar *dst, const char *src, size_t src_len /* = SIZE_MAX */,
           size_t *dst_len /* = 0 */)
{
    void* const result = _rw_expand (dst, sizeof *dst, src, src_len, dst_len);

    return _RWSTD_STATIC_CAST (UserChar*, result);
}


_TEST_EXPORT
char*
rw_narrow (char *dst, const UserChar *src, size_t len /* = SIZE_MAX */)
{
    // compute the length of src if not specified
    if (_RWSTD_SIZE_MAX == len) {
        if (src) {
            for (len = 0; src [len].f || src [len].c; ++len);
        }
        else
            len = 0;
    }

    // if len is non-zero dst must be non-0 as well
    RW_ASSERT (0 == len || 0 != dst);

    if (dst) {
        if (src) {
            // narrow src into dst one element at a time
            for (size_t i = 0; ; ++i) {
                if (i == len) {
                    dst [i] = '\0';
                    break;
                }

                dst [i] = char (src [i].c);
            }
        }
        else {
            // set dst to all NUL
            memset (dst, 0, len);
        }
    }

    return dst;
}


_TEST_EXPORT
size_t
rw_match (const char *s1, const UserChar *s2, size_t len /* = SIZE_MAX */)
{
    if (0 == s1) {
        if (s2) {
            // return the length of s2
            for (len = 0; s2 [len].f || s2 [len].c; ++len);
            return len;
        }

        return 0;
    }

    if (0 == s2)
        return rw_match (s1, (char*)0, len);

    const char*     p1 = s1;
    const UserChar* p2 = s2;

    // the length of the initial subsequence of s1 and s2
    // consisting solely of characters that compare equal
    size_t count = 0;

    size_t n1 = 0;
    size_t n2 = 0;

    unsigned long ch1;

    for (UserChar ch2; count < len; ) {

        while (0 == n1) {
            n1  = _RWSTD_SIZE_MAX == len ? len : len - count;
            ch1 = _rw_get_char (p1, &p1, &n1);
        }

        ch2 = *p2++;
        n2  = 1;

        if (ch1 != ch2.c)
            break;

        if (n1 < n2) {
            // the repeat count specified by the first directive
            // is less than the repeat count given by the second
            count += n1;
            n2    -= n1;
            n1     = 0;

        }
        else if (n2 <= n1) {
            // the repeat count specified by the second directive
            // is less than or equal than that given by the first
            count += n2;
            n1    -= n2;
            n2     = 0;
        }

        if (_RWSTD_SIZE_MAX == len && L'\0' == ch1)
            break;
    }

    return len < count ? len : count;
}

/**************************************************************************/

static int
_rw_fmtstringv (char **pbuf, size_t *pbufsize, const char *fmt, va_list va)
{
    RW_ASSERT (0 != pbuf);
    RW_ASSERT (0 != pbufsize);
    RW_ASSERT (0 != fmt);

    // directive syntax:
    // '/' [ '#' ] { '*' | <n> } [ '.' '*' | <n> ] "Gs"

    // NOTE:
    // leading slash (or any other violation of the "standard" directive
    // syntax) prevents the caller from extracting width and precision
    // (etc.) from its variable argument list and allows us to do so

    static int nested_call;

    if (nested_call || '/' != fmt [0])
        return _RWSTD_INT_MIN;

    ++nested_call;
    ++fmt;

    va_list* pva      =  0;
    bool     fl_pound = false;
    int      nelems   = -1;
    int      paramno  = -1;
    int      elemsize = -1;

    union UPtr {
        const char     *pc;
        const wchar_t  *pwc;
        const UserChar *puc;
    };

    if ('#' == *fmt) {
        fl_pound = true;
        ++fmt;
    }

    // saved caller's va_list in case it needs to be restored
    // to its orginal state after extracting argument from it
    va_list va_save;

    if ('*' == *fmt) {
        // process element width (i.e., sizeof(charT))
        pva = va_arg (va, va_list*);

        RW_ASSERT (0 != pva);

        _RWSTD_VA_COPY (va_save, *pva);

        // extract the width from rw_snprintfa's variable argument
        // list pass through to us by the caller
        elemsize = va_arg (*pva, int);
        ++fmt;
    }
    else if (isdigit (UChar (*fmt))) {
        // process positional parameter or width
        char* end = 0;
        const int arg = int (strtol (fmt, &end, 10));
        if ('$' == *end)
            paramno = arg;
        else
            elemsize = arg;

        fmt = end;
    }

    if ('.' == *fmt) {
        // process precision (the length of the array in elements)
        if ('*' == *++fmt) {
            if (0 == pva) {
                pva = va_arg (va, va_list*);

                RW_ASSERT (0 != pva);

                _RWSTD_VA_COPY (va_save, *pva);
            }

            // extract the width from rw_snprintfa's variable argument
            // list passed through to us by the caller
            nelems = va_arg (*pva, int);
            ++fmt;
        }
        else if (isdigit (UChar (*fmt))) {
            char* end = 0;
            nelems = int (strtol (fmt, &end, 10));
            fmt    = end;
        }
    }

    // extract the address of the caller's variable argument list
    if (0 == pva) {
        pva = va_arg (va, va_list*);

        RW_ASSERT (0 != pva);

        _RWSTD_VA_COPY (va_save, *pva);
    }

    if ('G' != fmt [0] || 's' != fmt [1] || '\0' != fmt [2]) {

        // restore caller's (potentially modified) va_list
        _RWSTD_VA_COPY (*pva, va_save);

        --nested_call;

        // uknown directive, let caller process
        return _RWSTD_INT_MIN;
    }

    // extract a pointer to the first character from rw_snprintfa's
    // variable argument list pass through to us by the caller 
    const UPtr beg = { va_arg (*pva, char*) };

    {
        // extract the address where to store the extracted argument
        // for use by any subsequent positional paramaters
        const char** const pparam = va_arg (va, const char**);

        RW_ASSERT (0 != pparam);

        // store the extracted argument
        *pparam = beg.pc;
    }

    // compute the length of the buffer formatted so far
    const size_t buflen_0 = *pbuf ? strlen (*pbuf) : 0;

    int nbytes = 0;

    //////////////////////////////////////////////////////////////////
    // invoke rw_asnprintf() recursively to format our arguments
    // and append the result to the end of the buffer; pass the
    // value returned from rw_asnprintf() (i.e., the number of
    // bytes appended) back to the caller

    if (-1 == elemsize || 1 == elemsize) {
        if (nelems < 0)
            nelems = beg.pc ? int (strlen (beg.pc)) : 0;

        nbytes = rw_asnprintf (pbuf, pbufsize, "%{+}%{#*s}", nelems, beg.pc);
    }
    else if (_RWSTD_WCHAR_SIZE == elemsize) {
        if (nelems < 0)
            nbytes = rw_asnprintf (pbuf, pbufsize, "%{+}%{#ls}", beg.pwc);
        else
            nbytes = rw_asnprintf (pbuf, pbufsize, "%{+}%{#*ls}",
                                   nelems, beg.pwc);
    }
    else if (sizeof (UserChar) == size_t (elemsize)) {

        // narrow the argument into a local buffer of sufficient size
        // (dynamically allocating memory only when the length of the
        // string exceeds the size of the buffer for efficiency) and formt
        // the resulting narrow string
        char smallbuf [256];
        const size_t len = nelems < 0 ? rw_match (0, beg.puc) : size_t (nelems);
        char* const pb = len < sizeof smallbuf ? smallbuf : new char [len + 1];
        rw_narrow (pb, beg.puc, len);

        if (nelems < 0)
            nelems = int (len);

        nbytes = rw_asnprintf (pbuf, pbufsize, "%{+}%{#*s}",
                               nelems, beg.pc ? pb : beg.pc);

        if (pb != smallbuf)
            delete[] pb;
    }
    else {
        nbytes = rw_asnprintf (pbuf, pbufsize,
                               "*** %%{/Gs}: bad element size: %d ***",
                               elemsize);
    }

    //////////////////////////////////////////////////////////////////

    // compute the new length of the buffer
    const size_t buflen_1 = *pbuf ? strlen (*pbuf) : 0;

    // assert that the function really appended as many characters
    // as it said it did (assumes no NULs embedded in the output)
    // and that it didn't write past the end of the buffer
    RW_ASSERT (buflen_1 == buflen_0 + nbytes);
    RW_ASSERT (buflen_1 < *pbufsize);

    --nested_call;

    return nbytes;
}


static int
_rw_fmtstring (char **pbuf, size_t *pbufsize, const char *fmt, ...)
{
    va_list va;
    va_start (va, fmt);

    const int nbytes = _rw_fmtstringv (pbuf, pbufsize, fmt, va);

    va_end (va);

    return nbytes;
}


UserCharFmatInit::
UserCharFmatInit ()
{
    // install the formatting callback function
    static int format_init = rw_printf ("%{+!}", _rw_fmtstring);

    _RWSTD_UNUSED (format_init);
}
