#include "priv.h"
#include "unicwrap.h"

/*****************************************************************************\
    FUNCTION: SHLoadRegUIString

    DESCRIPTION:
        loads the data from the value given the hkey and
        pszValue. if the data is of the form:

        @[path\]<dllname>,-<strId>

        the string with id <strId> from <dllname> will be
        loaded. if not explicit path is provided then the
        dll will be chosen according to pluggable UI
        specifications, if possible.

        if the value's data doesn't yield a successful
        string load, then the data itself is returned

    NOTE:
        These strings are always loaded with cross codepage support.

    WARNING:
        This function can end up calling LoadLibrary and FreeLibrary.
        Therefore, you must not call SHLoadRegUIString during process
        attach or process detach.

    PARAMETERS:
        hkey        - hkey of where to look for pszValue
        pszValue    - value with text string or indirector (see above) to use
        pszOutBuf   - buffer in which to return the data or indirected string
        cchOutBuf   - size of pszOutBuf
\*****************************************************************************/

LANGID GetNormalizedLangId(DWORD dwFlag);

STDAPI
SHLoadRegUIStringW(HKEY     hkey,
                   LPCWSTR  pszValue,
                   LPWSTR   pszOutBuf,
                   UINT     cchOutBuf)
{
    HRESULT hr;

    RIP(hkey != NULL);
    RIP(hkey != INVALID_HANDLE_VALUE);
    RIP(NULL == pszValue || IS_VALID_STRING_PTRW(pszValue, -1));
    RIP(IS_VALID_WRITE_BUFFER(pszOutBuf, WCHAR, cchOutBuf));

    DEBUGWhackPathBufferW(pszOutBuf, cchOutBuf);

    // Lots of people (regfldr.cpp, for example)
    // assume they'll get back an empty string on failure,
    // so let's give the public what it wants
    if (cchOutBuf)
        pszOutBuf[0] = 0;

    hr = E_INVALIDARG;

    if (hkey != INVALID_HANDLE_VALUE &&
        hkey != NULL &&
        pszOutBuf != NULL)
    {
        DWORD   cb;
        DWORD   dwRet;
        WCHAR * pszValueDataBuf;

        hr = E_FAIL;

        // first try to get the indirected text which will
        // point to a string id in a dll somewhere... this
        // allows plugUI enabled registry UI strings

        pszValueDataBuf = pszOutBuf;
        cb = CbFromCchW(cchOutBuf);

        dwRet = SHQueryValueExW(hkey, pszValue, NULL, NULL, (LPBYTE)pszValueDataBuf, &cb);
        if (dwRet == ERROR_SUCCESS || dwRet == ERROR_MORE_DATA)
        {
            BOOL fAlloc;

            fAlloc = (dwRet == ERROR_MORE_DATA);

            // if we didn't have space, this is where we correct the problem.
            // we create a buffer big enough, load the data, and leave
            // ourselves with pszValueDataBuf pointing at a valid buffer
            // containing valid data, exactly what we hoped for in the
            // SHQueryValueExW above

            if (fAlloc)
            {
                pszValueDataBuf = new WCHAR[(cb+1)/2];
                
                if (pszValueDataBuf != NULL)
                {
                    // try to load again... overwriting dwRet on purpose
                    // because we only need to know whether we successfully filled
                    // the buffer at some point (whether then or now)
                    
                    dwRet = SHQueryValueExW(hkey, pszValue, NULL, NULL, (LPBYTE)pszValueDataBuf, &cb);
                }
                else
                {
                    hr = E_OUTOFMEMORY;
                }                
            }

            // proceed if we succesfully loaded something via one of the
            // two SHQueryValueExW calls.
            // we should have the data we want in a buffer pointed
            // to by pszValueDataBuf.
            
            if (dwRet == ERROR_SUCCESS)
            {
                hr = SHLoadIndirectString(pszValueDataBuf, pszOutBuf, cchOutBuf, NULL);
            }

            if (fAlloc && pszValueDataBuf != NULL)
            {
                delete [] pszValueDataBuf;
            }
        }
    }

    return hr;
}

STDAPI
SHLoadRegUIStringA(HKEY     hkey,
                   LPCSTR   pszValue,
                   LPSTR    pszOutBuf,
                   UINT     cchOutBuf)
{
    HRESULT     hr;

    RIP(hkey != NULL);
    RIP(hkey != INVALID_HANDLE_VALUE);
    RIP(IS_VALID_STRING_PTRA(pszValue, -1));
    RIP(IS_VALID_WRITE_BUFFER(pszOutBuf, char, cchOutBuf));

    CStrInW     strV(pszValue);
    CStrOutW    strOut(pszOutBuf, cchOutBuf);

    hr = SHLoadRegUIStringW(hkey, strV, strOut, strOut.BufSize());

    return hr;
}

HRESULT _LoadDllString(LPCWSTR pszSource, LPWSTR pszOutBuf, UINT cchOutBuf)
{
    HRESULT hr = E_FAIL;
    WCHAR * szParseBuf;
    int     nStrId;

    UINT cchSource = lstrlenW(pszSource)+1;

    szParseBuf = new WCHAR[cchSource];
    if (szParseBuf != NULL)
    {
        StrCpyW(szParseBuf, pszSource);

        // see if this is a special string reference.
        // such strings take the form [path\]dllname.dll,-123
        // where 123 is the id of the string resource
        // note that reference by index is not permitted

        nStrId = PathParseIconLocationW(szParseBuf);
        nStrId *= -1;

        if (nStrId > 0)
        {
            LPWSTR      pszDllName;
            HINSTANCE   hinst;
            BOOL        fUsedMLLoadLibrary = FALSE;

            pszDllName = PathFindFileNameW(szParseBuf);
            ASSERT(pszDllName >= szParseBuf);

            // try loading the dll with MLLoadLibrary, but
            // only if an explicit path was not provided.
            // we assume an explicit path means that
            // the caller knows precisely which dll is needed
            // use MLLoadLibrary first, otherwise we'll miss
            // out chance to have plugUI behavior

            hinst = NULL;
            if (pszDllName == szParseBuf)
            {
                if (StrStrI(pszDllName, L"LC.DLL"))
                {
                    // note: using HINST_THISDLL (below) is sort of a hack because that's
                    // techinically supposed to be the *parent* dll's hinstance...
                    // however we get called from lots of places and therefore
                    // don't know the parent dll, and the hinst for browseui.dll
                    // is good enough since all the hinst is really used for is to
                    // find the path to check if the install language is the
                    // currently selected UI language. this will usually be
                    // something like "\winnt\system32"

                    hinst = MLLoadLibraryW(pszDllName, HINST_THISDLL, ML_CROSSCODEPAGE);
                    fUsedMLLoadLibrary = (hinst != NULL);
                }
                else
                    hinst = LoadLibraryExWrapW(pszDllName, NULL, LOAD_LIBRARY_AS_DATAFILE);
            }

            if (!hinst)
            {
                // our last chance to load something is if a full
                // path was provided... if there's a full path it
                // will start at the beginning of the szParseBuf buffer

                if (pszDllName > szParseBuf)
                {
                    // don't bother if the file isn't there
                    // failling in LoadLibrary is slow
                    if (PathFileExistsW(szParseBuf))
                    {
                        hinst = LoadLibraryExWrapW(szParseBuf, NULL, LOAD_LIBRARY_AS_DATAFILE);
                    }
                }
            }

            if (hinst)
            {
                // dll found, so load the string
                if (LoadStringWrapW(hinst, nStrId, pszOutBuf, cchOutBuf))
                {
                    hr = S_OK;
                }
                else
                {
                    TraceMsg(TF_WARNING,
                             "SHLoadRegUIString(): Failure loading string %d from module %ws for valid load request %ws.",
                             nStrId,
                             szParseBuf,
                             pszSource);
                }

                if (fUsedMLLoadLibrary)
                {
                    MLFreeLibrary(hinst);
                }
                else
                {
                    FreeLibrary(hinst);
                }
            }
        }

        delete [] szParseBuf;
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }

    return hr;
}

inline BOOL _CanCacheMUI()
{
    if (!g_bRunningOnNT)
        return (GetNormalizedLangId(ML_CROSSCODEPAGE_NT) == MLGetUILanguage());
    return TRUE;
}

// Note: pszSource and pszOutBuf may be the same buffer
LWSTDAPI SHLoadIndirectString(LPCWSTR pszSource, LPWSTR pszOutBuf, UINT cchOutBuf, void **ppvReserved)
{
    HRESULT hr = E_FAIL;

    RIP(IS_VALID_WRITE_BUFFER(pszOutBuf, WCHAR, cchOutBuf));
    RIP(!ppvReserved);

    if (pszSource[0] == L'@') // "@dllname,-id" or "@dllname,-id?lid,string"
    {
        LPWSTR pszResource = StrDupW(pszSource);
        if (pszResource)
        {
            LANGID lidUI =0;
            //  the LidString is there to support our old caching model.
            //  the new caching model doesnt require any work for the caller
            LPWSTR pszLidString = StrChrW(pszResource+1, L'?');
            DWORD cchResource = lstrlen(pszResource);

            //  used to use '@' as the second delimiter as well.
            //  but it has collisions with filesystem paths.
            if (!pszLidString)
                pszLidString = StrChrW(pszResource+1, L'@');
                
            if (pszLidString)
            {
                cchResource = (DWORD)(pszLidString - pszResource);
                // NULL terminate the dll,id just in case we need to actually load
                pszResource[cchResource] = 0;
            }

            DWORD cb = CbFromCchW(cchOutBuf);
            hr = SKGetValue(SHELLKEY_HKCULM_MUICACHE, NULL, pszResource, NULL, pszOutBuf, &cb);
            
            if (FAILED(hr))
            {
                WCHAR wszDllId[MAX_PATH + 1 + 6]; // path + comma + -65536
                SHExpandEnvironmentStringsW(pszResource+1, wszDllId, ARRAYSIZE(wszDllId));
                hr = _LoadDllString(wszDllId, pszOutBuf, cchOutBuf);

                // Might as well write the new string out so we don't have to load the DLL next time through
                // but we don't write cross codepage string on Win9x
                if (SUCCEEDED(hr) && _CanCacheMUI())
                {
                    SKSetValue(SHELLKEY_HKCULM_MUICACHE, NULL, pszResource, REG_SZ, pszOutBuf, CbFromCchW(lstrlenW(pszOutBuf)+1));
                }
            }
            LocalFree(pszResource);

        }
        else
            hr = E_OUTOFMEMORY;

        if (FAILED(hr))
        {
            if (cchOutBuf)
                pszOutBuf[0] = L'\0'; // can't hand out an "@shell32.dll,-525" string
        }
    }
    else
    {
        if (pszOutBuf != pszSource)
            StrCpyN(pszOutBuf, pszSource, cchOutBuf);

        hr = S_OK;
    }

    return hr;
}