/***************************************************************************
 *
 * facet.cpp
 *
 * $Id: facet.cpp 650350 2008-04-22 01:35:17Z sebor $
 *
 ***************************************************************************
 *
 * 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-2006 Rogue Wave Software.
 * 
 **************************************************************************/

#define _RWSTD_LIB_SRC

#include <rw/_defs.h>

#include <locale.h>   // for LC_XXX costants
#include <stdlib.h>   // for bsearch(), getenv(), qsort()
#include <string.h>   // for memcpy(), strlen()

#include <iosfwd>         // for mbstate_t

#include "access.h"
#include "mman.h"

#include <loc/_facet.h>
#include <loc/_locale.h>
#include <loc/_localedef.h>

#include <rw/_mutex.h>

#include "locale_body.h"
#include "podarray.h"    // for __rw_chararry


_RWSTD_NAMESPACE (__rw) { 

// computes LC_XXX category from a numeric facet id, returns the
// LC_XXX category for standard facets, LC_ALL for all others
int __rw_get_cat (int);

// retrieves the literal name for a given category value
static inline 
const char* __rw_get_cat_name (int category)
{
    // FIXME: merge this struct and lookup with __rw_cats in rw_locale.cpp
    // maps LC_XXX category values to their names
    static const struct {
        int         cat;           // LC_XXX category
        const char *name;          // string corresponding to LC_XXX
    } cats [] = {
        { _RWSTD_LC_COLLATE,  "LC_COLLATE"},
        { _RWSTD_LC_CTYPE,    "LC_CTYPE"},
        { _RWSTD_LC_MONETARY, "LC_MONETARY"},
        { _RWSTD_LC_NUMERIC,  "LC_NUMERIC"},
        { _RWSTD_LC_TIME,     "LC_TIME"}

#ifdef _RWSTD_LC_MESSAGES
        , { _RWSTD_LC_MESSAGES, "LC_MESSAGES" }
#endif   // _RWSTD_LC_MESSAGES
    };

    // find facet's category name
    for (_RWSTD_SIZE_T i = 0; i != sizeof cats / sizeof *cats; ++i) {
        if (category == cats [i].cat)
            return cats [i].name;
    }

    return 0;
}


int __rw_facet::_C_opts = 0

#ifndef _RWSTD_NO_INITIAL_CAT_SEP
    | __rw_facet::_C_prepend_cat_sep
#endif   // _RWSTD_NO_INITIAL_CAT_SEP

#ifndef _RWSTD_NO_CAT_NAMES
    | __rw_facet::_C_use_cat_names
#endif   // _RWSTD_NO_CAT_NAMES

#ifndef _RWSTD_NO_CONDENSED_NAME
    | __rw_facet::_C_condensed_name
#endif   // _RWSTD_NO_CONDENSED_NAME

    | 0;



__rw_facet::__rw_facet (_RWSTD_SIZE_T __ref /* = 0 */)
    : _C_name (0), _C_buf (0), _C_impdata (0), _C_impsize (0),
    _C_type (_C_unknown), _C_ref (__ref), _C_pid (0 /* invalid */)
{
}


__rw_facet::~__rw_facet ()
{
    _RWSTD_ASSERT (!_C_name && !_C_buf || _C_name && _C_buf);

    static const char destroyed[] = "*** destroyed facet ***";

    // (try to) detect double deletion
    _RWSTD_ASSERT (destroyed != _C_name);
    _RWSTD_ASSERT (_C_invalid != _C_type);

    // mark invalid
    _C_type = _C_invalid;

    if (_C_name != _C_buf)
        //  MSVC requires that we cast away const before calling delete.
        delete[] _RWSTD_CONST_CAST (char*, _C_name);

    // mark destroyed
    _C_name = destroyed;

    // unmap only if both `impadata' and `impsize' are valid
    // (derived facets may want to set `impdata' to their own,
    // not necessarily memory mapped, data w/o setting `impsize'
    // to something meaningful
    if ((_RWSTD_SIZE_T)(-1) == _C_impsize)
        ::operator delete (_RWSTD_CONST_CAST (void*, _C_impdata));
    else if (_C_impdata && _C_impsize) 
        __rw_release_facet_data (_C_impdata, _C_impsize);
}


void __rw_facet::_C_set_name (const char *name, char *buf, _RWSTD_SIZE_T size)
{
    _RWSTD_ASSERT (0 != name);
    _RWSTD_ASSERT (0 != buf || 0 == size);

    // expected to be called only from the ctor
    _RWSTD_ASSERT (0 == _C_name);
    _RWSTD_ASSERT (0 == _C_buf);

    _RWSTD_SIZE_T len = ::strlen (name) + 1;

    char *tmp = len <= size ? buf : new char [len];

    ::memcpy (tmp, name, len);
    _C_name = tmp;
    _C_buf  = buf;
}


const void* __rw_facet::_C_get_data ()
{
    // check initialization
    if (_C_impsize)
        return _C_impdata;

    // `pid' may be 0 if the facet has not been obtained
    // by a call to use_facet (but instead constructed
    // and used directly without having been installed
    // in a locale object); in that case, locale database
    // mapping for the facet is not available
    if (!_C_pid)
        return _C_impdata;

    // lock the object
    _RWSTD_MT_GUARD (&_C_mutex);

    // check again for initialization
    if (_C_impsize)
        return _C_impdata;

    __rw_chararray locname (_C_name && _C_name [0] ? _C_name : "C");

    // implementation database mapping data
    const void* pdata = 0;
    _RWSTD_SIZE_T sz = 0;

    _RWSTD_ASSERT (0 != _C_pid);
    const int cat = __rw_get_cat (int (*_C_pid));

    // use facet's id and name to determine whether the type of the facet is
    // codecvt_byname<wchar_t, char, mbstate_t> (_C_type is not sufficiently
    // reliable since it is set to _C_unknown for all facet objects contructed
    // outside of the library)
    const bool is_wcodecvt_byname =
        _C_wcodecvt_byname == __rw_locale::_C_get_facet_type (*this);

    // map the database; first try with the full name (including
    // any modifiers) since such a database may actually exist
    pdata = __rw_get_facet_data (cat, sz, locname.data ());
    if (!pdata) {

        // the mapping failure can be attributed to the use of a
        // special name, eg <locale>.<codeset>@UCS-4 which calls
        // for the stripping of the special suffix
        _RW::__rw_chararray strip_name;

        const char*  pstr = strstr (locname.data (), "@UCS");
        const size_t plen = size_t (pstr - locname.data ());

        if (pstr && !pstr [4]) {

            // @UCS suffix is recognized irrespective of the size of wchar_t,
            strip_name.append (locname.data (), plen);
            pdata = __rw_get_facet_data (cat, sz, strip_name.data ());
        }
        else if (   pstr && pstr [4] == '-' && !pstr [6]
                 && (   '4' == pstr [5] && sizeof (wchar_t) == 4
                     || '2' == pstr [5] && sizeof (wchar_t) == 2)) {

            // @UCS-4 is only recognized where sizeof (wchar_t) == 4
            // @UCS-2 is only recognized where sizeof (wchar_t) == 2
            // no other modifier is recognized
            strip_name.append (locname.data (), plen);
            pdata = __rw_get_facet_data (cat, sz, strip_name.data ());
        }

        // if the type of the facet is codecvt_byname<wchar_t,char,mbstate_t>
        // then the name is possibly in the format codeset | codeset@modifier
        if (!pdata && is_wcodecvt_byname) {

            pstr = pstr ? strip_name.data () : locname.data ();
            pdata = __rw_get_facet_data (cat, sz, 0, pstr /* codeset */);
        }
        else
            pstr = 0;

        // this is the definitive result for facets other than 
        // codecvt_byname<wchar_t, char, mbstate_t>
        if (!pdata) {
            _RWSTD_ASSERT (0 == _C_impdata);

            // set `impsize' to non-zero value to avoid subsequent
            // attempts at reinitialization
            _C_impsize = (_RWSTD_SIZE_T)-1;
            return 0;
        }

        if (!is_wcodecvt_byname || pstr && pdata) {
            _C_impdata = pdata; 
            _C_impsize = sz;
            return _C_impdata;
        }
    }
    else if (!is_wcodecvt_byname) {
        _C_impdata = pdata;
        _C_impsize = sz;
        return _C_impdata;
    }

    // for codecvt_byname facet map the database indicated by 
    // codeset_name offset in LC_CTYPE database
    const _RW::__rw_ctype_t* pctype  = 
        _RWSTD_STATIC_CAST (const _RW::__rw_ctype_t*, pdata);
 
    _RWSTD_ASSERT (0 != pctype);

    // the facet name may be an ordinary filename or a (relative
    // or absolute) pathname; unless it's the former, prepend
    // the name of the directory containing the facet to the
    // name of the codeset before trying to map it
    __rw_chararray codeset_fname;
    const char* codeset = pctype->codeset_name ();

    const char* const slash = strrchr (locname.data (), _RWSTD_PATH_SEP);
    if (slash) {
        const size_t dirlen = size_t (slash - locname.data ()) + 1;

        codeset_fname.assign (locname.data (), dirlen);
        codeset_fname.append (codeset);
        codeset = codeset_fname.data ();
    }

    // map the codecvt database
    _C_impdata = __rw_get_facet_data (cat, _C_impsize, 0, codeset);
    
    // unmap the LC_CTYPE database
    __rw_munmap (pdata, sz);

    return _C_impdata;
}


extern "C" {

// compares two facets, returns 0 if equal, -1 if less, +1 otherwise
static int cmpfacets (const void *pv1, const void *pv2)
{
    _RWSTD_ASSERT (0 != pv1);
    _RWSTD_ASSERT (0 != pv2);

    const __rw_facet* const pf1 =
        *_RWSTD_STATIC_CAST (const __rw_facet* const*, pv1);
    const __rw_facet* const pf2 =
        *_RWSTD_STATIC_CAST (const __rw_facet* const*, pv2);

    if (__rw_access::_C_get_type(*pf1) == __rw_access::_C_get_type(*pf2)) {
        return ::strcmp (__rw_access::_C_get_name(*pf1) ? 
                         __rw_access::_C_get_name(*pf1) : "C",
                         __rw_access::_C_get_name(*pf2) ? 
                         __rw_access::_C_get_name(*pf2) : "C");
    }

    return __rw_access::_C_get_type(*pf2) - __rw_access::_C_get_type(*pf1);
}


struct __rw_facet_info
{
    const char                *_C_name;
    __rw_facet::_C_facet_type  _C_type;
};

// compares a key to a facet, returns 0 if equal, -1 if less, +1 otherwise
static int cmpfacet (const void *pv1, const void *pv2)
{
    _RWSTD_ASSERT (0 != pv1);
    _RWSTD_ASSERT (0 != pv2);

    const __rw_facet_info* const pinfo =
        _RWSTD_STATIC_CAST (const __rw_facet_info*, pv1);

    const __rw_facet* const pfacet =
        *_RWSTD_STATIC_CAST (const __rw_facet* const*, pv2);

    if (pinfo->_C_type == __rw_access::_C_get_type(*pfacet)) {
        return ::strcmp (pinfo->_C_name  ? pinfo->_C_name   : "C",
                         __rw_access::_C_get_name(*pfacet) ? 
                         __rw_access::_C_get_name(*pfacet) : "C");
    }

    return __rw_access::_C_get_type(*pfacet) - pinfo->_C_type;
}

}   // extern "C"


/* static */ __rw_facet*
__rw_facet::_C_manage (__rw_facet            *pfacet,
                       _C_facet_type          type,
                       const char            *name,
                       __rw_facet::_C_ctor_t *ctor)
{
    // a per-process array of facet pointers sufficiently large
    // to hold (pointers to) all standard facets for 8 locales
    static __rw_facet*  std_facet_buf [__rw_facet::_C_last_type * 8];
    static __rw_facet** std_facets = std_facet_buf;
    static _RWSTD_SIZE_T n_std_facets = 0;
    static _RWSTD_SIZE_T std_facet_bufsize =
        sizeof std_facet_buf / sizeof *std_facet_buf;

    // acquire lock
    _RWSTD_MT_STATIC_GUARD (_RW::__rw_facet);

    if (pfacet) {
        // remove facet from the facet repository (if it is found) and
        // destroy it (ordinary unnamed standard facets are never destroyed)

        if (!name) {
            // find facet by its name
            name = __rw_locale::_C_get_cat_name (*pfacet);
        }

        _RWSTD_ASSERT (0 != n_std_facets);

        const void *pf =
            ::bsearch (&pfacet, std_facets, n_std_facets,
                       sizeof pfacet, &cmpfacets);
        if (pf) {

            const _RWSTD_SIZE_T inx =
                  _RWSTD_STATIC_CAST (const __rw_facet* const*, pf)
                - _RWSTD_CONST_CAST (const __rw_facet* const*, std_facets);

            pfacet = std_facets [inx];

            if (0 == __rw_locale::_C_remove_ref (*pfacet)) {

                static const _RWSTD_SIZE_T bufsize =
                    sizeof std_facet_buf / sizeof *std_facet_buf;

                --n_std_facets;

                if (n_std_facets < bufsize / 2 && std_facets != std_facet_buf) {

                    _RWSTD_ASSERT (inx < bufsize);

                    // when the number of facets in the dynamically allocated
                    // repository drops below half the capacity of the
                    // statically allocated buffer, copy facets from the
                    // former into the latter and deallocate the former
                    ::memcpy (std_facet_buf, std_facets,
                              inx * sizeof (*std_facets));

                    ::memcpy (std_facet_buf + inx,
                              std_facets + inx + 1,
                              (n_std_facets - inx)
                              * sizeof (*std_facets));

                    delete[] std_facets;
                    std_facets = std_facet_buf;
                }
                else {

                    // move facet pointers back
                    ::memmove (std_facets + inx,
                               std_facets + inx + 1,
                               (n_std_facets - inx)
                               * sizeof (*std_facets));
                }

                // delete facet unless it's one of the ordinary (unnamed)
                // standard factes which are statically allocated
                if (pfacet->_C_name)
                    delete pfacet;
            }
        }
        else {

            // facet is an ordinary (unnamed) standard facet
            const _RWSTD_SIZE_T ref = __rw_locale::_C_remove_ref (*pfacet);
            _RWSTD_ASSERT (ref + 1U != 0);
            _RWSTD_UNUSED (ref);
        }

        pfacet = 0;
    }
    else {

        // find facet in the repository or construct it if not found

        const __rw_facet_info info = { name, type };

        const void *pcf = ::bsearch (&info, std_facets, n_std_facets,
                                     sizeof pfacet, &cmpfacet);

        if (pcf) {
            // facet already exists in the repository, bump up its ref count

            void *pf = _RWSTD_CONST_CAST (void*, pcf);
            pfacet = *_RWSTD_STATIC_CAST (__rw_facet**, pf);

            _RWSTD_ASSERT (0 != pfacet);

            __rw_locale::_C_add_ref (*pfacet);
        }
        else {

            if (n_std_facets == std_facet_bufsize) {

                // reallocate buffer of facet pointers
                __rw_facet **tmp = new __rw_facet* [n_std_facets * 2];

                ::memcpy (tmp, std_facets, n_std_facets * sizeof *tmp);

                if (std_facets != std_facet_buf)
                    delete[] std_facets;

                std_facets         = tmp;
                std_facet_bufsize *= 2;
            }

            // construct a facet with the initial reference count of 1
            // if no such facet exists yet or bump up the facet's ref
            // count by 1 if it does

            _RWSTD_ASSERT (0 != ctor);

            pfacet = ctor (1, type & 1 ? 0 : name);

            _RWSTD_ASSERT (0 != pfacet);

            // set the facet's numeric id
            *__rw_access::_C_get_pid (*pfacet) =
                _RWSTD_STATIC_CAST (_RWSTD_SIZE_T, (type + 1) / 2);

            if (__rw_access::_C_get_type (*pfacet) != type) {
                // set the type of the facet if not already set
                __rw_access::_C_get_type (*pfacet) = type;
            }

            if (__rw_access::_C_get_ref (*pfacet) != 1) {
                // set the refcount of the facet if 0

                // the refcount is set during construction which occurs
                // at most once in a program, but may decrease to 0 if
                // all locales containing that facet are destroyed

                _RWSTD_ASSERT (0 == __rw_access::_C_get_ref (*pfacet));

                __rw_access::_C_get_ref (*pfacet) = 1;
            }

            // add facet pointer to the end of the repository
            std_facets [n_std_facets++] = pfacet;

            // sort facet pointers for fast access
            ::qsort (std_facets, n_std_facets, sizeof pfacet, &cmpfacets);
            
        }
    }

    return pfacet;
}


const void* __rw_get_facet_data (int cat, _RWSTD_SIZE_T &impsize, 
                                 const char* name, const char* encoding) 
{
    if (__rw_facet::_C_use_libc == (__rw_facet::_C_opts & 
                                    (__rw_facet::_C_use_libc | 
                                     __rw_facet::_C_use_libstd))) {
        // fail but allow further calls to this function
        return 0;
    }

    __rw_chararray  pathname;
    const char     *path;

    // null name means C locale
    const char *locname = name ? name : "C";

    if ('/' == *locname) {
        // facet name is an absolute pathname to a locale/category database
        path = locname;
    }
    else {

#ifndef _RWSTD_LOCALE_ROOT_ENVVAR
#  define _RWSTD_LOCALE_ROOT_ENVVAR "RWSTD_LOCALE_ROOT"
#endif

        // look up the value of the environment variable
        const char *root = getenv (_RWSTD_LOCALE_ROOT_ENVVAR);

        // use the cwd if none given
        if (!root || !*root)
            root = "./";

        // compose the pathname of the locale/category database
        pathname.append (root);
        if (pathname.data () [pathname.size () - 1] != '/')
            pathname.append ("/", 1);

        // the database name for the facet will hold either the
        // name of the category (ie LC_CTYPE, etc.) or the name
        // of the encoding (codecvt database)
        const char* db;

        if (encoding)
            db = encoding;
        else {
            // only append the locname if we are not going to be 
            // appending an encoding
            pathname.append (locname).append ("/", 1);
            db = __rw_get_cat_name (cat);
        }
        if (!db)
            return 0;

        // append database name to the end of the pathname
        // to form a complete name of the locale/category database
        pathname.append (db);
        path = pathname.data ();
    }

    // map the database
    return __rw_mmap (path, &impsize);
}

// Its counterpart - does the database unmapping
void __rw_release_facet_data (const void* pv, _RWSTD_SIZE_T sz)
{
    __rw_munmap (pv, sz);
}


// standard facet id's (i.e., those instantiated and installed
// by the library are in the range [1, __rw_facet::_C_last_type]
// all others (i.e., id's of all user-defined facet types or
// specializations of standard facets on user-defined types)
// are in the range (__rw_facet::_C_last_type, ...)
static _RWSTD_SIZE_T __rw_id_gen = __rw_facet::_C_last_type + 1;


_RWSTD_SIZE_T __rw_facet_id::_C_init () const
{
    // return immediately if already initialized
    if (_C_id)
        return _C_id;

    // atomically increment id generator
    // the above initialization is already guarded and the initialization
    // of standard facets has a chance of completing at the step above

    // generate a unique id
    const _RWSTD_SIZE_T new_id =
        _RWSTD_ATOMIC_PREINCREMENT (__rw_id_gen, false);

#ifndef _RWSTD_NO_MUTABLE

    _C_id = new_id;

#else   // if defined (_RWSTD_NO_MUTABLE)

    _RWSTD_CONST_CAST (__rw_facet_id*, this)->_C_id = new_id;

#endif   // _RWSTD_NO_MUTABLE

    return _C_id;
}

}   // namespace __rw
