/*
 * String manipulation functions
 *
 * Copyright 1998 Eric Kohl
 *           1998 Juergen Schmied <j.schmied@metronet.de>
 *           2000 Eric Kohl for CodeWeavers
 * Copyright 2002 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include <stdarg.h>
#include <string.h>
#include <stdlib.h> /* atoi */

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

#include "wine/unicode.h"

#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(commctrl);

/*************************************************************************
 * COMCTL32_ChrCmpHelperA
 *
 * Internal helper for ChrCmpA/COMCTL32_ChrCmpIA.
 *
 * NOTES
 *  Both this function and its Unicode counterpart are very inneficient. To
 *  fix this, CompareString must be completely implemented and optimised
 *  first. Then the core character test can be taken out of that function and
 *  placed here, so that it need never be called at all. Until then, do not
 *  attempt to optimise this code unless you are willing to test that it
 *  still performs correctly.
 */
static BOOL COMCTL32_ChrCmpHelperA(WORD ch1, WORD ch2, DWORD dwFlags)
{
  char str1[3], str2[3];

  str1[0] = LOBYTE(ch1);
  if (IsDBCSLeadByte(str1[0]))
  {
    str1[1] = HIBYTE(ch1);
    str1[2] = '\0';
  }
  else
    str1[1] = '\0';

  str2[0] = LOBYTE(ch2);
  if (IsDBCSLeadByte(str2[0]))
  {
    str2[1] = HIBYTE(ch2);
    str2[2] = '\0';
  }
  else
    str2[1] = '\0';

  return CompareStringA(GetThreadLocale(), dwFlags, str1, -1, str2, -1) - 2;
}

/*************************************************************************
 * COMCTL32_ChrCmpHelperW
 *
 * Internal helper for COMCTL32_ChrCmpW/ChrCmpIW.
 */
static BOOL COMCTL32_ChrCmpHelperW(WCHAR ch1, WCHAR ch2, DWORD dwFlags)
{
  WCHAR str1[2], str2[2];

  str1[0] = ch1;
  str1[1] = '\0';
  str2[0] = ch2;
  str2[1] = '\0';
  return CompareStringW(GetThreadLocale(), dwFlags, str1, 2, str2, 2) - 2;
}

/*************************************************************************
 * COMCTL32_ChrCmpA (internal)
 *
 * Internal helper function.
 */
static BOOL COMCTL32_ChrCmpA(WORD ch1, WORD ch2)
{
  return COMCTL32_ChrCmpHelperA(ch1, ch2, 0);
}

/*************************************************************************
 * COMCTL32_ChrCmpIA	(internal)
 *
 * Compare two characters, ignoring case.
 *
 * PARAMS
 *  ch1 [I] First character to compare
 *  ch2 [I] Second character to compare
 *
 * RETURNS
 *  FALSE, if the characters are equal.
 *  Non-zero otherwise.
 */
static BOOL COMCTL32_ChrCmpIA(WORD ch1, WORD ch2)
{
  TRACE("(%d,%d)\n", ch1, ch2);

  return COMCTL32_ChrCmpHelperA(ch1, ch2, NORM_IGNORECASE);
}

/*************************************************************************
 * COMCTL32_ChrCmpW
 *
 * Internal helper function.
 */
static BOOL COMCTL32_ChrCmpW(WCHAR ch1, WCHAR ch2)
{
  return COMCTL32_ChrCmpHelperW(ch1, ch2, 0);
}

/*************************************************************************
 * COMCTL32_ChrCmpIW
 *
 * Internal helper function.
 */
static BOOL COMCTL32_ChrCmpIW(WCHAR ch1, WCHAR ch2)
{
  return COMCTL32_ChrCmpHelperW(ch1, ch2, NORM_IGNORECASE);
}

/**************************************************************************
 * StrChrA [COMCTL32.350]
 *
 * Find a given character in a string.
 *
 * PARAMS
 *  lpszStr [I] String to search in.
 *  ch      [I] Character to search for.
 *
 * RETURNS
 *  Success: A pointer to the first occurrence of ch in lpszStr, or NULL if
 *           not found.
 *  Failure: NULL, if any arguments are invalid.
 */
LPSTR WINAPI StrChrA(LPCSTR lpszStr, WORD ch)
{
  TRACE("(%s,%i)\n", debugstr_a(lpszStr), ch);

  if (lpszStr)
  {
    while (*lpszStr)
    {
      if (!COMCTL32_ChrCmpA(*lpszStr, ch))
        return (LPSTR)lpszStr;
      lpszStr = CharNextA(lpszStr);
    }
  }
  return NULL;
}

/*************************************************************************
 * COMCTL32_StrStrHelperA
 *
 * Internal implementation of StrStrA/StrStrIA
 */
static LPSTR COMCTL32_StrStrHelperA(LPCSTR lpszStr, LPCSTR lpszSearch,
                                    int (*pStrCmpFn)(LPCSTR,LPCSTR,size_t))
{
  size_t iLen;

  if (!lpszStr || !lpszSearch || !*lpszSearch)
    return NULL;

  iLen = strlen(lpszSearch);

  while (*lpszStr)
  {
    if (!pStrCmpFn(lpszStr, lpszSearch, iLen))
      return (LPSTR)lpszStr;
    lpszStr = CharNextA(lpszStr);
  }
  return NULL;
}

/*************************************************************************
 * COMCTL32_StrStrHelperW
 *
 * Internal implementation of StrStrW/StrStrIW
 */
static LPWSTR COMCTL32_StrStrHelperW(LPCWSTR lpszStr, LPCWSTR lpszSearch,
                                     int (*pStrCmpFn)(LPCWSTR,LPCWSTR,int))
{
  int iLen;

  if (!lpszStr || !lpszSearch || !*lpszSearch)
    return NULL;

  iLen = strlenW(lpszSearch);

  while (*lpszStr)
  {
    if (!pStrCmpFn(lpszStr, lpszSearch, iLen))
      return (LPWSTR)lpszStr;
    lpszStr = CharNextW(lpszStr);
  }
  return NULL;
}

/**************************************************************************
 * StrStrIA [COMCTL32.355]
 *
 * Find a substring within a string, ignoring case.
 *
 * PARAMS
 *  lpszStr    [I] String to search in
 *  lpszSearch [I] String to look for
 *
 * RETURNS
 *  The start of lpszSearch within lpszStr, or NULL if not found.
 */
LPSTR WINAPI StrStrIA(LPCSTR lpszStr, LPCSTR lpszSearch)
{
  TRACE("(%s,%s)\n", debugstr_a(lpszStr), debugstr_a(lpszSearch));

  return COMCTL32_StrStrHelperA(lpszStr, lpszSearch, strncasecmp);
}

/**************************************************************************
 * StrToIntA [COMCTL32.357]
 *
 * Read a signed integer from a string.
 *
 * PARAMS
 *  lpszStr [I] String to read integer from
 *
 * RETURNS
 *   The signed integer value represented by the string, or 0 if no integer is
 *   present.
 */
INT WINAPI StrToIntA (LPSTR lpszStr)
{
    return atoi(lpszStr);
}

/**************************************************************************
 * StrStrIW [COMCTL32.363]
 *
 * See StrStrIA.
 */
LPWSTR WINAPI StrStrIW(LPCWSTR lpszStr, LPCWSTR lpszSearch)
{
  TRACE("(%s,%s)\n", debugstr_w(lpszStr), debugstr_w(lpszSearch));

  return COMCTL32_StrStrHelperW(lpszStr, lpszSearch, strncmpiW);
}

/**************************************************************************
 * StrToIntW [COMCTL32.365]
 *
 * See StrToIntA.
 */
INT WINAPI StrToIntW (LPWSTR lpString)
{
    return atoiW(lpString);
}

/*************************************************************************
 * COMCTL32_StrSpnHelperA (internal)
 *
 * Internal implementation of StrSpnA/StrCSpnA/StrCSpnIA
 */
static int COMCTL32_StrSpnHelperA(LPCSTR lpszStr, LPCSTR lpszMatch,
                                  LPSTR (WINAPI *pStrChrFn)(LPCSTR,WORD),
                                  BOOL bInvert)
{
  LPCSTR lpszRead = lpszStr;
  if (lpszStr && *lpszStr && lpszMatch)
  {
    while (*lpszRead)
    {
      LPCSTR lpszTest = pStrChrFn(lpszMatch, *lpszRead);

      if (!bInvert && !lpszTest)
        break;
      if (bInvert && lpszTest)
        break;
      lpszRead = CharNextA(lpszRead);
    };
  }
  return lpszRead - lpszStr;
}

/**************************************************************************
 * StrCSpnA [COMCTL32.356]
 *
 * Find the length of the start of a string that does not contain certain
 * characters.
 *
 * PARAMS
 *  lpszStr   [I] String to search
 *  lpszMatch [I] Characters that cannot be in the substring
 *
 * RETURNS
 *  The length of the part of lpszStr containing only chars not in lpszMatch,
 *  or 0 if any parameter is invalid.
 */
int WINAPI StrCSpnA(LPCSTR lpszStr, LPCSTR lpszMatch)
{
  TRACE("(%s,%s)\n",debugstr_a(lpszStr), debugstr_a(lpszMatch));

  return COMCTL32_StrSpnHelperA(lpszStr, lpszMatch, StrChrA, TRUE);
}

/**************************************************************************
 * StrChrW [COMCTL32.358]
 *
 * See StrChrA.
 */
LPWSTR WINAPI StrChrW(LPCWSTR lpszStr, WCHAR ch)
{
  LPWSTR lpszRet = NULL;

  TRACE("(%s,%i)\n", debugstr_w(lpszStr), ch);

  if (lpszStr)
    lpszRet = strchrW(lpszStr, ch);
  return lpszRet;
}

/**************************************************************************
 * StrCmpNA [COMCTL32.352]
 *
 * Compare two strings, up to a maximum length.
 *
 * PARAMS
 *  lpszStr  [I] First string to compare
 *  lpszComp [I] Second string to compare
 *  iLen     [I] Maximum number of chars to compare.
 *
 * RETURNS
 *  An integer less than, equal to or greater than 0, indicating that
 *  lpszStr is less than, the same, or greater than lpszComp.
 */
INT WINAPI StrCmpNA(LPCSTR lpszStr, LPCSTR lpszComp, INT iLen)
{
  INT iRet;

  TRACE("(%s,%s,%i)\n", debugstr_a(lpszStr), debugstr_a(lpszComp), iLen);

  iRet = CompareStringA(GetThreadLocale(), 0, lpszStr, iLen, lpszComp, iLen);
  return iRet == CSTR_LESS_THAN ? -1 : iRet == CSTR_GREATER_THAN ? 1 : 0;
}

/**************************************************************************
 * StrCmpNIA [COMCTL32.353]
 *
 * Compare two strings, up to a maximum length, ignoring case.
 *
 * PARAMS
 *  lpszStr  [I] First string to compare
 *  lpszComp [I] Second string to compare
 *  iLen     [I] Maximum number of chars to compare.
 *
 * RETURNS
 *  An integer less than, equal to or greater than 0, indicating that
 *  lpszStr is less than, the same, or greater than lpszComp.
 */
int WINAPI StrCmpNIA(LPCSTR lpszStr, LPCSTR lpszComp, int iLen)
{
  INT iRet;

  TRACE("(%s,%s,%i)\n", debugstr_a(lpszStr), debugstr_a(lpszComp), iLen);

  iRet = CompareStringA(GetThreadLocale(), NORM_IGNORECASE, lpszStr, iLen, lpszComp, iLen);
  return iRet == CSTR_LESS_THAN ? -1 : iRet == CSTR_GREATER_THAN ? 1 : 0;
}

/*************************************************************************
 * StrCmpNIW	[COMCTL32.361]
 *
 * See StrCmpNIA.
 */
INT WINAPI StrCmpNIW(LPCWSTR lpszStr, LPCWSTR lpszComp, int iLen)
{
  INT iRet;

  TRACE("(%s,%s,%i)\n", debugstr_w(lpszStr), debugstr_w(lpszComp), iLen);

  iRet = CompareStringW(GetThreadLocale(), NORM_IGNORECASE, lpszStr, iLen, lpszComp, iLen);
  return iRet == CSTR_LESS_THAN ? -1 : iRet == CSTR_GREATER_THAN ? 1 : 0;
}

/**************************************************************************
 * StrCmpNW [COMCTL32.360]
 *
 * See StrCmpNA.
 */
INT WINAPI StrCmpNW(LPCWSTR lpszStr, LPCWSTR lpszComp, INT iLen)
{
  INT iRet;

  TRACE("(%s,%s,%i)\n", debugstr_w(lpszStr), debugstr_w(lpszComp), iLen);

  iRet = CompareStringW(GetThreadLocale(), 0, lpszStr, iLen, lpszComp, iLen);
  return iRet == CSTR_LESS_THAN ? -1 : iRet == CSTR_GREATER_THAN ? 1 : 0;
}

/**************************************************************************
 * StrRChrA [COMCTL32.351]
 *
 * Find the last occurrence of a character in string.
 *
 * PARAMS
 *  lpszStr [I] String to search in
 *  lpszEnd [I] Place to end search, or NULL to search until the end of lpszStr
 *  ch      [I] Character to search for.
 *
 * RETURNS
 *  Success: A pointer to the last occurrence of ch in lpszStr before lpszEnd,
 *           or NULL if not found.
 *  Failure: NULL, if any arguments are invalid.
 */
LPSTR WINAPI StrRChrA(LPCSTR lpszStr, LPCSTR lpszEnd, WORD ch)
{
  LPCSTR lpszRet = NULL;

  TRACE("(%s,%s,%x)\n", debugstr_a(lpszStr), debugstr_a(lpszEnd), ch);

  if (lpszStr)
  {
    WORD ch2;

    if (!lpszEnd)
      lpszEnd = lpszStr + lstrlenA(lpszStr);

    while (*lpszStr && lpszStr <= lpszEnd)
    {
      ch2 = IsDBCSLeadByte(*lpszStr)? *lpszStr << 8 | lpszStr[1] : *lpszStr;

      if (!COMCTL32_ChrCmpA(ch, ch2))
        lpszRet = lpszStr;
      lpszStr = CharNextA(lpszStr);
    }
  }
  return (LPSTR)lpszRet;
}


/**************************************************************************
 * StrRChrW [COMCTL32.359]
 *
 * See StrRChrA.
 */
LPWSTR WINAPI StrRChrW(LPCWSTR lpszStr, LPCWSTR lpszEnd, WORD ch)
{
  LPCWSTR lpszRet = NULL;

  TRACE("(%s,%s,%x)\n", debugstr_w(lpszStr), debugstr_w(lpszEnd), ch);

  if (lpszStr)
  {
    if (!lpszEnd)
      lpszEnd = lpszStr + strlenW(lpszStr);

    while (*lpszStr && lpszStr <= lpszEnd)
    {
      if (!COMCTL32_ChrCmpW(ch, *lpszStr))
        lpszRet = lpszStr;
      lpszStr = CharNextW(lpszStr);
    }
  }
  return (LPWSTR)lpszRet;
}

/**************************************************************************
 * StrStrA [COMCTL32.354]
 *
 * Find a substring within a string.
 *
 * PARAMS
 *  lpszStr    [I] String to search in
 *  lpszSearch [I] String to look for
 *
 * RETURNS
 *  The start of lpszSearch within lpszStr, or NULL if not found.
 */
LPSTR WINAPI StrStrA(LPCSTR lpszStr, LPCSTR lpszSearch)
{
  TRACE("(%s,%s)\n", debugstr_a(lpszStr), debugstr_a(lpszSearch));

  return COMCTL32_StrStrHelperA(lpszStr, lpszSearch, strncmp);
}

/**************************************************************************
 * StrStrW [COMCTL32.362]
 *
 * See StrStrA.
 */
LPWSTR WINAPI StrStrW(LPCWSTR lpszStr, LPCWSTR lpszSearch)
{
  TRACE("(%s,%s)\n", debugstr_w(lpszStr), debugstr_w(lpszSearch));

  return COMCTL32_StrStrHelperW(lpszStr, lpszSearch, strncmpW);
}

/*************************************************************************
 * StrChrIA	[COMCTL32.366]
 *
 * Find a given character in a string, ignoring case.
 *
 * PARAMS
 *  lpszStr [I] String to search in.
 *  ch      [I] Character to search for.
 *
 * RETURNS
 *  Success: A pointer to the first occurrence of ch in lpszStr, or NULL if
 *           not found.
 *  Failure: NULL, if any arguments are invalid.
 */
LPSTR WINAPI StrChrIA(LPCSTR lpszStr, WORD ch)
{
  TRACE("(%s,%i)\n", debugstr_a(lpszStr), ch);

  if (lpszStr)
  {
    while (*lpszStr)
    {
      if (!COMCTL32_ChrCmpIA(*lpszStr, ch))
        return (LPSTR)lpszStr;
      lpszStr = CharNextA(lpszStr);
    }
  }
  return NULL;
}

/*************************************************************************
 * StrChrIW	[COMCTL32.367]
 *
 * See StrChrA.
 */
LPWSTR WINAPI StrChrIW(LPCWSTR lpszStr, WCHAR ch)
{
  TRACE("(%s,%i)\n", debugstr_w(lpszStr), ch);

  if (lpszStr)
  {
    ch = toupperW(ch);
    while (*lpszStr)
    {
      if (toupperW(*lpszStr) == ch)
        return (LPWSTR)lpszStr;
      lpszStr = CharNextW(lpszStr);
    }
    lpszStr = NULL;
  }
  return (LPWSTR)lpszStr;
}

/*************************************************************************
 * StrRStrIA	[COMCTL32.372]
 *
 * Find the last occurrence of a substring within a string.
 *
 * PARAMS
 *  lpszStr    [I] String to search in
 *  lpszEnd    [I] End of lpszStr
 *  lpszSearch [I] String to look for
 *
 * RETURNS
 *  The last occurrence lpszSearch within lpszStr, or NULL if not found.
 */
LPSTR WINAPI StrRStrIA(LPCSTR lpszStr, LPCSTR lpszEnd, LPCSTR lpszSearch)
{
  LPSTR lpszRet = NULL;
  WORD ch1, ch2;
  INT iLen;
 
  TRACE("(%s,%s)\n", debugstr_a(lpszStr), debugstr_a(lpszSearch));
 
  if (!lpszStr || !lpszSearch || !*lpszSearch)
    return NULL;

  if (!lpszEnd)
    lpszEnd = lpszStr + lstrlenA(lpszStr);

  if (IsDBCSLeadByte(*lpszSearch))
    ch1 = *lpszSearch << 8 | lpszSearch[1];
  else
    ch1 = *lpszSearch;
  iLen = lstrlenA(lpszSearch);

  while (lpszStr <= lpszEnd  && *lpszStr)
  {
    ch2 = IsDBCSLeadByte(*lpszStr)? *lpszStr << 8 | lpszStr[1] : *lpszStr;
    if (!COMCTL32_ChrCmpIA(ch1, ch2))
    {
      if (!StrCmpNIA(lpszStr, lpszSearch, iLen))
        lpszRet = (LPSTR)lpszStr;
    }
    lpszStr = CharNextA(lpszStr);
  }
  return lpszRet;
}

/*************************************************************************
 * StrRStrIW	[COMCTL32.373]
 *
 * See StrRStrIA.
 */
LPWSTR WINAPI StrRStrIW(LPCWSTR lpszStr, LPCWSTR lpszEnd, LPCWSTR lpszSearch)
{
  LPWSTR lpszRet = NULL;
  INT iLen;

  TRACE("(%s,%s)\n", debugstr_w(lpszStr), debugstr_w(lpszSearch));

  if (!lpszStr || !lpszSearch || !*lpszSearch)
    return NULL;

  if (!lpszEnd)
    lpszEnd = lpszStr + strlenW(lpszStr);

  iLen = strlenW(lpszSearch);

  while (lpszStr <= lpszEnd  && *lpszStr)
  {
    if (!COMCTL32_ChrCmpIW(*lpszSearch, *lpszStr))
    {
      if (!StrCmpNIW(lpszStr, lpszSearch, iLen))
        lpszRet = (LPWSTR)lpszStr;
    }
    lpszStr = CharNextW(lpszStr);
  }
  return lpszRet;
}

/*************************************************************************
 * COMCTL32_StrSpnHelperW
 *
 * Internal implementation of StrSpnW/StrCSpnW/StrCSpnIW
 */
static int COMCTL32_StrSpnHelperW(LPCWSTR lpszStr, LPCWSTR lpszMatch,
                                  LPWSTR (WINAPI *pStrChrFn)(LPCWSTR,WCHAR),
                                  BOOL bInvert)
{
  LPCWSTR lpszRead = lpszStr;
  if (lpszStr && *lpszStr && lpszMatch)
  {
    while (*lpszRead)
    {
      LPCWSTR lpszTest = pStrChrFn(lpszMatch, *lpszRead);

      if (!bInvert && !lpszTest)
        break;
      if (bInvert && lpszTest)
        break;
      lpszRead = CharNextW(lpszRead);
    };
  }
  return lpszRead - lpszStr;
}

/*************************************************************************
 * StrCSpnIA	[COMCTL32.374]
 *
 * Find the length of the start of a string that does not contain certain
 * characters, ignoring case.
 *
 * PARAMS
 *  lpszStr   [I] String to search
 *  lpszMatch [I] Characters that cannot be in the substring
 *
 * RETURNS
 *  The length of the part of lpszStr containing only chars not in lpszMatch,
 *  or 0 if any parameter is invalid.
 */
int WINAPI StrCSpnIA(LPCSTR lpszStr, LPCSTR lpszMatch)
{
  TRACE("(%s,%s)\n",debugstr_a(lpszStr), debugstr_a(lpszMatch));

  return COMCTL32_StrSpnHelperA(lpszStr, lpszMatch, StrChrIA, TRUE);
}

/*************************************************************************
 * StrCSpnIW	[COMCTL32.375]
 *
 * See StrCSpnIA.
 */
int WINAPI StrCSpnIW(LPCWSTR lpszStr, LPCWSTR lpszMatch)
{
  TRACE("(%s,%s)\n",debugstr_w(lpszStr), debugstr_w(lpszMatch));

  return COMCTL32_StrSpnHelperW(lpszStr, lpszMatch, StrChrIW, TRUE);
}

/**************************************************************************
 * StrRChrIA	[COMCTL32.368]
 *
 * Find the last occurrence of a character in string, ignoring case.
 *
 * PARAMS
 *  lpszStr [I] String to search in
 *  lpszEnd [I] Place to end search, or NULL to search until the end of lpszStr
 *  ch      [I] Character to search for.
 *
 * RETURNS
 *  Success: A pointer to the last occurrence of ch in lpszStr before lpszEnd,
 *           or NULL if not found.
 *  Failure: NULL, if any arguments are invalid.
 */
LPSTR WINAPI StrRChrIA(LPCSTR lpszStr, LPCSTR lpszEnd, WORD ch)
{
  LPCSTR lpszRet = NULL;

  TRACE("(%s,%s,%x)\n", debugstr_a(lpszStr), debugstr_a(lpszEnd), ch);

  if (lpszStr)
  {
    WORD ch2;

    if (!lpszEnd)
      lpszEnd = lpszStr + lstrlenA(lpszStr);

    while (*lpszStr && lpszStr <= lpszEnd)
    {
      ch2 = IsDBCSLeadByte(*lpszStr)? *lpszStr << 8 | lpszStr[1] : *lpszStr;

      if (ch == ch2)
        lpszRet = lpszStr;
      lpszStr = CharNextA(lpszStr);
    }
  }
  return (LPSTR)lpszRet;
}

/**************************************************************************
 * StrRChrIW	[COMCTL32.369]
 *
 * See StrRChrIA.
 */
LPWSTR WINAPI StrRChrIW(LPCWSTR lpszStr, LPCWSTR lpszEnd, WORD ch)
{
  LPCWSTR lpszRet = NULL;

  TRACE("(%s,%s,%x)\n", debugstr_w(lpszStr), debugstr_w(lpszEnd), ch);

  if (lpszStr)
  {
    if (!lpszEnd)
      lpszEnd = lpszStr + strlenW(lpszStr);

    while (*lpszStr && lpszStr <= lpszEnd)
    {
      if (ch == *lpszStr)
        lpszRet = lpszStr;
      lpszStr = CharNextW(lpszStr);
    }
  }
  return (LPWSTR)lpszRet;
}

/*************************************************************************
 * StrCSpnW	[COMCTL32.364]
 *
 * See StrCSpnA.
 */
int WINAPI StrCSpnW(LPCWSTR lpszStr, LPCWSTR lpszMatch)
{
  TRACE("(%s,%s)\n",debugstr_w(lpszStr), debugstr_w(lpszMatch));

  return COMCTL32_StrSpnHelperW(lpszStr, lpszMatch, StrChrW, TRUE);
}

/*************************************************************************
 * IntlStrEqWorkerA	[COMCTL32.376]
 *
 * Compare two strings.
 *
 * PARAMS
 *  bCase    [I] Whether to compare case sensitively
 *  lpszStr  [I] First string to compare
 *  lpszComp [I] Second string to compare
 *  iLen     [I] Length to compare
 *
 * RETURNS
 *  TRUE  If the strings are equal.
 *  FALSE Otherwise.
 */
BOOL WINAPI IntlStrEqWorkerA(BOOL bCase, LPCSTR lpszStr, LPCSTR lpszComp,
                             int iLen)
{
  DWORD dwFlags = LOCALE_USE_CP_ACP;
  int iRet;

  TRACE("(%d,%s,%s,%d)\n", bCase,
        debugstr_a(lpszStr), debugstr_a(lpszComp), iLen);

  /* FIXME: These flags are undocumented and unknown by our CompareString.
   *        We need defines for them.
   */
  dwFlags |= bCase ? 0x10000000 : 0x10000001;

  iRet = CompareStringA(GetThreadLocale(),
                        dwFlags, lpszStr, iLen, lpszComp, iLen);

  if (!iRet)
    iRet = CompareStringA(2048, dwFlags, lpszStr, iLen, lpszComp, iLen);

  return iRet == 2 ? TRUE : FALSE;
}

/*************************************************************************
 * IntlStrEqWorkerW	[COMCTL32.377]
 *
 * See IntlStrEqWorkerA.
 */
BOOL WINAPI IntlStrEqWorkerW(BOOL bCase, LPCWSTR lpszStr, LPCWSTR lpszComp,
                             int iLen)
{
  DWORD dwFlags;
  int iRet;

  TRACE("(%d,%s,%s,%d)\n", bCase,
        debugstr_w(lpszStr),debugstr_w(lpszComp), iLen);

  /* FIXME: These flags are undocumented and unknown by our CompareString.
   *        We need defines for them.
   */
  dwFlags = bCase ? 0x10000000 : 0x10000001;

  iRet = CompareStringW(GetThreadLocale(),
                        dwFlags, lpszStr, iLen, lpszComp, iLen);

  if (!iRet)
    iRet = CompareStringW(2048, dwFlags, lpszStr, iLen, lpszComp, iLen);

  return iRet == 2 ? TRUE : FALSE;
}