
""" The rpython-level part of locale module
"""

import sys

from pypy.rpython.lltypesystem import rffi, lltype
from pypy.translator.tool.cbuild import ExternalCompilationInfo
from pypy.rpython.tool import rffi_platform as platform
from pypy.rpython.extfunc import register_external

class LocaleError(Exception):
    def __init__(self, message):
        self.message = message

HAVE_LANGINFO = sys.platform != 'win32'
HAVE_LIBINTL  = sys.platform != 'win32'
libraries = []

if HAVE_LIBINTL:
    try:
        platform.verify_eci(ExternalCompilationInfo(includes=['libintl.h'],
                                                    libraries=['intl']))
        libraries.append('intl')
    except platform.CompilationError:
        try:
            platform.verify_eci(ExternalCompilationInfo(includes=['libintl.h']))
        except platform.CompilationError:
            HAVE_LIBINTL = False

class CConfig:
    includes = ['locale.h', 'limits.h', 'ctype.h']
    libraries = libraries

    if HAVE_LANGINFO:
        includes += ['langinfo.h']
    if HAVE_LIBINTL:
        includes += ['libintl.h']
    if sys.platform == 'win32':
        includes += ['windows.h']
    _compilation_info_ = ExternalCompilationInfo(
        includes=includes, libraries=libraries
    )
    HAVE_BIND_TEXTDOMAIN_CODESET = platform.Has('bind_textdomain_codeset')
    lconv = platform.Struct("struct lconv", [
            # Numeric (non-monetary) information.
            ("decimal_point", rffi.CCHARP),    # Decimal point character.
            ("thousands_sep", rffi.CCHARP),    # Thousands separator.

            ## Each element is the number of digits in each group;
            ## elements with higher indices are farther left.
            ## An element with value CHAR_MAX means that no further grouping is done.
            ## An element with value 0 means that the previous element is used
            ## for all groups farther left.  */
            ("grouping", rffi.CCHARP),

            ## Monetary information.

            ## First three chars are a currency symbol from ISO 4217.
            ## Fourth char is the separator.  Fifth char is '\0'.
            ("int_curr_symbol", rffi.CCHARP),
            ("currency_symbol", rffi.CCHARP),   # Local currency symbol.
            ("mon_decimal_point", rffi.CCHARP), # Decimal point character.
            ("mon_thousands_sep", rffi.CCHARP), # Thousands separator.
            ("mon_grouping", rffi.CCHARP),      # Like `grouping' element (above).
            ("positive_sign", rffi.CCHARP),     # Sign for positive values.
            ("negative_sign", rffi.CCHARP),     # Sign for negative values.
            ("int_frac_digits", rffi.UCHAR),    # Int'l fractional digits.

            ("frac_digits", rffi.UCHAR),        # Local fractional digits.
            ## 1 if currency_symbol precedes a positive value, 0 if succeeds.
            ("p_cs_precedes", rffi.UCHAR),
            ## 1 iff a space separates currency_symbol from a positive value.
            ("p_sep_by_space", rffi.UCHAR),
            ## 1 if currency_symbol precedes a negative value, 0 if succeeds.
            ("n_cs_precedes", rffi.UCHAR),
            ## 1 iff a space separates currency_symbol from a negative value.
            ("n_sep_by_space", rffi.UCHAR),

            ## Positive and negative sign positions:
            ## 0 Parentheses surround the quantity and currency_symbol.
            ## 1 The sign string precedes the quantity and currency_symbol.
            ## 2 The sign string follows the quantity and currency_symbol.
            ## 3 The sign string immediately precedes the currency_symbol.
            ## 4 The sign string immediately follows the currency_symbol.
            ("p_sign_posn", rffi.UCHAR),
            ("n_sign_posn", rffi.UCHAR),
            ])


constants = {}
constant_names = (
        'LC_CTYPE',
        'LC_NUMERIC',
        'LC_TIME',
        'LC_COLLATE',
        'LC_MONETARY',
        'LC_MESSAGES',
        'LC_ALL',
        'LC_PAPER',
        'LC_NAME',
        'LC_ADDRESS',
        'LC_TELEPHONE',
        'LC_MEASUREMENT',
        'LC_IDENTIFICATION',
        'LC_MIN',
        'LC_MAX',
        # from limits.h
        'CHAR_MAX',
        )

for name in constant_names:
    setattr(CConfig, name, platform.DefinedConstantInteger(name))

langinfo_names = []
if HAVE_LANGINFO:
    # some of these consts have an additional #ifdef directives
    # should we support them?
    langinfo_names.extend('RADIXCHAR THOUSEP CRNCYSTR D_T_FMT D_FMT T_FMT '
                        'AM_STR PM_STR CODESET T_FMT_AMPM ERA ERA_D_FMT '
                        'ERA_D_T_FMT ERA_T_FMT ALT_DIGITS YESEXPR NOEXPR '
                        '_DATE_FMT'.split())
    for i in range(1, 8):
        langinfo_names.append("DAY_%d" % i)
        langinfo_names.append("ABDAY_%d" % i)
    for i in range(1, 13):
        langinfo_names.append("MON_%d" % i)
        langinfo_names.append("ABMON_%d" % i)

if sys.platform == 'win32':
    langinfo_names.extend('LOCALE_USER_DEFAULT LOCALE_SISO639LANGNAME '
                      'LOCALE_SISO3166CTRYNAME LOCALE_IDEFAULTLANGUAGE '
                      ''.split())


for name in langinfo_names:
    setattr(CConfig, name, platform.DefinedConstantInteger(name))

class cConfig(object):
    pass

for k, v in platform.configure(CConfig).items():
    setattr(cConfig, k, v)

# needed to export the constants inside and outside. see __init__.py
for name in constant_names:
    value = getattr(cConfig, name)
    if value is not None:
        constants[name] = value

for name in langinfo_names:
    value = getattr(cConfig, name)
    if value is not None and sys.platform != 'win32':
        constants[name] = value

locals().update(constants)

HAVE_BIND_TEXTDOMAIN_CODESET = cConfig.HAVE_BIND_TEXTDOMAIN_CODESET

def external(name, args, result, calling_conv='c', **kwds):
    return rffi.llexternal(name, args, result,
                           compilation_info=CConfig._compilation_info_,
                           calling_conv=calling_conv,
                           sandboxsafe=True, **kwds)

_lconv = lltype.Ptr(cConfig.lconv)
localeconv = external('localeconv', [], _lconv)

def numeric_formatting():
    """Specialized function to get formatting for numbers"""
    return numeric_formatting_impl()

def numeric_formatting_impl():
    conv = localeconv()
    decimal_point = rffi.charp2str(conv.c_decimal_point)
    thousands_sep = rffi.charp2str(conv.c_thousands_sep)
    grouping = rffi.charp2str(conv.c_grouping)
    return decimal_point, thousands_sep, grouping

def oo_numeric_formatting():
    return '.', '', ''

register_external(numeric_formatting, [], (str, str, str),
                  llimpl=numeric_formatting_impl,
                  ooimpl=oo_numeric_formatting,
                  sandboxsafe=True)


_setlocale = external('setlocale', [rffi.INT, rffi.CCHARP], rffi.CCHARP)

def setlocale(category, locale):
    if cConfig.LC_MAX is not None:
        if not cConfig.LC_MIN <= category <= cConfig.LC_MAX:
            raise LocaleError("invalid locale category")
    ll_result = _setlocale(rffi.cast(rffi.INT, category), locale)
    if not ll_result:
        raise LocaleError("unsupported locale setting")
    return rffi.charp2str(ll_result)

isalpha = external('isalpha', [rffi.INT], rffi.INT, oo_primitive='locale_isalpha')
isupper = external('isupper', [rffi.INT], rffi.INT, oo_primitive='locale_isupper')
islower = external('islower', [rffi.INT], rffi.INT, oo_primitive='locale_islower')
tolower = external('tolower', [rffi.INT], rffi.INT, oo_primitive='locale_tolower')
isalnum = external('isalnum', [rffi.INT], rffi.INT, oo_primitive='locale_isalnum')

if HAVE_LANGINFO:
    _nl_langinfo = external('nl_langinfo', [rffi.INT], rffi.CCHARP)

    def nl_langinfo(key):
        if key in constants.values():
            return rffi.charp2str(_nl_langinfo(rffi.cast(rffi.INT, key)))
        raise ValueError

#___________________________________________________________________
# getdefaultlocale() implementation for Windows

if sys.platform == 'win32':
    from pypy.rlib import rwin32
    LCID = LCTYPE = rwin32.DWORD
    GetACP = external('GetACP',
                      [], rffi.INT,
                      calling_conv='win')
    GetLocaleInfo = external('GetLocaleInfoA',
                             [LCID, LCTYPE, rwin32.LPSTR, rffi.INT], rffi.INT,
                             calling_conv='win')

    def getdefaultlocale():
        encoding = "cp%d" % GetACP()

        BUFSIZE = 50
        buf_lang = lltype.malloc(rffi.CCHARP.TO, BUFSIZE, flavor='raw')
        buf_country = lltype.malloc(rffi.CCHARP.TO, BUFSIZE, flavor='raw')

        try:
            if (GetLocaleInfo(cConfig.LOCALE_USER_DEFAULT,
                              cConfig.LOCALE_SISO639LANGNAME,
                              buf_lang, BUFSIZE) and
                GetLocaleInfo(cConfig.LOCALE_USER_DEFAULT,
                              cConfig.LOCALE_SISO3166CTRYNAME,
                              buf_country, BUFSIZE)):
                lang = rffi.charp2str(buf_lang)
                country = rffi.charp2str(buf_country)
                language = "%s_%s" % (lang, country)

            # If we end up here, this windows version didn't know about
            # ISO639/ISO3166 names (it's probably Windows 95).  Return the
            # Windows language identifier instead (a hexadecimal number)
            elif GetLocaleInfo(cConfig.LOCALE_USER_DEFAULT,
                               cConfig.LOCALE_IDEFAULTLANGUAGE,
                               buf_lang, BUFSIZE):
                lang = rffi.charp2str(buf_lang)
                language = "0x%s" % (lang,)

            else:
                language = None
        finally:
            lltype.free(buf_lang, flavor='raw')
            lltype.free(buf_country, flavor='raw')

        return language, encoding
