/***************************************************************************
 *
 * locale.cpp
 *
 * $Id: locale.cpp 648752 2008-04-16 17:01:56Z faridz $
 *
 ***************************************************************************
 *
 * 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.
 *
 * Copyright 2001-2008 Rogue Wave Software, Inc.
 * 
 **************************************************************************/

#include <rw/_defs.h>

#if defined (__linux__) && !defined (_XOPEN_SOURCE) 
   // on Linux define _XOPEN_SOURCE to get CODESET defined in <langinfo.h>
#  define _XOPEN_SOURCE 500   /* Single UNIX conformance */
#endif   // __linux__

#ifdef _RWSTD_EDG_ECCP
   // disable error #450-D: the type "long long" is nonstandard
   // issued for uses of the type in Linux system headers (e.g.,
   // pthreadtypes.h)
#  pragma diag_suppress 450
#endif   // vanilla EDG eccp demo

#include _RWSTD_SYS_TYPES_H

#if _RWSTD_PATH_SEP == '/'
#  define SLASH      "/"
#  define LS_1       "ls -1 "
#else
#  define SLASH      "\\"
#  define LS_1       "dir /B /A:D "
#endif

#ifndef _MSC_VER
#  include <sys/mman.h>   // for mmap()
#  include <unistd.h>     // for close ()
#  ifndef _RWSTD_NO_ICONV
#    include <iconv.h>
#  endif
#  ifndef _RWSTD_NO_NL_LANGINFO
#    include <langinfo.h>
#  endif
#else
#  include <io.h>         // for open()
#  include <windows.h>
#endif  // _MSC_VER

#include <limits.h>      // for INT_MAX, INT_MIN
#include <sys/stat.h>
#include <fcntl.h>

#include <locale>
#include <map>           // for map
#include <vector>        // for vector
#include <iostream>      // for cerr, cout
#include <iomanip>
#include <fstream>       // for fstream

// #include <cerrno>
#include <clocale>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cassert>

#include <loc/_localedef.h>

#include "aliases.h"
#include "charmap.h"
#include "loc_exception.h"
#include "memchk.h"
#include "diagnostic.h"

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

#ifndef LC_MESSAGES
#  ifdef _RWSTD_LC_MESSAGES
#    define LC_MESSAGES   _RWSTD_LC_MESSAGES
#  else
#    define LC_MESSAGES   -1
#  endif   // _RWSTD_LC_MESSAGES
#endif   // LC_MESSAGES


// set to true in response to the "-c" command line option requesting
// locale to print the name of each section before printing out its
// contents
static bool print_sect_names = false;

// set to true in response to the "-k" command line option requesting
// locale to print the name of each keyword before printing out its
// value
static bool print_keywords = false;

// set to true in response to the "-h" command line option requesting
// locale to try to use character names from the original charmap used
// to create the locale (if possible)
static bool decode = false;

// set to false in response to the "-l" command line option requesting
// locale to produce output suitable for processing by the localedef
// utility
static bool posix_output = true;

// set to true in response to the "-p" command line option requesting
// locale to produce output using the symbols from the POSIX Portable
// Character Set
static bool use_pcs = false;

static bool is_utf8 = true;


static const _RW::__rw_codecvt_t*  codecvt_st   = 0;
static const _RW::__rw_collate_t*  collate_st   = 0;
static const _RW::__rw_ctype_t*    ctype_st     = 0;
static const _RW::__rw_time_t*     time_st      = 0;
static const _RW::__rw_num_t*      num_st       = 0;
static const _RW::__rw_mon_t*      mon_st       = 0;
static const _RW::__rw_punct_t*    num_punct_st = 0;
static const _RW::__rw_punct_t*    mon_punct_st = 0;
static const _RW::__rw_messages_t* messages_st  = 0;


static const char sect_lc_all[]      = "LC_ALL";
static const char sect_lc_collate[]  = "LC_COLLATE";
static const char sect_lc_ctype[]    = "LC_CTYPE";
static const char sect_lc_messages[] = "LC_MESSAGES";
static const char sect_lc_monetary[] = "LC_MONETARY";
static const char sect_lc_numeric[]  = "LC_NUMERIC";
static const char sect_lc_time[]     = "LC_TIME";

// length of the largest string you want printed
#define MAX_LINE_LEN 50

// for convenience
typedef unsigned char UChar;

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

int validate (const void *addr, std::size_t nbytes)
{
    const std::size_t size = memchk (addr, nbytes);

    if (nbytes != size) {
        if (size)
            issue_diag (E_INVAL, true, 0,
                        "invalid size at address %p: %u, expected %u\n",
                        addr, size, nbytes);
        else
            issue_diag (E_INVAL, true, 0,
                        "invalid address: %p (%u bytes)\n", addr, nbytes);
            
        return -1;
    }

    return 0;
}


int validate (const char *str)
{
    if (std::size_t (-1) == strchk (str)) {

        const std::size_t first_byte = memchk (str, 1);

        if (1 == first_byte)
            issue_diag (E_INVAL, true, 0,
                        "invalid string address: %p\n", str);
        else
            issue_diag (E_INVAL, true, 0,
                        "invalid string at %p: no terminating NUL\n", str);
        
        return -1;
    }

    return 0;
}


// initialize the LC_CTYPE, LC_MONETARY, LC_COLLATE, LC_NUMERIC and LC_TIME
// structures by finding the locale database and mmaping the file to the
// structure.
// Note: if the locale is not found no error msg is printed, instead we
// silently use the hardcoded 'POSIX' locale

const void*
init_struct (const std::string &loc_path_root,
             const char *cat_name, const char *codeset_name)
{
    // check to see if the LC_ALL or LC_LANG environment variable
    // was set, if LC_LANG was not set it defaults to "C"
    static const char* const all_val = std::getenv ("LC_ALL");
    static const char*       lang_val   = std::getenv ("LANG");
    if (0 == lang_val)
        lang_val = "C";

    // if LC_ALL is set, use that value for this locale
    // otherwise, find out what the environment value is for this category
    // is and if it is not set use the value in LC_LANG
    const char* env_val = all_val;
    if (!all_val || '\0' == *all_val) {
        env_val = cat_name ? std::getenv (cat_name) : "";
        if (!env_val)
            env_val = lang_val;
    }

    // if env_val is POSIX or C then return NULL otherwise
    // try to map the locale category database into memory
    if (std::strcmp (env_val, "C") && std::strcmp (env_val, "POSIX")) {

        // construct the path to the locale database file
        std::string loc_path;

        // if the LC_XXX environment variable contains a slash
        // it is treated as a pathname (possibly relative to
        // the current working directory, when the slash is not
        // the first character), otherwise as a filename residing
        // under `loc_path_root'
        const char* const slash = std::strrchr (env_val, _RWSTD_PATH_SEP);
        if (!slash)
            loc_path = loc_path_root;

        if (codeset_name) {
            if (slash)
                loc_path = std::string (env_val, slash - env_val);

            (loc_path += _RWSTD_PATH_SEP) += codeset_name;
        }
        else
            ((loc_path += env_val) += _RWSTD_PATH_SEP) += cat_name;

        struct stat st;
        const int fd = open (loc_path.c_str (), O_RDONLY);

        // get the size of the file by using fstat
        if (fd != -1 && 0 == fstat (fd, &st)) {
            // map the file to a pointer and if it succeeds
            // return the pointer, otherwise return 0

#ifndef _MSC_VER
            void* const ret = mmap (0, st.st_size, PROT_READ | PROT_WRITE,
                                    MAP_PRIVATE, fd, 0);
            if (MAP_FAILED == ret)
                return 0;

#else
            HANDLE file = CreateFile (loc_path.c_str (), GENERIC_READ,
                                      FILE_SHARE_READ, 0,
                                      OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
                                      NULL);

            HANDLE mapping = CreateFileMapping (file, 0, PAGE_READONLY,
                                                0, 0, 0);

            void* const ret = MapViewOfFile (mapping, FILE_MAP_READ, 0,
                                             0, st.st_size);

#endif  // _MSC_VER

            return ret;
        }
    }

    return 0;
}


template <class FacetData>
const FacetData*
init_section (const std::string &loc_path_root,
              const char *cat_name, const char *codeset_name,
              FacetData*)
{
    const void* const ptr =
        init_struct (loc_path_root, cat_name, codeset_name);

    if (ptr && 0 == validate (ptr, sizeof (FacetData)))
        return static_cast<const FacetData*>(ptr);

    return 0;
}


static void
init_sections ()
{
    std::string loc_path_root;

    // if the locale root environment is not set
    // use the current directory as the locale root
    const char* const root = std::getenv ("RWSTD_LOCALE_ROOT");

    loc_path_root  = root ? root : ".";
    loc_path_root += _RWSTD_PATH_SEP;

    // points to the name of the codeset used by all non-empty
    // locale categories, otherwise 0
    const char *codeset = 0;

    // points to the name of the LC_XXX environment variable that refers
    // to the collate category whose codeset is described by codeset;
    // the value of the variable is used to determine the location of
    // the codeset conversion database file, e.g., when the environment
    // variable RWSTD_LOCALE_ROOT is not defined and locale is invoked
    // like so:
    //   LC_CTYPE=/foo/bar/somelocale locale --charmap
    const char *section = 0;

    // initialize collate_st
    collate_st = init_section (loc_path_root, sect_lc_collate, 0,
                               (_RW::__rw_collate_t*)0);

    if (collate_st) {
        validate (collate_st->codeset_name ());

        codeset = collate_st->codeset_name ();
        section = sect_lc_collate;
    }

    // initialize ctype_st
    ctype_st = init_section (loc_path_root, sect_lc_ctype, 0,
                           (_RW::__rw_ctype_t*)0);

    if (ctype_st) {
        validate (ctype_st->codeset_name ());

        if (codeset) {
            if (std::strcmp (codeset, ctype_st->codeset_name ()))
                codeset = 0;
        }
        else {
            codeset = ctype_st->codeset_name ();
            section = sect_lc_ctype;
        }
    }

    // initialize mon_st
    mon_punct_st = init_section (loc_path_root, sect_lc_monetary, 0,
                               (_RW::__rw_punct_t*)0);

    if (0 != mon_punct_st) {
        mon_st = _RWSTD_STATIC_CAST (const _RW::__rw_mon_t*,
                                     mon_punct_st->get_ext());
        validate (mon_st, sizeof *mon_st);
    }

    if (mon_st) {
        validate (mon_st->codeset_name ());

        if (codeset) {
            if (std::strcmp (codeset, mon_st->codeset_name ()))
                codeset = 0;
        }
        else {
            codeset = mon_st->codeset_name ();
            section = sect_lc_monetary;
        }
    }

    // initialize num_st
    num_punct_st = init_section (loc_path_root, sect_lc_numeric, 0,
                               (_RW::__rw_punct_t*)0);
    if (0 != num_punct_st) {
        num_st = _RWSTD_STATIC_CAST (const _RW::__rw_num_t*,
                                     num_punct_st->get_ext());
        validate (num_st, sizeof *mon_st);
    }

    if (num_st) {
        validate (num_st->codeset_name ());

        if (codeset) {
            if (std::strcmp (codeset, num_st->codeset_name ()))
                codeset = 0;
        }
        else {
            codeset = num_st->codeset_name ();
            section = sect_lc_numeric;
        }
    }

    // initialize time_st
    time_st = init_section (loc_path_root, sect_lc_time, 0,
                            (_RW::__rw_time_t*)0);
    if (time_st) {
        validate (time_st->codeset_name ());

        if (codeset) {
            if (std::strcmp (codeset, time_st->codeset_name ()))
                codeset = 0;
        }
        else {
            codeset = time_st->codeset_name ();
            section = sect_lc_time;
        }
    }

    // initialize messages_st
    messages_st = init_section (loc_path_root, sect_lc_messages, 0,
                              (_RW::__rw_messages_t*)0);

    if (messages_st) {
        validate (messages_st->codeset_name ());

        if (codeset) {
            if (std::strcmp (codeset, messages_st->codeset_name ()))
                codeset = 0;
        }
        else {
            codeset = messages_st->codeset_name ();
            section = sect_lc_messages;
        }
    }

    if (codeset) {
        // initialize codecvt_st
        codecvt_st = init_section (loc_path_root, section, codeset,
                                   (_RW::__rw_codecvt_t*)0);
    }
}


static const char*
set_locale (int lc_cat, const char *locname, const std::string &charmap_name)
{
#ifndef _MSC_VER

    assert (0 != locname);

    locname = std::setlocale (lc_cat, locname);

    if (locname)
        return locname;

    std::string encoding (charmap_name.substr (0, charmap_name.rfind ('.')));

    std::vector<std::string> aliases;
    get_cname_aliases (encoding, aliases);

    std::vector<std::string>::iterator pos;

    for (pos = aliases.begin(); !is_utf8 && pos != aliases.end(); pos++)
        if (*pos == "utf8")
            is_utf8 = true;

    const char* names = get_installed_locales ();

    for (; *names; names += std::strlen (names) + 1) {

        if (0 != std::setlocale (LC_CTYPE, names)) {

            const char* const codeset = nl_langinfo (CODESET);

            for (pos = aliases.begin (); pos != aliases.end (); ++pos)
                if (codeset == *pos)
                    return names;
        }
    }

#endif  // _MSC_VER

    return 0;
}


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

static const Charmap*
get_charmap (int lc_cat)
{
    if (!decode)
        return 0;

    static const char* const lang        = std::getenv ("LANG");
    static const char* const lc_all      = std::getenv (sect_lc_all);
    static const char* const lc_collate  = std::getenv (sect_lc_collate);
    static const char* const lc_ctype    = std::getenv (sect_lc_ctype);
    static const char* const lc_messages = std::getenv (sect_lc_messages);
    static const char* const lc_monetary = std::getenv (sect_lc_monetary);
    static const char* const lc_numeric  = std::getenv (sect_lc_numeric);
    static const char* const lc_time     = std::getenv (sect_lc_time);

    static const Charmap* cmaps [6];
    static bool           cmaps_tried [sizeof cmaps / sizeof *cmaps];

    const char* charmap = 0;
    const char* locale  = 0;

    const Charmap** cmap = 0;

    switch (lc_cat) {

    case LC_COLLATE:
        charmap = collate_st ? collate_st->charmap_name () : "";
        locale  = lc_collate;
        cmap    = cmaps + 0;
        break;

    case LC_CTYPE:
        charmap = ctype_st ? ctype_st->charmap_name () : "";
        locale  = lc_ctype;
        cmap    = cmaps + 1;
        break;

    case LC_MESSAGES:
        charmap = messages_st ? messages_st->charmap_name () : "";
        locale  = lc_messages;
        cmap    = cmaps + 2;
        break;

    case LC_MONETARY:
        charmap = mon_st ? mon_st->charmap_name () : "";
        locale  = lc_monetary;
        cmap    = cmaps + 3;
        break;

    case LC_NUMERIC:
        charmap = num_st ? num_st->charmap_name () : "";
        locale  = lc_numeric;
        cmap    = cmaps + 4;
        break;

    case LC_TIME:
        charmap = time_st ? time_st->charmap_name () : "";
        locale  = lc_time;
        cmap    = cmaps + 5;
        break;

    default: assert (!"bad LC_XXX constant");
    }

    assert (0 != cmap);
    assert (0 != charmap);

    if (*cmap && (*cmap)->get_charmap_name () == charmap)
        return *cmap;

    if (cmaps_tried [cmap - cmaps])
        return *cmap;

    cmaps_tried [cmap - cmaps] = true;

    // LC_ALL overrides any other LC_XXX setting
    if (lc_all && *lc_all)
        locale = lc_all;

    // when neither LC_ALL or LC_XX is specified fall back on LANG
    if (0 == locale)
        locale = lang ? lang : "POSIX";

    set_locale (lc_cat, locale, charmap);

    // look for the charmap directory
    static const char* const src_root = std::getenv ("RWSTD_SRC_ROOT");

    if (0 == src_root) {
        static int warning_issued = 0;
        if (0 == warning_issued++)
            issue_diag (W_CHARMAP, false, 0, "RWSTD_SRC_ROOT not set\n");
    }
    else if ('\0' == *charmap) {
        static int warning_issued = 0;
        if (0 == warning_issued++)
            issue_diag (W_CHARMAP, false, 0, "no charmap name\n");
    }
    else {
        std::string charmap_path = src_root;

        charmap_path += _RWSTD_PATH_SEP;
        charmap_path += "charmaps";
        charmap_path += _RWSTD_PATH_SEP;
        charmap_path += charmap;

        // search for a C library locale that uses the same encoding
        std::string std_encoding (charmap);

# if !defined(_MSC_VER)
        std::string C_locale (get_C_encoding_locale (std_encoding));
# else
        std::string C_locale ("");
# endif

        *cmap = new Charmap (C_locale.c_str (), charmap_path.c_str(),
                             0 == std::strcmp ("utf8.cm", charmap),
                             false, true, false);
    }

    return cmap ? *cmap : 0;
}


static std::string
escape_value (unsigned val, bool always = false)
{
    // set of printable ASCII characters
    static const char printable [] = {
        //        ................
        //        0123456789abcdef
        /* 0. */ "                "
        /* 1. */ "                "
        /* 2. */ " ! #$%''()*+,-./"
        /* 3. */ "0123456789: <=>?"
        /* 4. */ "@ABCDEFGHIJKLMNO"
        /* 5. */ "PQRSTUVWXYZ[ ]^_"
        /* 6. */ "`abcdefghijklmno"
        /* 7. */ "pqrstuvwxyz{|}~ "
    };

    static const char hexdigit[] = "0123456789abcdef";

    std::string ret;

    if (   (UChar (';') == val || UChar ('"') == val || UChar ('\\') == val)
        && !always) {
        ret += '\\';
        ret += char (val);
    }
    else if (   use_pcs && val <= UChar ('~')
             && Charmap::portable_charset [val]) {
        // use symbols from the Portable Character Set
        ret += Charmap::portable_charset [val];
    }
    else if (val < sizeof printable - 1 && ' ' != printable [val] && !always) {
        // represent non-space printable characters as themselves
        ret += printable [val];
    }
    else if (UChar (' ') == val && !always) {
        // represent space as itself unless always escaping
        ret += ' ';
    }
    else {
        // non-printable character or escaping requested

        char buf [40];
        char *pbuf = buf;

        if (0xffU < val)
            *pbuf++ = '"';

        const unsigned mask = 0xffU << (sizeof mask - 1) * 8;

        while (val && 0 == (val & mask))
            val <<= 8;

        do {
            *pbuf++ = '\\';
            *pbuf++ = 'x';
            *pbuf++ = hexdigit [val >> (sizeof val * 8 - 4)];
            val   <<= 4;
            *pbuf++ = hexdigit [val >> (sizeof val * 8 - 4)];
        } while (val <<= 4);

        if ('"' == *buf)
            *pbuf++ = '"';

        *pbuf = '\0';

        ret = buf;
    }

    return ret;
}


static std::string
create_str (const char* str, const wchar_t* wstr, const Charmap *charmap)
{
    if (0 == str)
        str = "";

    std::string ret;

    if (0 == charmap) {

        // when no character map is provided format non-printable
        // characters using escape sequences
        for (; *str; ++str)
            ret += escape_value (UChar (*str));

        return ret;
    }

    typedef std::map <UChar, std::string>::const_iterator rn_cmap_iter;
    typedef std::map <wchar_t, std::string>::const_iterator rw_cmap_iter;

    if (0 == ctype_st || 1 == ctype_st->mb_cur_max) {
        // look up each character in the character map and (try
        // to) get the symbolic name associated with each, falling
        // back on ordinary formatting using escape sequences when
        // the character is not found in the map

        const rn_cmap_iter end = charmap->get_rn_cmap ().end ();

        for (; *str != '\0'; ++str) {
            const UChar uc = UChar (*str);
            const rn_cmap_iter it = charmap->get_rn_cmap ().find (uc);

            ret += (it == end) ? escape_value (uc) : it->second;
        }
    }
    else {
        const rw_cmap_iter end = charmap->get_rw_cmap ().end ();

        if (0 == wstr)
            wstr = L"";

        for ( ; *wstr != '\0'; ++wstr) {

            const unsigned uc = unsigned (*wstr);

            const rw_cmap_iter it = charmap->get_rw_cmap ().find (uc);

            ret += (it == end) ? escape_value (uc) : it->second;
        }
    }

    return ret;
}


// formats a character in human readable form
static std::string
create_str (unsigned ch, const Charmap *charmap)
{
    typedef std::map <wchar_t, std::string>::const_iterator rw_cmap_iter;

    std::string ret;

    if (charmap) {
        // look up character in the reverse wide character map
        const rw_cmap_iter ch_pos = charmap->get_rw_cmap ().find (ch);

        if (ch_pos == charmap->get_rw_cmap ().end ()) {
            // issue a warning when not found and format it below
            issue_diag (W_CHAR, true, 0, "wide character L'\\%x' "
                        "not found in character map\n", ch);
        }
        else
            return ch_pos->second;
    }

    return escape_value (ch);
}


static void
print_section (const char* name, bool end = false)
{
    assert (0 != name);

    if (print_sect_names)
        std::cout << (end ? "END " : "") << name << '\n';
}


static void
print_keyword (const char* str)
{
    assert (0 != str);

    if (print_keywords)
        std::cout << str << (posix_output ? '=' : ' ');
}

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

// place holder for function that prints the "C" locale LC_COLLATE category
static void
print_c_lc_collate ()
{
    assert (0);
}

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

static wchar_t
get_wchar_from_offset(unsigned int offset,
                      const unsigned int* wchar_map,
                      unsigned int num_wchars) {
    unsigned int beg = 0;
    unsigned int end = num_wchars;
    while (beg <= end) {
        unsigned int cur = (beg + end) / 2;
        if (*(wchar_map + cur * 2 + 1) == offset) {
            return *(const wchar_t*)(wchar_map + cur * 2);
        }
        else if (*(wchar_map + cur *2) > offset)
            end = cur - 1;
        else
            beg = cur + 1;
    }
    return 0;
}


static void
print_weight (const unsigned int* weightp, 
              unsigned int        num_weights,
              unsigned int        longest_weight)
{
    // FIXME: ignore the order of the element
    ++weightp;

    for (unsigned k = 0; k != num_weights; ++k) {

        for (unsigned x = 0; x != longest_weight; ++x, ++weightp) {

            if (*weightp != UINT_MAX) {
                if (0 == *weightp) 
                    std::cout << "IGNORE;";
                else
                    std::cout << escape_value (*weightp, true) << ';';
            }
        }
    }

    std::cout << '\n';
}


static void
write_coll_info (const std::string &ch, unsigned int idx,
                 unsigned int tab_num)
{
    if (collate_st->num_elms > 1) {
        const Charmap* const cmap = get_charmap (LC_COLLATE);

        typedef std::map <std::string, wchar_t>::const_iterator n_cmap2_iter;
        const unsigned int* tab = collate_st->get_n_tab (tab_num);
        unsigned int first = collate_st->get_first_char_in_n_tab(tab_num);
        for (unsigned int i = first; i <= UCHAR_MAX; i++) {
            std::string new_ch = ch;
            new_ch += UChar (i);
            if (tab[i - first] != UINT_MAX) {
                if (tab[i - first] & 0x80000000) {
                    // it's an offset to another table
                    write_coll_info (new_ch, idx + 1,
                                     tab[i - first] &~ 0x80000000);
                }
                else {
                    // we found the offset to the weight
                    if (cmap) {
                        // first we need to print the symbolic name
                        n_cmap2_iter n_cmap2_it =
                            cmap->get_mb_cmap().find(new_ch);
                        if (n_cmap2_it != cmap->get_mb_cmap().end())
                            std::cout << cmap->get_rw_cmap().find(
                                n_cmap2_it->second)->second;
                        else
                            ;
                    }
                    else {
                        for (unsigned int j = 0; j < idx; j++) {
                            const UChar uc = UChar (new_ch [j]);

                            std::cout << "\\d" << int (uc);

                            if (j != idx - 1)
                                std::cout << ';';
                        }
                    }
                    std::cout << "  ";
                    print_weight (collate_st->get_weight (tab[i - first]),
                                  collate_st->num_weights,
                                  collate_st->longest_weight);

                }
            }
        }
    }
}


static void
print_ce_info (unsigned                             tab_num,
               const std::string                   &mb_str,
               std::map<std::string, unsigned int> &ce_map)
{
    static unsigned ce_num = 0;

    const unsigned* const tab = collate_st->get_n_ce_tab (tab_num);

    const unsigned first = collate_st->get_first_char_in_n_ce_tab (tab_num);
    const unsigned last  = collate_st->get_last_char_in_n_ce_tab (tab_num);

    std::string mbchar;

    const Charmap* const cmap = get_charmap (LC_COLLATE);

    for (unsigned i = first; i <= last; ++i) {

        mbchar = mb_str;
        mbchar += UChar (i);

        const unsigned elem = tab [i - first];

        if (elem != UINT_MAX) {

            if (elem & 0x80000000) {
                // it's an offset to another table. recursively call
                // print_ce_info with the new table number.

                print_ce_info (elem &~ 0x80000000, mbchar, ce_map);
            }
            else {
                // we found the end of the collating element
                // now we need to do a lookup in the narrow character maps
                // and find the symbolic names for the characters that make
                // up this collating element

                // sym is sized at 13 because there will never be more then
                // 99,999 collating elements
                char sym [13];
                std::sprintf (sym, "<RW_CE_%u>", ce_num++);

                ce_map.insert (std::make_pair (sym, elem));
                std::cout << "collating-element " << sym << " from \"";

                if (0 == cmap)
                    std::cout << mbchar << "\"\n";
                else {
                    const std::map<std::string, wchar_t> &cm =
                        cmap->get_mb_cmap ();

                    unsigned ch = 0;

                    for (unsigned j = 1; j <= mbchar.size (); ++j) {
                        const std::map<std::string, wchar_t>::const_iterator
                            it = cm.find (mbchar.substr (ch, j));

                        if (it != cm.end ()) {
                            std::cout << (cmap->get_rw_cmap ().find
                                          (it->second))->second;
                            ch = j;
                        }
                    }

                    std::cout << "\"\n";
                }
            }
        }
    }
}


static int
print_lc_collate (const char *section, int)
{
    print_section (section);

    if (collate_st) {

        if (decode) {

            // print out internal collate_st data
            std::cout <<   "# collate data:"
                      << "\n#   codeset: \""
                      << collate_st->codeset_name ()
                      << "\"\n#   charmap: \""
                      << collate_st->charmap_name ()
                      << "\"\n#   codeset_off: "
                      << collate_st->codeset_off
                      << "\n#   charmap_off: "
                      << collate_st->charmap_off
                      << "\n#   elm_size: "
                      << collate_st->elm_size
                      << "\n#   num_elms: "
                      << collate_st->num_elms
                      << "\n#   num_wchars: "
                      << collate_st->num_wchars
                      << "\n#   longest_weight: "
                      << int (collate_st->longest_weight)
                      << "\n#   num_weights: "
                      << int (collate_st->num_weights)
                      << "\n#   largest_ce: "
                      << int (collate_st->largest_ce)
                      << '\n';
        }

        std::map<std::string, unsigned int> ce_map;
        typedef std::map<std::string, unsigned int>::iterator ce_map_iter;

        if (collate_st->largest_ce > 1) {
            print_ce_info (0, "", ce_map);
        }

        std::cout << "\norder_start ";
        // if there are more then one weight then each should be seperated
        // by a semicolon.  Print out all the weights names in this format
        // except for the last one.
        for (int i = 0; i < collate_st->num_weights; i++) {
            switch (collate_st->weight_type[i]) {
            case 0:
                std::cout << "forward";
                break;
            case 1:
                std::cout << "backward";
                break;
            case 2:
                std::cout << "forward,position";
                break;
            case 3:
                std::cout << "backward,position";
                break;
            default:
                issue_diag (502, true, 0, "invalid order type found "
                            "in collate definition\n");
            }
            if (i == collate_st->num_weights - 1)
                std::cout << '\n';
            else
                std::cout << ';';
        }

        // Print out the characters and their weights in
        // decimal format


        // first print out the collating elements
        for (ce_map_iter it = ce_map.begin (); it != ce_map.end (); ++it) {
            std::cout << it->first << "  ";

            print_weight (collate_st->get_weight (it->second),
                          collate_st->num_weights,
                          collate_st->longest_weight);
        }

        write_coll_info ("", 0, 0);

        if (collate_st->undefined_optimization) {
            std::cout << "UNDEFINED ";

            const unsigned idx = collate_st->undefined_weight_idx;

            print_weight (collate_st->get_weight (idx),
                          collate_st->num_weights,
                          collate_st->longest_weight);
        }

        std::cout << "\norder_end\n";
    }

    print_section (section, true);

    return 0;
}

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

// print_mask takes in a ctype_base::mask value and prints all the
// characters in that mask category.  If a charmap was given on the
// command line then it will look up the symbolic name and print that instead
static int
print_mask (const char *section, int mask)
{
    std::string out;

    if (section)
        print_section (section);

    const char *keyword = 0;

    switch (mask) {
    case std::ctype_base::upper:  keyword = "upper"; break;
    case std::ctype_base::lower:  keyword = "lower"; break;
    case std::ctype_base::space:  keyword = "space"; break;
    case std::ctype_base::print:  keyword = "print"; break;
    case std::ctype_base::alpha:  keyword = "alpha"; break;
    case std::ctype_base::cntrl:  keyword = "cntrl"; break;
    case std::ctype_base::punct:  keyword = "punct"; break;
    case std::ctype_base::graph:  keyword = "graph"; break;
    case std::ctype_base::digit:  keyword = "digit"; break;
    case std::ctype_base::xdigit: keyword = "xdigit"; break;
    default: break;
    }

    if (keyword)
        print_keyword (keyword);

    int nchars = 0;

    if (0 != ctype_st) {
        const Charmap* const cmap = get_charmap (LC_CTYPE);

        // go through the entire mask_table and print out each
        // character that has the specified mask, counting the
        // matching characters in the process
        // avoid printing masks without a keyword while still
        // counting such characters
#if 1
        for (std::size_t i = 0; i != ctype_st->wmask_s; ++i) {

            if (ctype_st->wmask_tab (i).mask & mask) {

                ++nchars;

                if (keyword) {
                    std::cout << create_str (ctype_st->wmask_tab (i).ch, cmap);

                    if (i + 1 < ctype_st->wmask_s)
                        std::cout << ';';
                }
            }
        }
#else
        for (std::size_t k = 0; k < ctype_st->wmask_s; ++k) {

            if (ctype_st->wmask_tab (k).mask & mask) {

                if (keyword)
                    out += create_str (ctype_st->wmask_tab (k).ch, cmap);

                ++nchars;

                for (std::size_t j = k + 1; j < ctype_st->wmask_s; ++j) {

                    if (ctype_st->wmask_tab (j).mask & mask) {

                        if (keyword) {
                            out += ';';

                            if (out.size () >= MAX_LINE_LEN && !posix_output)
                                out += '\\';
                        }

                        break;
                    }
                }
            }

            if (keyword && MAX_LINE_LEN < out.size () && !posix_output) {
                std::cout << out << '\n';
                out.clear ();
            }
        }

        std::cout << out;
#endif

    }

    if (keyword)
        std::cout << '\n';

    return nchars;
}


static int
print_toupper (const char *keyword, int print_cat)
{
    if (print_cat)
        print_section (sect_lc_ctype);

    print_keyword (keyword);

    std::string out;

    // number of characters in the maps
    int nchars = 0;

    if (0 != ctype_st) {

        const Charmap* const cmap = get_charmap (LC_CTYPE);

        const std::size_t count = ctype_st->wtoupper_s ();

        nchars += count;

        // print out each pair in the toupper table.
        // the wide char table is used because it contains all characters
        for (std::size_t j = 0; j < count; ++j) {
            out += '(';
            out += create_str (ctype_st->wtoupper_tab (j).lower, cmap);
            out += ',';
            out += create_str (ctype_st->wtoupper_tab (j).upper, cmap);
            out += ')';

            if (j != count - 1) {
                if (out.size () >= MAX_LINE_LEN && !posix_output)
                    out += ";\\";
                else
                    out += ';';
            }

            if (out.size () > MAX_LINE_LEN && !posix_output) {
                std::cout << out << '\n';
                out.clear ();
            }
        }

        std::cout << out;
    }

    std::cout << '\n';

    return nchars;
}


static int
print_tolower (const char *keyword, int print_cat)
{
    if (print_cat)
        print_section (sect_lc_ctype);

    print_keyword (keyword);

    std::string out;

    // number of characters in the maps
    int nchars = 0;

    if (0 != ctype_st) {

        const Charmap* const cmap = get_charmap (LC_CTYPE);

        const std::size_t count = ctype_st->wtolower_s ();

        nchars += count;

        for (std::size_t j = 0; j < count; ++j) {
            out += '(';
            out += create_str (ctype_st->wtolower_tab (j).upper, cmap);
            out += ',';
            out += create_str (ctype_st->wtolower_tab (j).lower, cmap);
            out += ')';

            if (j != count - 1) {
                if (out.size () >= MAX_LINE_LEN && !posix_output)
                    out += ";\\";
                else
                    out += ';';
            }
            if (out.size () > MAX_LINE_LEN && !posix_output) {
                std::cout << out << '\n';
                out.clear ();
            }
        }
        std::cout << out;
    }

    std::cout << '\n';

    return nchars;
}


static int
print_lc_ctype (const char *section, int)
{
    print_section (section);

    if (0 != ctype_st && decode) {

        // print out internal ctype_st data
        std::cout <<   "# ctype data:"
                  << "\n#   codeset: \""
                  << ctype_st->codeset_name ()
                  << "\"\n#   charmap: \""
                  << ctype_st->charmap_name ()
                  << "\"\n#   codeset_off: "
                  << ctype_st->codeset_off
                  << "\n#   charmap_off: "
                  << ctype_st->charmap_off
                  << "\n#   wmask_s: "
                  << ctype_st->wmask_s
                  << "\n#   wtoupper_off: "
                  << ctype_st->wtoupper_off
                  << "\n#   wtolower_off: "
                  << ctype_st->wtolower_off
                  << "\n#   wmask_off: "
                  << ctype_st->wmask_off
                  << "\n#   ctype_ext_off: "
                  << ctype_st->ctype_ext_off
                  << "\n#   mb_cur_max: "
                  << unsigned (ctype_st->mb_cur_max)
                  << '\n';
    }

    const std::size_t nupper = print_mask (0, std::ctype_base::upper);
    const std::size_t nlower = print_mask (0, std::ctype_base::lower);
    const std::size_t nspace = print_mask (0, std::ctype_base::space);
    const std::size_t nprint = print_mask (0, std::ctype_base::print);
    const std::size_t ncntrl = print_mask (0, std::ctype_base::cntrl);
    const std::size_t nalpha = print_mask (0, std::ctype_base::alpha);
    const std::size_t ndigit = print_mask (0, std::ctype_base::digit);
    const std::size_t npunct = print_mask (0, std::ctype_base::punct);
    const std::size_t ngraph = print_mask (0, std::ctype_base::graph);
    const std::size_t nxdigit = print_mask (0, std::ctype_base::xdigit);

    const int all_masks =
          std::ctype_base::upper | std::ctype_base::lower
        | std::ctype_base::space | std::ctype_base::print
        | std::ctype_base::cntrl | std::ctype_base::alpha
        | std::ctype_base::digit | std::ctype_base::punct
        | std::ctype_base::graph | std::ctype_base::xdigit;

    // count the number of all characters in all tables
    const std::size_t nall = print_mask (0, -1);

    // count the number of characters whose mask is other than
    // any of those specified (probably mistakes)
    const std::size_t nother = print_mask (0, ~all_masks);

    const std::size_t ntoupper = print_toupper ("toupper", 0);
    const std::size_t ntolower = print_tolower ("tolower", 0);

    if (0 != ctype_st && decode) {
        std::cout << "\n# ctype stats:"
                  << "\n#   total characters: " << nall
                  << "\n#   upper characters: " << nupper
                  << "\n#   lower characters: " << nlower
                  << "\n#   space characters: " << nspace
                  << "\n#   print characters: " << nprint
                  << "\n#   cntrl characters: " << ncntrl
                  << "\n#   alpha characters: " << nalpha
                  << "\n#   digit characters: " << ndigit
                  << "\n#   punct characters: " << npunct
                  << "\n#   graph characters: " << ngraph
                  << "\n#   xdigit characters: " << nxdigit
                  << "\n#   unclassified: " << nother
                  << "\n#   tolower pairs: " << ntolower
                  << "\n#   toupper pairs: " << ntoupper
                  << "\n";
    }

    print_section (section, true);

    return 0;
}

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

static int
print_decimal_point (const char *keyword, int print_cat)
{
    if (print_cat)
        print_section (sect_lc_numeric);

    print_keyword (keyword);

    if (0 == num_st)
        std::cout << '"' << create_str (".", 0, 0) << "\"\n";
    else {
        const char* const dp =
            _RWSTD_STATIC_CAST (const char*, num_punct_st->decimal_point (0));

        const wchar_t* const wdp =
            _RWSTD_STATIC_CAST(const wchar_t*, num_punct_st->decimal_point (1));

        const Charmap* const cmap = get_charmap (LC_NUMERIC);

        std::cout <<  '"' << create_str (dp, wdp, cmap) << "\"\n";
    }

    return 0;
}


static int
print_thousands_sep (const char *keyword, int print_cat)
{
    if (print_cat)
        print_section (sect_lc_numeric);

    print_keyword (keyword);

    if (0 == num_st)
        std::cout << "\"\"\n";
    else {
        const char* const ts =
            _RWSTD_STATIC_CAST (const char*, num_punct_st->thousands_sep (0));

        const wchar_t* const wts =
            _RWSTD_STATIC_CAST(const wchar_t*, num_punct_st->thousands_sep (1));

        const Charmap* const cmap = get_charmap (LC_NUMERIC);

        std::cout <<  '"' << create_str (ts, wts, cmap) << "\"\n";
    }

    return 0;
}


static int
print_grouping (const char *keyword, int print_cat)
{
    if (print_cat)
        print_section (sect_lc_numeric);

    print_keyword (keyword);

    if (0 == num_st)
        std::cout << "-1\n";
    else {
        size_t i;
        for (i = 0;
             i < num_punct_st->punct_ext_off - num_punct_st->grouping_off - 2;
             i++)
            std::cout << (int)((const char*)num_punct_st->grouping())[i]
                      << ";";
        std::cout << (int)((const char*)num_punct_st->grouping())[i]
                  << '\n';
    }

    return 0;
}


static int
print_lc_numeric (const char *section, int)
{
    print_section (section);

    if (0 != num_st && decode) {

        // print out internal numeric_st data
        std::cout <<   "# numeric data:"
                  << "\n#   codeset: \""
                  << num_st->codeset_name ()
                  << "\"\n#   charmap: \""
                  << num_st->charmap_name ()
                  << "\"\n#   codeset_off: "
                  << num_st->codeset_off
                  << "\n#   charmap_off: "
                  << num_st->charmap_off
                  << '\n';
    }

    // keywords follow the established order on popular platforms
    print_decimal_point ("decimal_point", 0);
    print_thousands_sep ("thousands_sep", 0);
    print_grouping ("grouping", 0);

    print_section (section, true);

    return 0;
}

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

static int
print_int_curr_symbol (const char *keyword, int print_cat)
{
    if (print_cat)
        print_section (sect_lc_monetary);

    print_keyword (keyword);

    if (0 == mon_st)
        std::cout << "\"\"\n";
    else {
        const char* const cs =
            _RWSTD_STATIC_CAST (const char*, mon_st->curr_symbol (1, 0));

        const wchar_t* const wcs =
            _RWSTD_STATIC_CAST (const wchar_t*, mon_st->curr_symbol (1, 1));

        const Charmap* const cmap = get_charmap (LC_MONETARY);

        std::cout << '"' << create_str (cs, wcs, cmap) << "\"\n";
    }

    return 0;
}


static int
print_currency_symbol (const char *keyword, int print_cat)
{
    if (print_cat)
        print_section (sect_lc_monetary);

    print_keyword (keyword);

    if (0 == mon_st)
        std::cout << "\"\"\n";
    else {
        const char* const cs =
            _RWSTD_STATIC_CAST (const char*, mon_st->curr_symbol (0, 0));

        const wchar_t* const wcs =
            _RWSTD_STATIC_CAST (const wchar_t*, mon_st->curr_symbol (0, 1));

        const Charmap* const cmap = get_charmap (LC_MONETARY);

        std::cout << '"' << create_str (cs, wcs, cmap) << "\"\n";
    }

    return 0;
}


static int
print_mon_decimal_point (const char *keyword, int print_cat)
{
    if (print_cat)
        print_section (sect_lc_monetary);

    print_keyword (keyword);

    if (0 == mon_st)
        std::cout << "\"\"\n";
    else {
        const char* const dp =
            _RWSTD_STATIC_CAST (const char*, mon_punct_st->decimal_point (0));

        const wchar_t* const wdp =
            _RWSTD_STATIC_CAST(const wchar_t*, mon_punct_st->decimal_point (1));

        const Charmap* const cmap = get_charmap (LC_MONETARY);

        std::cout << '"' << create_str (dp, wdp, cmap) << "\"\n";
    }

    return 0;
}


static int
print_mon_thousands_sep (const char *keyword, int print_cat)
{
    if (print_cat)
        print_section (sect_lc_monetary);

    print_keyword (keyword);

    if (0 == mon_st)
        std::cout << "\"\"\n";
    else {
        const char* const ts =
            _RWSTD_STATIC_CAST (const char*, mon_punct_st->thousands_sep (0));

        const wchar_t* const wts =
            _RWSTD_STATIC_CAST(const wchar_t*, mon_punct_st->thousands_sep (1));

        const Charmap* const cmap = get_charmap (LC_MONETARY);

        std::cout << '"' << create_str (ts, wts, cmap) << "\"\n";
    }

    return 0;
}


static int
print_mon_grouping (const char *keyword, int print_cat)
{
    if (print_cat)
        print_section (sect_lc_monetary);

    print_keyword (keyword);

    if (0 == mon_st)
        std::cout << "-1\n";
    else {
        size_t i;
        for (i = 0;
             i < mon_punct_st->punct_ext_off - mon_punct_st->grouping_off - 2;
             i++)
            std::cout << (int)((const char*)mon_punct_st->grouping())[i] << ";";
        std::cout << (int)((const char*)mon_punct_st->grouping())[i]
                  << '\n';
    }

    return 0;
}


static int
print_positive_sign (const char *keyword, int print_cat)
{
    if (print_cat)
        print_section (sect_lc_monetary);

    print_keyword (keyword);

    if (0 == mon_st)
        std::cout << "\"\"\n";
    else {
        const char* const ps =
            _RWSTD_STATIC_CAST (const char*, mon_st->positive_sign (0));

        const wchar_t* const wps =
            _RWSTD_STATIC_CAST (const wchar_t*, mon_st->positive_sign (1));

        const Charmap* const cmap = get_charmap (LC_MONETARY);

        std::cout << '"' << create_str (ps, wps, cmap) << "\"\n";
    }

    return 0;
}


static int
print_negative_sign (const char *keyword, int print_cat)
{
    if (print_cat)
        print_section (sect_lc_monetary);

    print_keyword (keyword);

    if (0 == mon_st)
        std::cout << "\"\"\n";
    else {
        const char* const ns =
            _RWSTD_STATIC_CAST (const char*, mon_st->negative_sign (0));

        const wchar_t* const wns =
            _RWSTD_STATIC_CAST (const wchar_t*, mon_st->negative_sign (1));
        
        const Charmap* const cmap = get_charmap (LC_MONETARY);

        std::cout << '"' << create_str (ns, wns, cmap) << "\"\n";
    }

    return 0;
}


static void
print_int (const char *keyword, int val, int print_cat)
{
    if (print_cat)
        print_section (sect_lc_monetary);

    print_keyword (keyword);

    std::cout << val << '\n';
}


#define MON_INT(member)   \
    (mon_st && CHAR_MAX != (mon_st->member) ? int (UChar (mon_st->member)) : -1)

static int
print_int_frac_digits (const char *keyword, int print_cat)
{
    print_int (keyword, MON_INT (frac_digits [1]), print_cat);

    return 0;

}


static int
print_frac_digits (const char *keyword, int print_cat)
{
    print_int (keyword, MON_INT (frac_digits [0]), print_cat);

    return 0;
}


static int
print_p_cs_precedes (const char *keyword, int print_cat)
{
    print_int (keyword, MON_INT (p_cs_precedes [0]), print_cat);

    return 0;
}


static int
print_n_sep_by_space (const char *keyword, int print_cat)
{
    print_int (keyword, MON_INT (n_sep_by_space [0]), print_cat);

    return 0;
}


static int
print_p_sep_by_space (const char *keyword, int print_cat)
{
    print_int (keyword, MON_INT (p_sep_by_space [0]), print_cat);

    return 0;
}


static int
print_n_cs_precedes (const char *keyword, int print_cat)
{
    print_int (keyword, MON_INT (n_cs_precedes [0]), print_cat);

    return 0;
}


static int
print_p_sign_posn (const char *keyword, int print_cat)
{
    print_int (keyword, MON_INT (p_sign_posn [0]), print_cat);

    return 0;
}


static int
print_n_sign_posn (const char *keyword, int print_cat)
{
    print_int (keyword, MON_INT (n_sign_posn [0]), print_cat);

    return 0;
}


static int
print_int_p_cs_precedes (const char *keyword, int print_cat)
{
    print_int (keyword, MON_INT (p_cs_precedes [1]), print_cat);

    return 0;
}


static int
print_int_n_sep_by_space (const char *keyword, int print_cat)
{
    print_int (keyword, MON_INT (n_sep_by_space [1]), print_cat);

    return 0;
}


static int
print_int_p_sep_by_space (const char *keyword, int print_cat)
{
    print_int (keyword, MON_INT (p_sep_by_space [1]), print_cat);

    return 0;
}


static int
print_int_n_cs_precedes (const char *keyword, int print_cat)
{
    print_int (keyword, MON_INT (n_cs_precedes [1]), print_cat);

    return 0;
}


static int
print_int_p_sign_posn (const char *keyword, int print_cat)
{
    print_int (keyword, MON_INT (p_sign_posn [1]), print_cat);

    return 0;
}


static int
print_int_n_sign_posn (const char *keyword, int print_cat)
{
    print_int (keyword, MON_INT (n_sign_posn [1]), print_cat);

    return 0;
}


static int
print_lc_monetary (const char *section, int)
{
    print_section (section);

    if (mon_st && decode) {

        // print out internal monetary_st data
        std::cout <<   "# monetary data:"
                  << "\n#   codeset: \""
                  << mon_st->codeset_name ()
                  << "\"\n#   charmap: \""
                  << mon_st->charmap_name ()
                  << "\"\n#   codeset_off: "
                  << mon_st->codeset_off
                  << "\n#   charmap_off: "
                  << mon_st->charmap_off
                  << '\n';
    }

    // keywords follow the established order on popular platforms
    print_int_curr_symbol ("int_curr_symbol", 0);
    print_currency_symbol ("currency_symbol", 0);
    print_mon_decimal_point ("mon_decimal_point", 0);
    print_mon_thousands_sep ("mon_thousands_sep", 0);
    print_mon_grouping ("mon_grouping", 0);
    print_positive_sign ("positive_sign", 0);
    print_negative_sign ("negative_sign", 0);

    print_int_frac_digits ("int_frac_digits", 0);
    print_frac_digits ("frac_digits", 0);

    print_p_cs_precedes ("p_cs_precedes", 0);
    print_p_sep_by_space ("p_sep_by_space", 0);
    print_n_cs_precedes ("n_cs_precedes", 0);
    print_n_sep_by_space ("n_sep_by_space", 0);
    print_p_sign_posn ("p_sign_posn", 0);
    print_n_sign_posn ("n_sign_posn", 0);

    print_int_p_cs_precedes ("int_p_cs_precedes", 0);
    print_int_p_sep_by_space ("int_p_sep_by_space", 0);
    print_int_n_cs_precedes ("int_n_cs_precedes", 0);
    print_int_n_sep_by_space ("int_n_sep_by_space", 0);
    print_int_p_sign_posn ("int_p_sign_posn", 0);
    print_int_n_sign_posn ("int_n_sign_posn", 0);

    print_section (section, true);

    return 0;
}

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

static void
print_time_keyword (const char *keyword, int print_cat,
                    const void *vstr, const void *vwstr)
{
    if (print_cat)
        print_section (sect_lc_monetary);

    if (keyword)
        print_keyword (keyword);

    const Charmap* const cmap = get_charmap (LC_TIME);

    const char*    const str  = _RWSTD_STATIC_CAST (const char*, vstr);
    const wchar_t* const wstr = _RWSTD_STATIC_CAST (const wchar_t*, vwstr);

    std::cout << '"' << create_str (str, wstr, cmap) << "\"";
}


static int
print_abday (const char *keyword, int print_cat)
{
    static const char str[][4] = {
        "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
    };

    for (std::size_t i = 0; ; ) {

        print_time_keyword (keyword, print_cat,
                            time_st ? time_st->abday (i, 0) : str [i],
                            time_st ? time_st->abday (i, 1) : 0);

        keyword   = 0;
        print_cat = 0;

        if (sizeof str / sizeof *str == ++i)
            break;

        std::cout << ';';
    }

    std::cout << '\n';

    return 0;
}


static int
print_day (const char *keyword, int print_cat)
{
    static const char str[][10] = {
        "Sunday", "Monday", "Tuesday", "Wednesday",
        "Thursday", "Friday", "Saturday"
    };

    for (std::size_t i = 0; ; ) {

        print_time_keyword (keyword, print_cat,
                            time_st ? time_st->day (i, 0) : str [i],
                            time_st ? time_st->day (i, 1) : 0);

        keyword   = 0;
        print_cat = 0;

        if (sizeof str / sizeof *str == ++i)
            break;

        std::cout << ';';
    }

    std::cout << '\n';

    return 0;
}


static int
print_abmon (const char *keyword, int print_cat)
{
    static const char str[][4] = {
        "Jan", "Feb", "Mar", "Apr", "May", "Jun",
        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
    };

    for (std::size_t i = 0; ; ) {

        print_time_keyword (keyword, print_cat,
                            time_st ? time_st->abmon (i, 0) : str [i],
                            time_st ? time_st->abmon (i, 1) : 0);

        keyword   = 0;
        print_cat = 0;

        if (sizeof str / sizeof *str == ++i)
            break;

        std::cout << ';';
    }

    std::cout << '\n';

    return 0;
}


static int
print_mon (const char *keyword, int print_cat)
{
    static const char str[][10] = {
        "January", "February", "March", "April", "May", "June",
        "July", "August", "September", "October", "November", "December"
    };

    for (std::size_t i = 0; ; ) {

        print_time_keyword (keyword, print_cat,
                            time_st ? time_st->mon (i, 0) : str [i],
                            time_st ? time_st->mon (i, 1) : 0);

        keyword   = 0;
        print_cat = 0;

        if (sizeof str / sizeof *str == ++i)
            break;

        std::cout << ';';
    }

    std::cout << '\n';

    return 0;
}


static int
print_am_pm (const char *keyword, int print_cat)
{
    static const char str[][3] = { "AM", "PM" };
    
    print_time_keyword (keyword, print_cat,
                        time_st ? time_st->am_pm (0, 0) : str [0],
                        time_st ? time_st->am_pm (0, 1) : str [1]);

    std::cout << ';';

    print_time_keyword (0, 0,
                        time_st ? time_st->am_pm (1, 0) : str [0],
                        time_st ? time_st->am_pm (1, 1) : str [1]);

    std::cout << '\n';

    return 0;
}


static int
print_era (const char *keyword, int print_cat)
{
    if (print_cat)
        print_section (sect_lc_monetary);

    print_keyword (keyword);

    if (0 == time_st)
        std::cout << '"' << create_str ("", 0, 0)
                  << "\"\n";
    else if (0 == time_st->era_count ()) {
        // if there are no eras, print out the empty string
        std::cout << "\"\"\n";
    }
    else {
        const Charmap* const cmap = get_charmap (LC_TIME);

        // print each era in the database according to the format
        // specified by POSIX
        for (unsigned int i = 0; i < time_st->era_count(); i++) {
            const _RW::__rw_time_t::era_t *era_p = time_st->era (i);
            // first print the direction and the offset
            if (era_p->offset >= 0)
                std::cout << "\"+:" << era_p->offset << ":";
            else {
                std::cout << "\"-:" << era_p->offset * -1 << ":";
            }
            // now print the start year, if it's negative, make sure the
            // negative sign gets in front
            if (era_p->year[0] < 0)
                std::cout << '-' << std::setw(4) << std::setfill ('0')
                          << era_p->year[0] * -1;
            else
                std::cout << std::setw(4) << std::setfill ('0')
                          << era_p->year[0];

            // now print the rest of the start date
            std::cout << _RWSTD_PATH_SEP << std::setw(2)
                      << int(era_p->month[0]) + 1 << _RWSTD_PATH_SEP 
                      << std::setw(2) << int(era_p->day[0]) << ":";

            // now print the end year, INT_MIN is the beginning of time
            // and INT_MAX is the end of time
            if (era_p->year[1] == INT_MIN)
                std::cout << "-*:";
            else if (era_p->year[1] == INT_MAX)
                std::cout << "+*:";
            else {
                if (era_p->year[1] < 0)
                    std::cout << '-' << std::setw(4) << era_p->year[1] * -1;
                else
                    std::cout << std::setw(4) << era_p->year[1];

                // now print out the rest of the end date
                std::cout << _RWSTD_PATH_SEP << std::setw(2) 
                          << int(era_p->month[1]) + 1 << _RWSTD_PATH_SEP 
                          << std::setw(2) << int(era_p->day[1]) << ":";
            }

            const char* const era =
                _RWSTD_STATIC_CAST (const char*, time_st->era_name (i, 0));

            const wchar_t* const wera =
                _RWSTD_STATIC_CAST (const wchar_t*, time_st->era_name (i, 1));

            const char* const fmt =
                _RWSTD_STATIC_CAST (const char*, time_st->era_fmt (i, 0));

            const wchar_t* const wfmt =
                _RWSTD_STATIC_CAST (const wchar_t*, time_st->era_fmt (i, 1));

            std::cout << create_str (era, wera, cmap) << ":"
                      << create_str (fmt, wfmt, cmap);

            if (i != time_st->era_count () - 1)
                std::cout << "\";";
            else
                std::cout << "\"\n";
        }
    }

    return 0;
}


static int
print_d_t_fmt (const char *keyword, int print_cat)
{
    static const char str[] = "%a %b %e %H:%M:%S %Y";

    print_time_keyword (keyword, print_cat,
                        time_st ? time_st->d_t_fmt (0) : str,
                        time_st ? time_st->d_t_fmt (1) : 0);

    std::cout << '\n';

    return 0;
}


static int
print_era_d_t_fmt (const char *keyword, int print_cat)
{
    print_time_keyword (keyword, print_cat,
                        time_st ? time_st->era_d_t_fmt (0) : 0,
                        time_st ? time_st->era_d_t_fmt (1) : 0);

    std::cout << '\n';

    return 0;
}


static int
print_d_fmt (const char *keyword, int print_cat)
{
    static const char str[] = "%m/%d/%y";

    print_time_keyword (keyword, print_cat,
                        time_st ? time_st->d_fmt (0) : str,
                        time_st ? time_st->d_fmt (1) : 0);

    std::cout << '\n';

    return 0;
}


static int
print_era_d_fmt (const char *keyword, int print_cat)
{
    print_time_keyword (keyword, print_cat,
                        time_st ? time_st->era_d_fmt (0) : 0,
                        time_st ? time_st->era_d_fmt (1) : 0);

    std::cout << '\n';

    return 0;
}


static int
print_t_fmt (const char *keyword, int print_cat)
{
    static const char str[] = "%H:%M:%S";

    print_time_keyword (keyword, print_cat,
                        time_st ? time_st->t_fmt (0) : str,
                        time_st ? time_st->t_fmt (1) : 0);

    std::cout << '\n';

    return 0;
}


static int
print_era_t_fmt (const char *keyword, int print_cat)
{
    print_time_keyword (keyword, print_cat,
                        time_st ? time_st->era_t_fmt (0) : 0,
                        time_st ? time_st->era_t_fmt (1) : 0);

    std::cout << '\n';

    return 0;
}


static int
print_t_fmt_ampm (const char *keyword, int print_cat)
{
    static const char str[] = "%I:%M:%S %p";

    print_time_keyword (keyword, print_cat,
                        time_st ? time_st->t_fmt_ampm (0) : str,
                        time_st ? time_st->t_fmt_ampm (1) : 0);

    std::cout << '\n';

    return 0;
}


static int
print_alt_digits (const char *keyword, int print_cat)
{
    if (time_st) {
        const unsigned ndigits = time_st->alt_digits_count ();

        for (unsigned i = 0; ; ) {

            print_time_keyword (keyword, print_cat,
                                i < ndigits ? time_st->alt_digits (i, 0) : 0,
                                i < ndigits ? time_st->alt_digits (i, 1) : 0);

            keyword   = 0;
            print_cat = 0;

            if (ndigits <= ++i)
                break;

            std::cout << ';';
        }

        std::cout << '\n';
    }
    else {
        print_time_keyword (keyword, print_cat, 0, 0);
    }

    return 0;
}


static int
print_lc_time (const char *section, int)
{
    print_section (section);

    if (time_st && decode) {

        // print out internal time_st data
        std::cout <<   "# time data:"
                  << "\n#   codeset: \""
                  << time_st->codeset_name ()
                  << "\"\n#   charmap: \""
                  << time_st->charmap_name ()
                  << "\"\n#   codeset_off: "
                  << time_st->codeset_off
                  << "\n#   charmap_off: "
                  << time_st->charmap_off
                  << '\n';
    }

    print_abday ("abday", 0);
    print_day ("day", 0);
    print_abmon ("abmon", 0);
    print_mon ("mon", 0);
    print_am_pm ("am_pm", 0);
    print_d_t_fmt ("d_t_fmt", 0);
    print_d_fmt ("d_fmt", 0);
    print_t_fmt ("t_fmt", 0);
    print_t_fmt_ampm ("t_fmt_ampm", 0);
    print_era ("era", 0);
    print_era_d_t_fmt ("era_d_t_fmt", 0);
    print_era_d_fmt ("era_d_fmt", 0);
    print_era_t_fmt ("era_t_fmt", 0);
    print_alt_digits ("alt_digits", 0);

    print_section (section, true);

    return 0;
}

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

static int
print_noexpr (const char *keyword, int print_cat)
{
    if (print_cat)
        print_section (sect_lc_messages);

    print_keyword (keyword);

    if (0 == messages_st)
        std::cout << '"' << create_str ("^[nN]", 0, 0) << "\"\n";
    else {
        const char* const no
            = _RWSTD_STATIC_CAST (const char*, messages_st->noexpr (0));

        const wchar_t* const wno
            = _RWSTD_STATIC_CAST (const wchar_t*, messages_st->noexpr (1));

        const Charmap* const cmap = get_charmap (LC_MESSAGES);

        std::cout << '"' << create_str (no, wno, cmap) << "\"\n";
    }

    return 0;
}


static int
print_yesexpr (const char *keyword, int print_cat)
{
    if (print_cat)
        print_section (sect_lc_messages);

    print_keyword (keyword);

    if (0 == messages_st)
        std::cout << '"' << create_str ("^[yY]", 0, 0) << "\"\n";
    else {
        const char* const yes
            = _RWSTD_STATIC_CAST (const char*, messages_st->yesexpr (0));

        const wchar_t* const wyes
            = _RWSTD_STATIC_CAST (const wchar_t*, messages_st->yesexpr (1));

        const Charmap* const cmap = get_charmap (LC_MESSAGES);

        std::cout << '"' << create_str (yes, wyes, cmap) << "\"\n";
    }

    return 0;
}


static int
print_lc_messages (const char *section, int)
{
    print_section (section);

    if (messages_st && decode) {

        // print out internal time_st data
        std::cout <<   "# messages data:"
                  << "\n#   codeset: \""
                  << messages_st->codeset_name ()
                  << "\"\n#   charmap: \""
                  << messages_st->charmap_name ()
                  << "\"\n#   codeset_off: "
                  << messages_st->codeset_off
                  << "\n#   charmap_off: "
                  << messages_st->charmap_off
                  << '\n';
    }

    print_yesexpr ("yesexpr", 0);
    print_noexpr ("noexpr", 0);

    print_section (section, true);

    return 0;
}

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

static int
print_lc_all (const char*, int)
{
    print_lc_collate (sect_lc_collate, 0);
    std::cout << '\n';

    print_lc_ctype (sect_lc_ctype, 0);
    std::cout << '\n';

    print_lc_monetary (sect_lc_monetary, 0);
    std::cout << '\n';

    print_lc_numeric (sect_lc_numeric, 0);
    std::cout << '\n';

    print_lc_time (sect_lc_time, 0);
    std::cout << '\n';

    print_lc_messages (sect_lc_messages, 0);

    return 0;
}

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

static int
print_charmap_name (const char *section, int)
{
    if (0 != ctype_st) {
        print_section (section);

        print_keyword ("charmap");
        std::cout << '"'<< ctype_st->charmap_name () << "\"\n";
    }

    return 0;
}


#ifdef _WIN32
static BOOL CALLBACK
EnumLocales (char* locale_id)
{
    const LCID lcid = std::strtoul (locale_id, 0, 16);

    char buf [80];
    const int bufsize = sizeof (buf) / sizeof (*buf);

    std::string name;

    if (GetLocaleInfo (lcid, LOCALE_SENGLANGUAGE, buf, bufsize))
        name = buf;

    if (GetLocaleInfo (lcid, LOCALE_SENGCOUNTRY, buf, bufsize)) {
        name += '_';
        name += buf;
    }

    if (   GetLocaleInfo (lcid, LOCALE_IDEFAULTANSICODEPAGE , buf, bufsize)
        && std::strtoul (buf, 0, 10)) {
        name += '.';
        name += buf;
    }

    if (const char* locname = std::setlocale (LC_ALL, name.c_str ()))
        std::cout << locname << '\n';

    return TRUE;
}
#endif

// print the available locales
static void
print_locale_names ()
{
    std::cout << "C\n";

    const char* const locale_root = std::getenv ("RWSTD_LOCALE_ROOT");

    if (0 != locale_root) {
        const std::string cmd = std::string (LS_1) + locale_root;

        std::system (cmd.c_str ());
    }
    else {
#ifndef _WIN32
        std::system ("/usr/bin/locale -a");
#else
        EnumSystemLocales (EnumLocales, LCID_INSTALLED);
#endif
    }
}


// print the available charmaps
static void
print_charmap_names ()
{
    const char* const locale_root = std::getenv ("RWSTD_SRC_ROOT");

    if (0 != locale_root) {
        const std::string cmd =
            std::string (LS_1) + locale_root + SLASH "charmaps";

        std::system (cmd.c_str());
    }
}

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

static void
print_help ()
{
    static const char* msg =
        "NAME\n\tlocale - get locale-specific information\n\n"
        "SYNOPSIS\n"
        "\tlocale [-a | -m | -? | --help | --charmap]\n\n"
        "\tlocale [-ckhlw] name ...\n\n"
        "DESCRIPTION\n"
        "\tThe locale utility writes information about the current\n"
        "\tlocale environment, or all public locales, to the standard\n"
        "\toutput. For the purposes of this section, a public locale is\n"
        "\tone provided by the implementation that is accessible to the\n"
        "\tapplication.\n\n"
        "\tWhen locale is invoked without any arguments, it summarizes\n"
        "\tthe current locale environment for each locale category as\n"
        "\tdetermined by the settings of the environment variables.\n\n"
        "\tWhen invoked with operands, it writes values that have been\n"
        "\tassigned to the keywords in the locale categories.\n\n"
        "OPTIONS\n"
        "\tThe following options are supported:\n\n"
        "\t-a\tWrite information about all available public locales.\n"
        "\t\tThe available locales include C, representing the\n"
        "\t\tPOSIX locale.\n\n"
        "\t-c\tWrite the names of selected locale categories.\n\n"
        "\t-k\tWrite the names and values of selected keywords.\n\n"
        "\t-m\tWrite names of available charmaps.\n\n"
        "\t-h\tIf available, use the original character map and \n"
        "\t\twrite the symbolic names instead of the character values\n\n"
        "\t-l\tWrite the locale information in a format readable by\n"
        "\t\tthe localedef utility\n\n"
        "\t-w\tTurn off all warning messages\n\n"
        "\t-w###\tTurn off a specific warning message\n\n"
        "\t-?\tWrite this message\n\n"
        "\t--help\tWrite this message\n\n"
        "\t--charmap\tWrite the character map.\n\n"
        "OPERANDS\n"
        "\tThe following operand is supported:\n\n"
        "\tname\tThe name of a locale category, the name of a keyword\n"
        "\t\tin a locale category, or the reserved name charmap.\n"
        "\t\tWhen charmap is specified, the utility writes the name\n"
        "\t\tof the character set description file or files used\n"
        "\t\tto create the current locale environment.\n\n";

    std::cout << msg;
}

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

static void
print_ellipsis (const char *mbchar,
                unsigned    last_byte [2],
                unsigned    last_ucs4 [2],
                unsigned    last_wchar [2])
{
    // close the ellipsis
    const int open_width  = last_ucs4 [0] < 0x10000 ? 4 : 8;
    const int close_width = last_ucs4 [1] < 0x10000 ? 4 : 8;

    // the number of spaces between the symbolic character name
    // and its multibyte character representation
    int pad = 28 - open_width;

    const unsigned nchars = last_byte [1] - last_byte [0] + 1;

    if (1 < nchars) {

        pad -= close_width + 8;

        // print the UCS code, followed by the MB character,
        // followed by the libc wchar_t value of the character
        std::printf ("<U%0*X>..<U%0*X>%*s%s\\x%02x   "
                     "# L'\\x%02x' (%u characters)\n",
                     open_width, last_ucs4 [0],
                     close_width, last_ucs4 [1], pad, " ",
                     mbchar, last_byte [0], last_wchar [0],
                     nchars);
    }
    else {
        pad -= 3;

        std::printf ("<U%0*X>%*s%s\\x%02x   "
                     "# L'\\x%02x'\n",
                     open_width, last_ucs4 [0], pad, " ",
                     mbchar, last_byte [0], last_wchar [0]);
    }

    last_byte [0]  = last_byte [1] = UINT_MAX;
    last_ucs4 [0]  = last_ucs4 [1] = 0;
    last_wchar [0] = last_wchar [1] = 0;
}

// traverses codeset conversion tables and prints out the character
// map in POSIX format with the internal wchar_t encoding in comments
// collects space utilization statistics
static void
print_charmap (const __rw::__rw_codecvt_t *cvt,
               char           *mbchar,
               const unsigned *tab,
               unsigned       *ntables,
               unsigned       *nchars,
               unsigned       *nunused)
{
    const unsigned* const table = cvt->n_to_w_tab ();

    if (!tab)
        tab = table;

    // the length of the multibyte character (prefix) formatted
    // in a human-readable form (e.g., "\x80\xff" or similar)
    const std::size_t mbchar_len = std::strlen (mbchar);

    // index into the array of counters corresponding to the
    // table being processed at this level of recursion
    const std::size_t count_inx = mbchar_len / 4 + 1;

    if (ntables) {
        // increment the grand total of all tables
        ++ntables [0];
        // increment the number of tables for characters
        // of this length
        ++ntables [count_inx];
    }

    unsigned last_byte [2]  = { UINT_MAX, 0 };
    unsigned last_ucs4 [2]  = { 0, 0 };
    unsigned last_wchar [2] = { 0, 0 };

    // print out all multibyte characters in this map
    for (std::size_t i = 0; i != UCHAR_MAX + 1U; ++i) {

        if (tab [i] & 0x80000000) {

            if (last_byte [0] <= UCHAR_MAX) {
                // print the last character or ellipsis
                print_ellipsis (mbchar, last_byte, last_ucs4, last_wchar);
            }

            // skip invalid character or next table
            continue;
        }

        // look up the UCS-4 and the wchar_t value of the MB character
        const unsigned ucs4  = cvt->get_ucs4_at_offset (tab [i]);
        const unsigned wchar = cvt->get_internal_at_offset (tab [i]);

        if (UCHAR_MAX < last_byte [0]) {
            last_byte [0]  = i;
            last_ucs4 [0]  = ucs4;
            last_wchar [0] = wchar;
        }
        else if (1 < ucs4 - last_ucs4 [1] || 1 < wchar - last_wchar [1]) {
            // print the last character or ellipsis
            print_ellipsis (mbchar, last_byte, last_ucs4, last_wchar);

            last_byte [0]  = i;
            last_ucs4 [0]  = ucs4;
            last_wchar [0] = wchar;
        }

        last_byte [1]  = i;
        last_ucs4 [1]  = ucs4;
        last_wchar [1] = wchar;

        if (nchars) {
            // increment the grand total of all characters
            ++nchars [0];

            // increment the number of multibyte characters
            // of this length
            ++nchars [count_inx];
        }
    }

    if (last_byte [0] <= UCHAR_MAX)
        print_ellipsis (mbchar, last_byte, last_ucs4, last_wchar);

    // process subsequent maps
    for (unsigned i = 0; i != UCHAR_MAX + 1U; ++i) {

        if (UINT_MAX == tab [i]) {
            // invalid multibyte sequence (i.e., unused slot)

            if (nunused) {
                // increment counters
                ++nunused [0];
                ++nunused [count_inx];
            }

            continue;
        }

        if (!(tab [i] & 0x80000000)) {
            // valid multibyte characater already printed out above
            continue;
        }

        // invoke self recursively to print out the contents
        // of the next map
        std::sprintf (mbchar + mbchar_len, "\\x%02x", i);

        print_charmap (cvt, mbchar, table + 256 * (tab [i] & 0x7fffffff),
                       ntables, nchars, nunused);

        mbchar [mbchar_len] = '\0';
    }
}


// print out the contents of the __rw_codecvt_t structure
static void
print_charmap (const __rw::__rw_codecvt_t *cvt = 0)
{
    if (0 == cvt) {
        // use the codecvt map when none is specified
        cvt = codecvt_st;
    }

    if (0 == cvt)
        return;

    std::cout <<   "<escape_char>   \\"
              << "\n<comment_char>  #"
              << "\n<code_set_name> " << cvt->codeset_name ()
              << "\n<mb_cur_max>    " << int (cvt->mb_cur_max)
              << "\nCHARMAP\n";

    if (decode) {
        std::cout << "# charmap data:"
                  << "\n#   charmap name: "
                  << cvt->charmap_name ()
                  << "\n#   n_to_w_tab_off: "
                  << cvt->n_to_w_tab_off
                  << "\n#   w_to_n_tab_off: "
                  << cvt->w_to_n_tab_off
                  << "\n#   utf8_to_ext_tab_off: "
                  << cvt->utf8_to_ext_tab_off
                  << "\n#   xliteration_off: "
                  << cvt->xliteration_off
                  << "\n#   wchar_off: "
                  << cvt->wchar_off
                  << "\n#   codeset_off: "
                  << cvt->codeset_off
                  << "\n#   charmap_off: "
                  << cvt->charmap_off
                  << "\n#   codecvt_ext_off: "
                  << cvt->codecvt_ext_off
                  << '\n';
    }
            
    char buf [1024];
    *buf = '\0';

    // total number of conversion tables for characters
    // of each length up to MB_CUR_MAX with ntables being
    // the sum of all of them
    unsigned ntables [64] = { 0 };

    // total number of unused slots in tables at each level
    // (i.e., slots not used either to encode character or
    // to store an offset to the next table)
    unsigned nunused [64] = { 0 };

    // total numbers of multibyte characters of each lenght
    // up to MB_CUR_MAX with nchars [0] being the sum of all
    // of them
    unsigned nchars [64] = { 0 };

    print_charmap (cvt, buf, 0, ntables, nchars, nunused);

    std::cout << "END CHARMAP\n"
              << "\n# charmap stats:";

    for (std::size_t i = 1; i != sizeof nchars / sizeof *nchars; ++i) {
        if (0 != nchars [i] || 0 != ntables [i]) {

            const double waste =
                (100.0 * nunused [i]) / (ntables [i] * UCHAR_MAX);

            std::cout << "\n#   ";
            std::cout << i << " byte characters: " << nchars [i]
                      << " (" << ntables [i] << " tables, "
                      << unsigned (waste) << "% waste)";
        }
    }

    const double waste = (100.0 * nunused [0]) / (ntables [0] * UCHAR_MAX);

    std::cout << "\n#   total characters:  " << nchars [0]
              << " (" << ntables [0] << " tables, "
              << unsigned (waste) << "% waste)\n";
}

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

// print the value of a single localization environment variable
// according to POSIX rules
static void
print_lc_var (const char* cat_name, const char* val,
              const char *lc_all, const char *lang)
{
    std::cout << cat_name << '=';

    if (0 == val)
        std::cout << '"';

    const char* const strval = 
        (lc_all ? lc_all : val ? val : lang ? lang : "");

    // POSIX requires that environment variable "values be properly
    // quoted for possible later reentry to the shell"
    for (const char* s = strval; *s; ++s) {
        switch (*s) {
        case '$': case '"':
            // escape unconditionally
            std::cout << '\\' << *s; break;

        case '|': case '&': case '\\': case '<': case '>':
        case '`': case ' ':
            // escape only when not quoted
            if (val) std::cout << '\\' << *s;
            
        default:
            // do not escape
            std::cout << *s;
        }
    }

    if (0 == val)
        std::cout << '"';

    std::cout << '\n';
}

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

// print the values of the LANG and LC_XXX environment variables
// according to POSIX rules
static void
print_lc_vars ()
{
    const char* const lang        = std::getenv ("LANG");
    const char* const lc_all      = std::getenv (sect_lc_all);
    const char* const lc_collate  = std::getenv (sect_lc_collate);
    const char* const lc_ctype    = std::getenv (sect_lc_ctype);
    const char* const lc_messages = std::getenv (sect_lc_messages);
    const char* const lc_monetary = std::getenv (sect_lc_monetary);
    const char* const lc_numeric  = std::getenv (sect_lc_numeric);
    const char* const lc_time     = std::getenv (sect_lc_time);

    print_lc_var ("LANG",           lang ? lang : "", 0, 0);
    print_lc_var (sect_lc_ctype,    lc_ctype,    lc_all, lang);
    print_lc_var (sect_lc_collate,  lc_collate,  lc_all, lang);
    print_lc_var (sect_lc_time,     lc_time,     lc_all, lang);
    print_lc_var (sect_lc_monetary, lc_monetary, lc_all, lang);
    print_lc_var (sect_lc_numeric,  lc_numeric,  lc_all, lang);
    print_lc_var (sect_lc_messages, lc_messages, lc_all, lang);
    print_lc_var (sect_lc_all,      lc_all ? lc_all : "", 0, 0);
}

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

static const struct {
    const char *name;
    int       (*print)(const char*, int);
    int         arg;
} handlers[] = {

    { sect_lc_time,     print_lc_time, 0 },
    { sect_lc_monetary, print_lc_monetary, 0 },
    { sect_lc_numeric,  print_lc_numeric, 0 },
    { sect_lc_ctype,    print_lc_ctype, 0 },
    { sect_lc_collate,  print_lc_collate, 0 },
    { sect_lc_messages, print_lc_messages, 0 },
    { sect_lc_all,      print_lc_all, 0 },

    { "abday", print_abday, -1 },
    { "day",  print_day, -1 },
    { "abmon", print_abmon, -1 },
    { "mon", print_mon, -1 },
    { "am_pm", print_am_pm, -1 },
    { "d_t_fmt", print_d_t_fmt, -1 },
    { "t_fmt", print_t_fmt, -1 },
    { "d_fmt", print_d_fmt, -1 },
    { "t_fmt_ampm", print_t_fmt_ampm, -1 },
    { "era_d_t_fmt", print_era_d_t_fmt, -1 },
    { "era_t_fmt", print_era_t_fmt, -1 },
    { "era_d_fmt", print_era_d_fmt, -1 },
    { "era", print_era, -1 },
    { "alt_digits", print_alt_digits, -1 },
    { "yesexpr", print_yesexpr, -1 },
    { "noexpr", print_noexpr, -1 },
    { "int_curr_symbol", print_int_curr_symbol, -1 },
    { "currency_symbol", print_currency_symbol, -1 },
    { "mon_decimal_point", print_mon_decimal_point, -1 },
    { "mon_thousands_sep", print_mon_thousands_sep, -1 },
    { "mon_grouping", print_mon_grouping, -1 },
    { "positive_sign", print_positive_sign, -1 },
    { "negative_sign", print_negative_sign, -1 },
    { "int_frac_digits", print_int_frac_digits, -1 },
    { "frac_digits", print_frac_digits, -1 },
    { "p_cs_precedes", print_p_cs_precedes, -1 },
    { "p_sep_by_space", print_p_sep_by_space, -1 },
    { "n_cs_precedes", print_n_cs_precedes, -1 },
    { "n_sep_by_space", print_n_sep_by_space, -1 },
    { "p_sign_posn", print_p_sign_posn, -1 },
    { "n_sign_posn", print_n_sign_posn, -1 },
    { "int_p_cs_precedes", print_int_p_cs_precedes, -1 },
    { "int_p_sep_by_space", print_int_p_sep_by_space, -1 },
    { "int_n_cs_precedes", print_int_n_cs_precedes, -1 },
    { "int_n_sep_by_space", print_int_n_sep_by_space, -1 },
    { "int_p_sign_posn", print_int_p_sign_posn, -1 },
    { "int_n_sign_posn", print_int_n_sign_posn, -1 },
    { "decimal_point", print_decimal_point, -1 },
    { "thousands_sep", print_thousands_sep, -1 },
    { "grouping", print_grouping, -1 },
    { "upper", print_mask, std::ctype_base::upper },
    { "lower", print_mask, std::ctype_base::lower },
    { "space", print_mask, std::ctype_base::space },
    { "print", print_mask, std::ctype_base::print },
    { "cntrl", print_mask, std::ctype_base::cntrl },
    { "alpha", print_mask, std::ctype_base::alpha },
    { "digit", print_mask, std::ctype_base::digit },
    { "punct", print_mask, std::ctype_base::punct },
    { "graph", print_mask, std::ctype_base::graph },
    { "xdigit", print_mask, std::ctype_base::xdigit },
    { "tolower", print_tolower, -1 },
    { "toupper", print_toupper, -1 },
    { "charmap", print_charmap_name, 0 },

    // sentinel
    { 0, 0, 0 }
};


int locale_main (int argc, char *argv[])
{
    const char* const program_name = argv [0];

    if (1 == argc) {
        // print all localization environment variables
        print_lc_vars ();
    }
    else {
        init_sections ();

        --argc;

        while (0 != *++argv && 0 < argc-- && '-' == **argv) {

            switch (*++*argv) {

            case 'a':
                // -a: print the names of all installed locales
                print_locale_names ();
                return EXIT_SUCCESS;

            case 'k':
                // -k: print the names of keywords when printing
                //     their values
                print_keywords = true;
                break;

            case 'c':
                // -c[k]: print the name of each section
                print_sect_names = true;
                if (*(*argv + 1) == 'k')
                    print_keywords = true;
                break;

            case 'm':
                print_charmap_names ();
                return EXIT_SUCCESS;

            case 'h':
                // -h: use character names from the original charmap
                //     used by localedef to build the locale (if possible)
                decode = true;
                break;

            case 'l':
                // -l: produce output suitable as input for processing
                //     by the localedef utility
                posix_output = false;
                break;

            case 'p':
                // -p: produce output using symbols from the Portable
                //     Character Set whenever possible
                use_pcs      = true;
                posix_output = false;
                break;

            case 'w':
                if (std::strlen (*argv) == 1)
                    issue_diag (W_DISABLE, 0, 0, 0);
                else
                    issue_diag (std::atoi (++*argv), 0, 0, 0);
                break;

            case '?':
                print_help ();
                return EXIT_SUCCESS;

            case '-':
                if (0 == std::strcmp (*argv, "-help")) {
                    print_help ();
                    return EXIT_SUCCESS;
                }

                if (0 == std::strcmp (*argv, "-charmap")) {
                    print_charmap ();
                    return EXIT_SUCCESS;
                }

                // fall through...

            default:
                issue_diag (504, true, 0, "%s: invalid option "
                            "-%s\n", program_name, *argv);
            }
        }

        if (0 == *argv) {
            issue_diag (505, true, 0, "%s: missing argument\n Try '"
                        "%s --help'\n", program_name, program_name);
        }

        for (; 0 != *argv; ++argv) {

            for (std::size_t i = 0; handlers [i].name; ++i) {
                if (0 == std::strcmp (*argv, handlers [i].name)) {

                    const int arg =
                        -1 == handlers [i].arg ? print_sect_names
                        : handlers [i].arg;

                    handlers [i].print (handlers [i].name, arg);
                }
            }
        }
    }

    return EXIT_SUCCESS;
}
