/*
 * msvcrt.dll mbcs functions
 *
 * Copyright 1999 Alexandre Julliard
 * Copyright 2000 Jon Griffths
 *
 * 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
 *
 * FIXME
 * Not currently binary compatible with win32. MSVCRT_mbctype must be
 * populated correctly and the ismb* functions should reference it.
 */

#include <precomp.h>

#include <mbctype.h>

/* It seems that the data about valid trail bytes is not available from kernel32
 * so we have to store is here. The format is the same as for lead bytes in CPINFO */
struct cp_extra_info_t
{
    int cp;
    BYTE TrailBytes[MAX_LEADBYTES];
};

static struct cp_extra_info_t g_cpextrainfo[] =
{
    {932, {0x40, 0x7e, 0x80, 0xfc, 0, 0}},
    {936, {0x40, 0xfe, 0, 0}},
    {949, {0x41, 0xfe, 0, 0}},
    {950, {0x40, 0x7e, 0xa1, 0xfe, 0, 0}},
    {1361, {0x31, 0x7e, 0x81, 0xfe, 0, 0}},
    {20932, {1, 255, 0, 0}},  /* seems to give different results on different systems */
    {0, {1, 255, 0, 0}}       /* match all with FIXME */
};

/*********************************************************************
 * INTERNAL: _setmbcp_l
 */
int _setmbcp_l(int cp, LCID lcid, MSVCRT_pthreadmbcinfo mbcinfo)
{
  const char format[] = ".%d";

  int newcp;
  CPINFO cpi;
  BYTE *bytes;
  WORD chartypes[256];
  char bufA[256];
  WCHAR bufW[256];
  int charcount;
  int ret;
  int i;

  if(!mbcinfo)
      mbcinfo = get_mbcinfo();

  switch (cp)
  {
    case _MB_CP_ANSI:
      newcp = GetACP();
      break;
    case _MB_CP_OEM:
      newcp = GetOEMCP();
      break;
    case _MB_CP_LOCALE:
      newcp = get_locinfo()->lc_codepage;
      if(newcp)
          break;
      /* fall through (C locale) */
    case _MB_CP_SBCS:
      newcp = 20127;   /* ASCII */
      break;
    default:
      newcp = cp;
      break;
  }

  if(lcid == -1) {
    sprintf(bufA, format, newcp);
    mbcinfo->mblcid = MSVCRT_locale_to_LCID(bufA, NULL);
  } else {
    mbcinfo->mblcid = lcid;
  }

  if(mbcinfo->mblcid == -1)
  {
    WARN("Can't assign LCID to codepage (%d)\n", mbcinfo->mblcid);
    mbcinfo->mblcid = 0;
  }

  if (!GetCPInfo(newcp, &cpi))
  {
    WARN("Codepage %d not found\n", newcp);
    *_errno() = EINVAL;
    return -1;
  }

  /* setup the _mbctype */
  memset(mbcinfo->mbctype, 0, sizeof(unsigned char[257]));
  memset(mbcinfo->mbcasemap, 0, sizeof(unsigned char[256]));

  bytes = cpi.LeadByte;
  while (bytes[0] || bytes[1])
  {
    for (i = bytes[0]; i <= bytes[1]; i++)
      mbcinfo->mbctype[i + 1] |= _M1;
    bytes += 2;
  }

  if (cpi.MaxCharSize > 1)
  {
    /* trail bytes not available through kernel32 but stored in a structure in msvcrt */
    struct cp_extra_info_t *cpextra = g_cpextrainfo;

    mbcinfo->ismbcodepage = 1;
    while (TRUE)
    {
      if (cpextra->cp == 0 || cpextra->cp == newcp)
      {
        if (cpextra->cp == 0)
          FIXME("trail bytes data not available for DBCS codepage %d - assuming all bytes\n", newcp);

        bytes = cpextra->TrailBytes;
        while (bytes[0] || bytes[1])
        {
          for (i = bytes[0]; i <= bytes[1]; i++)
            mbcinfo->mbctype[i + 1] |= _M2;
          bytes += 2;
        }
        break;
      }
      cpextra++;
    }
  }
  else
    mbcinfo->ismbcodepage = 0;

  /* we can't use GetStringTypeA directly because we don't have a locale - only a code page
   */
  charcount = 0;
  for (i = 0; i < 256; i++)
    if (!(mbcinfo->mbctype[i + 1] & _M1))
      bufA[charcount++] = i;

  ret = MultiByteToWideChar(newcp, 0, bufA, charcount, bufW, charcount);
  if (ret != charcount)
    ERR("MultiByteToWideChar of chars failed for cp %d, ret=%d (exp %d), error=%d\n", newcp, ret, charcount, GetLastError());

  GetStringTypeW(CT_CTYPE1, bufW, charcount, chartypes);

  charcount = 0;
  for (i = 0; i < 256; i++)
    if (!(mbcinfo->mbctype[i + 1] & _M1))
    {
      if (chartypes[charcount] & C1_UPPER)
      {
        mbcinfo->mbctype[i + 1] |= _SBUP;
        bufW[charcount] = tolowerW(bufW[charcount]);
      }
      else if (chartypes[charcount] & C1_LOWER)
      {
	mbcinfo->mbctype[i + 1] |= _SBLOW;
        bufW[charcount] = toupperW(bufW[charcount]);
      }
      charcount++;
    }

  ret = WideCharToMultiByte(newcp, 0, bufW, charcount, bufA, charcount, NULL, NULL);
  if (ret != charcount)
    ERR("WideCharToMultiByte failed for cp %d, ret=%d (exp %d), error=%d\n", newcp, ret, charcount, GetLastError());

  charcount = 0;
  for (i = 0; i < 256; i++)
  {
    if(!(mbcinfo->mbctype[i + 1] & _M1))
    {
      if(mbcinfo->mbctype[i] & (C1_UPPER|C1_LOWER))
        mbcinfo->mbcasemap[i] = bufA[charcount];
      charcount++;
    }
  }

  if (newcp == 932)   /* CP932 only - set _MP and _MS */
  {
    /* On Windows it's possible to calculate the _MP and _MS from CT_CTYPE1
     * and CT_CTYPE3. But as of Wine 0.9.43 we return wrong values what makes
     * it hard. As this is set only for codepage 932 we hardcode it what gives
     * also faster execution.
     */
    for (i = 161; i <= 165; i++)
      mbcinfo->mbctype[i + 1] |= _MP;
    for (i = 166; i <= 223; i++)
      mbcinfo->mbctype[i + 1] |= _MS;
  }

  mbcinfo->mbcodepage = newcp;
  if(global_locale && mbcinfo == MSVCRT_locale->mbcinfo)
    memcpy(_mbctype, MSVCRT_locale->mbcinfo->mbctype, sizeof(_mbctype));

  return 0;
}

/*********************************************************************
 *              _setmbcp (MSVCRT.@)
 */
int CDECL _setmbcp(int cp)
{
    return _setmbcp_l(cp, -1, NULL);
}