/*
 * msvcrt.dll locale functions
 *
 * Copyright 2000 Jon Griffiths
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <limits.h>
#include <locale.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <mbctype.h>
#include <wctype.h>

#include "windef.h"
#include "winbase.h"
#include "winuser.h"
#include "winnls.h"

#include "msvcrt.h"
#include "mtdll.h"

#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(msvcrt);

#define MAX_ELEM_LEN 64 /* Max length of country/language/CP string */
#define MAX_LOCALE_LENGTH 256
_locale_t MSVCRT_locale = NULL;
unsigned short *MSVCRT__pctype = NULL;
unsigned int MSVCRT___lc_codepage = 0;
int MSVCRT___lc_collate_cp = 0;
LCID MSVCRT___lc_handle[LC_MAX - LC_MIN + 1] = { 0 };
int MSVCRT___mb_cur_max = 1;
BOOL initial_locale = TRUE;

#define MSVCRT_LEADBYTE  0x8000
#define MSVCRT_C1_DEFINED 0x200

__lc_time_data cloc_time_data =
{
    {{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
      "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday",
      "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
      "January", "February", "March", "April", "May", "June", "July",
      "August", "September", "October", "November", "December",
      "AM", "PM", "MM/dd/yy", "dddd, MMMM dd, yyyy", "HH:mm:ss"}},
#if _MSVCR_VER < 110
    MAKELCID(LANG_ENGLISH, SORT_DEFAULT),
#endif
    1, 0,
#if _MSVCR_VER == 0 || _MSVCR_VER >= 100
    {{L"Sun", L"Mon", L"Tue", L"Wed", L"Thu", L"Fri", L"Sat",
      L"Sunday", L"Monday", L"Tuesday", L"Wednesday", L"Thursday", L"Friday", L"Saturday",
      L"Jan", L"Feb", L"Mar", L"Apr", L"May", L"Jun", L"Jul", L"Aug", L"Sep", L"Oct", L"Nov", L"Dec",
      L"January", L"February", L"March", L"April", L"May", L"June", L"July",
      L"August", L"September", L"October", L"November", L"December",
      L"AM", L"PM", L"MM/dd/yy", L"dddd, MMMM dd, yyyy", L"HH:mm:ss"}},
#endif
#if _MSVCR_VER >= 110
    L"en-US",
#endif
};

static const unsigned char cloc_clmap[256] =
{
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
    0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
    0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
    0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
    0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
    0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
    0x78, 0x79, 0x7a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
    0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
    0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
    0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
    0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
    0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
    0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
    0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
    0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
    0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
    0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
    0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
    0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
    0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
    0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
    0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
    0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
    0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
    0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
};

static const unsigned char cloc_cumap[256] =
{
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
    0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
    0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
    0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
    0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
    0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
    0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
    0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
    0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
    0x58, 0x59, 0x5a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
    0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
    0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
    0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
    0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
    0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
    0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
    0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
    0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
    0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
    0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
    0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
    0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
    0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
    0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
};

static char empty[] = "";
static char cloc_dec_point[] = ".";
#if _MSVCR_VER >= 100
static wchar_t emptyW[] = L"";
static wchar_t cloc_dec_pointW[] = L".";
#endif
static struct lconv cloc_lconv =
{
    cloc_dec_point, empty, empty, empty, empty, empty, empty, empty, empty, empty,
    CHAR_MAX, CHAR_MAX, CHAR_MAX, CHAR_MAX, CHAR_MAX, CHAR_MAX, CHAR_MAX, CHAR_MAX,
#if _MSVCR_VER >= 100
    cloc_dec_pointW, emptyW, emptyW, emptyW, emptyW, emptyW, emptyW, emptyW
#endif
};

/* Friendly country strings & language names abbreviations. */
static const char * const _country_synonyms[] =
{
    "american", "enu",
    "american english", "enu",
    "american-english", "enu",
    "english-american", "enu",
    "english-us", "enu",
    "english-usa", "enu",
    "us", "enu",
    "usa", "enu",
    "australian", "ena",
    "english-aus", "ena",
    "belgian", "nlb",
    "french-belgian", "frb",
    "canadian", "enc",
    "english-can", "enc",
    "french-canadian", "frc",
    "chinese", "chs",
    "chinese-simplified", "chs",
    "chinese-traditional", "cht",
    "dutch-belgian", "nlb",
    "english-nz", "enz",
    "uk", "eng",
    "english-uk", "eng",
    "french-swiss", "frs",
    "swiss", "des",
    "german-swiss", "des",
    "italian-swiss", "its",
    "german-austrian", "dea",
    "portuguese", "ptb",
    "portuguese-brazil", "ptb",
    "spanish-mexican", "esm",
    "norwegian-bokmal", "nor",
    "norwegian-nynorsk", "non",
    "spanish-modern", "esn"
};

/* INTERNAL: Map a synonym to an ISO code */
static void remap_synonym(char *name)
{
  unsigned int i;
  for (i = 0; i < ARRAY_SIZE(_country_synonyms); i += 2)
  {
    if (!_stricmp(_country_synonyms[i],name))
    {
      TRACE(":Mapping synonym %s to %s\n",name,_country_synonyms[i+1]);
      strcpy(name, _country_synonyms[i+1]);
      return;
    }
  }
}

/* Note: Flags are weighted in order of matching importance */
#define FOUND_SNAME            0x4
#define FOUND_LANGUAGE         0x2
#define FOUND_COUNTRY          0x1

typedef struct {
  char search_language[MAX_ELEM_LEN];
  char search_country[MAX_ELEM_LEN];
  DWORD found_codepage;
  unsigned int match_flags;
  LANGID found_lang_id;
  BOOL allow_sname;
} locale_search_t;

#define CONTINUE_LOOKING TRUE
#define STOP_LOOKING     FALSE

/* INTERNAL: Get and compare locale info with a given string */
static int compare_info(LCID lcid, DWORD flags, char* buff, const char* cmp, BOOL exact)
{
  int len;

  if(!cmp[0])
      return 0;

  buff[0] = 0;
  GetLocaleInfoA(lcid, flags|LOCALE_NOUSEROVERRIDE, buff, MAX_ELEM_LEN);
  if (!buff[0])
    return 0;

  /* Partial matches are only allowed on language/country names */
  len = strlen(cmp);
  if(exact || len<=3)
    return !_stricmp(cmp, buff);
  else
    return !_strnicmp(cmp, buff, len);
}

static BOOL CALLBACK
find_best_locale_proc(HMODULE hModule, LPCSTR type, LPCSTR name, WORD LangID, LONG_PTR lParam)
{
  locale_search_t *res = (locale_search_t *)lParam;
  const LCID lcid = MAKELCID(LangID, SORT_DEFAULT);
  char buff[MAX_ELEM_LEN];
  unsigned int flags = 0;

  if(PRIMARYLANGID(LangID) == LANG_NEUTRAL)
    return CONTINUE_LOOKING;

#if _MSVCR_VER >= 110
  if (res->allow_sname && compare_info(lcid,LOCALE_SNAME,buff,res->search_language, TRUE))
  {
    TRACE(":Found locale: %s->%s\n", res->search_language, buff);
    res->match_flags = FOUND_SNAME;
    res->found_lang_id = LangID;
    return STOP_LOOKING;
  }
#endif

  /* Check Language */
  if (compare_info(lcid,LOCALE_SISO639LANGNAME,buff,res->search_language, TRUE) ||
      compare_info(lcid,LOCALE_SABBREVLANGNAME,buff,res->search_language, TRUE) ||
      compare_info(lcid,LOCALE_SENGLANGUAGE,buff,res->search_language, FALSE))
  {
    TRACE(":Found language: %s->%s\n", res->search_language, buff);
    flags |= FOUND_LANGUAGE;
  }
  else if (res->match_flags & FOUND_LANGUAGE)
  {
    return CONTINUE_LOOKING;
  }

  /* Check Country */
  if (compare_info(lcid,LOCALE_SISO3166CTRYNAME,buff,res->search_country, TRUE) ||
      compare_info(lcid,LOCALE_SABBREVCTRYNAME,buff,res->search_country, TRUE) ||
      compare_info(lcid,LOCALE_SENGCOUNTRY,buff,res->search_country, FALSE))
  {
    TRACE("Found country:%s->%s\n", res->search_country, buff);
    flags |= FOUND_COUNTRY;
  }
  else if (!flags && (res->match_flags & FOUND_COUNTRY))
  {
    return CONTINUE_LOOKING;
  }

  if (flags > res->match_flags)
  {
    /* Found a better match than previously */
    res->match_flags = flags;
    res->found_lang_id = LangID;
  }
  if ((flags & (FOUND_LANGUAGE | FOUND_COUNTRY)) ==
        (FOUND_LANGUAGE | FOUND_COUNTRY))
  {
    TRACE(":found exact locale match\n");
    return STOP_LOOKING;
  }
  return CONTINUE_LOOKING;
}

/* Internal: Find the LCID for a locale specification */
LCID locale_to_LCID(const char *locale, unsigned short *codepage, BOOL *sname)
{
    thread_data_t *data = msvcrt_get_thread_data();
    const char *cp, *region;
    BOOL is_sname = FALSE;
    DWORD locale_cp;
    LCID lcid;

    if (!strcmp(locale, data->cached_locale)) {
        if (codepage)
            *codepage = data->cached_cp;
        if (sname)
            *sname = data->cached_sname;
        return data->cached_lcid;
    }

    cp = strchr(locale, '.');
    region = strchr(locale, '_');

    if(!locale[0] || (cp == locale && !region)) {
        lcid = GetUserDefaultLCID();
    } else {
        locale_search_t search;

        memset(&search, 0, sizeof(locale_search_t));
        lstrcpynA(search.search_language, locale, MAX_ELEM_LEN);
        if(region) {
            lstrcpynA(search.search_country, region+1, MAX_ELEM_LEN);
            if(region-locale < MAX_ELEM_LEN)
                search.search_language[region-locale] = '\0';
        } else
            search.search_country[0] = '\0';

        if(cp) {
            if(region && cp-region-1<MAX_ELEM_LEN)
                search.search_country[cp-region-1] = '\0';
            if(cp-locale < MAX_ELEM_LEN)
                search.search_language[cp-locale] = '\0';
        }

        if(!cp && !region)
        {
            remap_synonym(search.search_language);
            search.allow_sname = TRUE;
        }

        if(!_stricmp(search.search_country, "China"))
            strcpy(search.search_country, "People's Republic of China");

        EnumResourceLanguagesA(GetModuleHandleA("KERNEL32"), (LPSTR)RT_STRING,
                (LPCSTR)LOCALE_ILANGUAGE,find_best_locale_proc,
                (LONG_PTR)&search);

        if (!search.match_flags)
            return -1;

        /* If we were given something that didn't match, fail */
        if (search.search_language[0] && !(search.match_flags & (FOUND_SNAME | FOUND_LANGUAGE)))
            return -1;
        if (search.search_country[0] && !(search.match_flags & FOUND_COUNTRY))
            return -1;

        lcid =  MAKELCID(search.found_lang_id, SORT_DEFAULT);
        is_sname = (search.match_flags & FOUND_SNAME) != 0;
    }

    /* Obtain code page */
    if (!cp || !cp[1] || !_strnicmp(cp, ".ACP", 4)) {
        GetLocaleInfoW(lcid, LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER,
                (WCHAR *)&locale_cp, sizeof(DWORD)/sizeof(WCHAR));
        if (!locale_cp)
            locale_cp = GetACP();
    } else if (!_strnicmp(cp, ".OCP", 4)) {
        GetLocaleInfoW(lcid, LOCALE_IDEFAULTCODEPAGE | LOCALE_RETURN_NUMBER,
                (WCHAR *)&locale_cp, sizeof(DWORD)/sizeof(WCHAR));
#if _MSVCR_VER >= 140
    } else if (!_strnicmp(cp, ".UTF-8", 6)
            || !_strnicmp(cp, ".UTF8", 5)) {
        locale_cp = CP_UTF8;
#endif
    } else {
        locale_cp = atoi(cp + 1);
    }
    if (!IsValidCodePage(locale_cp))
        return -1;

    if (!locale_cp)
        return -1;

    if (codepage)
        *codepage = locale_cp;
    if (sname)
        *sname = is_sname;

    if (strlen(locale) < sizeof(data->cached_locale)) {
        strcpy(data->cached_locale, locale);
        data->cached_lcid = lcid;
        data->cached_cp = locale_cp;
        data->cached_sname = is_sname;
    }

    return lcid;
}

static void copy_threadlocinfo_category(pthreadlocinfo locinfo,
        const threadlocinfo *old_locinfo, int category)
{
    locinfo->lc_handle[category] = old_locinfo->lc_handle[category];
    locinfo->lc_id[category] = old_locinfo->lc_id[category];
    if(!locinfo->lc_category[category].locale) {
        locinfo->lc_category[category].locale = old_locinfo->lc_category[category].locale;
        locinfo->lc_category[category].refcount = old_locinfo->lc_category[category].refcount;
        InterlockedIncrement(locinfo->lc_category[category].refcount);
    }
#if _MSVCR_VER >= 110
    locinfo->lc_name[category] = old_locinfo->lc_name[category];
    locinfo->lc_category[category].wrefcount = old_locinfo->lc_category[category].wrefcount;
    if(locinfo->lc_category[category].wrefcount)
        InterlockedIncrement(locinfo->lc_category[category].wrefcount);
#endif
}

static BOOL init_category_name(const char *name, int len,
        pthreadlocinfo locinfo, int category)
{
    locinfo->lc_category[category].locale = malloc(len+1);
    locinfo->lc_category[category].refcount = malloc(sizeof(int));
    if(!locinfo->lc_category[category].locale
            || !locinfo->lc_category[category].refcount) {
        free(locinfo->lc_category[category].locale);
        free(locinfo->lc_category[category].refcount);
        locinfo->lc_category[category].locale = NULL;
        locinfo->lc_category[category].refcount = NULL;
        return FALSE;
    }

    memcpy(locinfo->lc_category[category].locale, name, len);
    locinfo->lc_category[category].locale[len] = 0;
    *locinfo->lc_category[category].refcount = 1;
    return TRUE;
}

#if _MSVCR_VER >= 110
static inline BOOL set_lc_locale_name(pthreadlocinfo locinfo, int cat)
{
    LCID lcid = locinfo->lc_handle[cat];
    WCHAR buf[100];
    int len;

    locinfo->lc_category[cat].wrefcount = malloc(sizeof(int));
    if(!locinfo->lc_category[cat].wrefcount)
        return FALSE;
    *locinfo->lc_category[cat].wrefcount = 1;

    len = GetLocaleInfoW(lcid, LOCALE_SISO639LANGNAME
            |LOCALE_NOUSEROVERRIDE, buf, 100);
    if(!len) return FALSE;

    if(LocaleNameToLCID(buf, 0) != lcid)
        len = LCIDToLocaleName(lcid, buf, 100, 0);

    if(!len || !(locinfo->lc_name[cat] = malloc(len*sizeof(wchar_t))))
        return FALSE;

    memcpy(locinfo->lc_name[cat], buf, len*sizeof(wchar_t));
    return TRUE;
}
#else
static inline BOOL set_lc_locale_name(pthreadlocinfo locinfo, int cat)
{
    return TRUE;
}
#endif

/* INTERNAL: Set lc_handle, lc_id and lc_category in threadlocinfo struct */
static BOOL update_threadlocinfo_category(LCID lcid, unsigned short cp,
        pthreadlocinfo locinfo, int category)
{
    char buf[256], *p;

    if(GetLocaleInfoA(lcid, LOCALE_ILANGUAGE|LOCALE_NOUSEROVERRIDE, buf, 256)) {
        p = buf;

        locinfo->lc_id[category].wLanguage = 0;
        while(*p) {
            locinfo->lc_id[category].wLanguage *= 16;

            if(*p <= '9')
                locinfo->lc_id[category].wLanguage += *p-'0';
            else
                locinfo->lc_id[category].wLanguage += *p-'a'+10;

            p++;
        }

        locinfo->lc_id[category].wCountry =
            locinfo->lc_id[category].wLanguage;
    }

    locinfo->lc_id[category].wCodePage = cp;

    locinfo->lc_handle[category] = lcid;

    set_lc_locale_name(locinfo, category);

    if(!locinfo->lc_category[category].locale) {
        int len = 0;

        len += GetLocaleInfoA(lcid, LOCALE_SENGLANGUAGE
                |LOCALE_NOUSEROVERRIDE, buf, 256);
        buf[len-1] = '_';
        len += GetLocaleInfoA(lcid, LOCALE_SENGCOUNTRY
                |LOCALE_NOUSEROVERRIDE, &buf[len], 256-len);
        buf[len-1] = '.';
        sprintf(buf+len, "%d", cp);
        len += strlen(buf+len);

        return init_category_name(buf, len, locinfo, category);
    }

    return TRUE;
}

/*********************************************************************
 *      _lock_locales (UCRTBASE.@)
 */
void CDECL _lock_locales(void)
{
    _lock(_SETLOCALE_LOCK);
}

/*********************************************************************
 *      _unlock_locales (UCRTBASE.@)
 */
void CDECL _unlock_locales(void)
{
    _unlock(_SETLOCALE_LOCK);
}

static void CDECL grab_locinfo(pthreadlocinfo locinfo)
{
    int i;

    InterlockedIncrement(&locinfo->refcount);
    for(i=LC_MIN+1; i<=LC_MAX; i++)
    {
        InterlockedIncrement(locinfo->lc_category[i].refcount);
        if(locinfo->lc_category[i].wrefcount)
            InterlockedIncrement(locinfo->lc_category[i].wrefcount);
    }
    if(locinfo->lconv_intl_refcount)
        InterlockedIncrement(locinfo->lconv_intl_refcount);
    if(locinfo->lconv_num_refcount)
        InterlockedIncrement(locinfo->lconv_num_refcount);
    if(locinfo->lconv_mon_refcount)
        InterlockedIncrement(locinfo->lconv_mon_refcount);
    if(locinfo->ctype1_refcount)
        InterlockedIncrement(locinfo->ctype1_refcount);
    InterlockedIncrement(&locinfo->lc_time_curr->refcount);
}

static void CDECL update_thread_locale(thread_data_t *data)
{
    if((data->locale_flags & LOCALE_FREE) && ((data->locale_flags & LOCALE_THREAD) ||
                (data->locinfo == MSVCRT_locale->locinfo && data->mbcinfo == MSVCRT_locale->mbcinfo)))
        return;

    if(data->locale_flags & LOCALE_FREE)
    {
        free_locinfo(data->locinfo);
        free_mbcinfo(data->mbcinfo);
    }

    _lock_locales();
    data->locinfo = MSVCRT_locale->locinfo;
    grab_locinfo(data->locinfo);
    _unlock_locales();

    _lock(_MB_CP_LOCK);
    data->mbcinfo = MSVCRT_locale->mbcinfo;
    InterlockedIncrement(&data->mbcinfo->refcount);
    _unlock(_MB_CP_LOCK);

    data->locale_flags |= LOCALE_FREE;
}

/* INTERNAL: returns threadlocinfo struct */
pthreadlocinfo CDECL get_locinfo(void) {
    thread_data_t *data = msvcrt_get_thread_data();
    update_thread_locale(data);
    return data->locinfo;
}

/* INTERNAL: returns pthreadmbcinfo struct */
pthreadmbcinfo CDECL get_mbcinfo(void) {
    thread_data_t *data = msvcrt_get_thread_data();
    update_thread_locale(data);
    return data->mbcinfo;
}

/* INTERNAL: constructs string returned by setlocale */
static inline char* construct_lc_all(pthreadlocinfo locinfo) {
    static char current_lc_all[MAX_LOCALE_LENGTH];

    int i;

    for(i=LC_MIN+1; i<LC_MAX; i++) {
        if(strcmp(locinfo->lc_category[i].locale,
                    locinfo->lc_category[i+1].locale))
            break;
    }

    if(i==LC_MAX)
        return locinfo->lc_category[LC_COLLATE].locale;

    sprintf(current_lc_all,
            "LC_COLLATE=%s;LC_CTYPE=%s;LC_MONETARY=%s;LC_NUMERIC=%s;LC_TIME=%s",
            locinfo->lc_category[LC_COLLATE].locale,
            locinfo->lc_category[LC_CTYPE].locale,
            locinfo->lc_category[LC_MONETARY].locale,
            locinfo->lc_category[LC_NUMERIC].locale,
            locinfo->lc_category[LC_TIME].locale);

    return current_lc_all;
}


/*********************************************************************
 *		_Getdays (MSVCRT.@)
 */
char* CDECL _Getdays(void)
{
    __lc_time_data *cur = get_locinfo()->lc_time_curr;
    int i, len, size = 0;
    char *out;

    TRACE("\n");

    for(i=0; i<7; i++) {
        size += strlen(cur->str.names.short_wday[i]) + 1;
        size += strlen(cur->str.names.wday[i]) + 1;
    }
    out = malloc(size+1);
    if(!out)
        return NULL;

    size = 0;
    for(i=0; i<7; i++) {
        out[size++] = ':';
        len = strlen(cur->str.names.short_wday[i]);
        memcpy(&out[size], cur->str.names.short_wday[i], len);
        size += len;

        out[size++] = ':';
        len = strlen(cur->str.names.wday[i]);
        memcpy(&out[size], cur->str.names.wday[i], len);
        size += len;
    }
    out[size] = '\0';

    return out;
}

#if _MSVCR_VER >= 110
/*********************************************************************
 *		_W_Getdays (MSVCR110.@)
 */
wchar_t* CDECL _W_Getdays(void)
{
    __lc_time_data *cur = get_locinfo()->lc_time_curr;
    wchar_t *out;
    int i, len, size = 0;

    TRACE("\n");

    for(i=0; i<7; i++) {
        size += wcslen(cur->wstr.names.short_wday[i]) + 1;
        size += wcslen(cur->wstr.names.wday[i]) + 1;
    }
    out = malloc((size+1)*sizeof(*out));
    if(!out)
        return NULL;

    size = 0;
    for(i=0; i<7; i++) {
        out[size++] = ':';
        len = wcslen(cur->wstr.names.short_wday[i]);
        memcpy(&out[size], cur->wstr.names.short_wday[i], len*sizeof(*out));
        size += len;

        out[size++] = ':';
        len = wcslen(cur->wstr.names.wday[i]);
        memcpy(&out[size], cur->wstr.names.wday[i], len*sizeof(*out));
        size += len;
    }
    out[size] = '\0';

    return out;
}
#endif

/*********************************************************************
 *		_Getmonths (MSVCRT.@)
 */
char* CDECL _Getmonths(void)
{
    __lc_time_data *cur = get_locinfo()->lc_time_curr;
    int i, len, size = 0;
    char *out;

    TRACE("\n");

    for(i=0; i<12; i++) {
        size += strlen(cur->str.names.short_mon[i]) + 1;
        size += strlen(cur->str.names.mon[i]) + 1;
    }
    out = malloc(size+1);
    if(!out)
        return NULL;

    size = 0;
    for(i=0; i<12; i++) {
        out[size++] = ':';
        len = strlen(cur->str.names.short_mon[i]);
        memcpy(&out[size], cur->str.names.short_mon[i], len);
        size += len;

        out[size++] = ':';
        len = strlen(cur->str.names.mon[i]);
        memcpy(&out[size], cur->str.names.mon[i], len);
        size += len;
    }
    out[size] = '\0';

    return out;
}

#if _MSVCR_VER >= 110
/*********************************************************************
 *		_W_Getmonths (MSVCR110.@)
 */
wchar_t* CDECL _W_Getmonths(void)
{
    __lc_time_data *cur = get_locinfo()->lc_time_curr;
    wchar_t *out;
    int i, len, size = 0;

    TRACE("\n");

    for(i=0; i<12; i++) {
        size += wcslen(cur->wstr.names.short_mon[i]) + 1;
        size += wcslen(cur->wstr.names.mon[i]) + 1;
    }
    out = malloc((size+1)*sizeof(*out));
    if(!out)
        return NULL;

    size = 0;
    for(i=0; i<12; i++) {
        out[size++] = ':';
        len = wcslen(cur->wstr.names.short_mon[i]);
        memcpy(&out[size], cur->wstr.names.short_mon[i], len*sizeof(*out));
        size += len;

        out[size++] = ':';
        len = wcslen(cur->wstr.names.mon[i]);
        memcpy(&out[size], cur->wstr.names.mon[i], len*sizeof(*out));
        size += len;
    }
    out[size] = '\0';

    return out;
}
#endif

/*********************************************************************
 *		_Gettnames (MSVCRT.@)
 */
void* CDECL _Gettnames(void)
{
    __lc_time_data *ret, *cur = get_locinfo()->lc_time_curr;
    unsigned int i, len, size = sizeof(__lc_time_data);

    TRACE("\n");

    for(i=0; i<ARRAY_SIZE(cur->str.str); i++)
        size += strlen(cur->str.str[i])+1;
#if _MSVCR_VER >= 110
    for(i=0; i<ARRAY_SIZE(cur->wstr.wstr); i++)
        size += (wcslen(cur->wstr.wstr[i]) + 1) * sizeof(wchar_t);
#endif

    ret = malloc(size);
    if(!ret)
        return NULL;
    memcpy(ret, cur, sizeof(*ret));

    size = 0;
    for(i=0; i<ARRAY_SIZE(cur->str.str); i++) {
        len = strlen(cur->str.str[i])+1;
        memcpy(&ret->data[size], cur->str.str[i], len);
        ret->str.str[i] = &ret->data[size];
        size += len;
    }
#if _MSVCR_VER >= 110
    for(i=0; i<ARRAY_SIZE(cur->wstr.wstr); i++) {
        len = (wcslen(cur->wstr.wstr[i]) + 1) * sizeof(wchar_t);
        memcpy(&ret->data[size], cur->wstr.wstr[i], len);
        ret->wstr.wstr[i] = (wchar_t*)&ret->data[size];
        size += len;
    }
#endif

    return ret;
}

#if _MSVCR_VER >= 110
/*********************************************************************
 *              _W_Gettnames (MSVCR110.@)
 */
void* CDECL _W_Gettnames(void)
{
    return _Gettnames();
}
#endif

/*********************************************************************
 *		__crtLCMapStringA (MSVCRT.@)
 */
int CDECL __crtLCMapStringA(
  LCID lcid, DWORD mapflags, const char* src, int srclen, char* dst,
  int dstlen, unsigned int codepage, int xflag
) {
    WCHAR buf_in[32], *in = buf_in;
    WCHAR buf_out[32], *out = buf_out;
    int in_len, out_len, r;

    TRACE("(lcid %x, flags %x, %s(%d), %p(%d), %x, %d), partial stub!\n",
            lcid, mapflags, src, srclen, dst, dstlen, codepage, xflag);

    in_len = MultiByteToWideChar(codepage, MB_ERR_INVALID_CHARS, src, srclen, NULL, 0);
    if (!in_len) return 0;
    if (in_len > ARRAY_SIZE(buf_in))
    {
        in = malloc(in_len * sizeof(WCHAR));
        if (!in) return 0;
    }

    r = MultiByteToWideChar(codepage, MB_ERR_INVALID_CHARS, src, srclen, in, in_len);
    if (!r) goto done;

    if (mapflags & LCMAP_SORTKEY)
    {
        r = LCMapStringW(lcid, mapflags, in, in_len, (WCHAR*)dst, dstlen);
        goto done;
    }

    r = LCMapStringW(lcid, mapflags, in, in_len, NULL, 0);
    if (!r) goto done;
    out_len = r;
    if (r > ARRAY_SIZE(buf_out))
    {
        out = malloc(r * sizeof(WCHAR));
        if (!out)
        {
            r = 0;
            goto done;
        }
    }

    r = LCMapStringW(lcid, mapflags, in, in_len, out, out_len);
    if (!r) goto done;

    r = WideCharToMultiByte(codepage, 0, out, out_len, dst, dstlen, NULL, NULL);

done:
    if (in != buf_in) free(in);
    if (out != buf_out) free(out);
    return r;
}

/*********************************************************************
 *              __crtLCMapStringW (MSVCRT.@)
 */
int CDECL __crtLCMapStringW(LCID lcid, DWORD mapflags, const wchar_t *src,
        int srclen, wchar_t *dst, int dstlen, unsigned int codepage, int xflag)
{
    FIXME("(lcid %x, flags %x, %s(%d), %p(%d), %x, %d), partial stub!\n",
            lcid, mapflags, debugstr_w(src), srclen, dst, dstlen, codepage, xflag);

    return LCMapStringW(lcid, mapflags, src, srclen, dst, dstlen);
}

/*********************************************************************
 *		__crtCompareStringA (MSVCRT.@)
 */
int CDECL __crtCompareStringA( LCID lcid, DWORD flags, const char *src1, int len1,
                               const char *src2, int len2 )
{
    FIXME("(lcid %x, flags %x, %s(%d), %s(%d), partial stub\n",
          lcid, flags, debugstr_a(src1), len1, debugstr_a(src2), len2 );
    /* FIXME: probably not entirely right */
    return CompareStringA( lcid, flags, src1, len1, src2, len2 );
}

/*********************************************************************
 *		__crtCompareStringW (MSVCRT.@)
 */
int CDECL __crtCompareStringW( LCID lcid, DWORD flags, const wchar_t *src1, int len1,
                               const wchar_t *src2, int len2 )
{
    FIXME("(lcid %x, flags %x, %s(%d), %s(%d), partial stub\n",
          lcid, flags, debugstr_w(src1), len1, debugstr_w(src2), len2 );
    /* FIXME: probably not entirely right */
    return CompareStringW( lcid, flags, src1, len1, src2, len2 );
}

/*********************************************************************
 *		__crtGetLocaleInfoW (MSVCRT.@)
 */
int CDECL __crtGetLocaleInfoW( LCID lcid, LCTYPE type, wchar_t *buffer, int len )
{
    FIXME("(lcid %x, type %x, %p(%d), partial stub\n", lcid, type, buffer, len );
    /* FIXME: probably not entirely right */
    return GetLocaleInfoW( lcid, type, buffer, len );
}

#if _MSVCR_VER >= 110
/*********************************************************************
 *		__crtGetLocaleInfoEx (MSVC110.@)
 */
int CDECL __crtGetLocaleInfoEx( const WCHAR *locale, LCTYPE type, wchar_t *buffer, int len )
{
    TRACE("(%s, %x, %p, %d)\n", debugstr_w(locale), type, buffer, len);
    return GetLocaleInfoEx(locale, type, buffer, len);
}
#endif

/*********************************************************************
 *              __crtGetStringTypeW(MSVCRT.@)
 *
 * This function was accepting different number of arguments in older
 * versions of msvcrt.
 */
BOOL CDECL __crtGetStringTypeW(DWORD unk, DWORD type,
        wchar_t *buffer, int len, WORD *out)
{
    FIXME("(unk %x, type %x, wstr %p(%d), %p) partial stub\n",
            unk, type, buffer, len, out);

    return GetStringTypeW(type, buffer, len, out);
}

/*********************************************************************
 *		localeconv (MSVCRT.@)
 */
struct lconv* CDECL localeconv(void)
{
    return get_locinfo()->lconv;
}

/*********************************************************************
 *		__lconv_init (MSVCRT.@)
 */
int CDECL __lconv_init(void)
{
    /* this is used to make chars unsigned */
    cloc_lconv.int_frac_digits = (char)UCHAR_MAX;
    cloc_lconv.frac_digits = (char)UCHAR_MAX;
    cloc_lconv.p_cs_precedes = (char)UCHAR_MAX;
    cloc_lconv.p_sep_by_space = (char)UCHAR_MAX;
    cloc_lconv.n_cs_precedes = (char)UCHAR_MAX;
    cloc_lconv.n_sep_by_space = (char)UCHAR_MAX;
    cloc_lconv.p_sign_posn = (char)UCHAR_MAX;
    cloc_lconv.n_sign_posn = (char)UCHAR_MAX;
    return 0;
}

/*********************************************************************
 *      ___lc_handle_func (MSVCRT.@)
 */
LCID* CDECL ___lc_handle_func(void)
{
    return (LCID *)get_locinfo()->lc_handle;
}

#if _MSVCR_VER >= 110
/*********************************************************************
 *      ___lc_locale_name_func (MSVCR110.@)
 */
wchar_t** CDECL ___lc_locale_name_func(void)
{
    return get_locinfo()->lc_name;
}
#endif

/*********************************************************************
 *      ___lc_codepage_func (MSVCRT.@)
 */
unsigned int CDECL ___lc_codepage_func(void)
{
    return get_locinfo()->lc_codepage;
}

/*********************************************************************
 *      ___lc_collate_cp_func (MSVCRT.@)
 */
int CDECL ___lc_collate_cp_func(void)
{
    return get_locinfo()->lc_collate_cp;
}

/* INTERNAL: frees pthreadlocinfo struct */
void free_locinfo(pthreadlocinfo locinfo)
{
    int i;

    if(!locinfo)
        return;

    for(i=LC_MIN+1; i<=LC_MAX; i++) {
        if(!locinfo->lc_category[i].refcount
                || !InterlockedDecrement(locinfo->lc_category[i].refcount)) {
            free(locinfo->lc_category[i].locale);
            free(locinfo->lc_category[i].refcount);
        }
        if(!locinfo->lc_category[i].wrefcount
                || !InterlockedDecrement(locinfo->lc_category[i].wrefcount)) {
#if _MSVCR_VER >= 110
            free(locinfo->lc_name[i]);
#endif
            free(locinfo->lc_category[i].wrefcount);
        }
    }

    if(locinfo->lconv_num_refcount
            && !InterlockedDecrement(locinfo->lconv_num_refcount)) {
        free(locinfo->lconv->decimal_point);
        free(locinfo->lconv->thousands_sep);
        free(locinfo->lconv->grouping);
#if _MSVCR_VER >= 100
        free(locinfo->lconv->_W_decimal_point);
        free(locinfo->lconv->_W_thousands_sep);
#endif
        free(locinfo->lconv_num_refcount);
    }
    if(locinfo->lconv_mon_refcount
            && !InterlockedDecrement(locinfo->lconv_mon_refcount)) {
        free(locinfo->lconv->int_curr_symbol);
        free(locinfo->lconv->currency_symbol);
        free(locinfo->lconv->mon_decimal_point);
        free(locinfo->lconv->mon_thousands_sep);
        free(locinfo->lconv->mon_grouping);
        free(locinfo->lconv->positive_sign);
        free(locinfo->lconv->negative_sign);
#if _MSVCR_VER >= 100
        free(locinfo->lconv->_W_int_curr_symbol);
        free(locinfo->lconv->_W_currency_symbol);
        free(locinfo->lconv->_W_mon_decimal_point);
        free(locinfo->lconv->_W_mon_thousands_sep);
        free(locinfo->lconv->_W_positive_sign);
        free(locinfo->lconv->_W_negative_sign);
#endif
        free(locinfo->lconv_mon_refcount);
    }
    if(locinfo->lconv_intl_refcount
            && !InterlockedDecrement(locinfo->lconv_intl_refcount)) {
        free(locinfo->lconv_intl_refcount);
        free(locinfo->lconv);
    }

    if(locinfo->ctype1_refcount
            && !InterlockedDecrement(locinfo->ctype1_refcount)) {
        free(locinfo->ctype1_refcount);
        free(locinfo->ctype1);
        free((void*)locinfo->pclmap);
        free((void*)locinfo->pcumap);
    }

    if(locinfo->lc_time_curr && !InterlockedDecrement(&locinfo->lc_time_curr->refcount)
            && locinfo->lc_time_curr != &cloc_time_data)
        free(locinfo->lc_time_curr);

    if(InterlockedDecrement(&locinfo->refcount))
        return;

    free(locinfo);
}

/* INTERNAL: frees pthreadmbcinfo struct */
void free_mbcinfo(pthreadmbcinfo mbcinfo)
{
    if(!mbcinfo)
        return;

    if(InterlockedDecrement(&mbcinfo->refcount))
        return;

    free(mbcinfo);
}

_locale_t CDECL get_current_locale_noalloc(_locale_t locale)
{
    thread_data_t *data = msvcrt_get_thread_data();

    update_thread_locale(data);
    locale->locinfo = data->locinfo;
    locale->mbcinfo = data->mbcinfo;

    grab_locinfo(locale->locinfo);
    InterlockedIncrement(&locale->mbcinfo->refcount);
    return locale;
}

void CDECL free_locale_noalloc(_locale_t locale)
{
    free_locinfo(locale->locinfo);
    free_mbcinfo(locale->mbcinfo);
}

/*********************************************************************
 *      _get_current_locale (MSVCRT.@)
 */
_locale_t CDECL _get_current_locale(void)
{
    _locale_t loc = malloc(sizeof(_locale_tstruct));
    if(!loc)
        return NULL;

    return get_current_locale_noalloc(loc);
}

/*********************************************************************
 *      _free_locale (MSVCRT.@)
 */
void CDECL _free_locale(_locale_t locale)
{
    if (!locale)
        return;

    free_locale_noalloc(locale);
    free(locale);
}

static inline BOOL category_needs_update(int cat,
        const threadlocinfo *locinfo, LCID lcid, unsigned short cp)
{
    if(!locinfo) return TRUE;
    return lcid!=locinfo->lc_handle[cat] || cp!=locinfo->lc_id[cat].wCodePage;
}

static __lc_time_data* create_time_data(LCID lcid)
{
    static const DWORD time_data[] = {
        LOCALE_SABBREVDAYNAME7, LOCALE_SABBREVDAYNAME1, LOCALE_SABBREVDAYNAME2,
        LOCALE_SABBREVDAYNAME3, LOCALE_SABBREVDAYNAME4, LOCALE_SABBREVDAYNAME5,
        LOCALE_SABBREVDAYNAME6,
        LOCALE_SDAYNAME7, LOCALE_SDAYNAME1, LOCALE_SDAYNAME2, LOCALE_SDAYNAME3,
        LOCALE_SDAYNAME4, LOCALE_SDAYNAME5, LOCALE_SDAYNAME6,
        LOCALE_SABBREVMONTHNAME1, LOCALE_SABBREVMONTHNAME2, LOCALE_SABBREVMONTHNAME3,
        LOCALE_SABBREVMONTHNAME4, LOCALE_SABBREVMONTHNAME5, LOCALE_SABBREVMONTHNAME6,
        LOCALE_SABBREVMONTHNAME7, LOCALE_SABBREVMONTHNAME8, LOCALE_SABBREVMONTHNAME9,
        LOCALE_SABBREVMONTHNAME10, LOCALE_SABBREVMONTHNAME11, LOCALE_SABBREVMONTHNAME12,
        LOCALE_SMONTHNAME1, LOCALE_SMONTHNAME2, LOCALE_SMONTHNAME3, LOCALE_SMONTHNAME4,
        LOCALE_SMONTHNAME5, LOCALE_SMONTHNAME6, LOCALE_SMONTHNAME7, LOCALE_SMONTHNAME8,
        LOCALE_SMONTHNAME9, LOCALE_SMONTHNAME10, LOCALE_SMONTHNAME11, LOCALE_SMONTHNAME12,
        LOCALE_S1159, LOCALE_S2359,
        LOCALE_SSHORTDATE, LOCALE_SLONGDATE,
        LOCALE_STIMEFORMAT
    };

    __lc_time_data *cur;
    int i, ret, size;

    size = sizeof(__lc_time_data);
    for(i=0; i<ARRAY_SIZE(time_data); i++) {
        ret = GetLocaleInfoA(lcid, time_data[i], NULL, 0);
        if(!ret)
            return NULL;
        size += ret;

#if _MSVCR_VER == 0 || _MSVCR_VER >= 100
        ret = GetLocaleInfoW(lcid, time_data[i], NULL, 0);
        if(!ret)
            return NULL;
        size += ret*sizeof(wchar_t);
#endif
    }
#if _MSVCR_VER >= 110
    size += LCIDToLocaleName(lcid, NULL, 0, 0)*sizeof(wchar_t);
#endif

    cur = malloc(size);
    if(!cur)
        return NULL;

    ret = 0;
    for(i=0; i<ARRAY_SIZE(time_data); i++) {
        cur->str.str[i] = &cur->data[ret];
        ret += GetLocaleInfoA(lcid, time_data[i], &cur->data[ret], size-ret);
    }
#if _MSVCR_VER == 0 || _MSVCR_VER >= 100
    for(i=0; i<ARRAY_SIZE(time_data); i++) {
        cur->wstr.wstr[i] = (wchar_t*)&cur->data[ret];
        ret += GetLocaleInfoW(lcid, time_data[i],
                (wchar_t*)&cur->data[ret], size-ret)*sizeof(wchar_t);
    }
#endif
#if _MSVCR_VER >= 110
    cur->locname = (wchar_t*)&cur->data[ret];
    LCIDToLocaleName(lcid, (wchar_t*)&cur->data[ret], (size-ret)/sizeof(wchar_t), 0);
#else
    cur->lcid = lcid;
#endif
    cur->unk = 1;
    cur->refcount = 1;

    return cur;
}

static pthreadlocinfo create_locinfo(int category,
        const char *locale, const threadlocinfo *old_locinfo)
{
    static const char collate[] = "COLLATE=";
    static const char ctype[] = "CTYPE=";
    static const char monetary[] = "MONETARY=";
    static const char numeric[] = "NUMERIC=";
    static const char time[] = "TIME=";

    pthreadlocinfo locinfo;
    LCID lcid[6] = { 0 };
    unsigned short cp[6] = { 0 };
    const char *locale_name[6] = { 0 };
    int val, locale_len[6] = { 0 };
    char buf[256];
    BOOL sname;
#if _MSVCR_VER >= 100
    wchar_t wbuf[256];
#endif
    int i;

    TRACE("(%d %s)\n", category, locale);

    if(category<LC_MIN || category>LC_MAX || !locale)
        return NULL;

    if(locale[0]=='C' && !locale[1]) {
        lcid[0] = 0;
        cp[0] = CP_ACP;
    } else if (locale[0] == 'L' && locale[1] == 'C' && locale[2] == '_') {
        const char *p;

        while(1) {
            locale += 3; /* LC_ */
            if(!memcmp(locale, collate, sizeof(collate)-1)) {
                i = LC_COLLATE;
                locale += sizeof(collate)-1;
            } else if(!memcmp(locale, ctype, sizeof(ctype)-1)) {
                i = LC_CTYPE;
                locale += sizeof(ctype)-1;
            } else if(!memcmp(locale, monetary, sizeof(monetary)-1)) {
                i = LC_MONETARY;
                locale += sizeof(monetary)-1;
            } else if(!memcmp(locale, numeric, sizeof(numeric)-1)) {
                i = LC_NUMERIC;
                locale += sizeof(numeric)-1;
            } else if(!memcmp(locale, time, sizeof(time)-1)) {
                i = LC_TIME;
                locale += sizeof(time)-1;
            } else
                return NULL;

            p = strchr(locale, ';');
            if(locale[0]=='C' && (locale[1]==';' || locale[1]=='\0')) {
                lcid[i] = 0;
                cp[i] = CP_ACP;
            } else if(p) {
                memcpy(buf, locale, p-locale);
                buf[p-locale] = '\0';
                lcid[i] = locale_to_LCID(buf, &cp[i], &sname);
                if(sname) {
                    locale_name[i] = locale;
                    locale_len[i] = p-locale;
                }
            } else {
                lcid[i] = locale_to_LCID(locale, &cp[i], &sname);
                if(sname) {
                    locale_name[i] = locale;
                    locale_len[i] = strlen(locale);
                }
            }

            if(lcid[i] == -1)
                return NULL;

            if(!p || *(p+1)!='L' || *(p+2)!='C' || *(p+3)!='_')
                break;

            locale = p+1;
        }
    } else {
        lcid[0] = locale_to_LCID(locale, &cp[0], &sname);
        if(lcid[0] == -1)
            return NULL;
        if(sname) {
            locale_name[0] = locale;
            locale_len[0] = strlen(locale);
        }

        for(i=1; i<6; i++) {
            lcid[i] = lcid[0];
            cp[i] = cp[0];
            locale_name[i] = locale_name[0];
            locale_len[i] = locale_len[0];
        }
    }

    for(i=1; i<6; i++) {
        if(category!=LC_ALL && category!=i) {
            if(old_locinfo) {
                lcid[i] = old_locinfo->lc_handle[i];
                cp[i] = old_locinfo->lc_id[i].wCodePage;
            } else {
                lcid[i] = 0;
                cp[i] = 0;
            }
        }
    }

    locinfo = malloc(sizeof(threadlocinfo));
    if(!locinfo)
        return NULL;

    memset(locinfo, 0, sizeof(threadlocinfo));
    locinfo->refcount = 1;

    if(locale_name[LC_COLLATE] &&
            !init_category_name(locale_name[LC_COLLATE],
                locale_len[LC_COLLATE], locinfo, LC_COLLATE)) {
        free_locinfo(locinfo);
        return NULL;
    }

    if(!category_needs_update(LC_COLLATE, old_locinfo,
                lcid[LC_COLLATE], cp[LC_COLLATE])) {
        copy_threadlocinfo_category(locinfo, old_locinfo, LC_COLLATE);
        locinfo->lc_collate_cp = old_locinfo->lc_collate_cp;
    } else if(lcid[LC_COLLATE]) {
        if(!update_threadlocinfo_category(lcid[LC_COLLATE],
                    cp[LC_COLLATE], locinfo, LC_COLLATE)) {
            free_locinfo(locinfo);
            return NULL;
        }

        locinfo->lc_collate_cp = locinfo->lc_id[LC_COLLATE].wCodePage;
    } else {
        if(!init_category_name("C", 1, locinfo, LC_COLLATE)) {
            free_locinfo(locinfo);
            return NULL;
        }
    }

    if(locale_name[LC_CTYPE] &&
            !init_category_name(locale_name[LC_CTYPE],
                locale_len[LC_CTYPE], locinfo, LC_CTYPE)) {
        free_locinfo(locinfo);
        return NULL;
    }

    if(!category_needs_update(LC_CTYPE, old_locinfo,
                lcid[LC_CTYPE], cp[LC_CTYPE])) {
        copy_threadlocinfo_category(locinfo, old_locinfo, LC_CTYPE);
        locinfo->lc_codepage = old_locinfo->lc_codepage;
        locinfo->lc_clike = old_locinfo->lc_clike;
        locinfo->mb_cur_max = old_locinfo->mb_cur_max;
        locinfo->ctype1 = old_locinfo->ctype1;
        locinfo->ctype1_refcount = old_locinfo->ctype1_refcount;
        locinfo->pctype = old_locinfo->pctype;
        locinfo->pclmap = old_locinfo->pclmap;
        locinfo->pcumap = old_locinfo->pcumap;
        if(locinfo->ctype1_refcount)
            InterlockedIncrement(locinfo->ctype1_refcount);
    } else if(lcid[LC_CTYPE]) {
        CPINFO cp_info;
        int j;

        if(!update_threadlocinfo_category(lcid[LC_CTYPE],
                    cp[LC_CTYPE], locinfo, LC_CTYPE)) {
            free_locinfo(locinfo);
            return NULL;
        }

        locinfo->lc_codepage = locinfo->lc_id[LC_CTYPE].wCodePage;
        locinfo->lc_clike = 1;
        if(!GetCPInfo(locinfo->lc_codepage, &cp_info)) {
            free_locinfo(locinfo);
            return NULL;
        }
        locinfo->mb_cur_max = cp_info.MaxCharSize;

        locinfo->ctype1_refcount = malloc(sizeof(int));
        if(!locinfo->ctype1_refcount) {
            free_locinfo(locinfo);
            return NULL;
        }
        *locinfo->ctype1_refcount = 1;

        locinfo->ctype1 = malloc(sizeof(short[257]));
        locinfo->pclmap = malloc(sizeof(char[256]));
        locinfo->pcumap = malloc(sizeof(char[256]));
        if(!locinfo->ctype1 || !locinfo->pclmap || !locinfo->pcumap) {
            free_locinfo(locinfo);
            return NULL;
        }

        locinfo->ctype1[0] = 0;
        locinfo->pctype = locinfo->ctype1+1;

        buf[1] = buf[2] = '\0';
        for(i=1; i<257; i++) {
            buf[0] = i-1;

            /* builtin GetStringTypeA doesn't set output to 0 on invalid input */
            locinfo->ctype1[i] = 0;

            GetStringTypeA(lcid[LC_CTYPE], CT_CTYPE1, buf,
                    1, locinfo->ctype1+i);
        }

        for(i=0; cp_info.LeadByte[i+1]!=0; i+=2)
            for(j=cp_info.LeadByte[i]; j<=cp_info.LeadByte[i+1]; j++)
                locinfo->ctype1[j+1] |= _LEADBYTE;

        for(i=0; i<256; i++) {
            if(locinfo->pctype[i] & _LEADBYTE)
                buf[i] = ' ';
            else
                buf[i] = i;
        }

        LCMapStringA(lcid[LC_CTYPE], LCMAP_LOWERCASE, buf, 256,
                (char*)locinfo->pclmap, 256);
        LCMapStringA(lcid[LC_CTYPE], LCMAP_UPPERCASE, buf, 256,
                (char*)locinfo->pcumap, 256);
    } else {
        locinfo->lc_clike = 1;
        locinfo->mb_cur_max = 1;
        locinfo->pctype = MSVCRT__ctype+1;
        locinfo->pclmap = cloc_clmap;
        locinfo->pcumap = cloc_cumap;
        if(!init_category_name("C", 1, locinfo, LC_CTYPE)) {
            free_locinfo(locinfo);
            return NULL;
        }
    }

    if(!category_needs_update(LC_MONETARY, old_locinfo,
                lcid[LC_MONETARY], cp[LC_MONETARY]) &&
            !category_needs_update(LC_NUMERIC, old_locinfo,
                lcid[LC_NUMERIC], cp[LC_NUMERIC])) {
        locinfo->lconv = old_locinfo->lconv;
        locinfo->lconv_intl_refcount = old_locinfo->lconv_intl_refcount;
        if(locinfo->lconv_intl_refcount)
            InterlockedIncrement(locinfo->lconv_intl_refcount);
    } else if(lcid[LC_MONETARY] || lcid[LC_NUMERIC]) {
        locinfo->lconv = malloc(sizeof(struct lconv));
        locinfo->lconv_intl_refcount = malloc(sizeof(int));
        if(!locinfo->lconv || !locinfo->lconv_intl_refcount) {
            free(locinfo->lconv);
            free(locinfo->lconv_intl_refcount);
            locinfo->lconv = NULL;
            locinfo->lconv_intl_refcount = NULL;
            free_locinfo(locinfo);
            return NULL;
        }
        memset(locinfo->lconv, 0, sizeof(struct lconv));
        *locinfo->lconv_intl_refcount = 1;
    } else {
        locinfo->lconv = &cloc_lconv;
    }

    if(locale_name[LC_MONETARY] &&
            !init_category_name(locale_name[LC_MONETARY],
                locale_len[LC_MONETARY], locinfo, LC_MONETARY)) {
        free_locinfo(locinfo);
        return NULL;
    }

    if(!category_needs_update(LC_MONETARY, old_locinfo,
                lcid[LC_MONETARY], cp[LC_MONETARY])) {
        copy_threadlocinfo_category(locinfo, old_locinfo, LC_MONETARY);
        locinfo->lconv_mon_refcount = old_locinfo->lconv_mon_refcount;
        if(locinfo->lconv_mon_refcount)
            InterlockedIncrement(locinfo->lconv_mon_refcount);
        if(locinfo->lconv != &cloc_lconv && locinfo->lconv != old_locinfo->lconv) {
            locinfo->lconv->int_curr_symbol = old_locinfo->lconv->int_curr_symbol;
            locinfo->lconv->currency_symbol = old_locinfo->lconv->currency_symbol;
            locinfo->lconv->mon_decimal_point = old_locinfo->lconv->mon_decimal_point;
            locinfo->lconv->mon_thousands_sep = old_locinfo->lconv->mon_thousands_sep;
            locinfo->lconv->mon_grouping = old_locinfo->lconv->mon_grouping;
            locinfo->lconv->positive_sign = old_locinfo->lconv->positive_sign;
            locinfo->lconv->negative_sign = old_locinfo->lconv->negative_sign;
            locinfo->lconv->int_frac_digits = old_locinfo->lconv->int_frac_digits;
            locinfo->lconv->frac_digits = old_locinfo->lconv->frac_digits;
            locinfo->lconv->p_cs_precedes = old_locinfo->lconv->p_cs_precedes;
            locinfo->lconv->p_sep_by_space = old_locinfo->lconv->p_sep_by_space;
            locinfo->lconv->n_cs_precedes = old_locinfo->lconv->n_cs_precedes;
            locinfo->lconv->n_sep_by_space = old_locinfo->lconv->n_sep_by_space;
            locinfo->lconv->p_sign_posn = old_locinfo->lconv->p_sign_posn;
            locinfo->lconv->n_sign_posn = old_locinfo->lconv->n_sign_posn;
#if _MSVCR_VER >= 100
            locinfo->lconv->_W_int_curr_symbol = old_locinfo->lconv->_W_int_curr_symbol;
            locinfo->lconv->_W_currency_symbol = old_locinfo->lconv->_W_currency_symbol;
            locinfo->lconv->_W_mon_decimal_point = old_locinfo->lconv->_W_mon_decimal_point;
            locinfo->lconv->_W_mon_thousands_sep = old_locinfo->lconv->_W_mon_thousands_sep;
            locinfo->lconv->_W_positive_sign = old_locinfo->lconv->_W_positive_sign;
            locinfo->lconv->_W_negative_sign = old_locinfo->lconv->_W_negative_sign;
#endif
        }
    } else if(lcid[LC_MONETARY]) {
        if(!update_threadlocinfo_category(lcid[LC_MONETARY],
                    cp[LC_MONETARY], locinfo, LC_MONETARY)) {
            free_locinfo(locinfo);
            return NULL;
        }

        locinfo->lconv_mon_refcount = malloc(sizeof(int));
        if(!locinfo->lconv_mon_refcount) {
            free_locinfo(locinfo);
            return NULL;
        }

        *locinfo->lconv_mon_refcount = 1;

        i = GetLocaleInfoA(lcid[LC_MONETARY], LOCALE_SINTLSYMBOL
                |LOCALE_NOUSEROVERRIDE, buf, 256);
        if(i && (locinfo->lconv->int_curr_symbol = malloc(i)))
            memcpy(locinfo->lconv->int_curr_symbol, buf, i);
        else {
            free_locinfo(locinfo);
            return NULL;
        }

        i = GetLocaleInfoA(lcid[LC_MONETARY], LOCALE_SCURRENCY
                |LOCALE_NOUSEROVERRIDE, buf, 256);
        if(i && (locinfo->lconv->currency_symbol = malloc(i)))
            memcpy(locinfo->lconv->currency_symbol, buf, i);
        else {
            free_locinfo(locinfo);
            return NULL;
        }

        i = GetLocaleInfoA(lcid[LC_MONETARY], LOCALE_SMONDECIMALSEP
                |LOCALE_NOUSEROVERRIDE, buf, 256);
        if(i && (locinfo->lconv->mon_decimal_point = malloc(i)))
            memcpy(locinfo->lconv->mon_decimal_point, buf, i);
        else {
            free_locinfo(locinfo);
            return NULL;
        }

        i = GetLocaleInfoA(lcid[LC_MONETARY], LOCALE_SMONTHOUSANDSEP
                |LOCALE_NOUSEROVERRIDE, buf, 256);
        if(i && (locinfo->lconv->mon_thousands_sep = malloc(i)))
            memcpy(locinfo->lconv->mon_thousands_sep, buf, i);
        else {
            free_locinfo(locinfo);
            return NULL;
        }

        i = GetLocaleInfoA(lcid[LC_MONETARY], LOCALE_SMONGROUPING
                |LOCALE_NOUSEROVERRIDE, buf, 256);
        if(i>1)
            i = i/2 + (buf[i-2]=='0'?0:1);
        if(i && (locinfo->lconv->mon_grouping = malloc(i))) {
            for(i=0; buf[i+1]==';'; i+=2)
                locinfo->lconv->mon_grouping[i/2] = buf[i]-'0';
            locinfo->lconv->mon_grouping[i/2] = buf[i]-'0';
            if(buf[i] != '0')
                locinfo->lconv->mon_grouping[i/2+1] = 127;
        } else {
            free_locinfo(locinfo);
            return NULL;
        }

        i = GetLocaleInfoA(lcid[LC_MONETARY], LOCALE_SPOSITIVESIGN
                |LOCALE_NOUSEROVERRIDE, buf, 256);
        if(i && (locinfo->lconv->positive_sign = malloc(i)))
            memcpy(locinfo->lconv->positive_sign, buf, i);
        else {
            free_locinfo(locinfo);
            return NULL;
        }

        i = GetLocaleInfoA(lcid[LC_MONETARY], LOCALE_SNEGATIVESIGN
                |LOCALE_NOUSEROVERRIDE, buf, 256);
        if(i && (locinfo->lconv->negative_sign = malloc(i)))
            memcpy(locinfo->lconv->negative_sign, buf, i);
        else {
            free_locinfo(locinfo);
            return NULL;
        }

        if(GetLocaleInfoW(lcid[LC_MONETARY], LOCALE_IINTLCURRDIGITS
                          |LOCALE_NOUSEROVERRIDE|LOCALE_RETURN_NUMBER, (WCHAR *)&val, 2))
            locinfo->lconv->int_frac_digits = val;
        else {
            free_locinfo(locinfo);
            return NULL;
        }

        if(GetLocaleInfoW(lcid[LC_MONETARY], LOCALE_ICURRDIGITS
                          |LOCALE_NOUSEROVERRIDE|LOCALE_RETURN_NUMBER, (WCHAR *)&val, 2))
            locinfo->lconv->frac_digits = val;
        else {
            free_locinfo(locinfo);
            return NULL;
        }

        if(GetLocaleInfoW(lcid[LC_MONETARY], LOCALE_IPOSSYMPRECEDES
                          |LOCALE_NOUSEROVERRIDE|LOCALE_RETURN_NUMBER, (WCHAR *)&val, 2))
            locinfo->lconv->p_cs_precedes = val;
        else {
            free_locinfo(locinfo);
            return NULL;
        }

        if(GetLocaleInfoW(lcid[LC_MONETARY], LOCALE_IPOSSEPBYSPACE
                          |LOCALE_NOUSEROVERRIDE|LOCALE_RETURN_NUMBER, (WCHAR *)&val, 2))
            locinfo->lconv->p_sep_by_space = val;
        else {
            free_locinfo(locinfo);
            return NULL;
        }

        if(GetLocaleInfoW(lcid[LC_MONETARY], LOCALE_INEGSYMPRECEDES
                          |LOCALE_NOUSEROVERRIDE|LOCALE_RETURN_NUMBER, (WCHAR *)&val, 2))
            locinfo->lconv->n_cs_precedes = val;
        else {
            free_locinfo(locinfo);
            return NULL;
        }

        if(GetLocaleInfoW(lcid[LC_MONETARY], LOCALE_INEGSEPBYSPACE
                          |LOCALE_NOUSEROVERRIDE|LOCALE_RETURN_NUMBER, (WCHAR *)&val, 2))
            locinfo->lconv->n_sep_by_space = val;
        else {
            free_locinfo(locinfo);
            return NULL;
        }

        if(GetLocaleInfoW(lcid[LC_MONETARY], LOCALE_IPOSSIGNPOSN
                          |LOCALE_NOUSEROVERRIDE|LOCALE_RETURN_NUMBER, (WCHAR *)&val, 2))
            locinfo->lconv->p_sign_posn = val;
        else {
            free_locinfo(locinfo);
            return NULL;
        }

        if(GetLocaleInfoW(lcid[LC_MONETARY], LOCALE_INEGSIGNPOSN
                          |LOCALE_NOUSEROVERRIDE|LOCALE_RETURN_NUMBER, (WCHAR *)&val, 2))
            locinfo->lconv->n_sign_posn = val;
        else {
            free_locinfo(locinfo);
            return NULL;
        }

#if _MSVCR_VER >= 100
        i = GetLocaleInfoW(lcid[LC_MONETARY], LOCALE_SINTLSYMBOL
                |LOCALE_NOUSEROVERRIDE, wbuf, 256);
        if(i && (locinfo->lconv->_W_int_curr_symbol = malloc(i * sizeof(wchar_t))))
            memcpy(locinfo->lconv->_W_int_curr_symbol, wbuf, i * sizeof(wchar_t));
        else {
            free_locinfo(locinfo);
            return NULL;
        }

        i = GetLocaleInfoW(lcid[LC_MONETARY], LOCALE_SCURRENCY
                |LOCALE_NOUSEROVERRIDE, wbuf, 256);
        if(i && (locinfo->lconv->_W_currency_symbol = malloc(i * sizeof(wchar_t))))
            memcpy(locinfo->lconv->_W_currency_symbol, wbuf, i * sizeof(wchar_t));
        else {
            free_locinfo(locinfo);
            return NULL;
        }

        i = GetLocaleInfoW(lcid[LC_MONETARY], LOCALE_SMONDECIMALSEP
                |LOCALE_NOUSEROVERRIDE, wbuf, 256);
        if(i && (locinfo->lconv->_W_mon_decimal_point = malloc(i * sizeof(wchar_t))))
            memcpy(locinfo->lconv->_W_mon_decimal_point, wbuf, i * sizeof(wchar_t));
        else {
            free_locinfo(locinfo);
            return NULL;
        }

        i = GetLocaleInfoW(lcid[LC_MONETARY], LOCALE_SMONTHOUSANDSEP
                |LOCALE_NOUSEROVERRIDE, wbuf, 256);
        if(i && (locinfo->lconv->_W_mon_thousands_sep = malloc(i * sizeof(wchar_t))))
            memcpy(locinfo->lconv->_W_mon_thousands_sep, wbuf, i * sizeof(wchar_t));
        else {
            free_locinfo(locinfo);
            return NULL;
        }

        i = GetLocaleInfoW(lcid[LC_MONETARY], LOCALE_SPOSITIVESIGN
                |LOCALE_NOUSEROVERRIDE, wbuf, 256);
        if(i && (locinfo->lconv->_W_positive_sign = malloc(i * sizeof(wchar_t))))
            memcpy(locinfo->lconv->_W_positive_sign, wbuf, i * sizeof(wchar_t));
        else {
            free_locinfo(locinfo);
            return NULL;
        }

        i = GetLocaleInfoW(lcid[LC_MONETARY], LOCALE_SNEGATIVESIGN
                |LOCALE_NOUSEROVERRIDE, wbuf, 256);
        if(i && (locinfo->lconv->_W_negative_sign = malloc(i * sizeof(wchar_t))))
            memcpy(locinfo->lconv->_W_negative_sign, wbuf, i * sizeof(wchar_t));
        else {
            free_locinfo(locinfo);
            return NULL;
        }
#endif
    } else {
        if (locinfo->lconv != &cloc_lconv) {
            locinfo->lconv->int_curr_symbol = cloc_lconv.int_curr_symbol;
            locinfo->lconv->currency_symbol = cloc_lconv.currency_symbol;
            locinfo->lconv->mon_decimal_point = cloc_lconv.mon_decimal_point;
            locinfo->lconv->mon_thousands_sep = cloc_lconv.mon_thousands_sep;
            locinfo->lconv->mon_grouping = cloc_lconv.mon_grouping;
            locinfo->lconv->positive_sign = cloc_lconv.positive_sign;
            locinfo->lconv->negative_sign = cloc_lconv.negative_sign;
            locinfo->lconv->int_frac_digits = cloc_lconv.int_frac_digits;
            locinfo->lconv->frac_digits = cloc_lconv.frac_digits;
            locinfo->lconv->p_cs_precedes = cloc_lconv.p_cs_precedes;
            locinfo->lconv->p_sep_by_space = cloc_lconv.p_sep_by_space;
            locinfo->lconv->n_cs_precedes = cloc_lconv.n_cs_precedes;
            locinfo->lconv->n_sep_by_space = cloc_lconv.n_sep_by_space;
            locinfo->lconv->p_sign_posn = cloc_lconv.p_sign_posn;
            locinfo->lconv->n_sign_posn = cloc_lconv.n_sign_posn;

#if _MSVCR_VER >= 100
            locinfo->lconv->_W_int_curr_symbol = cloc_lconv._W_int_curr_symbol;
            locinfo->lconv->_W_currency_symbol = cloc_lconv._W_currency_symbol;
            locinfo->lconv->_W_mon_decimal_point = cloc_lconv._W_mon_decimal_point;
            locinfo->lconv->_W_mon_thousands_sep = cloc_lconv._W_mon_thousands_sep;
            locinfo->lconv->_W_positive_sign = cloc_lconv._W_positive_sign;
            locinfo->lconv->_W_negative_sign = cloc_lconv._W_negative_sign;
#endif
        }

        if(!init_category_name("C", 1, locinfo, LC_MONETARY)) {
            free_locinfo(locinfo);
            return NULL;
        }
    }

    if(locale_name[LC_NUMERIC] &&
            !init_category_name(locale_name[LC_NUMERIC],
                locale_len[LC_NUMERIC], locinfo, LC_NUMERIC)) {
        free_locinfo(locinfo);
        return NULL;
    }

    if(!category_needs_update(LC_NUMERIC, old_locinfo,
                lcid[LC_NUMERIC], cp[LC_NUMERIC])) {
        copy_threadlocinfo_category(locinfo, old_locinfo, LC_NUMERIC);
        locinfo->lconv_num_refcount = old_locinfo->lconv_num_refcount;
        if(locinfo->lconv_num_refcount)
            InterlockedIncrement(locinfo->lconv_num_refcount);
        if(locinfo->lconv != &cloc_lconv && locinfo->lconv != old_locinfo->lconv) {
            locinfo->lconv->decimal_point = old_locinfo->lconv->decimal_point;
            locinfo->lconv->thousands_sep = old_locinfo->lconv->thousands_sep;
            locinfo->lconv->grouping = old_locinfo->lconv->grouping;
#if _MSVCR_VER >= 100
            locinfo->lconv->_W_decimal_point = old_locinfo->lconv->_W_decimal_point;
            locinfo->lconv->_W_thousands_sep = old_locinfo->lconv->_W_thousands_sep;
#endif
        }
    } else if(lcid[LC_NUMERIC]) {
        if(!update_threadlocinfo_category(lcid[LC_NUMERIC],
                    cp[LC_NUMERIC], locinfo, LC_NUMERIC)) {
            free_locinfo(locinfo);
            return NULL;
        }

        locinfo->lconv_num_refcount = malloc(sizeof(int));
        if(!locinfo->lconv_num_refcount) {
            free_locinfo(locinfo);
            return NULL;
        }

        *locinfo->lconv_num_refcount = 1;

        i = GetLocaleInfoA(lcid[LC_NUMERIC], LOCALE_SDECIMAL
                |LOCALE_NOUSEROVERRIDE, buf, 256);
        if(i && (locinfo->lconv->decimal_point = malloc(i)))
            memcpy(locinfo->lconv->decimal_point, buf, i);
        else {
            free_locinfo(locinfo);
            return NULL;
        }

        i = GetLocaleInfoA(lcid[LC_NUMERIC], LOCALE_STHOUSAND
                |LOCALE_NOUSEROVERRIDE, buf, 256);
        if(i && (locinfo->lconv->thousands_sep = malloc(i)))
            memcpy(locinfo->lconv->thousands_sep, buf, i);
        else {
            free_locinfo(locinfo);
            return NULL;
        }

        i = GetLocaleInfoA(lcid[LC_NUMERIC], LOCALE_SGROUPING
                |LOCALE_NOUSEROVERRIDE, buf, 256);
        if(i>1)
            i = i/2 + (buf[i-2]=='0'?0:1);
        if(i && (locinfo->lconv->grouping = malloc(i))) {
            for(i=0; buf[i+1]==';'; i+=2)
                locinfo->lconv->grouping[i/2] = buf[i]-'0';
            locinfo->lconv->grouping[i/2] = buf[i]-'0';
            if(buf[i] != '0')
                locinfo->lconv->grouping[i/2+1] = 127;
        } else {
            free_locinfo(locinfo);
            return NULL;
        }

#if _MSVCR_VER >= 100
        i = GetLocaleInfoW(lcid[LC_NUMERIC], LOCALE_SDECIMAL
                |LOCALE_NOUSEROVERRIDE, wbuf, 256);
        if(i && (locinfo->lconv->_W_decimal_point = malloc(i * sizeof(wchar_t))))
            memcpy(locinfo->lconv->_W_decimal_point, wbuf, i * sizeof(wchar_t));
        else {
            free_locinfo(locinfo);
            return NULL;
        }

        i = GetLocaleInfoW(lcid[LC_NUMERIC], LOCALE_STHOUSAND
                |LOCALE_NOUSEROVERRIDE, wbuf, 256);
        if(i && (locinfo->lconv->_W_thousands_sep = malloc(i * sizeof(wchar_t))))
            memcpy(locinfo->lconv->_W_thousands_sep, wbuf, i * sizeof(wchar_t));
        else {
            free_locinfo(locinfo);
            return NULL;
        }
#endif
    } else {
        if (locinfo->lconv != &cloc_lconv) {
            locinfo->lconv->decimal_point = cloc_lconv.decimal_point;
            locinfo->lconv->thousands_sep = cloc_lconv.thousands_sep;
            locinfo->lconv->grouping = cloc_lconv.grouping;

#if _MSVCR_VER >= 100
            locinfo->lconv->_W_decimal_point = cloc_lconv._W_decimal_point;
            locinfo->lconv->_W_thousands_sep = cloc_lconv._W_thousands_sep;
#endif
        }

        if (!init_category_name("C", 1, locinfo, LC_NUMERIC)) {
            free_locinfo(locinfo);
            return NULL;
        }
    }

    if(locale_name[LC_TIME] &&
            !init_category_name(locale_name[LC_TIME],
                locale_len[LC_TIME], locinfo, LC_TIME)) {
        free_locinfo(locinfo);
        return NULL;
    }

    if(!category_needs_update(LC_TIME, old_locinfo,
                lcid[LC_TIME], cp[LC_TIME])) {
        copy_threadlocinfo_category(locinfo, old_locinfo, LC_TIME);
        locinfo->lc_time_curr = old_locinfo->lc_time_curr;
        InterlockedIncrement(&locinfo->lc_time_curr->refcount);
    } else if(lcid[LC_TIME]) {
        if(!update_threadlocinfo_category(lcid[LC_TIME],
                    cp[LC_TIME], locinfo, LC_TIME)) {
            free_locinfo(locinfo);
            return NULL;
        }

        locinfo->lc_time_curr = create_time_data(lcid[LC_TIME]);
        if(!locinfo->lc_time_curr) {
            free_locinfo(locinfo);
            return NULL;
        }
    } else {
        if(!init_category_name("C", 1, locinfo, LC_TIME)) {
            free_locinfo(locinfo);
            return NULL;
        }
        locinfo->lc_time_curr = &cloc_time_data;
        InterlockedIncrement(&locinfo->lc_time_curr->refcount);
    }

    return locinfo;
}

/*********************************************************************
 *      _create_locale (MSVCRT.@)
 */
_locale_t CDECL _create_locale(int category, const char *locale)
{
    _locale_t loc;

    loc = malloc(sizeof(_locale_tstruct));
    if(!loc)
        return NULL;

    loc->locinfo = create_locinfo(category, locale, NULL);
    if(!loc->locinfo) {
        free(loc);
        return NULL;
    }

    loc->mbcinfo = create_mbcinfo(loc->locinfo->lc_id[LC_CTYPE].wCodePage,
            loc->locinfo->lc_handle[LC_CTYPE], NULL);
    if(!loc->mbcinfo) {
        free_locinfo(loc->locinfo);
        free(loc);
        return NULL;
    }
    return loc;
}

#if _MSVCR_VER >= 110
/*********************************************************************
 *      _wcreate_locale (MSVCR110.@)
 */
_locale_t CDECL _wcreate_locale(int category, const wchar_t *locale)
{
    _locale_t loc;
    size_t len;
    char *str;

    if(category<LC_MIN || category>LC_MAX || !locale)
        return NULL;

    len = wcstombs(NULL, locale, 0);
    if(len == -1)
        return NULL;
    if(!(str = malloc(++len)))
        return NULL;
    wcstombs(str, locale, len);

    loc = _create_locale(category, str);

    free(str);
    return loc;
}
#endif

/*********************************************************************
 *             setlocale (MSVCRT.@)
 */
char* CDECL setlocale(int category, const char* locale)
{
    thread_data_t *data = msvcrt_get_thread_data();
    pthreadlocinfo locinfo = get_locinfo(), newlocinfo;

    if(category<LC_MIN || category>LC_MAX)
        return NULL;

    if(!locale) {
        if(category == LC_ALL)
            return construct_lc_all(locinfo);

        return locinfo->lc_category[category].locale;
    }

    newlocinfo = create_locinfo(category, locale, locinfo);
    if(!newlocinfo) {
        WARN("%d %s failed\n", category, locale);
        return NULL;
    }

    if(locale[0] != 'C' || locale[1] != '\0')
        initial_locale = FALSE;

    if(data->locale_flags & LOCALE_THREAD)
    {
        if(data->locale_flags & LOCALE_FREE)
            free_locinfo(data->locinfo);
        data->locinfo = newlocinfo;
    }
    else
    {
        int i;

        _lock_locales();
        free_locinfo(MSVCRT_locale->locinfo);
        MSVCRT_locale->locinfo = newlocinfo;

        MSVCRT___lc_codepage = newlocinfo->lc_codepage;
        MSVCRT___lc_collate_cp = newlocinfo->lc_collate_cp;
        MSVCRT___mb_cur_max = newlocinfo->mb_cur_max;
        MSVCRT__pctype = newlocinfo->pctype;
        for(i=LC_MIN; i<=LC_MAX; i++)
            MSVCRT___lc_handle[i] = MSVCRT_locale->locinfo->lc_handle[i];
        _unlock_locales();
        update_thread_locale(data);
    }

    if(category == LC_ALL)
        return construct_lc_all(data->locinfo);

    return data->locinfo->lc_category[category].locale;
}

/*********************************************************************
 *		_wsetlocale (MSVCRT.@)
 */
wchar_t* CDECL _wsetlocale(int category, const wchar_t* wlocale)
{
    static wchar_t current_lc_all[MAX_LOCALE_LENGTH];

    char *locale = NULL;
    const char *ret;
    size_t len;

    if(wlocale) {
        len = wcstombs(NULL, wlocale, 0);
        if(len == -1)
            return NULL;

        locale = malloc(++len);
        if(!locale)
            return NULL;

        wcstombs(locale, wlocale, len);
    }

    _lock_locales();
    ret = setlocale(category, locale);
    free(locale);

    if(ret && mbstowcs(current_lc_all, ret, MAX_LOCALE_LENGTH)==-1)
        ret = NULL;

    _unlock_locales();
    return ret ? current_lc_all : NULL;
}

#if _MSVCR_VER >= 80
/*********************************************************************
 *		_configthreadlocale (MSVCR80.@)
 */
int CDECL _configthreadlocale(int type)
{
    thread_data_t *data = msvcrt_get_thread_data();
    int ret;

    ret = (data->locale_flags & LOCALE_THREAD ? _ENABLE_PER_THREAD_LOCALE :
            _DISABLE_PER_THREAD_LOCALE);

    if(type == _ENABLE_PER_THREAD_LOCALE)
        data->locale_flags |= LOCALE_THREAD;
    else if(type == _DISABLE_PER_THREAD_LOCALE)
        data->locale_flags &= ~LOCALE_THREAD;
    else if(type)
        ret = -1;

    return ret;
}
#endif

BOOL msvcrt_init_locale(void)
{
    int i;

    _lock_locales();
    MSVCRT_locale = _create_locale(0, "C");
    _unlock_locales();
    if(!MSVCRT_locale)
        return FALSE;

    MSVCRT___lc_codepage = MSVCRT_locale->locinfo->lc_codepage;
    MSVCRT___lc_collate_cp = MSVCRT_locale->locinfo->lc_collate_cp;
    MSVCRT___mb_cur_max = MSVCRT_locale->locinfo->mb_cur_max;
    MSVCRT__pctype = MSVCRT_locale->locinfo->pctype;
    for(i=LC_MIN; i<=LC_MAX; i++)
        MSVCRT___lc_handle[i] = MSVCRT_locale->locinfo->lc_handle[i];
    _setmbcp(_MB_CP_ANSI);
    return TRUE;
}

#if _MSVCR_VER >= 120
/*********************************************************************
 *      wctrans (MSVCR120.@)
 */
wctrans_t CDECL wctrans(const char *property)
{
    static const char str_tolower[] = "tolower";
    static const char str_toupper[] = "toupper";

    if(!strcmp(property, str_tolower))
        return 2;
    if(!strcmp(property, str_toupper))
        return 1;
    return 0;
}
#endif