/*
 * Shell Desktop
 *
 * Copyright 2008 Thomas Bluemel
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "precomp.h"

WINE_DEFAULT_DEBUG_CHANNEL(desktop);

BOOL WINAPI SetShellWindowEx(HWND, HWND);

#define SHDESK_TAG 0x4b534544

static const WCHAR szProgmanClassName [] = { 'P', 'r', 'o', 'g', 'm', 'a', 'n' };
static const WCHAR szProgmanWindowName [] = {
    'P', 'r', 'o', 'g', 'r', 'a', 'm', ' ', 'M', 'a', 'n', 'a', 'g', 'e', 'r'
};

class CDesktopBrowser :
    public CComObjectRootEx<CComMultiThreadModelNoCS>,
    public IShellBrowser,
    public ICommDlgBrowser,
    public IServiceProvider
{
public:
    DWORD Tag;
private:
    HWND hWnd;
    HWND hWndShellView;
    HWND hWndDesktopListView;
    CComPtr<IShellDesktopTray>        ShellDesk;
    CComPtr<IShellView>                DesktopView;
    IShellBrowser *DefaultShellBrowser;
    LPITEMIDLIST pidlDesktopDirectory;
    LPITEMIDLIST pidlDesktop;
public:
    CDesktopBrowser();
    ~CDesktopBrowser();
    HRESULT Initialize(HWND hWndx, IShellDesktopTray *ShellDeskx);
    HWND FindDesktopListView();
    BOOL CreateDeskWnd();
    HWND DesktopGetWindowControl(IN UINT id);
    static LRESULT CALLBACK ProgmanWindowProc(IN HWND hwnd, IN UINT uMsg, IN WPARAM wParam, IN LPARAM lParam);
    static BOOL MessageLoop();

    // *** IOleWindow methods ***
    virtual HRESULT STDMETHODCALLTYPE GetWindow(HWND *lphwnd);
    virtual HRESULT STDMETHODCALLTYPE ContextSensitiveHelp(BOOL fEnterMode);

    // *** IShellBrowser methods ***
    virtual HRESULT STDMETHODCALLTYPE InsertMenusSB(HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths);
    virtual HRESULT STDMETHODCALLTYPE SetMenuSB(HMENU hmenuShared, HOLEMENU holemenuRes, HWND hwndActiveObject);
    virtual HRESULT STDMETHODCALLTYPE RemoveMenusSB(HMENU hmenuShared);
    virtual HRESULT STDMETHODCALLTYPE SetStatusTextSB(LPCOLESTR pszStatusText);
    virtual HRESULT STDMETHODCALLTYPE EnableModelessSB(BOOL fEnable);
    virtual HRESULT STDMETHODCALLTYPE TranslateAcceleratorSB(MSG *pmsg, WORD wID);
    virtual HRESULT STDMETHODCALLTYPE BrowseObject(LPCITEMIDLIST pidl, UINT wFlags);
    virtual HRESULT STDMETHODCALLTYPE GetViewStateStream(DWORD grfMode, IStream **ppStrm);
    virtual HRESULT STDMETHODCALLTYPE GetControlWindow(UINT id, HWND *lphwnd);
    virtual HRESULT STDMETHODCALLTYPE SendControlMsg(UINT id, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *pret);
    virtual HRESULT STDMETHODCALLTYPE QueryActiveShellView(struct IShellView **ppshv);
    virtual HRESULT STDMETHODCALLTYPE OnViewWindowActive(struct IShellView *ppshv);
    virtual HRESULT STDMETHODCALLTYPE SetToolbarItems(LPTBBUTTON lpButtons, UINT nButtons, UINT uFlags);

    // *** ICommDlgBrowser methods ***
    virtual HRESULT STDMETHODCALLTYPE OnDefaultCommand(struct IShellView *ppshv);
    virtual HRESULT STDMETHODCALLTYPE OnStateChange(struct IShellView *ppshv, ULONG uChange);
    virtual HRESULT STDMETHODCALLTYPE IncludeObject(struct IShellView *ppshv, LPCITEMIDLIST pidl);

    // *** IServiceProvider methods ***
    virtual HRESULT STDMETHODCALLTYPE QueryService(REFGUID guidService, REFIID riid, void **ppvObject);

    BEGIN_COM_MAP(CDesktopBrowser)
        COM_INTERFACE_ENTRY_IID(IID_IOleWindow, IOleWindow)
        COM_INTERFACE_ENTRY_IID(IID_IShellBrowser, IShellBrowser)
        COM_INTERFACE_ENTRY_IID(IID_ICommDlgBrowser, ICommDlgBrowser)
        COM_INTERFACE_ENTRY_IID(IID_IServiceProvider, IServiceProvider)
    END_COM_MAP()
};

CDesktopBrowser::CDesktopBrowser()
{
    Tag = SHDESK_TAG;
    hWnd = NULL;
    hWndShellView = NULL;
    hWndDesktopListView = NULL;
    DefaultShellBrowser = NULL;
    pidlDesktopDirectory = NULL;
    pidlDesktop = NULL;
}

CDesktopBrowser::~CDesktopBrowser()
{
    if (DesktopView.p != NULL)
    {
        if (hWndShellView != NULL)
            DesktopView->DestroyViewWindow();

        hWndShellView = NULL;
        hWndDesktopListView = NULL;
    }

    if (pidlDesktopDirectory != NULL)
    {
        ILFree(pidlDesktopDirectory);
        pidlDesktopDirectory = NULL;
    }

    if (pidlDesktop != NULL)
    {
        ILFree(pidlDesktop);
        pidlDesktop = NULL;
    }
}

HRESULT CDesktopBrowser::Initialize(HWND hWndx, IShellDesktopTray *ShellDeskx)
{
    CComPtr<IShellFolder>    psfDesktopFolder;
    CSFV                    csfv;
    HRESULT                    hRet;

    hWnd = hWndx;
    ShellDesk = ShellDeskx;
    ShellDesk->AddRef();

    pidlDesktopDirectory = SHCloneSpecialIDList(hWnd, CSIDL_DESKTOPDIRECTORY, FALSE);
    hRet = SHGetSpecialFolderLocation(hWnd, CSIDL_DESKTOP, &pidlDesktop);
    if (FAILED(hRet))
        return hRet;

    hRet = SHGetDesktopFolder(&psfDesktopFolder);
    if (FAILED(hRet))
        return hRet;

    ZeroMemory(&csfv, sizeof(csfv));
    csfv.cbSize = sizeof(csfv);
    csfv.pshf = psfDesktopFolder;
    csfv.psvOuter = NULL;

    hRet = SHCreateShellFolderViewEx(&csfv, &DesktopView);

    return hRet;
}

static CDesktopBrowser *SHDESK_Create(HWND hWnd, LPCREATESTRUCT lpCreateStruct)
{
    IShellDesktopTray        *ShellDesk;
    CComObject<CDesktopBrowser>        *pThis;
    HRESULT                    hRet;

    ShellDesk = static_cast<IShellDesktopTray *>(lpCreateStruct->lpCreateParams);
    if (ShellDesk == NULL)
    {
        WARN("No IShellDesk interface provided!");
        return NULL;
    }

    pThis = new CComObject<CDesktopBrowser>;
    if (pThis == NULL)
        return NULL;
    pThis->AddRef();

    hRet = pThis->Initialize(hWnd, ShellDesk);
    if (FAILED(hRet))
    {
        pThis->Release();
        return NULL;
    }

    return pThis;
}

HWND CDesktopBrowser::FindDesktopListView()
{
    return FindWindowExW(hWndShellView, NULL, WC_LISTVIEW, NULL);
}

BOOL CDesktopBrowser::CreateDeskWnd()
{
    FOLDERSETTINGS fs;
    RECT rcClient;
    HRESULT hRet;

    if (!GetClientRect(hWnd, &rcClient))
    {
        return FALSE;
    }

    fs.ViewMode = FVM_ICON;
    fs.fFlags = FWF_DESKTOP | FWF_NOCLIENTEDGE | FWF_NOSCROLL | FWF_TRANSPARENT;
    hRet = DesktopView->CreateViewWindow(NULL, &fs, static_cast<IShellBrowser *>(this), &rcClient, &hWndShellView);
    if (!SUCCEEDED(hRet))
        return FALSE;

    SetShellWindowEx(hWnd, FindDesktopListView());

#if 1
    /* A windows 8 specific hack */
    ::ShowWindow(hWndShellView, SW_SHOW);
    ::ShowWindow(FindDesktopListView(), SW_SHOW);
#endif

    return TRUE;
}

HRESULT STDMETHODCALLTYPE CDesktopBrowser::GetWindow(HWND *phwnd)
{
    if (hWnd != NULL)
    {
        *phwnd = hWnd;
        return S_OK;
    }

    *phwnd = NULL;
    return E_UNEXPECTED;
}

HRESULT STDMETHODCALLTYPE CDesktopBrowser::ContextSensitiveHelp(BOOL fEnterMode)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE CDesktopBrowser::InsertMenusSB(HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE CDesktopBrowser::SetMenuSB(HMENU hmenuShared, HOLEMENU holemenuRes, HWND hwndActiveObject)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE CDesktopBrowser::RemoveMenusSB(HMENU hmenuShared)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE CDesktopBrowser::SetStatusTextSB(LPCOLESTR lpszStatusText)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE CDesktopBrowser::EnableModelessSB(BOOL fEnable)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE CDesktopBrowser::TranslateAcceleratorSB(LPMSG lpmsg, WORD wID)
{
    return S_FALSE;
}

typedef HRESULT(WINAPI *SH_OPEN_NEW_FRAME)(LPITEMIDLIST pidl, IUnknown *paramC, long param10, long param14);

HRESULT STDMETHODCALLTYPE CDesktopBrowser::BrowseObject(LPCITEMIDLIST pidl, UINT wFlags)
{
    /* FIXME: Implement executing filebrowser.exe and somehow pass the pidl to it */

    /* Returning failure here will make windows 7 and 8 to use the default file browser */
    return E_FAIL;
}

HRESULT STDMETHODCALLTYPE CDesktopBrowser::GetViewStateStream(DWORD grfMode, IStream **ppStrm)
{
    return E_NOTIMPL;
}

HWND CDesktopBrowser::DesktopGetWindowControl(IN UINT id)
{
    switch (id)
    {
    case FCW_TOOLBAR:
    case FCW_STATUS:
    case FCW_TREE:
    case FCW_PROGRESS:
        return NULL;

    default:
        return NULL;
    }

}

HRESULT STDMETHODCALLTYPE CDesktopBrowser::GetControlWindow(UINT id, HWND *lphwnd)
{
    HWND hWnd;

    hWnd = DesktopGetWindowControl(id);
    if (hWnd != NULL)
    {
        *lphwnd = hWnd;
        return S_OK;
    }

    *lphwnd = NULL;
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE CDesktopBrowser::SendControlMsg(UINT id, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *pret)
{
    HWND                        hWnd;

    if (pret == NULL)
        return E_POINTER;

    hWnd = DesktopGetWindowControl(id);
    if (hWnd != NULL)
    {
        *pret = SendMessageW(hWnd, uMsg, wParam, lParam);
        return S_OK;
    }

    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE CDesktopBrowser::QueryActiveShellView(IShellView **ppshv)
{
    *ppshv = DesktopView;
    if (DesktopView != NULL)
        DesktopView->AddRef();

    return S_OK;
}

HRESULT STDMETHODCALLTYPE CDesktopBrowser::OnViewWindowActive(IShellView *ppshv)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE CDesktopBrowser::SetToolbarItems(LPTBBUTTON lpButtons, UINT nButtons, UINT uFlags)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE CDesktopBrowser::OnDefaultCommand(IShellView *ppshv)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE CDesktopBrowser::OnStateChange(IShellView *ppshv, ULONG uChange)
{
    return S_OK;
}

HRESULT STDMETHODCALLTYPE CDesktopBrowser::IncludeObject(IShellView *ppshv, LPCITEMIDLIST pidl)
{
    return S_OK;
}

HRESULT STDMETHODCALLTYPE CDesktopBrowser::QueryService(REFGUID guidService, REFIID riid, PVOID *ppv)
{
    /* FIXME - handle guidService */
    return QueryInterface(riid, ppv);
}

BOOL CDesktopBrowser::MessageLoop()
{
    MSG Msg;
    BOOL bRet;

    while ((bRet = GetMessageW(&Msg, NULL, 0, 0)) != 0)
    {
        if (bRet != -1)
        {
            TranslateMessage(&Msg);
            DispatchMessageW(&Msg);
        }
    }

    return TRUE;
}

LRESULT CALLBACK CDesktopBrowser::ProgmanWindowProc(IN HWND hwnd, IN UINT uMsg, IN WPARAM wParam, IN LPARAM lParam)
{
    CDesktopBrowser *pThis = NULL;
    LRESULT Ret = FALSE;

    if (uMsg != WM_NCCREATE)
    {
        pThis = reinterpret_cast<CDesktopBrowser*>(GetWindowLongPtrW(hwnd, 0));
        if (pThis == NULL)
            goto DefMsgHandler;
    }

    if (pThis != NULL || uMsg == WM_NCCREATE)
    {
        switch (uMsg)
        {
        case WM_ERASEBKGND:
            return (LRESULT) PaintDesktop(reinterpret_cast<HDC>(wParam));

        case WM_GETISHELLBROWSER:
            Ret = reinterpret_cast<LRESULT>(static_cast<IShellBrowser *>(pThis));
            break;

        case WM_SIZE:
            if (wParam == SIZE_MINIMIZED)
            {
                /* Hey, we're the desktop!!! */
                ShowWindow(hwnd,
                    SW_RESTORE);
            }
            else
            {
                RECT rcDesktop;

                rcDesktop.left = GetSystemMetrics(SM_XVIRTUALSCREEN);
                rcDesktop.top = GetSystemMetrics(SM_YVIRTUALSCREEN);
                rcDesktop.right = GetSystemMetrics(SM_CXVIRTUALSCREEN);
                rcDesktop.bottom = GetSystemMetrics(SM_CYVIRTUALSCREEN);

                /* FIXME: Update work area */
            }
            break;

        case WM_SYSCOLORCHANGE:
        case WM_SETTINGCHANGE:
        {
            if (uMsg == WM_SYSCOLORCHANGE || wParam == SPI_SETDESKWALLPAPER || wParam == 0)
            {
                if (pThis->hWndShellView != NULL)
                {
                    /* Forward the message */
                    SendMessageW(pThis->hWndShellView,
                        uMsg,
                        wParam,
                        lParam);
                }
            }
            break;
        }

        case WM_CREATE:
        {
            pThis->ShellDesk->RegisterDesktopWindow(pThis->hWnd);

            if (!pThis->CreateDeskWnd())
                WARN("Could not create the desktop view control!\n");
            break;
        }

        case WM_NCCREATE:
        {
            LPCREATESTRUCT CreateStruct = reinterpret_cast<LPCREATESTRUCT>(lParam);
            pThis = SHDESK_Create(hwnd, CreateStruct);
            if (pThis == NULL)
            {
                WARN("Failed to create desktop structure\n");
                break;
            }

            SetWindowLongPtrW(hwnd,
                0,
                reinterpret_cast<LONG_PTR>(pThis));
            Ret = TRUE;
            break;
        }

        case WM_NCDESTROY:
        {
            pThis->Release();
            break;
        }

        default:
        DefMsgHandler :
            Ret = DefWindowProcW(hwnd, uMsg, wParam, lParam);
                      break;
        }
    }

    return Ret;
}

static BOOL
RegisterProgmanWindowClass(VOID)
{
    WNDCLASSW wcProgman;

    wcProgman.style = CS_DBLCLKS;
    wcProgman.lpfnWndProc = CDesktopBrowser::ProgmanWindowProc;
    wcProgman.cbClsExtra = 0;
    wcProgman.cbWndExtra = sizeof(CDesktopBrowser *);
    wcProgman.hInstance = shell32_hInstance;
    wcProgman.hIcon = NULL;
    wcProgman.hCursor = LoadCursorW(NULL, IDC_ARROW);
    wcProgman.hbrBackground = (HBRUSH) (COLOR_BACKGROUND + 1);
    wcProgman.lpszMenuName = NULL;
    wcProgman.lpszClassName = szProgmanClassName;

    return RegisterClassW(&wcProgman) != 0;
}


/*************************************************************************
 * SHCreateDesktop            [SHELL32.200]
 *
 */
HANDLE WINAPI SHCreateDesktop(IShellDesktopTray *ShellDesk)
{
    HWND hWndDesk;
    RECT rcDesk;

    if (ShellDesk == NULL)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return NULL;
    }

    if (RegisterProgmanWindowClass() == 0)
    {
        WARN("Failed to register the Progman window class!\n");
        return NULL;
    }

    rcDesk.left = GetSystemMetrics(SM_XVIRTUALSCREEN);
    rcDesk.top = GetSystemMetrics(SM_YVIRTUALSCREEN);
    rcDesk.right = rcDesk.left + GetSystemMetrics(SM_CXVIRTUALSCREEN);
    rcDesk.bottom = rcDesk.top + GetSystemMetrics(SM_CYVIRTUALSCREEN);

    if (IsRectEmpty(&rcDesk))
    {
        rcDesk.left = rcDesk.top = 0;
        rcDesk.right = GetSystemMetrics(SM_CXSCREEN);
        rcDesk.bottom = GetSystemMetrics(SM_CYSCREEN);
    }

    hWndDesk = CreateWindowExW(0, szProgmanClassName, szProgmanWindowName,
        WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
        rcDesk.left, rcDesk.top, rcDesk.right, rcDesk.bottom,
        NULL, NULL, shell32_hInstance, reinterpret_cast<LPVOID>(ShellDesk));
    if (hWndDesk != NULL)
        return (HANDLE) GetWindowLongPtrW(hWndDesk, 0);

    return NULL;
}

/*************************************************************************
 * SHCreateDesktop            [SHELL32.201]
 *
 */
BOOL WINAPI SHDesktopMessageLoop(HANDLE hDesktop)
{
    CDesktopBrowser *Desk = reinterpret_cast<CDesktopBrowser *>(hDesktop);

    if (Desk == NULL || Desk->Tag != SHDESK_TAG)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    return Desk->MessageLoop();
}