/***********************************************************
Copyright (C) 1997, 2002, 2003, 2007, 2008 Martin von Loewis

Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appear in all copies.

This software comes with no warranty. Use at your own risk.

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

#include "Python.h"
#include "pycore_fileutils.h"     // _Py_GetLocaleconvNumeric()
#include "pycore_pymem.h"         // _PyMem_Strdup()

#include <locale.h>               // setlocale()
#include <string.h>               // strlen()
#ifdef HAVE_ERRNO_H
#  include <errno.h>              // errno
#endif
#ifdef HAVE_LANGINFO_H
#  include <langinfo.h>           // nl_langinfo()
#endif
#ifdef HAVE_LIBINTL_H
#  include <libintl.h>
#endif
#ifdef MS_WINDOWS
#  ifndef WIN32_LEAN_AND_MEAN
#    define WIN32_LEAN_AND_MEAN
#  endif
#  include <windows.h>
#endif

PyDoc_STRVAR(locale__doc__, "Support for POSIX locales.");

typedef struct _locale_state {
    PyObject *Error;
} _locale_state;

static inline _locale_state*
get_locale_state(PyObject *m)
{
    void *state = PyModule_GetState(m);
    assert(state != NULL);
    return (_locale_state *)state;
}

#include "clinic/_localemodule.c.h"

/*[clinic input]
module _locale
[clinic start generated code]*/
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=ed98569b726feada]*/

/* support functions for formatting floating-point numbers */

/* the grouping is terminated by either 0 or CHAR_MAX */
static PyObject*
copy_grouping(const char* s)
{
    int i;
    PyObject *result, *val = NULL;

    if (s[0] == '\0') {
        /* empty string: no grouping at all */
        return PyList_New(0);
    }

    for (i = 0; s[i] != '\0' && s[i] != CHAR_MAX; i++)
        ; /* nothing */

    result = PyList_New(i+1);
    if (!result)
        return NULL;

    i = -1;
    do {
        i++;
        val = PyLong_FromLong(s[i]);
        if (val == NULL) {
            Py_DECREF(result);
            return NULL;
        }
        PyList_SET_ITEM(result, i, val);
    } while (s[i] != '\0' && s[i] != CHAR_MAX);

    return result;
}

#if defined(MS_WINDOWS)

// 16 is the number of elements in the szCodePage field
// of the __crt_locale_strings structure.
#define MAX_CP_LEN 15

static int
check_locale_name(const char *locale, const char *end)
{
    size_t len = end ? (size_t)(end - locale) : strlen(locale);
    const char *dot = memchr(locale, '.', len);
    if (dot && locale + len - dot - 1 > MAX_CP_LEN) {
        return -1;
    }
    return 0;
}

static int
check_locale_name_all(const char *locale)
{
    const char *start = locale;
    while (1) {
        const char *end = strchr(start, ';');
        if (check_locale_name(start, end) < 0) {
            return -1;
        }
        if (end == NULL) {
            break;
        }
        start = end + 1;
    }
    return 0;
}
#endif

/*[clinic input]
_locale.setlocale

    category: int
    locale: str(accept={str, NoneType}) = NULL
    /

Activates/queries locale processing.
[clinic start generated code]*/

static PyObject *
_locale_setlocale_impl(PyObject *module, int category, const char *locale)
/*[clinic end generated code: output=a0e777ae5d2ff117 input=dbe18f1d66c57a6a]*/
{
    char *result;
    PyObject *result_object;

#if defined(MS_WINDOWS)
    if (category < LC_MIN || category > LC_MAX)
    {
        PyErr_SetString(get_locale_state(module)->Error,
                        "invalid locale category");
        return NULL;
    }
    if (locale) {
        if ((category == LC_ALL
             ? check_locale_name_all(locale)
             : check_locale_name(locale, NULL)) < 0)
        {
            /* Debug assertion failure on Windows.
             * _Py_BEGIN_SUPPRESS_IPH/_Py_END_SUPPRESS_IPH do not help. */
            PyErr_SetString(get_locale_state(module)->Error,
                "unsupported locale setting");
            return NULL;
        }
    }
#endif

    if (locale) {
        /* set locale */
        result = setlocale(category, locale);
        if (!result) {
            /* operation failed, no setting was changed */
            PyErr_SetString(get_locale_state(module)->Error,
                            "unsupported locale setting");
            return NULL;
        }
        result_object = PyUnicode_DecodeLocale(result, NULL);
        if (!result_object)
            return NULL;
    } else {
        /* get locale */
        result = setlocale(category, NULL);
        if (!result) {
            PyErr_SetString(get_locale_state(module)->Error,
                            "locale query failed");
            return NULL;
        }
        result_object = PyUnicode_DecodeLocale(result, NULL);
    }
    return result_object;
}

static int
locale_is_ascii(const char *str)
{
    return (strlen(str) == 1 && ((unsigned char)str[0]) <= 127);
}

static int
is_all_ascii(const char *str)
{
    for (; *str; str++) {
        if ((unsigned char)*str > 127) {
            return 0;
        }
    }
    return 1;
}

static int
locale_decode_monetary(PyObject *dict, struct lconv *lc)
{
#ifndef MS_WINDOWS
    int change_locale;
    change_locale = (!locale_is_ascii(lc->int_curr_symbol)
                     || !locale_is_ascii(lc->currency_symbol)
                     || !locale_is_ascii(lc->mon_decimal_point)
                     || !locale_is_ascii(lc->mon_thousands_sep));

    /* Keep a copy of the LC_CTYPE locale */
    char *oldloc = NULL, *loc = NULL;
    if (change_locale) {
        oldloc = setlocale(LC_CTYPE, NULL);
        if (!oldloc) {
            PyErr_SetString(PyExc_RuntimeWarning,
                            "failed to get LC_CTYPE locale");
            return -1;
        }

        oldloc = _PyMem_Strdup(oldloc);
        if (!oldloc) {
            PyErr_NoMemory();
            return -1;
        }

        loc = setlocale(LC_MONETARY, NULL);
        if (loc != NULL && strcmp(loc, oldloc) == 0) {
            loc = NULL;
        }

        if (loc != NULL) {
            /* Only set the locale temporarily the LC_CTYPE locale
               to the LC_MONETARY locale if the two locales are different and
               at least one string is non-ASCII. */
            setlocale(LC_CTYPE, loc);
        }
    }

#define GET_LOCALE_STRING(ATTR) PyUnicode_DecodeLocale(lc->ATTR, NULL)
#else  /* MS_WINDOWS */
/* Use _W_* fields of Windows struct lconv */
#define GET_LOCALE_STRING(ATTR) PyUnicode_FromWideChar(lc->_W_ ## ATTR, -1)
#endif /* MS_WINDOWS */

    int res = -1;

#define RESULT_STRING(ATTR) \
    do { \
        PyObject *obj; \
        obj = GET_LOCALE_STRING(ATTR); \
        if (obj == NULL) { \
            goto done; \
        } \
        if (PyDict_SetItemString(dict, Py_STRINGIFY(ATTR), obj) < 0) { \
            Py_DECREF(obj); \
            goto done; \
        } \
        Py_DECREF(obj); \
    } while (0)

    RESULT_STRING(int_curr_symbol);
    RESULT_STRING(currency_symbol);
    RESULT_STRING(mon_decimal_point);
    RESULT_STRING(mon_thousands_sep);
#undef RESULT_STRING
#undef GET_LOCALE_STRING

    res = 0;

done:
#ifndef MS_WINDOWS
    if (loc != NULL) {
        setlocale(LC_CTYPE, oldloc);
    }
    PyMem_Free(oldloc);
#endif
    return res;
}

/*[clinic input]
_locale.localeconv

Returns numeric and monetary locale-specific parameters.
[clinic start generated code]*/

static PyObject *
_locale_localeconv_impl(PyObject *module)
/*[clinic end generated code: output=43a54515e0a2aef5 input=f1132d15accf4444]*/
{
    PyObject* result;
    struct lconv *lc;
    PyObject *x;

    result = PyDict_New();
    if (!result) {
        return NULL;
    }

    /* if LC_NUMERIC is different in the C library, use saved value */
    lc = localeconv();

    /* hopefully, the localeconv result survives the C library calls
       involved herein */

#define RESULT(key, obj)\
    do { \
        if (obj == NULL) \
            goto failed; \
        if (PyDict_SetItemString(result, key, obj) < 0) { \
            Py_DECREF(obj); \
            goto failed; \
        } \
        Py_DECREF(obj); \
    } while (0)

#ifdef MS_WINDOWS
/* Use _W_* fields of Windows struct lconv */
#define GET_LOCALE_STRING(ATTR) PyUnicode_FromWideChar(lc->_W_ ## ATTR, -1)
#else
#define GET_LOCALE_STRING(ATTR) PyUnicode_DecodeLocale(lc->ATTR, NULL)
#endif
#define RESULT_STRING(s)\
    do { \
        x = GET_LOCALE_STRING(s); \
        RESULT(#s, x); \
    } while (0)

#define RESULT_INT(i)\
    do { \
        x = PyLong_FromLong(lc->i); \
        RESULT(#i, x); \
    } while (0)

    /* Monetary information: LC_MONETARY encoding */
    if (locale_decode_monetary(result, lc) < 0) {
        goto failed;
    }
    x = copy_grouping(lc->mon_grouping);
    RESULT("mon_grouping", x);

    RESULT_STRING(positive_sign);
    RESULT_STRING(negative_sign);
    RESULT_INT(int_frac_digits);
    RESULT_INT(frac_digits);
    RESULT_INT(p_cs_precedes);
    RESULT_INT(p_sep_by_space);
    RESULT_INT(n_cs_precedes);
    RESULT_INT(n_sep_by_space);
    RESULT_INT(p_sign_posn);
    RESULT_INT(n_sign_posn);

    /* Numeric information: LC_NUMERIC encoding */
    PyObject *decimal_point = NULL, *thousands_sep = NULL;
    if (_Py_GetLocaleconvNumeric(lc, &decimal_point, &thousands_sep) < 0) {
        Py_XDECREF(decimal_point);
        Py_XDECREF(thousands_sep);
        goto failed;
    }

    if (PyDict_SetItemString(result, "decimal_point", decimal_point) < 0) {
        Py_DECREF(decimal_point);
        Py_DECREF(thousands_sep);
        goto failed;
    }
    Py_DECREF(decimal_point);

    if (PyDict_SetItemString(result, "thousands_sep", thousands_sep) < 0) {
        Py_DECREF(thousands_sep);
        goto failed;
    }
    Py_DECREF(thousands_sep);

    x = copy_grouping(lc->grouping);
    RESULT("grouping", x);

    return result;

  failed:
    Py_DECREF(result);
    return NULL;

#undef RESULT
#undef RESULT_STRING
#undef RESULT_INT
#undef GET_LOCALE_STRING
}

#if defined(HAVE_WCSCOLL)

/*[clinic input]
_locale.strcoll

    os1: unicode
    os2: unicode
    /

Compares two strings according to the locale.
[clinic start generated code]*/

static PyObject *
_locale_strcoll_impl(PyObject *module, PyObject *os1, PyObject *os2)
/*[clinic end generated code: output=82ddc6d62c76d618 input=693cd02bcbf38dd8]*/
{
    PyObject *result = NULL;
    wchar_t *ws1 = NULL, *ws2 = NULL;

    /* Convert the unicode strings to wchar[]. */
    ws1 = PyUnicode_AsWideCharString(os1, NULL);
    if (ws1 == NULL)
        goto done;
    ws2 = PyUnicode_AsWideCharString(os2, NULL);
    if (ws2 == NULL)
        goto done;
    /* Collate the strings. */
    result = PyLong_FromLong(wcscoll(ws1, ws2));
  done:
    /* Deallocate everything. */
    if (ws1) PyMem_Free(ws1);
    if (ws2) PyMem_Free(ws2);
    return result;
}
#endif

#ifdef HAVE_WCSXFRM

/*[clinic input]
_locale.strxfrm

    string as str: unicode
    /

Return a string that can be used as a key for locale-aware comparisons.
[clinic start generated code]*/

static PyObject *
_locale_strxfrm_impl(PyObject *module, PyObject *str)
/*[clinic end generated code: output=3081866ebffc01af input=1378bbe6a88b4780]*/
{
    Py_ssize_t n1;
    wchar_t *s = NULL, *buf = NULL;
    wchar_t dummy[1];
    size_t n2;
    PyObject *result = NULL;

    s = PyUnicode_AsWideCharString(str, &n1);
    if (s == NULL)
        goto exit;
    if (wcslen(s) != (size_t)n1) {
        PyErr_SetString(PyExc_ValueError,
                        "embedded null character");
        goto exit;
    }

    errno = 0;
    /* Query the size with a real one-element buffer: DragonFly BSD's
       wcsxfrm() crashes when the destination is NULL or the size is 0. */
    n2 = wcsxfrm(dummy, s, 1);
    if (errno && errno != ERANGE) {
        PyErr_SetFromErrno(PyExc_OSError);
        goto exit;
    }
    buf = PyMem_New(wchar_t, n2+1);
    if (!buf) {
        PyErr_NoMemory();
        goto exit;
    }

    errno = 0;
    n2 = wcsxfrm(buf, s, n2+1);
    if (errno) {
        PyErr_SetFromErrno(PyExc_OSError);
        goto exit;
    }
    /* The result is just a sequence of integers, they are not necessary
       Unicode code points, so PyUnicode_FromWideChar() cannot be used
       here. For example, 0xD83D 0xDC0D should not be larger than 0xFF41.
     */
#if SIZEOF_WCHAR_T == 4
    {
        /* Some codes can exceed the range of Unicode code points
           (0 - 0x10FFFF), so they cannot be directly used in
           PyUnicode_FromKindAndData(). They should be first encoded in
           a way that preserves the lexicographical order.

           Codes in the range 0-0xFFFF represent themself.
           Codes larger than 0xFFFF are encoded as a pair:
           * 0x1xxxx -- the highest 16 bits
           * 0x0xxxx -- the lowest 16 bits
         */
        size_t n3 = 0;
        for (size_t i = 0; i < n2; i++) {
            if ((Py_UCS4)buf[i] > 0x10000u) {
                n3++;
            }
        }
        if (n3) {
            n3 += n2; // no integer overflow
            Py_UCS4 *buf2 = PyMem_New(Py_UCS4, n3);
            if (buf2 == NULL) {
                PyErr_NoMemory();
                goto exit;
            }
            size_t j = 0;
            for (size_t i = 0; i < n2; i++) {
                Py_UCS4 c = (Py_UCS4)buf[i];
                if (c > 0x10000u) {
                    buf2[j++] = (c >> 16) | 0x10000u;
                    buf2[j++] = c & 0xFFFFu;
                }
                else {
                    buf2[j++] = c;
                }
            }
            assert(j == n3);
            result = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf2, n3);
            PyMem_Free(buf2);
            goto exit;
        }
    }
#endif
    result = PyUnicode_FromKindAndData(sizeof(wchar_t), buf, n2);
exit:
    PyMem_Free(buf);
    PyMem_Free(s);
    return result;
}
#endif

#if defined(MS_WINDOWS)

/*[clinic input]
_locale._getdefaultlocale

[clinic start generated code]*/

static PyObject *
_locale__getdefaultlocale_impl(PyObject *module)
/*[clinic end generated code: output=e6254088579534c2 input=003ea41acd17f7c7]*/
{
    char encoding[20];
    char locale[100];

    PyOS_snprintf(encoding, sizeof(encoding), "cp%u", GetACP());

    if (GetLocaleInfoA(LOCALE_USER_DEFAULT,
                      LOCALE_SISO639LANGNAME,
                      locale, sizeof(locale))) {
        Py_ssize_t i = strlen(locale);
        locale[i++] = '_';
        if (GetLocaleInfoA(LOCALE_USER_DEFAULT,
                          LOCALE_SISO3166CTRYNAME,
                          locale+i, (int)(sizeof(locale)-i)))
            return Py_BuildValue("ss", locale, encoding);
    }

    /* cannot determine the language code (very unlikely) */
    return Py_BuildValue("Os", Py_None, encoding);
}
#endif

#ifdef HAVE_LANGINFO_H
/* On glibc, LC_TIME text items also have a wide (_NL_W*) form, named
   _NL_W<narrow name>, that nl_langinfo() returns as wchar_t*.  LANGINFO_NW
   marks the items that have no wide counterpart. */
#ifdef __GLIBC__
#  define LANGINFO(X, Y) {#X, X, Y, _NL_W ## X}
#  define LANGINFO_NW(X, Y) {#X, X, Y, 0}
#else
#  define LANGINFO(X, Y) {#X, X, Y}
#  define LANGINFO_NW(X, Y) {#X, X, Y}
#endif
static struct langinfo_constant{
    const char *name;
    int value;
    int category;
#ifdef __GLIBC__
    nl_item wide;  /* wide _NL_W* counterpart, or 0 if none */
#endif
} langinfo_constants[] =
{
    /* These constants should exist on any langinfo implementation */
    LANGINFO(DAY_1, LC_TIME),
    LANGINFO(DAY_2, LC_TIME),
    LANGINFO(DAY_3, LC_TIME),
    LANGINFO(DAY_4, LC_TIME),
    LANGINFO(DAY_5, LC_TIME),
    LANGINFO(DAY_6, LC_TIME),
    LANGINFO(DAY_7, LC_TIME),

    LANGINFO(ABDAY_1, LC_TIME),
    LANGINFO(ABDAY_2, LC_TIME),
    LANGINFO(ABDAY_3, LC_TIME),
    LANGINFO(ABDAY_4, LC_TIME),
    LANGINFO(ABDAY_5, LC_TIME),
    LANGINFO(ABDAY_6, LC_TIME),
    LANGINFO(ABDAY_7, LC_TIME),

    LANGINFO(MON_1, LC_TIME),
    LANGINFO(MON_2, LC_TIME),
    LANGINFO(MON_3, LC_TIME),
    LANGINFO(MON_4, LC_TIME),
    LANGINFO(MON_5, LC_TIME),
    LANGINFO(MON_6, LC_TIME),
    LANGINFO(MON_7, LC_TIME),
    LANGINFO(MON_8, LC_TIME),
    LANGINFO(MON_9, LC_TIME),
    LANGINFO(MON_10, LC_TIME),
    LANGINFO(MON_11, LC_TIME),
    LANGINFO(MON_12, LC_TIME),

    LANGINFO(ABMON_1, LC_TIME),
    LANGINFO(ABMON_2, LC_TIME),
    LANGINFO(ABMON_3, LC_TIME),
    LANGINFO(ABMON_4, LC_TIME),
    LANGINFO(ABMON_5, LC_TIME),
    LANGINFO(ABMON_6, LC_TIME),
    LANGINFO(ABMON_7, LC_TIME),
    LANGINFO(ABMON_8, LC_TIME),
    LANGINFO(ABMON_9, LC_TIME),
    LANGINFO(ABMON_10, LC_TIME),
    LANGINFO(ABMON_11, LC_TIME),
    LANGINFO(ABMON_12, LC_TIME),

#ifdef RADIXCHAR
    /* The following are not available with glibc 2.0 */
    LANGINFO_NW(RADIXCHAR, LC_NUMERIC),
    LANGINFO_NW(THOUSEP, LC_NUMERIC),
    /* YESSTR and NOSTR are deprecated in glibc, since they are
       a special case of message translation, which should be rather
       done using gettext. So we don't expose it to Python in the
       first place.
    LANGINFO(YESSTR, LC_MESSAGES),
    LANGINFO(NOSTR, LC_MESSAGES),
    */
    LANGINFO_NW(CRNCYSTR, LC_MONETARY),
#endif

    LANGINFO(D_T_FMT, LC_TIME),
    LANGINFO(D_FMT, LC_TIME),
    LANGINFO(T_FMT, LC_TIME),
    LANGINFO(AM_STR, LC_TIME),
    LANGINFO(PM_STR, LC_TIME),

    /* The following constants are available only with XPG4, but...
       OpenBSD doesn't have CODESET but has T_FMT_AMPM, and doesn't have
       a few of the others.
       Solution: ifdef-test them all. */
#ifdef CODESET
    LANGINFO_NW(CODESET, LC_CTYPE),
#endif
#ifdef T_FMT_AMPM
    LANGINFO(T_FMT_AMPM, LC_TIME),
#endif
#ifdef ERA
    LANGINFO_NW(ERA, LC_TIME),
#endif
#ifdef ERA_D_FMT
    LANGINFO(ERA_D_FMT, LC_TIME),
#endif
#ifdef ERA_D_T_FMT
    LANGINFO(ERA_D_T_FMT, LC_TIME),
#endif
#ifdef ERA_T_FMT
    LANGINFO(ERA_T_FMT, LC_TIME),
#endif
#ifdef ALT_DIGITS
    LANGINFO(ALT_DIGITS, LC_TIME),
#endif
#ifdef YESEXPR
    LANGINFO_NW(YESEXPR, LC_MESSAGES),
#endif
#ifdef NOEXPR
    LANGINFO_NW(NOEXPR, LC_MESSAGES),
#endif
#ifdef _DATE_FMT
    /* This is not available in all glibc versions that have CODESET. */
    LANGINFO(_DATE_FMT, LC_TIME),
#endif
    {0, 0, 0}
};

/* Temporary make the LC_CTYPE locale to be the same as
 * the locale of the specified category. */
static int
change_locale(int category, char **oldloc)
{
    /* Keep a copy of the LC_CTYPE locale */
    *oldloc = setlocale(LC_CTYPE, NULL);
    if (!*oldloc) {
        PyErr_SetString(PyExc_RuntimeError, "failed to get LC_CTYPE locale");
        return -1;
    }
    *oldloc = _PyMem_Strdup(*oldloc);
    if (!*oldloc) {
        PyErr_NoMemory();
        return -1;
    }

    /* Set a new locale if it is different. */
    char *loc = setlocale(category, NULL);
    if (loc == NULL || strcmp(loc, *oldloc) == 0) {
        PyMem_Free(*oldloc);
        *oldloc = NULL;
        return 0;
    }

    setlocale(LC_CTYPE, loc);
    return 1;
}

/* Restore the old LC_CTYPE locale. */
static void
restore_locale(char *oldloc)
{
    if (oldloc != NULL) {
        setlocale(LC_CTYPE, oldloc);
        PyMem_Free(oldloc);
    }
}

#ifdef __GLIBC__
static PyObject *
decode_strings(const char *result)
{
    /* Convert a sequence of NUL-separated C strings to a Python string
     * containing semicolon separated items. */
    size_t i = 0;
    size_t count = 0;
    for (; result[i]; count++) {
        i += strlen(result + i) + 1;
    }
    char *buf = PyMem_Malloc(i);
    if (buf == NULL) {
        PyErr_NoMemory();
        return NULL;
    }
    memcpy(buf, result, i);
    /* Replace all NULs with semicolons. */
    i = 0;
    while (--count) {
        i += strlen(buf + i);
        buf[i++] = ';';
    }
    PyObject *pyresult = PyUnicode_DecodeLocale(buf, NULL);
    PyMem_Free(buf);
    return pyresult;
}
#endif

#ifdef __GLIBC__
static PyObject *
decode_wide_strings(const wchar_t *result, size_t max_count)
{
    /* Wide-character counterpart of decode_strings(): convert a sequence of
     * NUL-separated wchar_t strings to a str with semicolon-separated items. */
    size_t i = 0;
    size_t count = 0;
    for (; count < max_count && result[i]; count++) {
        i += wcslen(result + i) + 1;
    }
    wchar_t *buf = PyMem_New(wchar_t, i);
    if (buf == NULL) {
        PyErr_NoMemory();
        return NULL;
    }
    memcpy(buf, result, i * sizeof(wchar_t));
    /* Replace all NULs with semicolons. */
    i = 0;
    while (--count) {
        i += wcslen(buf + i);
        buf[i++] = L';';
    }
    PyObject *pyresult = PyUnicode_FromWideChar(buf, -1);
    PyMem_Free(buf);
    return pyresult;
}
#endif  /* __GLIBC__ */

/*[clinic input]
_locale.nl_langinfo

    key as item: int
    /

Return the value for the locale information associated with key.
[clinic start generated code]*/

static PyObject *
_locale_nl_langinfo_impl(PyObject *module, int item)
/*[clinic end generated code: output=6aea457b47e077a3 input=00798143eecfeddc]*/
{
    int i;
    /* Check whether this is a supported constant. GNU libc sometimes
       returns numeric values in the char* return value, which would
       crash PyUnicode_FromString.  */
    for (i = 0; langinfo_constants[i].name; i++) {
        if (langinfo_constants[i].value == item) {
#ifdef __GLIBC__
            /* Prefer the wide variant: it is decoded independently of the
               LC_CTYPE encoding. */
            nl_item wide = langinfo_constants[i].wide;
            if (wide) {
                const wchar_t *wresult = (const wchar_t *)nl_langinfo(wide);
                if (wresult == NULL) {
                    wresult = L"";
                }
                /* ALT_DIGITS is a sequence of NUL-separated strings. */
                if (item == ALT_DIGITS && *wresult) {
                    return decode_wide_strings(wresult, 100);
                }
                return PyUnicode_FromWideChar(wresult, -1);
            }
#endif
            /* Check NULL as a workaround for GNU libc's returning NULL
               instead of an empty string for nl_langinfo(ERA).  */
            const char *result = nl_langinfo(item);
            result = result != NULL ? result : "";
            char *oldloc = NULL;
            if (langinfo_constants[i].category != LC_CTYPE
                && *result && (
#ifdef __GLIBC__
                    // gh-133740: Always change the locale for ERA
                    item == ERA ||
#endif
                    !is_all_ascii(result))
                && change_locale(langinfo_constants[i].category, &oldloc) < 0)
            {
                return NULL;
            }
            PyObject *pyresult;
#ifdef __GLIBC__
            /* According to the POSIX specification the result must be
             * a sequence of semicolon-separated strings.
             * But in Glibc they are NUL-separated. */
            if (item == ERA && *result) {
                pyresult = decode_strings(result);
            }
            else
#endif
            {
                pyresult = PyUnicode_DecodeLocale(result, NULL);
            }
            restore_locale(oldloc);
            return pyresult;
        }
    }
    PyErr_SetString(PyExc_ValueError, "unsupported langinfo constant");
    return NULL;
}
#endif /* HAVE_LANGINFO_H */

#ifdef HAVE_LIBINTL_H

/*[clinic input]
_locale.gettext

    msg as in: str
    /

gettext(msg) -> string

Return translation of msg.
[clinic start generated code]*/

static PyObject *
_locale_gettext_impl(PyObject *module, const char *in)
/*[clinic end generated code: output=493bb4b38a4704fe input=949fc8efc2bb3bc3]*/
{
    return PyUnicode_DecodeLocale(gettext(in), NULL);
}

/*[clinic input]
_locale.dgettext

    domain: str(accept={str, NoneType})
    msg as in: str
    /

dgettext(domain, msg) -> string

Return translation of msg in domain.
[clinic start generated code]*/

static PyObject *
_locale_dgettext_impl(PyObject *module, const char *domain, const char *in)
/*[clinic end generated code: output=3c0cd5287b972c8f input=a277388a635109d8]*/
{
    return PyUnicode_DecodeLocale(dgettext(domain, in), NULL);
}

/*[clinic input]
_locale.dcgettext

    domain: str(accept={str, NoneType})
    msg as msgid: str
    category: int
    /

Return translation of msg in domain and category.
[clinic start generated code]*/

static PyObject *
_locale_dcgettext_impl(PyObject *module, const char *domain,
                       const char *msgid, int category)
/*[clinic end generated code: output=0f4cc4fce0aa283f input=ec5f8fed4336de67]*/
{
    return PyUnicode_DecodeLocale(dcgettext(domain,msgid,category), NULL);
}

/*[clinic input]
_locale.textdomain

    domain: str(accept={str, NoneType})
    /

Set the C library's textdmain to domain, returning the new domain.
[clinic start generated code]*/

static PyObject *
_locale_textdomain_impl(PyObject *module, const char *domain)
/*[clinic end generated code: output=7992df06aadec313 input=66359716f5eb1d38]*/
{
    domain = textdomain(domain);
    if (!domain) {
        PyErr_SetFromErrno(PyExc_OSError);
        return NULL;
    }
    return PyUnicode_DecodeLocale(domain, NULL);
}

/*[clinic input]
_locale.bindtextdomain

    domain: str
    dir as dirname_obj: object
    /

Bind the C library's domain to dir.
[clinic start generated code]*/

static PyObject *
_locale_bindtextdomain_impl(PyObject *module, const char *domain,
                            PyObject *dirname_obj)
/*[clinic end generated code: output=6d6f3c7b345d785c input=c0dff085acfe272b]*/
{
    const char *dirname, *current_dirname;
    PyObject *dirname_bytes = NULL, *result;

    if (!strlen(domain)) {
        PyErr_SetString(get_locale_state(module)->Error,
                        "domain must be a non-empty string");
        return 0;
    }
    if (dirname_obj != Py_None) {
        if (!PyUnicode_FSConverter(dirname_obj, &dirname_bytes))
            return NULL;
        dirname = PyBytes_AsString(dirname_bytes);
    } else {
        dirname_bytes = NULL;
        dirname = NULL;
    }
    current_dirname = bindtextdomain(domain, dirname);
    if (current_dirname == NULL) {
        PyErr_SetFromErrno(PyExc_OSError);
        Py_XDECREF(dirname_bytes);
        return NULL;
    }
    result = PyUnicode_DecodeLocale(current_dirname, NULL);
    Py_XDECREF(dirname_bytes);
    return result;
}

#ifdef HAVE_BIND_TEXTDOMAIN_CODESET

/*[clinic input]
_locale.bind_textdomain_codeset

    domain: str
    codeset: str(accept={str, NoneType})
    /

Bind the C library's domain to codeset.
[clinic start generated code]*/

static PyObject *
_locale_bind_textdomain_codeset_impl(PyObject *module, const char *domain,
                                     const char *codeset)
/*[clinic end generated code: output=fa452f9c8b1b9e89 input=23fbe3540400f259]*/
{
    codeset = bind_textdomain_codeset(domain, codeset);
    if (codeset) {
        return PyUnicode_DecodeLocale(codeset, NULL);
    }
    Py_RETURN_NONE;
}
#endif  // HAVE_BIND_TEXTDOMAIN_CODESET

#endif  // HAVE_LIBINTL_H


/*[clinic input]
_locale.getencoding

Get the current locale encoding.
[clinic start generated code]*/

static PyObject *
_locale_getencoding_impl(PyObject *module)
/*[clinic end generated code: output=86b326b971872e46 input=6503d11e5958b360]*/
{
    return _Py_GetLocaleEncodingObject();
}


static struct PyMethodDef PyLocale_Methods[] = {
    _LOCALE_SETLOCALE_METHODDEF
    _LOCALE_LOCALECONV_METHODDEF
#ifdef HAVE_WCSCOLL
    _LOCALE_STRCOLL_METHODDEF
#endif
#ifdef HAVE_WCSXFRM
    _LOCALE_STRXFRM_METHODDEF
#endif
#if defined(MS_WINDOWS)
    _LOCALE__GETDEFAULTLOCALE_METHODDEF
#endif
#ifdef HAVE_LANGINFO_H
    _LOCALE_NL_LANGINFO_METHODDEF
#endif
#ifdef HAVE_LIBINTL_H
    _LOCALE_GETTEXT_METHODDEF
    _LOCALE_DGETTEXT_METHODDEF
    _LOCALE_DCGETTEXT_METHODDEF
    _LOCALE_TEXTDOMAIN_METHODDEF
    _LOCALE_BINDTEXTDOMAIN_METHODDEF
#ifdef HAVE_BIND_TEXTDOMAIN_CODESET
    _LOCALE_BIND_TEXTDOMAIN_CODESET_METHODDEF
#endif
#endif
    _LOCALE_GETENCODING_METHODDEF
  {NULL, NULL}
};

static int
_locale_exec(PyObject *module)
{
#ifdef HAVE_LANGINFO_H
    int i;
#endif
#define ADD_INT(module, value)                                    \
    do {                                                          \
        if (PyModule_AddIntConstant(module, #value, value) < 0) { \
            return -1;                                            \
        }                                                         \
    } while (0)

    ADD_INT(module, LC_CTYPE);
    ADD_INT(module, LC_TIME);
    ADD_INT(module, LC_COLLATE);
    ADD_INT(module, LC_MONETARY);

#ifdef LC_MESSAGES
    ADD_INT(module, LC_MESSAGES);
#endif /* LC_MESSAGES */

    ADD_INT(module, LC_NUMERIC);
    ADD_INT(module, LC_ALL);
    ADD_INT(module, CHAR_MAX);

    _locale_state *state = get_locale_state(module);
    state->Error = PyErr_NewException("locale.Error", NULL, NULL);
    if (PyModule_AddObjectRef(module, "Error", state->Error) < 0) {
        return -1;
    }

#ifdef HAVE_LANGINFO_H
    for (i = 0; langinfo_constants[i].name; i++) {
        if (PyModule_AddIntConstant(module,
                                    langinfo_constants[i].name,
                                    langinfo_constants[i].value) < 0) {
            return -1;
        }
    }
#endif

    if (PyErr_Occurred()) {
        return -1;
    }
    return 0;

#undef ADD_INT
}

static struct PyModuleDef_Slot _locale_slots[] = {
    _Py_ABI_SLOT,
    {Py_mod_exec, _locale_exec},
    {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
    {Py_mod_gil, Py_MOD_GIL_NOT_USED},
    {0, NULL}
};

static int
locale_traverse(PyObject *module, visitproc visit, void *arg)
{
    _locale_state *state = get_locale_state(module);
    Py_VISIT(state->Error);
    return 0;
}

static int
locale_clear(PyObject *module)
{
    _locale_state *state = get_locale_state(module);
    Py_CLEAR(state->Error);
    return 0;
}

static void
locale_free(void *module)
{
    locale_clear((PyObject*)module);
}

static struct PyModuleDef _localemodule = {
    PyModuleDef_HEAD_INIT,
    "_locale",
    locale__doc__,
    sizeof(_locale_state),
    PyLocale_Methods,
    _locale_slots,
    locale_traverse,
    locale_clear,
    locale_free,
};

PyMODINIT_FUNC
PyInit__locale(void)
{
    return PyModuleDef_Init(&_localemodule);
}

/*
Local variables:
c-basic-offset: 4
indent-tabs-mode: nil
End:
*/
