///////////////////////////////////////////////////////////////////////////////
// Name:        src/msw/stdpaths.cpp
// Purpose:     wxStandardPaths implementation for Win32
// Author:      Vadim Zeitlin
// Created:     2004-10-19
// Copyright:   (c) 2004 Vadim Zeitlin <vadim@wxwidgets.org>
// Licence:     wxWindows licence
///////////////////////////////////////////////////////////////////////////////

// ============================================================================
// declarations
// ============================================================================

// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------

// for compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"


#if wxUSE_STDPATHS

#include "wx/stdpaths.h"

#ifndef WX_PRECOMP
    #include "wx/utils.h"
#endif //WX_PRECOMP

#include "wx/dynlib.h"
#include "wx/filename.h"

#include "wx/msw/private.h"
#include "wx/msw/wrapshl.h"
#include "wx/msw/private/cotaskmemptr.h"
#include <initguid.h>

// ----------------------------------------------------------------------------
// types
// ----------------------------------------------------------------------------

typedef HRESULT (WINAPI *SHGetKnownFolderPath_t)(const GUID&, DWORD, HANDLE, PWSTR *);

// ----------------------------------------------------------------------------
// constants
// ----------------------------------------------------------------------------

// used in our wxLogTrace messages
#define TRACE_MASK wxT("stdpaths")

#ifndef CSIDL_PERSONAL
    #define CSIDL_PERSONAL        0x0005
#endif

// ----------------------------------------------------------------------------
// module globals
// ----------------------------------------------------------------------------

namespace
{

DEFINE_GUID(wxFOLDERID_Downloads,
    0x374de290, 0x123f, 0x4565, 0x91, 0x64, 0x39, 0xc4, 0x92, 0x5e, 0x46, 0x7b);

struct ShellFunctions
{
    ShellFunctions()
    {
        pSHGetKnownFolderPath = nullptr;
        initialized = false;
    }

    SHGetKnownFolderPath_t pSHGetKnownFolderPath;

    bool initialized;
};

// in spite of using a static variable, this is MT-safe as in the worst case it
// results in initializing the function pointer several times -- but this is
// harmless
ShellFunctions gs_shellFuncs;

// ----------------------------------------------------------------------------
// private functions
// ----------------------------------------------------------------------------

void ResolveShellFunctions()
{
    // start with the newest functions, fall back to the oldest ones
    // first check for SHGetFolderPath (shell32.dll 5.0)
    wxString shellDllName(wxT("shell32"));

    wxDynamicLibrary dllShellFunctions( shellDllName );
    if ( !dllShellFunctions.IsLoaded() )
    {
        wxLogTrace(TRACE_MASK, wxT("Failed to load %s.dll"), shellDllName );
    }

    // don't give errors if the functions are unavailable, we're ready to deal
    // with this
    wxLogNull noLog;

    gs_shellFuncs.pSHGetKnownFolderPath = (SHGetKnownFolderPath_t)
        dllShellFunctions.GetSymbol("SHGetKnownFolderPath");

    // shell32.dll is going to be unloaded, but it still remains in memory
    // because we also link to it statically, so it's ok

    gs_shellFuncs.initialized = true;
}

} // anonymous namespace

// ============================================================================
// wxStandardPaths implementation
// ============================================================================

// ----------------------------------------------------------------------------
// private helpers
// ----------------------------------------------------------------------------

/* static */
wxString wxStandardPaths::DoGetDirectory(int csidl)
{
    wxString dir;
    HRESULT hr = E_FAIL;

    hr = ::SHGetFolderPath
            (
            nullptr,               // parent window, not used
            csidl,
            nullptr,               // access token (current user)
            SHGFP_TYPE_CURRENT, // current path, not just default value
            wxStringBuffer(dir, MAX_PATH)
            );

    if ( hr == E_FAIL )
    {
        // directory doesn't exist, maybe we can get its default value?
        hr = ::SHGetFolderPath
                (
                nullptr,
                csidl,
                nullptr,
                SHGFP_TYPE_DEFAULT,
                wxStringBuffer(dir, MAX_PATH)
                );
    }

    return dir;
}

wxString wxStandardPaths::DoGetKnownFolder(const GUID& rfid)
{
    if (!gs_shellFuncs.initialized)
        ResolveShellFunctions();

    wxString dir;

    if ( gs_shellFuncs.pSHGetKnownFolderPath )
    {
        wxCoTaskMemPtr<wchar_t> pDir;
        HRESULT hr = gs_shellFuncs.pSHGetKnownFolderPath(rfid, 0, 0, &pDir);
        if ( SUCCEEDED(hr) )
        {
            dir = pDir;
        }
    }

    return dir;
}


wxString wxStandardPaths::GetAppDir() const
{
    if ( m_appDir.empty() )
    {
        m_appDir = wxFileName(wxGetFullModuleName()).GetPath();
    }

    return m_appDir;
}

wxString wxStandardPaths::GetUserDir(Dir userDir) const
{
    int csidl;
    switch (userDir)
    {
        case Dir_Cache:
            csidl = CSIDL_LOCAL_APPDATA;
            break;
        case Dir_Config:
            csidl = CSIDL_APPDATA;
            break;
        case Dir_Desktop:
            csidl = CSIDL_DESKTOPDIRECTORY;
            break;
        case Dir_Downloads:
        {
            csidl = CSIDL_PERSONAL;
            // Downloads folder is only available since Vista
            wxString dir = DoGetKnownFolder(wxFOLDERID_Downloads);
            if ( !dir.empty() )
                return dir;
            break;
        }
        case Dir_Music:
            csidl = CSIDL_MYMUSIC;
            break;
        case Dir_Pictures:
            csidl = CSIDL_MYPICTURES;
            break;
        case Dir_Videos:
            csidl = CSIDL_MYVIDEO;
            break;
        default:
            csidl = CSIDL_PERSONAL;
            break;
    }

    return DoGetDirectory(csidl);
}

// ----------------------------------------------------------------------------
// MSW-specific functions
// ----------------------------------------------------------------------------

void wxStandardPaths::IgnoreAppSubDir(const wxString& subdirPattern)
{
    wxFileName fn = wxFileName::DirName(GetAppDir());

    if ( !fn.GetDirCount() )
    {
        // no last directory to ignore anyhow
        return;
    }

    const wxString lastdir = fn.GetDirs().Last().Lower();
    if ( lastdir.Matches(subdirPattern.Lower()) )
    {
        fn.RemoveLastDir();

        // store the cached value so that subsequent calls to GetAppDir() will
        // reuse it instead of using just the program binary directory
        m_appDir = fn.GetPath();
    }
}

void wxStandardPaths::IgnoreAppBuildSubDirs()
{
    IgnoreAppSubDir("debug");
    IgnoreAppSubDir("release");

    // there can also be an architecture-dependent parent directory, ignore it
    // as well
#ifdef __WIN64__
    IgnoreAppSubDir("Win64");
    IgnoreAppSubDir("x64");
    IgnoreAppSubDir("x86_64");
    IgnoreAppSubDir("ARM64");
#else // __WIN32__
    IgnoreAppSubDir("Win32");
    IgnoreAppSubDir("x86");
    IgnoreAppSubDir("ARM");
#endif // __WIN64__/__WIN32__

    wxString compilerPrefix;
#ifdef __VISUALC__
    compilerPrefix = "vc";
#elif defined(__GNUG__)
    compilerPrefix = "gcc";
#else
    return;
#endif

    IgnoreAppSubDir(compilerPrefix + "_msw*");
}

void wxStandardPaths::DontIgnoreAppSubDir()
{
    // this will force the next call to GetAppDir() to use the program binary
    // path as the application directory
    m_appDir.clear();
}

/* static */
wxString wxStandardPaths::MSWGetShellDir(int csidl)
{
    return DoGetDirectory(csidl);
}

// ----------------------------------------------------------------------------
// public functions
// ----------------------------------------------------------------------------

wxStandardPaths::wxStandardPaths()
{
    // make it possible to run uninstalled application from the build directory
    IgnoreAppBuildSubDirs();
}

wxString wxStandardPaths::GetExecutablePath() const
{
    return wxGetFullModuleName();
}

wxString wxStandardPaths::GetConfigDir() const
{
    return AppendAppInfo(DoGetDirectory(CSIDL_COMMON_APPDATA));
}

wxString wxStandardPaths::GetUserConfigDir() const
{
    return DoGetDirectory(CSIDL_APPDATA);
}

wxString wxStandardPaths::GetDataDir() const
{
    // under Windows each program is usually installed in its own directory and
    // so its datafiles are in the same directory as its main executable
    return GetAppDir();
}

wxString wxStandardPaths::GetUserDataDir() const
{
    return AppendAppInfo(GetUserConfigDir());
}

wxString wxStandardPaths::GetUserLocalDataDir() const
{
    return AppendAppInfo(DoGetDirectory(CSIDL_LOCAL_APPDATA));
}

wxString wxStandardPaths::GetPluginsDir() const
{
    // there is no standard location for plugins, suppose they're in the same
    // directory as the .exe
    return GetAppDir();
}


wxString
wxStandardPaths::MakeConfigFileName(const wxString& basename,
                                    ConfigFileConv WXUNUSED(conv)) const
{
    wxFileName fn(wxEmptyString, basename);
    fn.SetExt(wxT("ini"));
    return fn.GetFullName();
}

wxString wxStandardPaths::GetSharedLibrariesDir() const
{
    wxFileName fn( GetExecutablePath() );
    return fn.GetPath();
}

// ============================================================================
// wxStandardPathsWin16 implementation
// ============================================================================

wxString wxStandardPathsWin16::GetConfigDir() const
{
    // this is for compatibility with earlier wxFileConfig versions
    // which used the Windows directory for the global files
    wxString dir;
    if ( !::GetWindowsDirectory(wxStringBuffer(dir, MAX_PATH), MAX_PATH) )
    {
        wxLogLastError(wxT("GetWindowsDirectory"));
    }

    return dir;
}

wxString wxStandardPathsWin16::GetUserConfigDir() const
{
    // again, for wxFileConfig which uses $HOME for its user config file
    return wxGetHomeDir();
}

#endif // wxUSE_STDPATHS
