//============================================================================
//
// DBCS aware string routines...
//
//
//============================================================================

//#if defined(UNIX) && !defined(UNICODE)
//#define UNICODE
//#endif


#include "ctlspriv.h"

#ifdef WINNT
#include <winnlsp.h>    // Get private NORM_ flag for StrEqIntl()
#endif

// for those of us who don't ssync to nt's build headers
#ifndef NORM_STOP_ON_NULL
#define NORM_STOP_ON_NULL   0x10000000
#endif

// WARNING: all of these APIs do not setup DS, so you can not access
// any data in the default data seg of this DLL.
//
// do not create any global variables... talk to chrisg if you don't
// understand thid

#ifdef UNIX

#ifdef BIG_ENDIAN
#define READNATIVEWORD(x) MAKEWORD(*(char*)(x), *(char*)((char*)(x) + 1))
#else
#define READNATIVEWORD(x) MAKEWORD(*(char*)((char*)(x) + 1), *(char*)(x))
#endif

#else

#define READNATIVEWORD(x) (*(UNALIGNED WORD *)x)

#endif


/*
 * StrEndN - Find the end of a string, but no more than n bytes
 * Assumes   lpStart points to start of null terminated string
 *           nBufSize is the maximum length
 * returns ptr to just after the last byte to be included
 */
LPSTR lstrfns_StrEndNA(LPCSTR lpStart, int nBufSize)
{
  LPCSTR lpEnd;

  for (lpEnd = lpStart + nBufSize; *lpStart && OFFSETOF(lpStart) < OFFSETOF(lpEnd);
	lpStart = AnsiNext(lpStart))
    continue;   /* just getting to the end of the string */
  if (OFFSETOF(lpStart) > OFFSETOF(lpEnd))
    {
      /* We can only get here if the last byte before lpEnd was a lead byte
       */
      lpStart -= 2;
    }
  return((LPSTR)lpStart);
}

LPWSTR lstrfns_StrEndNW(LPCWSTR lpStart, int nBufSize)
{
#ifdef UNICODE
  LPCWSTR lpEnd;

  for (lpEnd = lpStart + nBufSize; *lpStart && (lpStart < lpEnd);
	lpStart++)
    continue;   /* just getting to the end of the string */

  return((LPWSTR)lpStart);

#else

  SetLastErrorEx(ERROR_CALL_NOT_IMPLEMENTED, SLE_WARNING);
  return NULL;

#endif
}


// REVIEW WIN32 HACK - Convert to 32bit asm.
#ifndef WIN32
/*
 * ReverseScan - Find last occurrence of a byte in a string
 * Assumes   lpSource points to first byte to check (end of the string)
 *           uLen is the number of bytes to check
 *           bMatch is the byte to match
 * returns ptr to the last occurrence of ch in str, NULL if not found.
 */
LPSTR PASCAL ReverseScan(LPCSTR lpSource, UINT uLen, BYTE bMatch)
{
  _asm
    {
	/* Load count */
	mov     cx,uLen
	jcxz ReverseScanFail    ; count is zero, return failure.

	/* Load up es:di, ax */
	les     di,lpSource
	mov     al,bMatch
	/* Set the direction flag based on bBackward
	 * Perform the search; return 0 if we reached the end of the string
	 * otherwise, return es:di+1
	 */
	std
	repne   scasb
	jne     ReverseScanFail     ; check result of last compare.

	inc     di
	mov     dx,es
	mov     ax,di
	jmp ReverseScanExit

ReverseScanFail:
	xor     ax,ax
	xor     dx,dx

	/* clear the direction flag and return
	 */
ReverseScanExit:
	cld
    }
    if (0) return 0;        // suppress warning, optimized out
}
#endif

/*
 * ChrCmp -  Case sensitive character comparison for DBCS
 * Assumes   w1, wMatch are characters to be compared
 * Return    FALSE if they match, TRUE if no match
 */
__inline BOOL ChrCmpA_inline(WORD w1, WORD wMatch)
{
  /* Most of the time this won't match, so test it first for speed.
   */
  if (LOBYTE(w1) == LOBYTE(wMatch))
    {
      if (IsDBCSLeadByte(LOBYTE(w1)))
	{
	  return(w1 != wMatch);
	}
      return FALSE;
    }
  return TRUE;
}

BOOL ChrCmpA(WORD w1, WORD wMatch)
{
  return ChrCmpA_inline(w1, wMatch);
}

#ifdef UNICODE
__inline BOOL ChrCmpW_inline(WCHAR w1, WCHAR wMatch)
{
    return(!(w1 == wMatch));
}
#endif

BOOL ChrCmpW(WCHAR w1, WCHAR wMatch)
{
#ifdef UNICODE
   return ChrCmpW_inline(w1, wMatch);
#else
    SetLastErrorEx(ERROR_CALL_NOT_IMPLEMENTED, SLE_WARNING);
    return FALSE;
#endif
}



/*
 * ChrCmpI - Case insensitive character comparison for DBCS
 * Assumes   w1, wMatch are characters to be compared;
 *           HIBYTE of wMatch is 0 if not a DBC
 * Return    FALSE if match, TRUE if not
 */
BOOL ChrCmpIA(WORD w1, WORD wMatch)
{
  char sz1[3], sz2[3];

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

#if defined(BIG_ENDIAN)
  sz2[0] = LOBYTE(wMatch);
  sz2[1] = HIBYTE(wMatch);
#else
  *(WORD FAR *)sz2 = wMatch;
#endif
  sz2[2] = '\0';
  return lstrcmpiA(sz1, sz2);
}

BOOL ChrCmpIW(WCHAR w1, WCHAR wMatch)
{
#ifdef UNICODE
  WCHAR sz1[2], sz2[2];

  sz1[0] = w1;
  sz1[1] = TEXT('\0');
  sz2[0] = wMatch;
  sz2[1] = TEXT('\0');

  return lstrcmpiW(sz1, sz2);

#else
  SetLastErrorEx(ERROR_CALL_NOT_IMPLEMENTED, SLE_WARNING);
  return FALSE;

#endif
}

LPWSTR StrCpyW(LPWSTR psz1, LPCWSTR psz2)
{
    LPWSTR psz = psz1;
    do {
        *psz1 = *psz2;
        psz1++;
    } while(*psz2++);
    return psz;
}


LPWSTR StrCpyNW(LPWSTR psz1, LPCWSTR psz2, int cchMax)
{
    LPWSTR psz = psz1;

    ASSERT(psz1);
    ASSERT(psz2);

    if (0 < cchMax)
    {
        // Leave room for the null terminator
        while (0 < --cchMax)
        {
            if ( !(*psz1++ = *psz2++) )
                break;
        }

        if (0 == cchMax)
            *psz1 = '\0';
    }

    return psz;
}

/*
 * StrChr - Find first occurrence of character in string
 * Assumes   lpStart points to start of null terminated string
 *           wMatch  is the character to match
 * returns ptr to the first occurrence of ch in str, NULL if not found.
 */
LPSTR FAR PASCAL StrChrA(LPCSTR lpStart, WORD wMatch)
{
  for ( ; *lpStart; lpStart = AnsiNext(lpStart))
    {
      if (!ChrCmpA_inline(READNATIVEWORD(lpStart), wMatch))
      {
	  return((LPSTR)lpStart);
      }
   }
   return (NULL);
}

#ifdef ALIGNMENT_SCENARIO

LPWSTR StrChrSlowW(const UNALIGNED WCHAR *lpStart, WCHAR wMatch)
{
    for ( ; *lpStart; lpStart++)
    {
      if (!ChrCmpW_inline(*lpStart, wMatch))
        {
            return((LPWSTR)lpStart);
        }
    }
    return NULL;
}
#endif

LPWSTR FAR PASCAL StrChrW(LPCWSTR lpStart, WCHAR wMatch)
{
#ifdef UNICODE
    //
    //  BUGBUG raymondc
    //  Apparently, somebody is passing unaligned strings to StrChrW.
    //  Find out who and make them stop.
    //
    ASSERT(!((ULONG_PTR)lpStart & 1)); // Assert alignedness

#ifdef ALIGNMENT_SCENARIO
    //
    //  Since unaligned strings arrive so rarely, put the slow
    //  version in a separate function so the common case stays
    //  fast.  Believe it or not, we call StrChrW so often that
    //  it is now a performance-sensitive function!
    //
    if ((ULONG_PTR)lpStart & 1)
        return StrChrSlowW(lpStart, wMatch);
#endif

    for ( ; *lpStart; lpStart++)
    {
      if (!ChrCmpW_inline(*lpStart, wMatch))
        {
            return((LPWSTR)lpStart);
        }
    }

  return (NULL);

#else

  SetLastErrorEx(ERROR_CALL_NOT_IMPLEMENTED, SLE_WARNING);
  return NULL;

#endif
}

/*
 * StrRChr - Find last occurrence of character in string
 * Assumes   lpStart points to start of string
 *           lpEnd   points to end of string (NOT included in search)
 *           wMatch  is the character to match
 * returns ptr to the last occurrence of ch in str, NULL if not found.
 */
LPSTR FAR PASCAL StrRChrA(LPCSTR lpStart, LPCSTR lpEnd, WORD wMatch)
{
  LPCSTR lpFound = NULL;

  if (!lpEnd)
      lpEnd = lpStart + lstrlenA(lpStart);

  for ( ; OFFSETOF(lpStart) < OFFSETOF(lpEnd); lpStart = AnsiNext(lpStart))
    {
      if (!ChrCmpA_inline(READNATIVEWORD(lpStart), wMatch))
	  lpFound = lpStart;
    }
  return ((LPSTR)lpFound);
}

LPWSTR FAR PASCAL StrRChrW(LPCWSTR lpStart, LPCWSTR lpEnd, WCHAR wMatch)
{
#ifdef UNICODE

  LPCWSTR lpFound = NULL;

  if (!lpEnd)
      lpEnd = lpStart + lstrlenW(lpStart);

  for ( ; lpStart < lpEnd; lpStart++)
    {
      if (!ChrCmpW_inline(*lpStart, wMatch))
	  lpFound = lpStart;
    }
  return ((LPWSTR)lpFound);

#else

  SetLastErrorEx(ERROR_CALL_NOT_IMPLEMENTED, SLE_WARNING);
  return NULL;

#endif
}


/*
 * StrRChrI - Find last occurrence of character in string, case insensitive
 * Assumes   lpStart points to start of string
 *           lpEnd   points to end of string (NOT included in search)
 *           wMatch  is the character to match
 * returns ptr to the last occurrence of ch in str, NULL if not found.
 */
LPSTR FAR PASCAL StrRChrIA(LPCSTR lpStart, LPCSTR lpEnd, WORD wMatch)
{
  LPCSTR lpFound = NULL;

  if (!lpEnd)
      lpEnd = lpStart + lstrlenA(lpStart);

  wMatch = (UINT)(IsDBCSLeadByte(LOBYTE(wMatch)) ? wMatch : LOBYTE(wMatch));

  for ( ; OFFSETOF(lpStart) < OFFSETOF(lpEnd); lpStart = AnsiNext(lpStart))
    {
      if (!ChrCmpIA(READNATIVEWORD(lpStart), wMatch))
          lpFound = lpStart;
    }
  return ((LPSTR)lpFound);
}

LPWSTR FAR PASCAL StrRChrIW(LPCWSTR lpStart, LPCWSTR lpEnd, WCHAR wMatch)
{
#ifdef UNICODE

  LPCWSTR lpFound = NULL;

  if (!lpEnd)
      lpEnd = lpStart + lstrlenW(lpStart);

  for ( ; lpStart < lpEnd; lpStart++)
    {
      if (!ChrCmpIW(*lpStart, wMatch))
          lpFound = lpStart;
    }
  return ((LPWSTR)lpFound);

#else

  SetLastErrorEx(ERROR_CALL_NOT_IMPLEMENTED, SLE_WARNING);
  return NULL;

#endif
}


// StrCSpn: return index to first char of lpStr that is present in lpSet.
// Includes the NUL in the comparison; if no lpSet chars are found, returns
// the index to the NUL in lpStr.
// Just like CRT strcspn.
//
int FAR PASCAL StrCSpnA(LPCSTR lpStr, LPCSTR lpSet)
{
	// nature of the beast: O(lpStr*lpSet) work
	LPCSTR lp = lpStr;
	if (!lpStr || !lpSet)
		return 0;

	while (*lp)
	{
 		if (StrChrA(lpSet, READNATIVEWORD(lp)))
			return (int)(lp-lpStr);
		lp = AnsiNext(lp);
	}

	return (int)(lp-lpStr); // ==lstrlen(lpStr)
}

int FAR PASCAL StrCSpnW(LPCWSTR lpStr, LPCWSTR lpSet)
{
#ifdef UNICODE

	// nature of the beast: O(lpStr*lpSet) work
	LPCWSTR lp = lpStr;
	if (!lpStr || !lpSet)
		return 0;

	while (*lp)
	{
		if (StrChrW(lpSet, *lp))
			return (int)(lp-lpStr);
		lp++;
	}

	return (int)(lp-lpStr); // ==lstrlen(lpStr)

#else

        SetLastErrorEx(ERROR_CALL_NOT_IMPLEMENTED, SLE_WARNING);
	return -1;

#endif
}


// StrCSpnI: case-insensitive version of StrCSpn.
//
int FAR PASCAL StrCSpnIA(LPCSTR lpStr, LPCSTR lpSet)
{
        // nature of the beast: O(lpStr*lpSet) work
        LPCSTR lp = lpStr;
        if (!lpStr || !lpSet)
                return 0;

        while (*lp)
        {
                if (StrChrIA(lpSet, READNATIVEWORD(lp)))
                        return (int)(lp-lpStr);
                lp = AnsiNext(lp);
        }

        return (int)(lp-lpStr); // ==lstrlen(lpStr)
}

int FAR PASCAL StrCSpnIW(LPCWSTR lpStr, LPCWSTR lpSet)
{
#ifdef UNICODE
        // nature of the beast: O(lpStr*lpSet) work
        LPCWSTR lp = lpStr;
        if (!lpStr || !lpSet)
                return 0;

        while (*lp)
        {
                if (StrChrIW(lpSet, *lp))
                        return (int)(lp-lpStr);
                lp++;
        }

        return (int)(lp-lpStr); // ==lstrlen(lpStr)

#else

        SetLastErrorEx(ERROR_CALL_NOT_IMPLEMENTED, SLE_WARNING);
        return -1;

#endif
}


/*
 * StrCmpN      - Compare n bytes
 *
 * returns   See lstrcmp return values.
 * BUGBUG, won't work if source strings are in ROM
 */
int FAR PASCAL StrCmpNA(LPCSTR lpStr1, LPCSTR lpStr2, int nChar)
{
    char sz1[4];
    char sz2[4];
    LPCSTR lpszEnd = lpStr1 + nChar;

    //DebugMsg(DM_TRACE, "StrCmpN: %s %s %d returns:", lpStr1, lpStr2, nChar);

    for ( ; (lpszEnd > lpStr1) && (*lpStr1 || *lpStr2); lpStr1 = AnsiNext(lpStr1), lpStr2 = AnsiNext(lpStr2)) {
        WORD wMatch;


        wMatch = (WORD) (*lpStr2 | (*(lpStr2+1)<<8));

        if (ChrCmpA_inline(READNATIVEWORD(lpStr1), wMatch)) {
            int iRet;

            (*(WORD FAR *)sz1) = READNATIVEWORD(lpStr1);
            (*(WORD FAR *)sz2) = wMatch;
#ifndef UNIX
            *AnsiNext(sz1) = 0;
            *AnsiNext(sz2) = 0;
#else
            *AnsiNext((LPWORD)sz1) = 0;
            *AnsiNext((LPWORD)sz2) = 0;
#endif

            iRet = lstrcmpA(sz1, sz2);
            //DebugMsg(DM_TRACE, ".................... %d", iRet);
            return iRet;
        }
    }

    //DebugMsg(DM_TRACE, ".................... 0");
    return 0;
}

int FAR PASCAL StrCmpNW(LPCWSTR lpStr1, LPCWSTR lpStr2, int nChar)
{
#ifdef UNICODE

    WCHAR sz1[2];
    WCHAR sz2[2];
    int i;
    LPCWSTR lpszEnd = lpStr1 + nChar;

    //DebugMsg(DM_TRACE, "StrCmpN: %s %s %d returns:", lpStr1, lpStr2, nChar);

    for ( ; (lpszEnd > lpStr1) && (*lpStr1 || *lpStr2); lpStr1++, lpStr2++) {
        i = ChrCmpW_inline(*lpStr1, *lpStr2);
        if (i) {
            int iRet;

            sz1[0] = *lpStr1;
            sz2[0] = *lpStr2;
            sz1[1] = TEXT('\0');
            sz2[1] = TEXT('\0');
            iRet = lstrcmpW(sz1, sz2);
            //DebugMsg(DM_TRACE, ".................... %d", iRet);
            return iRet;
        }
    }

    //DebugMsg(DM_TRACE, ".................... 0");
    return 0;

#else

  SetLastErrorEx(ERROR_CALL_NOT_IMPLEMENTED, SLE_WARNING);
  return -1;

#endif
}

/*
 * StrCmpNI     - Compare n bytes, case insensitive
 *
 * returns   See lstrcmpi return values.
 */

 #ifndef WINNT
__inline BOOL IsAsciiA(char ch)
{
    return !(ch & 0x80);
}

__inline char Ascii_ToLowerA(char ch)
{
    return (ch >= 'A' && ch <= 'Z') ? (ch - 'A' + 'a') : ch;
}

#ifdef UNICODE
__inline BOOL IsAsciiW(WCHAR ch)
{
    return ch < 128;
}

__inline WCHAR Ascii_ToLowerW(WCHAR ch)
{
    return (ch >= L'A' && ch <= L'Z') ? (ch - L'A' + L'a') : ch;
}
#endif

int FAR PASCAL StrCmpNIA(LPCSTR lpStr1, LPCSTR lpStr2, int nChar)
{
    int i;
    LPCSTR lpszEnd = lpStr1 + nChar;

    //DebugMsg(DM_TRACE, "StrCmpNI: %s %s %d returns:", lpStr1, lpStr2, nChar);

    for ( ; (lpszEnd > lpStr1) && (*lpStr1 || *lpStr2); (lpStr1 = AnsiNext(lpStr1)), (lpStr2 = AnsiNext(lpStr2))) {
        WORD wMatch;

        if (IsAsciiA(*lpStr1) && IsAsciiA(*lpStr2))
        {
            i = Ascii_ToLowerA(*lpStr1) - Ascii_ToLowerA(*lpStr2);
        }
        else
        {
#ifndef UNIX
            wMatch = (UINT)(IsDBCSLeadByte(*lpStr2)) ? *(WORD FAR *)lpStr2 : (WORD)(BYTE)(*lpStr2);
#else
            wMatch = (UINT)(IsDBCSLeadByte(*lpStr2)) ? (*lpStr2 | (*(lpStr2+1)<<8)) : (WORD)(BYTE)(*lpStr2);
#endif

            i = ChrCmpIA(READNATIVEWORD(lpStr1), wMatch);
        }
        if (i) {
            //DebugMsg(DM_TRACE, ".................... %d", i);
            if (i < 0)
            {
                return -1;
            }
            else
            {
                return 1;
            }
        }
    }
    //DebugMsg(DM_TRACE, ".................... 0");
    return 0;
}

int FAR PASCAL StrCmpNIW(LPCWSTR lpStr1, LPCWSTR lpStr2, int nChar)
{
#ifdef UNICODE

    int i;
    LPCWSTR lpszEnd = lpStr1 + nChar;

    //DebugMsg(DM_TRACE, "StrCmpNI: %s %s %d returns:", lpStr1, lpStr2, nChar);

    for ( ; (lpszEnd > lpStr1) && (*lpStr1 || *lpStr2); lpStr1++, lpStr2++) {
        if (IsAsciiW(*lpStr1) && IsAsciiW(*lpStr2))
        {
            i = Ascii_ToLowerW(*lpStr1) - Ascii_ToLowerW(*lpStr2);
        }
        else
        {
            i = ChrCmpIW(*lpStr1, *lpStr2);
        }
        if (i) {
            //DebugMsg(DM_TRACE, ".................... %d", i);
           if (i < 0)
           {
               return -1;
           }
           else
           {
               return 1;
           }
           return i;
        }
    }
    //DebugMsg(DM_TRACE, ".................... 0");
    return 0;

#else

  SetLastErrorEx(ERROR_CALL_NOT_IMPLEMENTED, SLE_WARNING);
  return -1;

#endif
}

#else // WINNT

int StrCmpNIA(LPCSTR lpStr1, LPCSTR lpStr2, int nChar)
{
    int i;
    
    //  Win95 doesn't support NORM_STOP_ON_NULL
    i = CompareStringA(GetThreadLocale(), NORM_IGNORECASE | NORM_STOP_ON_NULL, 
                       lpStr1, nChar, lpStr2, nChar);

    if (!i)
    {
        i = CompareStringA(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE | NORM_STOP_ON_NULL, 
                             lpStr1, nChar, lpStr2, nChar);
    }

    return i - CSTR_EQUAL;    
}

int StrCmpNIW(LPCWSTR lpStr1, LPCWSTR lpStr2, int nChar)
{
    int i;

    //  Win95 doesn't support NORM_STOP_ON_NULL
    i = CompareStringW(GetThreadLocale(), NORM_IGNORECASE | NORM_STOP_ON_NULL, 
                       lpStr1, nChar, lpStr2, nChar);

    if (!i)
    {
        i = CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE | NORM_STOP_ON_NULL, 
                             lpStr1, nChar, lpStr2, nChar);
    }

    return i - CSTR_EQUAL;    
}

#endif //   WINNT


/*
 * IntlStrEq
 *
 * returns TRUE if strings are equal, FALSE if not
 */
BOOL IntlStrEqWorkerA(BOOL fCaseSens, LPCSTR lpString1, LPCSTR lpString2, int nChar) {
    int retval;
    DWORD dwFlags = fCaseSens ? LOCALE_USE_CP_ACP : (NORM_IGNORECASE | LOCALE_USE_CP_ACP);

#ifdef WINNT
    //
    // On NT we can tell CompareString to stop at a '\0' if one is found before nChar chars
    //
    dwFlags |= NORM_STOP_ON_NULL;
#else
    //
    // On Win9x we have to do the check manually
    //
    if (nChar != -1) {
        LPCSTR psz1, psz2;
        int cch = 0;

        psz1 = lpString1;
        psz2 = lpString2;

        while( *psz1 != '\0' && *psz2 != '\0' && cch < nChar) {
#ifdef DBCS
            psz1 = CharNextA(psz1);
            psz2 = CharNextA(psz2);

            cch = min(psz1 - lpString1, psz2 - lpString2);
#else
            psz1++;
            psz2++;
            cch++;
#endif
        }

        // add one in for terminating '\0'
        cch++;

        if (cch < nChar) {
            nChar = cch;
        }
    }
#endif

    retval = CompareStringA( GetThreadLocale(),
                             dwFlags,
                             lpString1,
                             nChar,
                             lpString2,
                             nChar );
    if (retval == 0)
    {
        //
        // The caller is not expecting failure.  Try the system
        // default locale id.
        //
        retval = CompareStringA( GetSystemDefaultLCID(),
                                 dwFlags,
                                 lpString1,
                                 nChar,
                                 lpString2,
                                 nChar );
    }

    if (retval == 0)
    {
        if (lpString1 && lpString2)
        {
            //
            // The caller is not expecting failure.  We've never had a
            // failure indicator before.  We'll do a best guess by calling
            // the C runtimes to do a non-locale sensitive compare.
            //
            if (fCaseSens)
                retval = StrCmpNA(lpString1, lpString2, nChar) + 2;
            else {
                retval = StrCmpNIA(lpString1, lpString2, nChar) + 2;
            }
        }
        else
        {
            retval = 2;
        }
    }

    return (retval == 2);

}


BOOL IntlStrEqWorkerW(BOOL fCaseSens, LPCWSTR lpString1, LPCWSTR lpString2, int nChar) {

#ifdef UNICODE
    int retval;
    DWORD dwFlags = fCaseSens ? 0 : NORM_IGNORECASE;

#ifdef WINNT
    //
    // On NT we can tell CompareString to stop at a '\0' if one is found before nChar chars
    //
    dwFlags |= NORM_STOP_ON_NULL;
#else
    //
    // On Win9x we have to do the check manually
    //
    if (nChar != -1) {
        LPCWSTR psz1, psz2;
        int cch = 0;

        psz1 = lpString1;
        psz2 = lpString2;

        while( *psz1 != TEXT('\0') && *psz2 != TEXT('\0') && cch < nChar) {
            psz1++;
            psz2++;
            cch++;
        }

        // add one in for terminating '\0'
        cch++;

        if (cch < nChar) {
            nChar = cch;
        }
    }
#endif


    retval = CompareStringW( GetThreadLocale(),
                             dwFlags,
                             lpString1,
                             nChar,
                             lpString2,
                             nChar );
    if (retval == 0)
    {
        //
        // The caller is not expecting failure.  Try the system
        // default locale id.
        //
        retval = CompareStringW( GetSystemDefaultLCID(),
                                 dwFlags,
                                 lpString1,
                                 nChar,
                                 lpString2,
                                 nChar );
    }

    if (retval == 0)
    {
        if (lpString1 && lpString2)
        {
            //
            // The caller is not expecting failure.  We've never had a
            // failure indicator before.  We'll do a best guess by calling
            // the C runtimes to do a non-locale sensitive compare.
            //
            if (fCaseSens)
                retval = StrCmpNW(lpString1, lpString2, nChar) + 2;
            else {
                retval = StrCmpNIW(lpString1, lpString2, nChar) + 2;
            }
        }
        else
        {
            retval = 2;
        }
    }

    return (retval == 2);

#else
  SetLastErrorEx(ERROR_CALL_NOT_IMPLEMENTED, SLE_WARNING);
  return FALSE;
#endif
}



/*
 * StrRStrI      - Search for last occurrence of a substring
 *
 * Assumes   lpSource points to the null terminated source string
 *           lpLast points to where to search from in the source string
 *           lpLast is not included in the search
 *           lpSrch points to string to search for
 * returns   last occurrence of string if successful; NULL otherwise
 */
LPSTR FAR PASCAL StrRStrIA(LPCSTR lpSource, LPCSTR lpLast, LPCSTR lpSrch)
{
    LPCSTR lpFound = NULL;
    LPSTR lpEnd;
    char cHold;

    if (!lpLast)
        lpLast = lpSource + lstrlenA(lpSource);

    if (lpSource >= lpLast || *lpSrch == 0)
        return NULL;

    lpEnd = lstrfns_StrEndNA(lpLast, (UINT)(lstrlenA(lpSrch)-1));
    cHold = *lpEnd;
    *lpEnd = 0;

    while ((lpSource = StrStrIA(lpSource, lpSrch))!=0 &&
          OFFSETOF(lpSource) < OFFSETOF(lpLast))
    {
        lpFound = lpSource;
        lpSource = AnsiNext(lpSource);
    }
    *lpEnd = cHold;
    return((LPSTR)lpFound);
}

LPWSTR FAR PASCAL StrRStrIW(LPCWSTR lpSource, LPCWSTR lpLast, LPCWSTR lpSrch)
{
#ifdef UNICODE
    LPCWSTR lpFound = NULL;
    LPWSTR lpEnd;
    WCHAR cHold;

    if (!lpLast)
        lpLast = lpSource + lstrlenW(lpSource);

    if (lpSource >= lpLast || *lpSrch == 0)
        return NULL;

    lpEnd = lstrfns_StrEndNW(lpLast, (UINT)(lstrlenW(lpSrch)-1));
    cHold = *lpEnd;
    *lpEnd = 0;

    while ((lpSource = StrStrIW(lpSource, lpSrch))!=0 &&
          lpSource < lpLast)
    {
        lpFound = lpSource;
        lpSource++;
    }
    *lpEnd = cHold;
    return((LPWSTR)lpFound);

#else

  SetLastErrorEx(ERROR_CALL_NOT_IMPLEMENTED, SLE_WARNING);
  return NULL;

#endif
}



/*
 * StrStr      - Search for first occurrence of a substring
 *
 * Assumes   lpSource points to source string
 *           lpSrch points to string to search for
 * returns   first occurrence of string if successful; NULL otherwise
 */
LPSTR FAR PASCAL StrStrA(LPCSTR lpFirst, LPCSTR lpSrch)
{
  UINT uLen;
  WORD wMatch;

  uLen = (UINT)lstrlenA(lpSrch);
  wMatch = READNATIVEWORD(lpSrch);

  for ( ; (lpFirst=StrChrA(lpFirst, wMatch))!=0 && !IntlStrEqNA(lpFirst, lpSrch, uLen);
        lpFirst=AnsiNext(lpFirst))
    continue; /* continue until we hit the end of the string or get a match */

  return((LPSTR)lpFirst);
}

LPWSTR FAR PASCAL StrStrW(LPCWSTR lpFirst, LPCWSTR lpSrch)
{
#ifdef UNICODE

  UINT uLen;
  WCHAR wMatch;

  uLen = (UINT)lstrlenW(lpSrch);
  wMatch = *lpSrch;

  for ( ; (lpFirst=StrChrW(lpFirst, wMatch))!=0 && !IntlStrEqNW(lpFirst, lpSrch, uLen);
        lpFirst++)
    continue; /* continue until we hit the end of the string or get a match */

  return((LPWSTR)lpFirst);

#else

  SetLastErrorEx(ERROR_CALL_NOT_IMPLEMENTED, SLE_WARNING);
  return NULL;

#endif
}

/*
 * StrChrI - Find first occurrence of character in string, case insensitive
 * Assumes   lpStart points to start of null terminated string
 *           wMatch  is the character to match
 * returns ptr to the first occurrence of ch in str, NULL if not found.
 */
LPSTR FAR PASCAL StrChrIA(LPCSTR lpStart, WORD wMatch)
{
  wMatch = (UINT)(IsDBCSLeadByte(LOBYTE(wMatch)) ? wMatch : LOBYTE(wMatch));

  for ( ; *lpStart; lpStart = AnsiNext(lpStart))
    {
      if (!ChrCmpIA(READNATIVEWORD(lpStart), wMatch))
	  return((LPSTR)lpStart);
    }
  return (NULL);
}

LPWSTR FAR PASCAL StrChrIW(LPCWSTR lpStart, WCHAR wMatch)
{
  for ( ; *lpStart; lpStart++)
    {
      if (!ChrCmpIW(*lpStart, wMatch))
	  return((LPWSTR)lpStart);
    }
  return (NULL);
}


/*
 * StrStrI   - Search for first occurrence of a substring, case insensitive
 *
 * Assumes   lpFirst points to source string
 *           lpSrch points to string to search for
 * returns   first occurrence of string if successful; NULL otherwise
 */
LPSTR FAR PASCAL StrStrIA(LPCSTR lpFirst, LPCSTR lpSrch)
{
  UINT uLen;
  WORD wMatch;

  uLen = (UINT)lstrlenA(lpSrch);
  wMatch = READNATIVEWORD(lpSrch);

  for ( ; (lpFirst = StrChrIA(lpFirst, wMatch)) != 0 && !IntlStrEqNIA(lpFirst, lpSrch, uLen);
        lpFirst=AnsiNext(lpFirst))
      continue; /* continue until we hit the end of the string or get a match */

  return((LPSTR)lpFirst);
}

LPWSTR FAR PASCAL StrStrIW(LPCWSTR lpFirst, LPCWSTR lpSrch)
{
#ifdef UNICODE

  UINT uLen;
  WCHAR wMatch;

  uLen = (UINT)lstrlenW(lpSrch);
  wMatch = *lpSrch;

  for ( ; (lpFirst = StrChrIW(lpFirst, wMatch)) != 0 && !IntlStrEqNIW(lpFirst, lpSrch, uLen);
        lpFirst++)
      continue; /* continue until we hit the end of the string or get a match */

  return((LPWSTR)lpFirst);

#else

  SetLastErrorEx(ERROR_CALL_NOT_IMPLEMENTED, SLE_WARNING);
  return NULL;

#endif
}
#ifndef UNICODE

// TruncateString - bugbug: the same logic in shdocvw
//
// purpose: cut a string at the given length in dbcs safe manner
//          the string may be truncated at cch-1 if the sz[cch] points
//          to a lead byte that would result in cutting in the middle
//          of double byte character.
//          It is caller's responsibility to reserve enough buffer for
//          sz so we can put sz[cch]=0 safely.
//
//          *Note this logic is not much perf hit when called with sbcs
//          string, as it just bails out at the tail character always.
//
void  TruncateString(char *sz, int cchBufferSize)
{
    int cch = cchBufferSize - 1;
    LPSTR psz = &sz[cch];
    
    if (!sz || cchBufferSize <= 0) return;
    
    while (psz > sz)
    {
        psz--;
        if (!IsDBCSLeadByte(*psz))
        {
            // Found non-leadbyte for the first time.
            // This is either a trail byte of double byte char
            // or a single byte character we've first seen.
            // Thus, the next pointer must be at either of a leadbyte
            // or &sz[cch]
            psz++;
            break;
        }
    }
    if (((&sz[cch] - psz) & 1) && cch > 0)
    {
        // we're truncating the string in the middle of dbcs
        cch--;
    }
    sz[cch] = '\0';
    return;
}
#endif