/*
 * SHAppBarMessage implementation
 *
 * Copyright 2008 Vincent Povirk for CodeWeavers
 *
 * 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
 *
 * TODO: freedesktop _NET_WM_STRUT integration
 *
 * TODO: find when a fullscreen app is in the foreground and send FULLSCREENAPP
 *  notifications
 *
 * TODO: detect changes in the screen size and send ABN_POSCHANGED ?
 *
 * TODO: multiple monitor support
 */

#include "wine/unicode.h"

#include <windows.h>
#include <wine/debug.h>
#include "explorer_private.h"

#include "wine/list.h"

WINE_DEFAULT_DEBUG_CHANNEL(appbar);

struct appbar_data_msg  /* platform-independent data */
{
    LONG      hWnd;
    UINT      uCallbackMessage;
    UINT      uEdge;
    RECT      rc;
    ULONGLONG lParam;
};

struct appbar_cmd
{
    ULONG  return_map;
    DWORD  return_process;
    struct appbar_data_msg abd;
};

struct appbar_response
{
    ULONGLONG result;
    struct appbar_data_msg abd;
};

static HWND appbarmsg_window = NULL;

struct appbar_data
{
    struct list entry;
    HWND hwnd;
    UINT callback_msg;
    UINT edge;
    RECT rc;
    BOOL space_reserved;
    /* BOOL autohide; */
};

static struct list appbars = LIST_INIT(appbars);

static struct appbar_data* get_appbar(HWND hwnd)
{
    struct appbar_data* data;

    LIST_FOR_EACH_ENTRY(data, &appbars, struct appbar_data, entry)
    {
        if (data->hwnd == hwnd)
            return data;
    }

    return NULL;
}

/* send_poschanged: send ABN_POSCHANGED to every appbar except one */
static void send_poschanged(HWND hwnd)
{
    struct appbar_data* data;
    LIST_FOR_EACH_ENTRY(data, &appbars, struct appbar_data, entry)
    {
        if (data->hwnd != hwnd)
        {
            PostMessageW(data->hwnd, data->callback_msg, ABN_POSCHANGED, 0);
        }
    }
}

/* appbar_cliprect: cut out parts of the rectangle that interfere with existing appbars */
static void appbar_cliprect( HWND hwnd, RECT *rect )
{
    struct appbar_data* data;
    LIST_FOR_EACH_ENTRY(data, &appbars, struct appbar_data, entry)
    {
        if (data->hwnd == hwnd)
        {
            /* we only care about appbars that were added before this one */
            return;
        }
        if (data->space_reserved)
        {
            /* move in the side that corresponds to the other appbar's edge */
            switch (data->edge)
            {
            case ABE_BOTTOM:
                rect->bottom = min(rect->bottom, data->rc.top);
                break;
            case ABE_LEFT:
                rect->left = max(rect->left, data->rc.right);
                break;
            case ABE_RIGHT:
                rect->right = min(rect->right, data->rc.left);
                break;
            case ABE_TOP:
                rect->top = max(rect->top, data->rc.bottom);
                break;
            }
        }
    }
}

static UINT_PTR handle_appbarmessage(DWORD msg, struct appbar_data_msg *abd)
{
    struct appbar_data* data;
    HWND hwnd = LongToHandle( abd->hWnd );

    switch (msg)
    {
    case ABM_NEW:
        if (get_appbar(hwnd))
        {
            /* fail when adding an hwnd the second time */
            return FALSE;
        }

        data = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct appbar_data));
        if (!data)
        {
            WINE_ERR("out of memory\n");
            return FALSE;
        }
        data->hwnd = hwnd;
        data->callback_msg = abd->uCallbackMessage;

        list_add_tail(&appbars, &data->entry);

        return TRUE;
    case ABM_REMOVE:
        if ((data = get_appbar(hwnd)))
        {
            list_remove(&data->entry);

            send_poschanged(hwnd);

            HeapFree(GetProcessHeap(), 0, data);
        }
        else
            WINE_WARN("removing hwnd %p not on the list\n", hwnd);
        return TRUE;
    case ABM_QUERYPOS:
        if (abd->uEdge > ABE_BOTTOM)
            WINE_WARN("invalid edge %i for %p\n", abd->uEdge, hwnd);
        appbar_cliprect( hwnd, &abd->rc );
        return TRUE;
    case ABM_SETPOS:
        if (abd->uEdge > ABE_BOTTOM)
        {
            WINE_WARN("invalid edge %i for %p\n", abd->uEdge, hwnd);
            return TRUE;
        }
        if ((data = get_appbar(hwnd)))
        {
            /* calculate acceptable space */
            appbar_cliprect( hwnd, &abd->rc );

            if (!EqualRect(&abd->rc, &data->rc))
                send_poschanged(hwnd);

            /* reserve that space for this appbar */
            data->edge = abd->uEdge;
            data->rc = abd->rc;
            data->space_reserved = TRUE;
        }
        else
        {
            WINE_WARN("app sent ABM_SETPOS message for %p without ABM_ADD\n", hwnd);
        }
        return TRUE;
    case ABM_GETSTATE:
        WINE_FIXME("SHAppBarMessage(ABM_GETSTATE): stub\n");
        return ABS_ALWAYSONTOP | ABS_AUTOHIDE;
    case ABM_GETTASKBARPOS:
        WINE_FIXME("SHAppBarMessage(ABM_GETTASKBARPOS, hwnd=%p): stub\n", hwnd);
        /* Report the taskbar is at the bottom of the screen. */
        abd->rc.left = 0;
        abd->rc.right = GetSystemMetrics(SM_CXSCREEN);
        abd->rc.bottom = GetSystemMetrics(SM_CYSCREEN);
        abd->rc.top = abd->rc.bottom-1;
        abd->uEdge = ABE_BOTTOM;
        return TRUE;
    case ABM_ACTIVATE:
        return TRUE;
    case ABM_GETAUTOHIDEBAR:
        WINE_FIXME("SHAppBarMessage(ABM_GETAUTOHIDEBAR, hwnd=%p, edge=%x): stub\n", hwnd, abd->uEdge);
        return 0;
    case ABM_SETAUTOHIDEBAR:
        WINE_FIXME("SHAppBarMessage(ABM_SETAUTOHIDEBAR, hwnd=%p, edge=%x, lparam=%s): stub\n",
                   hwnd, abd->uEdge, wine_dbgstr_longlong(abd->lParam));
        return TRUE;
    case ABM_WINDOWPOSCHANGED:
        return TRUE;
    default:
        WINE_FIXME("SHAppBarMessage(%x) unimplemented\n", msg);
        return FALSE;
    }
}

static LRESULT CALLBACK appbar_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    switch (msg)
    {
    case WM_COPYDATA:
        {
            COPYDATASTRUCT* cds;
            struct appbar_cmd cmd;
            UINT_PTR result;
            HANDLE return_hproc;
            HANDLE return_map;
            LPVOID return_view;
            struct appbar_response* response;

            cds = (COPYDATASTRUCT*)lparam;
            if (cds->cbData != sizeof(struct appbar_cmd))
                return TRUE;
            CopyMemory(&cmd, cds->lpData, cds->cbData);

            result = handle_appbarmessage(cds->dwData, &cmd.abd);

            return_hproc = OpenProcess(PROCESS_DUP_HANDLE, FALSE, cmd.return_process);
            if (return_hproc == NULL)
            {
                WINE_ERR("couldn't open calling process\n");
                return TRUE;
            }

            if (!DuplicateHandle(return_hproc, UlongToHandle(cmd.return_map),
                                 GetCurrentProcess(), &return_map, 0, FALSE, DUPLICATE_SAME_ACCESS))
            {
                WINE_ERR("couldn't duplicate handle\n");
                CloseHandle(return_hproc);
                return TRUE;
            }
            CloseHandle(return_hproc);

            return_view = MapViewOfFile(return_map, FILE_MAP_WRITE, 0, 0, sizeof(struct appbar_response));

            if (return_view)
            {
                response = (struct appbar_response*)return_view;
                response->result = result;
                response->abd = cmd.abd;

                UnmapViewOfFile(return_view);
            }
            else
                WINE_ERR("couldn't map view of file\n");

            CloseHandle(return_map);
            return TRUE;
        }
    default:
        break;
    }

    return DefWindowProcW(hwnd, msg, wparam, lparam);
}

void initialize_appbar(void)
{
    WNDCLASSEXW class;
    static const WCHAR classname[] = {'W','i','n','e','A','p','p','B','a','r',0};

    /* register the appbar window class */
    ZeroMemory(&class, sizeof(class));
    class.cbSize = sizeof(class);
    class.lpfnWndProc = appbar_wndproc;
    class.hInstance = NULL;
    class.lpszClassName = classname;

    if (!RegisterClassExW(&class))
    {
        WINE_ERR("Could not register appbar message window class\n");
        return;
    }

    appbarmsg_window = CreateWindowW(classname, classname, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
    if (!appbarmsg_window)
    {
        WINE_ERR("Could not create appbar message window\n");
        return;
    }
}