//============================================================================
//
// UNICODE and ANSI conversion functions
//
//============================================================================

#include "priv.h"
#include <mlang.h>

/*
 *  @doc    INTERNAL
 *
 *  @func   int | SHAnsiToUnicodeNativeCP |
 *
 *          Convert an ANSI string to a UNICODE string via the
 *          specified Windows code page.  If the source string is too large
 *          for the destination buffer, then as many characters as
 *          possible are copied.
 *
 *          The resulting output string is always null-terminated.
 *
 *  @parm   UINT | uiCP |
 *
 *          The code page in which to perform the conversion.
 *          This must be a Windows code page.
 *
 *  @parm   LPCSTR | pszSrc |
 *
 *          Source buffer containing ANSI string to be converted.
 *
 *  @parm   int | cchSrc |
 *
 *          Source buffer length, including terminating null.
 *
 *  @parm   LPWSTR | pwszDst |
 *
 *          Destination buffer to receive converted UNICODE string.
 *
 *  @parm   int | cwchBuf |
 *
 *          Size of the destination buffer in <t WCHAR>s.
 *
 *  @returns
 *
 *          On success, the number of characters copied to the output
 *          buffer is returned, including the terminating null.
 */

int
SHAnsiToUnicodeNativeCP(UINT uiCP,
                        LPCSTR pszSrc, int cchSrc,
                        LPWSTR pwszDst, int cwchBuf)
{
    int cwchRc = 0;             /* Assume failure */

    /*
     *  Checks the caller should've made.
     */
    ASSERT(IS_VALID_STRING_PTRA(pszSrc, -1));
    ASSERT(cchSrc == lstrlenA(pszSrc) + 1);
    ASSERT(IS_VALID_WRITE_BUFFER(pwszDst, WCHAR, cwchBuf));
    ASSERT(pszSrc != NULL);
    ASSERT(uiCP != 1200 && uiCP != 65000 && uiCP != 50000 && uiCP != 65001);
    ASSERT(pwszDst);
    ASSERT(cwchBuf);

    cwchRc = MultiByteToWideChar(uiCP, 0, pszSrc, cchSrc, pwszDst, cwchBuf);
    if (cwchRc) {
        /*
         *  The output buffer was big enough; no double-buffering
         *  needed.
         */
    } else if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
        /*
         *  The output buffer wasn't big enough.  Need to double-buffer.
         */

        int cwchNeeded = MultiByteToWideChar(uiCP, 0, pszSrc, cchSrc,
                                             NULL, 0);

        ASSERT(cwchRc == 0);        /* In case we fail later */
        if (cwchNeeded) {
            LPWSTR pwsz = (LPWSTR)LocalAlloc(LMEM_FIXED,
                                             cwchNeeded * SIZEOF(WCHAR));
            if (pwsz) {
                cwchRc = MultiByteToWideChar(uiCP, 0, pszSrc, cchSrc,
                                             pwsz, cwchNeeded);
                if (cwchRc) {
                    StrCpyNW(pwszDst, pwsz, cwchBuf);
                    cwchRc = cwchBuf;
                }
                LocalFree(pwsz);
            }
        }
    } else {
        /* Possibly unsupported code page */
        ASSERT(!"Unexpected error in MultiByteToWideChar");
    }

    return cwchRc;
}

/*
 *  @doc    INTERNAL
 *
 *  @func   int | SHAnsiToUnicodeInetCP |
 *
 *          Convert an ANSI string to a UNICODE string via the
 *          specified Internet code page.  If the source string is too large
 *          for the destination buffer, then as many characters as
 *          possible are copied.
 *
 *          The resulting output string is always null-terminated.
 *
 *  @parm   UINT | uiCP |
 *
 *          The code page in which to perform the conversion.
 *          This must be an Internet code page.
 *
 *  @parm   LPCSTR | pszSrc |
 *
 *          Source buffer containing ANSI string to be converted.
 *
 *  @parm   int | cchSrc |
 *
 *          Source buffer length, including terminating null.
 *
 *  @parm   LPWSTR | pwszDst |
 *
 *          Destination buffer to receive converted UNICODE string.
 *
 *  @parm   int | cwchBuf |
 *
 *          Size of the destination buffer in <t WCHAR>s.
 *
 *  @returns
 *
 *          On success, the number of characters copied to the output
 *          buffer is returned, including the terminating null.
 */

int
SHAnsiToUnicodeInetCP(UINT uiCP,
                      LPCSTR pszSrc, int cchSrc,
                      LPWSTR pwszDst, int cwchBuf)
{
    int cchSrcT, cwchNeeded;
    int cwchRc = 0;             /* Assume failure */
    HRESULT hres;
    DWORD dwMode;

    /*
     *  Checks the caller should've made.
     */
    ASSERT(IS_VALID_STRING_PTRA(pszSrc, -1));
    ASSERT(cchSrc == lstrlenA(pszSrc) + 1);
    ASSERT(IS_VALID_WRITE_BUFFER(pwszDst, WCHAR, cwchBuf));
    ASSERT(pszSrc != NULL);
    ASSERT(uiCP == 1200 || uiCP == 65000 || uiCP == 65001);
    ASSERT(pwszDst);
    ASSERT(cwchBuf);

    cchSrcT = cchSrc;
    cwchNeeded = cwchBuf;

    dwMode = 0;
    hres = ConvertINetMultiByteToUnicode(&dwMode, uiCP, pszSrc,
                                         &cchSrcT, pwszDst, &cwchNeeded);
    if (SUCCEEDED(hres)) {
        if (cchSrcT >= cchSrc) {
            /*
             *  The output buffer was big enough; no double-buffering
             *  needed.
             */
            cwchRc = cwchNeeded;
        } else {
            /*
             *  The output buffer wasn't big enough.  Need to double-buffer.
             */
            LPWSTR pwsz = (LPWSTR)LocalAlloc(LMEM_FIXED,
                                             cwchNeeded * SIZEOF(WCHAR));
            if (pwsz) {
                dwMode = 0;
                hres = ConvertINetMultiByteToUnicode(&dwMode, uiCP, pszSrc,
                                            &cchSrc, pwsz, &cwchNeeded);
                if (SUCCEEDED(hres)) {
                    StrCpyNW(pwszDst, pwsz, cwchBuf);
                    cwchRc = cwchBuf;
                }
                LocalFree(pwsz);
            }
        }
    } else {
        /* Possibly unsupported code page */
        ASSERT(!"Unexpected error in ConvertInetMultiByteToUnicode");
    }

    return cwchRc;
}

/*
 *  @doc    EXTERNAL
 *
 *  @func   int | SHAnsiToUnicodeCP |
 *
 *          Convert an ANSI string to a UNICODE string via the
 *          specified code page, which can be either a native
 *          Windows code page or an Internet code page.
 *          If the source string is too large
 *          for the destination buffer, then as many characters as
 *          possible are copied.
 *
 *          The resulting output string is always null-terminated.
 *
 *  @parm   UINT | uiCP |
 *
 *          The code page in which to perform the conversion.
 *
 *  @parm   LPCSTR | pszSrc |
 *
 *          Source buffer containing ANSI string to be converted.
 *
 *  @parm   LPWSTR | pwszDst |
 *
 *          Destination buffer to receive converted UNICODE string.
 *
 *  @parm   int | cwchBuf |
 *
 *          Size of the destination buffer in <t WCHAR>s.
 *
 *  @returns
 *
 *          On success, the number of characters copied to the output
 *          buffer is returned, including the terminating null.
 */

int
SHAnsiToUnicodeCP(UINT uiCP, LPCSTR pszSrc, LPWSTR pwszDst, int cwchBuf)
{
    int cwchRc = 0;             /* Assume failure */

    RIPMSG(IS_VALID_STRING_PTRA(pszSrc, -1), "Caller of SHAnsiToUnicodeCP passed in a NULL pszSrc!");
    ASSERT(IS_VALID_WRITE_BUFFER(pwszDst, WCHAR, cwchBuf));

    /*
     *  Sanity check - NULL source string is treated as a null string.
     */
    if (pszSrc == NULL) {
        pszSrc = "";
    }

    /*
     *  Sanity check - Output buffer must be non-NULL and must be of
     *  nonzero size.
     */
    if (pwszDst && cwchBuf) {

        int cchSrc;

        pwszDst[0] = 0;         /* In case of error */

        cchSrc = lstrlenA(pszSrc) + 1;

        /*
         *  Decide what kind of code page it is.
         */
        switch (uiCP) {
        case 1200:                      // UCS-2 (Unicode)
            uiCP = 65001;
            // Fall through
        case 50000:                     // "User Defined"
        case 65000:                     // UTF-7
        case 65001:                     // UTF-8
            cwchRc = SHAnsiToUnicodeInetCP(uiCP, pszSrc, cchSrc, pwszDst, cwchBuf);
            break;

        default:
            cwchRc = SHAnsiToUnicodeNativeCP(uiCP, pszSrc, cchSrc, pwszDst, cwchBuf);
            break;
        }
    }

    return cwchRc;
}

// This function exists to make sure SHAnsiToAnsi and SHUnicodeToAnsi
// have the same return value.  Callers use SHTCharToAnsi and don't know
// when it callapses to SHAnsiToAnsi.
int SHAnsiToAnsi(LPCSTR pszSrc, LPSTR pszDst, int cchBuf)
{
    int cchRc = 0;          /* Assume failure */

    if (cchBuf)
    {
        // APP COMPAT! WARNING!  Sony PictureGear passes too-small buffers to
        // SHGetPathFromIDList (which uses SHAnsiToAnsi eventually), so we
        // must be careful to pass the actual buffer size to SHTruncateString
        // and not the theoretical maximum (rarely attained).

        LPSTR pszEnd = StrCpyNXA(pszDst, pszSrc, cchBuf);
        cchRc = (int)(pszEnd - pszDst) + 1;
        cchRc = SHTruncateString(pszDst, cchRc) + 1;
    }

    return cchRc;
}

// This function exists to make sure SHUnicodeToUnicode and SHUnicodeToAnsi
// have the same return value.  Callers use SHTCharToUnicode and don't know
// when it callapses to SHUnicodeToUnicode.
int SHUnicodeToUnicode(LPCWSTR pwzSrc, LPWSTR pwzDst, int cchBuf)
{
    return (int) (StrCpyNXW(pwzDst, pwzSrc, cchBuf) - pwzDst + 1); // size including terminator
}


/*
 *  @doc    EXTERNAL
 *
 *  @func   int | SHAnsiToUnicode |
 *
 *          Convert an ANSI string to a UNICODE string via the
 *          <c CP_ACP> code page.  If the source string is too large
 *          for the destination buffer, then as many characters as
 *          possible are copied.
 *
 *          The resulting output string is always null-terminated.
 *
 *  @parm   LPCSTR | pszSrc |
 *
 *          Source buffer containing ANSI string to be converted.
 *
 *  @parm   LPWSTR | pwszDst |
 *
 *          Destination buffer to receive converted UNICODE string.
 *
 *  @parm   int | cwchBuf |
 *
 *          Size of the destination buffer in <t WCHAR>s.
 *
 *  @returns
 *
 *          On success, the number of characters copied to the output
 *          buffer is returned, including the terminating null.
 *
 */

int
SHAnsiToUnicode(LPCSTR pszSrc, LPWSTR pwszDst, int cwchBuf)
{
    return SHAnsiToUnicodeCP(CP_ACP, pszSrc, pwszDst, cwchBuf);
}

/*
 *  @doc    INTERNAL
 *
 *  @func   int | SHUnicodeToAnsiNativeCP |
 *
 *          Convert a UNICODE string to an ANSI string via the
 *          specified Windows code page.  If the source string is too large
 *          for the destination buffer, then as many characters as
 *          possible are copied.  Care is taken not to break a double-byte
 *          character.
 *
 *          The resulting output string is always null-terminated.
 *
 *  @parm   UINT | uiCP |
 *
 *          The code page in which to perform the conversion.
 *          This must be a Windows code page.
 *
 *  @parm   LPCWSTR | pwszSrc |
 *
 *          Source buffer containing UNICODE string to be converted.
 *
 *  @parm   int | cwchSrc |
 *
 *          Number of characters in source buffer, including terminating
 *          null.
 *
 *  @parm   LPSTR | pszDst |
 *
 *          Destination buffer to receive converted ANSI string.
 *
 *  @parm   int | cchBuf |
 *
 *          Size of the destination buffer in <t CHAR>s.
 *
 *  @returns
 *
 *          On success, the number of characters copied to the output
 *          buffer is returned, including the terminating null.
 *          (For the purpose of this function, a double-byte character
 *          counts as two characters.)
 */

int
SHUnicodeToAnsiNativeCP(UINT uiCP,
                        LPCWSTR pwszSrc, int cwchSrc,
                        LPSTR pszDst, int cchBuf)

{
    int cchRc = 0;          /* Assume failure */

#ifdef DEBUG
    BOOL fVerify = TRUE;
    BOOL fLossy;
    if (uiCP == CP_ACPNOVALIDATE) {
        // -1 means use CP_ACP, but do *not* verify
        // kind of a hack, but it's DEBUG and leaves 99% of callers unchanged
        uiCP = CP_ACP;
        fVerify = FALSE;
    }
#define USUALLY_NULL    (&fLossy)
#else
#define USUALLY_NULL    NULL
#endif

    /*
     *  Checks the caller should've made.
     */
    ASSERT(IS_VALID_STRING_PTRW(pwszSrc, -1));
    ASSERT(cwchSrc == lstrlenW(pwszSrc) + 1);
    ASSERT(IS_VALID_WRITE_BUFFER(pszDst, CHAR, cchBuf));
    ASSERT(uiCP != 1200 && uiCP != 65000 && uiCP != 50000 && uiCP != 65001);
    ASSERT(pwszSrc);
    ASSERT(pszDst);
    ASSERT(cchBuf);

    cchRc = WideCharToMultiByte(uiCP, 0, pwszSrc, cwchSrc, pszDst, cchBuf,
                                NULL, USUALLY_NULL);
    if (cchRc) {
        /*
         *  The output buffer was big enough; no double-buffering
         *  needed.
         */
    } else if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
        /*
         *  The output buffer wasn't big enough.  Need to double-buffer.
         */

        int cchNeeded = WideCharToMultiByte(uiCP, 0, pwszSrc, cwchSrc,
                                            NULL, 0, NULL, NULL);

        ASSERT(cchRc == 0);         /* In case we fail later */
        if (cchNeeded) {
            LPSTR psz = (LPSTR)LocalAlloc(LMEM_FIXED,
                                          cchNeeded * SIZEOF(CHAR));
            if (psz) {
                cchRc = WideCharToMultiByte(uiCP, 0, pwszSrc, cwchSrc,
                                            psz, cchNeeded, NULL, USUALLY_NULL);
                if (cchRc) {
                    // lstrcpyn doesn't check if it's chopping a DBCS char
                    // so we need to use SHTruncateString.
                    //
                    // Add 1 because SHTruncateString doesn't count
                    // the trailing null but we do
                    //
                    // Assert that we meet the preconditions for
                    // SHTruncateString to return a valid value.
                    //
                    ASSERT(cchRc > cchBuf);
                    cchRc = SHTruncateString(psz, cchBuf) + 1;
                    lstrcpynA(pszDst, psz, cchBuf);
                }
                LocalFree(psz);
            }
        }
    } else {
        /* Possibly unsupported code page */
        ASSERT(!"Unexpected error in WideCharToMultiByte");
    }

#ifdef DEBUG
    TBOOL(!fVerify || !fLossy);
#endif

    return cchRc;
}

/*
 *  @doc    INTERNAL
 *
 *  @func   int | SHUnicodeToAnsiInetCP |
 *
 *          Convert a UNICODE string to an ANSI string via the
 *          specified Internet code page.  If the source string is too large
 *          for the destination buffer, then as many characters as
 *          possible are copied.  Care is taken not to break a double-byte
 *          character.
 *
 *          The resulting output string is always null-terminated.
 *
 *  @parm   UINT | uiCP |
 *
 *          The code page in which to perform the conversion.
 *          This must be an Internet code page.
 *
 *  @parm   LPCWSTR | pwszSrc |
 *
 *          Source buffer containing UNICODE string to be converted.
 *
 *  @parm   int | cwchSrc |
 *
 *          Number of characters in source buffer, including terminating
 *          null.
 *
 *  @parm   LPSTR | pszDst |
 *
 *          Destination buffer to receive converted ANSI string.
 *
 *  @parm   int | cchBuf |
 *
 *          Size of the destination buffer in <t CHAR>s.
 *
 *  @returns
 *
 *          On success, the number of characters copied to the output
 *          buffer is returned, including the terminating null.
 *          (For the purpose of this function, a double-byte character
 *          counts as two characters.)
 */

int
SHUnicodeToAnsiInetCP(UINT uiCP,
                      LPCWSTR pwszSrc, int cwchSrc,
                      LPSTR pszDst, int cchBuf)
{
    int cwchSrcT, cchNeeded;
    int cchRc = 0;          /* Assume failure */
    DWORD dwMode;
    HRESULT hres;

    /*
     *  Checks the caller should've made.
     */
    ASSERT(IS_VALID_STRING_PTRW(pwszSrc, -1));
    ASSERT(cwchSrc == lstrlenW(pwszSrc) + 1);
    ASSERT(IS_VALID_WRITE_BUFFER(pszDst, CHAR, cchBuf));
    ASSERT(uiCP == 1200 || uiCP == 65000 || uiCP == 65001);
    ASSERT(pwszSrc);
    ASSERT(pszDst);
    ASSERT(cchBuf);

    /*
     *  Note that not all encodings translate a null terminator into a null
     *  terminator, so we have to save the NUL for last.
     */

    cwchSrc--;                          /* Save the NUL for last */
    cwchSrcT = cwchSrc;
    cchNeeded = cchBuf - 1;             /* Save the NUL for last */

    dwMode = 0;                         /* Start fresh */
    hres = ConvertINetUnicodeToMultiByte(&dwMode, uiCP, pwszSrc,
                                         &cwchSrcT, pszDst, &cchNeeded);
    if (SUCCEEDED(hres)) {
        if (cwchSrcT >= cwchSrc) {
            /*
             *  The output buffer was big enough; no double-buffering
             *  needed.  Translate the NUL manually.
             */
            ASSERT(cchNeeded < cchBuf);
            pszDst[cchNeeded] = TEXT('\0');
            cchRc = cchNeeded + 1;
        } else {
            /*
             *  The output buffer wasn't big enough.  Need to double-buffer.
             */
            LPSTR psz = (LPSTR)LocalAlloc(LMEM_FIXED,
                                          cchNeeded * SIZEOF(CHAR));
            if (psz) {
                dwMode = 0;             /* Start fresh */
                hres = ConvertINetUnicodeToMultiByte(&dwMode, uiCP, pwszSrc,
                                            &cwchSrc, psz, &cchNeeded);
                if (SUCCEEDED(hres)) {
                    // lstrcpyn doesn't check if it's chopping a DBCS char
                    // so we need to use SHTruncateString.
                    //
                    // Add 1 because SHTruncateString doesn't count
                    // the trailing null but we do
                    //
                    // Assert that we meet the preconditions for
                    // SHTruncateString to return a valid value.
                    //
                    ASSERT(cchNeeded > cchBuf);
                    cchRc = SHTruncateString(psz, cchBuf) + 1;
                    lstrcpynA(pszDst, psz, cchBuf);
                }
                LocalFree(psz);
            }
        }
    } else {
        /* Possibly unsupported code page */
        ASSERT(!"Unexpected error in ConvertInetUnicodeToMultiByte");
    }

    return cchRc;
}

/*
 *  @doc    EXTERNAL
 *
 *  @func   int | SHUnicodeToAnsiCP |
 *
 *          Convert a UNICODE string to an ANSI string via the
 *          specified code page, which can be either a native
 *          Windows code page or an Internet code page.
 *          If the source string is too large
 *          for the destination buffer, then as many characters as
 *          possible are copied.  Care is taken not to break a double-byte
 *          character.
 *
 *          The resulting output string is always null-terminated.
 *
 *  @parm   UINT | uiCP |
 *
 *          The code page in which to perform the conversion.
 *
 *  @parm   LPCWSTR | pwszSrc |
 *
 *          Source buffer containing UNICODE string to be converted.
 *
 *  @parm   LPSTR | pszDst |
 *
 *          Destination buffer to receive converted ANSI string.
 *
 *  @parm   int | cchBuf |
 *
 *          Size of the destination buffer in <t CHAR>s.
 *
 *  @returns
 *
 *          On success, the number of characters copied to the output
 *          buffer is returned, including the terminating null.
 *          (For the purpose of this function, a double-byte character
 *          counts as two characters.)
 *
 */

int
SHUnicodeToAnsiCP(UINT uiCP, LPCWSTR pwszSrc, LPSTR pszDst, int cchBuf)
{
    int cchRc = 0;              /* Assume failure */
#ifdef DEBUG
#define GET_CP(uiCP)    (((uiCP) == CP_ACPNOVALIDATE) ? CP_ACP : (uiCP))
#else
#define GET_CP(uiCP)    uiCP
#endif

    RIPMSG(IS_VALID_STRING_PTRW(pwszSrc, -1), "Caller of SHUnicodeToAnsiCP passed in a NULL pwszSrc!");
    ASSERT(IS_VALID_WRITE_BUFFER(pszDst, CHAR, cchBuf));

    /*
     *  Sanity check - NULL source string is treated as a null string.
     */
    if (pwszSrc == NULL) {
        pwszSrc = L"";
    }

    /*
     *  Sanity check - Output buffer must be non-NULL and must be of
     *  nonzero size.
     */
    if (pszDst && cchBuf) {

        int cwchSrc;

        pszDst[0] = 0;          /* In case of error */

        cwchSrc = lstrlenW(pwszSrc) + 1; /* Yes, Win9x has lstrlenW */

        /*
         *  Decide what kind of code page it is.
         */
        switch (GET_CP(uiCP)) {
        case 1200:                      // UCS-2 (Unicode)
            uiCP = 65001;
            // Fall through
        case 50000:                     // "User Defined"
        case 65000:                     // UTF-7
        case 65001:                     // UTF-8
            cchRc = SHUnicodeToAnsiInetCP(GET_CP(uiCP), pwszSrc, cwchSrc, pszDst, cchBuf);
            break;

        default:
            cchRc = SHUnicodeToAnsiNativeCP(uiCP, pwszSrc, cwchSrc, pszDst, cchBuf);
            break;
        }
    }

    return cchRc;
}

/*
 *  @doc    EXTERNAL
 *
 *  @func   int | SHUnicodeToAnsi |
 *
 *          Convert a UNICODE string to an ANSI string via the
 *          <c CP_ACP> code page.  If the source string is too large
 *          for the destination buffer, then as many characters as
 *          possible are copied.  Care is taken not to break a double-byte
 *          character.
 *
 *          The resulting output string is always null-terminated.
 *
 *  @parm   LPCWSTR | pwszSrc |
 *
 *          Source buffer containing UNICODE string to be converted.
 *
 *  @parm   LPSTR | pszDst |
 *
 *          Destination buffer to receive converted ANSI string.
 *
 *  @parm   int | cchBuf |
 *
 *          Size of the destination buffer in <t CHAR>s.
 *
 *  @returns
 *
 *          On success, the number of characters copied to the output
 *          buffer is returned, including the terminating null.
 *          (For the purpose of this function, a double-byte character
 *          counts as two characters.)
 *
 */

int
SHUnicodeToAnsi(LPCWSTR pwszSrc, LPSTR pszDst, int cchBuf)
{
    return SHUnicodeToAnsiCP(CP_ACP, pwszSrc, pszDst, cchBuf);
}
