/*
 * PROJECT:     ReactOS api tests
 * LICENSE:     LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later)
 * PURPOSE:     Test for SHAppBarMessage
 * COPYRIGHT:   Copyright 2020 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
 */

#include "shelltest.h"
#include <windowsx.h>
#include <shlwapi.h>
#include <stdio.h>

/* Based on https://github.com/katahiromz/AppBarSample */

//#define VERBOSE

#define IDT_AUTOHIDE 1
#define IDT_AUTOUNHIDE 2

#define ID_ACTION 100

#define APPBAR_CALLBACK (WM_USER + 100)

#define LEFT_DOWN() mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
#define LEFT_UP() mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
#define MOVE(x, y) SetCursorPos((x), (y))

static const TCHAR s_szName[] = TEXT("AppBarSample");
static RECT s_rcWorkArea;
static HWND s_hwnd1 = NULL;
static HWND s_hwnd2 = NULL;

#ifdef VERBOSE
static LPCSTR MessageOfAppBar(DWORD dwMessage)
{
    static char s_buf[32];
    switch (dwMessage)
    {
    case ABM_NEW: return "ABM_NEW";
    case ABM_REMOVE: return "ABM_REMOVE";
    case ABM_QUERYPOS: return "ABM_QUERYPOS";
    case ABM_SETPOS: return "ABM_SETPOS";
    case ABM_GETSTATE: return "ABM_GETSTATE";
    case ABM_GETTASKBARPOS: return "ABM_GETTASKBARPOS";
    case ABM_ACTIVATE: return "ABM_ACTIVATE";
    case ABM_GETAUTOHIDEBAR: return "ABM_GETAUTOHIDEBAR";
    case ABM_SETAUTOHIDEBAR: return "ABM_SETAUTOHIDEBAR";
    case ABM_WINDOWPOSCHANGED: return "ABM_WINDOWPOSCHANGED";
    }
    wsprintfA(s_buf, "%lu", dwMessage);
    return s_buf;
}

static UINT WINAPI
SHAppBarMessageWrap(DWORD dwMessage, PAPPBARDATA pData)
{
    trace("SHAppBarMessage entered (dwMessage=%s, rc=(%ld, %ld, %ld, %ld))\n",
          MessageOfAppBar(dwMessage),
          pData->rc.left, pData->rc.top, pData->rc.right, pData->rc.bottom);
    UINT ret = SHAppBarMessage(dwMessage, pData);
    trace("SHAppBarMessage leaved (dwMessage=%s, rc=(%ld, %ld, %ld, %ld))\n",
          MessageOfAppBar(dwMessage),
          pData->rc.left, pData->rc.top, pData->rc.right, pData->rc.bottom);
    return ret;
}
#define SHAppBarMessage SHAppBarMessageWrap

#undef ARRAYSIZE
#define ARRAYSIZE _countof

void appbar_tprintf(const TCHAR *fmt, ...)
{
    TCHAR szText[512];
    va_list va;
    va_start(va, fmt);
    wvsprintf(szText, fmt, va);
#ifdef UNICODE
    printf("%ls", szText);
#else
    printf("%s", szText);
#endif
    va_end(va);
}

#define MSGDUMP_TPRINTF appbar_tprintf
#include "msgdump.h"

#endif  // def VERBOSE

void SlideWindow(HWND hwnd, LPRECT prc)
{
#define SLIDE_HIDE 400
#define SLIDE_SHOW 150
    RECT rcOld, rcNew = *prc;
    GetWindowRect(hwnd, &rcOld);

    BOOL fShow = (rcNew.bottom - rcNew.top > rcOld.bottom - rcOld.top) ||
                 (rcNew.right - rcNew.left > rcOld.right - rcOld.left);

    INT dx = (rcNew.right - rcOld.right) + (rcNew.left - rcOld.left);
    INT dy = (rcNew.bottom - rcOld.bottom) + (rcNew.top - rcOld.top);

    LONG dt = SLIDE_HIDE;
    if (fShow)
    {
        dt = SLIDE_SHOW;
        rcOld = rcNew;
        OffsetRect(&rcOld, -dx, -dy);
        SetWindowPos(hwnd, NULL, rcOld.left, rcOld.top,
                     rcOld.right - rcOld.left, rcOld.bottom - rcOld.top,
                     SWP_NOZORDER | SWP_NOACTIVATE | SWP_DRAWFRAME);
    }

    HANDLE hThread = GetCurrentThread();
    INT priority = GetThreadPriority(hThread);
    SetThreadPriority(hThread, THREAD_PRIORITY_HIGHEST);

    LONG t, t0 = GetTickCount();
    while ((t = GetTickCount()) < t0 + dt)
    {
        INT x = rcOld.left + dx * (t - t0) / dt;
        INT y = rcOld.top + dy * (t - t0) / dt;
        SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);

        UpdateWindow(hwnd);
        UpdateWindow(GetDesktopWindow());
    }

    SetThreadPriority(hThread, priority);
    SetWindowPos(hwnd, NULL, rcNew.left, rcNew.top,
                 rcNew.right - rcNew.left, rcNew.bottom - rcNew.top,
                 SWP_NOZORDER | SWP_NOACTIVATE | SWP_DRAWFRAME);
#undef SLIDE_HIDE
#undef SLIDE_SHOW
}

class Window
{
public:
    Window(INT cx, INT cy, BOOL fAutoHide = FALSE)
        : m_hwnd(NULL)
        , m_fAutoHide(fAutoHide)
        , m_cxWidth(cx)
        , m_cyHeight(cy)
    {
    }

    virtual ~Window()
    {
    }

    static BOOL DoRegisterClass(HINSTANCE hInstance)
    {
        WNDCLASS wc;
        ZeroMemory(&wc, sizeof(wc));
        wc.lpfnWndProc = Window::WindowProc;
        wc.hInstance = hInstance;
        wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
        wc.lpszClassName = s_szName;
        return !!RegisterClass(&wc);
    }

    static HWND DoCreateMainWnd(HINSTANCE hInstance, LPCTSTR pszText, INT cx, INT cy,
                                DWORD style = WS_POPUP | WS_THICKFRAME | WS_CLIPCHILDREN,
                                DWORD exstyle = WS_EX_WINDOWEDGE | WS_EX_TOOLWINDOW | WS_EX_TOPMOST,
                                BOOL fAutoHide = FALSE)
    {
        Window *this_ = new Window(cx, cy, fAutoHide);
        HWND hwnd = CreateWindowEx(exstyle, s_szName, pszText, style,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 50, 50,
                                   NULL, NULL, hInstance, this_);
        ShowWindow(hwnd, SW_SHOWNORMAL);
        UpdateWindow(hwnd);
        return hwnd;
    }

    static INT DoMainLoop()
    {
        MSG msg;
        while (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        return (INT)msg.wParam;
    }

    static Window *GetAppbarData(HWND hwnd)
    {
        return (Window *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
    }

    virtual LRESULT CALLBACK
    WindowProcDx(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
#ifdef VERBOSE
        MD_msgdump(hwnd, uMsg, wParam, lParam);
#endif
        switch (uMsg)
        {
        HANDLE_MSG(hwnd, WM_CREATE, OnCreate);
        HANDLE_MSG(hwnd, WM_COMMAND, OnCommand);
        HANDLE_MSG(hwnd, WM_ACTIVATE, OnActivate);
        HANDLE_MSG(hwnd, WM_WINDOWPOSCHANGED, OnWindowPosChanged);
        HANDLE_MSG(hwnd, WM_SIZE, OnSize);
        HANDLE_MSG(hwnd, WM_MOVE, OnMove);
        HANDLE_MSG(hwnd, WM_NCDESTROY, OnNCDestroy);
        HANDLE_MSG(hwnd, WM_TIMER, OnTimer);
        HANDLE_MSG(hwnd, WM_NCHITTEST, OnNCHitTest);
        HANDLE_MSG(hwnd, WM_LBUTTONDOWN, OnLButtonDown);
        HANDLE_MSG(hwnd, WM_MOUSEMOVE, OnMouseMove);
        HANDLE_MSG(hwnd, WM_LBUTTONUP, OnLButtonUp);
        HANDLE_MSG(hwnd, WM_RBUTTONDOWN, OnRButtonDown);
        HANDLE_MSG(hwnd, WM_KEYDOWN, OnKey);
        HANDLE_MSG(hwnd, WM_PAINT, OnPaint);

        case APPBAR_CALLBACK:
            OnAppBarCallback(hwnd, uMsg, wParam, lParam);
            break;

        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
        }
        return 0;
    }

    static LRESULT CALLBACK
    WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        Window *this_ = GetAppbarData(hwnd);
        if (uMsg == WM_CREATE)
        {
            LPCREATESTRUCT pCS = (LPCREATESTRUCT)lParam;
            this_ = (Window *)pCS->lpCreateParams;
            SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)this_);
        }
        if (this_)
            return this_->WindowProcDx(hwnd, uMsg, wParam, lParam);
        return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }

protected:
    HWND m_hwnd;
    BOOL m_fAutoHide;
    BOOL m_fOnTop;
    BOOL m_fHiding;
    UINT m_uSide;
    LONG m_cxWidth;
    LONG m_cyHeight;
    LONG m_cxSave;
    LONG m_cySave;
    BOOL m_fAppBarRegd;
    BOOL m_fMoving;
    BOOL m_bDragged;
    POINT m_ptDragOn;
    RECT m_rcAppBar;
    RECT m_rcDrag;

    void OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
    {
        HANDLE hThread;
        switch (id)
        {
        case ID_ACTION:
            PostMessage(s_hwnd2, WM_COMMAND, ID_ACTION + 1, 0);
            break;
        case ID_ACTION + 1:
            hThread = CreateThread(NULL, 0, ActionThreadFunc, this, 0, NULL);
            if (!hThread)
            {
                skip("failed to create thread\n");
                PostMessage(s_hwnd1, WM_CLOSE, 0, 0);
                PostMessage(s_hwnd2, WM_CLOSE, 0, 0);
                return;
            }
            CloseHandle(hThread);
        }
    }

    void OnPaint(HWND hwnd)
    {
        PAINTSTRUCT ps;

        TCHAR szText[64];
        GetWindowText(hwnd, szText, 64);

        RECT rc;
        GetClientRect(hwnd, &rc);

        if (HDC hdc = BeginPaint(hwnd, &ps))
        {
            DrawText(hdc, szText, -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
            EndPaint(hwnd, &ps);
        }
    }

    void OnRButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)
    {
        m_fAutoHide = !m_fAutoHide;
        AppBar_SetAutoHide(hwnd, m_fAutoHide);
    }

    void OnKey(HWND hwnd, UINT vk, BOOL fDown, int cRepeat, UINT flags)
    {
        if (vk == VK_ESCAPE)
            DestroyWindow(hwnd);
    }

    void OnAppBarCallback(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        static HWND s_hwndZOrder = NULL;

        switch (wParam)
        {
        case ABN_STATECHANGE:
            break;

        case ABN_FULLSCREENAPP:
            if (lParam)
            {
                s_hwndZOrder = GetWindow(hwnd, GW_HWNDPREV);
                SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, 0, 0,
                             SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
            }
            else
            {
                SetWindowPos(hwnd, m_fOnTop ? HWND_TOPMOST : s_hwndZOrder,
                             0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
                s_hwndZOrder = NULL;
            }
            break;

        case ABN_POSCHANGED:
            {
                APPBARDATA abd = { sizeof(abd) };
                abd.hWnd = hwnd;
                AppBar_PosChanged(&abd);
            }
            break;
        }
    }

    BOOL AppBar_Register(HWND hwnd)
    {
        APPBARDATA abd = { sizeof(abd) };
        abd.hWnd = hwnd;
        abd.uCallbackMessage = APPBAR_CALLBACK;

        m_fAppBarRegd = (BOOL)SHAppBarMessage(ABM_NEW, &abd);
        return m_fAppBarRegd;
    }

    BOOL AppBar_UnRegister(HWND hwnd)
    {
        APPBARDATA abd = { sizeof(abd) };
        abd.hWnd = hwnd;

        m_fAppBarRegd = !SHAppBarMessage(ABM_REMOVE, &abd);
        return !m_fAppBarRegd;
    }

    BOOL AppBar_SetAutoHide(HWND hwnd, BOOL fHide)
    {
        if (fHide)
            return AppBar_AutoHide(hwnd);
        else
            return AppBar_NoAutoHide(hwnd);
    }

    BOOL AppBar_AutoHide(HWND hwnd)
    {
        APPBARDATA abd = { sizeof(abd) };
        abd.hWnd = hwnd;
        abd.uEdge = m_uSide;

        HWND hwndAutoHide = (HWND)SHAppBarMessage(ABM_GETAUTOHIDEBAR, &abd);
        if (hwndAutoHide)
            return FALSE;

        abd.lParam = TRUE;
        if (!(BOOL)SHAppBarMessage(ABM_SETAUTOHIDEBAR, &abd))
            return FALSE;

        m_fAutoHide = TRUE;
        m_cxSave = m_cxWidth;
        m_cySave = m_cyHeight;

        RECT rc = m_rcAppBar;
        switch (m_uSide)
        {
        case ABE_TOP:
            rc.bottom = rc.top + 2;
            break;
        case ABE_BOTTOM:
            rc.top = rc.bottom - 2;
            break;
        case ABE_LEFT:
            rc.right = rc.left + 2;
            break;
        case ABE_RIGHT:
            rc.left = rc.right - 2;
            break;
        }

        AppBar_QueryPos(hwnd, &rc);
        abd.rc = rc;
        SHAppBarMessage(ABM_SETPOS, &abd);
        rc = abd.rc;

        m_fHiding = TRUE;
        SlideWindow(hwnd, &rc);

        AppBar_SetAutoHideTimer(hwnd);
        return TRUE;
    }

    BOOL AppBar_NoAutoHide(HWND hwnd)
    {
        APPBARDATA abd = { sizeof(abd) };
        abd.hWnd = hwnd;
        abd.uEdge = m_uSide;
        HWND hwndAutoHide = (HWND)SHAppBarMessage(ABM_GETAUTOHIDEBAR, &abd);
        if (hwndAutoHide != hwnd)
            return FALSE;

        abd.lParam = FALSE;
        if (!(BOOL)SHAppBarMessage(ABM_SETAUTOHIDEBAR, &abd))
            return FALSE;

        m_fAutoHide = FALSE;
        m_cxWidth = m_cxSave;
        m_cyHeight = m_cySave;
        AppBar_SetSide(hwnd, m_uSide);
        return TRUE;
    }

    BOOL AppBar_SetSide(HWND hwnd, UINT uSide)
    {
        RECT rc;
        SetRect(&rc, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));

        BOOL fAutoHide = FALSE;
        if (m_fAutoHide)
        {
            fAutoHide = m_fAutoHide;
            SetWindowRedraw(GetDesktopWindow(), FALSE);
            AppBar_SetAutoHide(hwnd, FALSE);
            m_fHiding = FALSE;
        }

        switch (uSide)
        {
        case ABE_TOP:
            rc.bottom = rc.top + m_cyHeight;
            break;
        case ABE_BOTTOM:
            rc.top = rc.bottom - m_cyHeight;
            break;
        case ABE_LEFT:
            rc.right = rc.left + m_cxWidth;
            break;
        case ABE_RIGHT:
            rc.left = rc.right - m_cxWidth;
            break;
        }

        APPBARDATA abd = { sizeof(abd) };
        abd.hWnd = hwnd;
        AppBar_QuerySetPos(uSide, &rc, &abd, TRUE);

        if (fAutoHide)
        {
            AppBar_SetAutoHide(hwnd, TRUE);
            m_fHiding = TRUE;

            SetWindowRedraw(GetDesktopWindow(), TRUE);
            RedrawWindow(GetDesktopWindow(), NULL, NULL,
                         RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN);
        }

        return TRUE;
    }

    void AppBar_SetAlwaysOnTop(HWND hwnd, BOOL fOnTop)
    {
        SetWindowPos(hwnd, (fOnTop ? HWND_TOPMOST : HWND_NOTOPMOST),
                     0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
        m_fOnTop = fOnTop;
    }

    void AppBar_Hide(HWND hwnd)
    {
        if (!m_fAutoHide)
            return;

        RECT rc = m_rcAppBar;
        switch (m_uSide)
        {
        case ABE_TOP:
            rc.bottom = rc.top + 2;
            break;
        case ABE_BOTTOM:
            rc.top = rc.bottom - 2;
            break;
        case ABE_LEFT:
            rc.right = rc.left + 2;
            break;
        case ABE_RIGHT:
            rc.left = rc.right - 2;
            break;
        }

        m_fHiding = TRUE;
        SlideWindow(hwnd, &rc);
    }

    void AppBar_UnHide(HWND hwnd)
    {
        SlideWindow(hwnd, &m_rcAppBar);
        m_fHiding = FALSE;

        AppBar_SetAutoHideTimer(hwnd);
    }

    void AppBar_SetAutoHideTimer(HWND hwnd)
    {
        if (m_fAutoHide)
        {
            SetTimer(hwnd, IDT_AUTOHIDE, 500, NULL);
        }
    }

    void AppBar_SetAutoUnhideTimer(HWND hwnd)
    {
        if (m_fAutoHide && m_fHiding)
        {
            SetTimer(hwnd, IDT_AUTOUNHIDE, 50, NULL);
        }
    }

    void AppBar_Size(HWND hwnd)
    {
        if (m_fAppBarRegd)
        {
            APPBARDATA abd = { sizeof(abd) };
            abd.hWnd = hwnd;

            RECT rc;
            GetWindowRect(hwnd, &rc);
            AppBar_QuerySetPos(m_uSide, &rc, &abd, TRUE);
        }
    }

    void AppBar_QueryPos(HWND hwnd, LPRECT lprc)
    {
        APPBARDATA abd = { sizeof(abd) };
        abd.hWnd = hwnd;
        abd.rc = *lprc;
        abd.uEdge = m_uSide;

        INT cx = 0, cy = 0;
        if (ABE_LEFT == abd.uEdge || ABE_RIGHT == abd.uEdge)
        {
            cx = abd.rc.right - abd.rc.left;
            abd.rc.top = 0;
            abd.rc.bottom = GetSystemMetrics(SM_CYSCREEN);
        }
        else
        {
            cy = abd.rc.bottom - abd.rc.top;
            abd.rc.left = 0;
            abd.rc.right = GetSystemMetrics(SM_CXSCREEN);
        }

        SHAppBarMessage(ABM_QUERYPOS, &abd);

        switch (abd.uEdge)
        {
        case ABE_LEFT:
            abd.rc.right = abd.rc.left + cx;
            break;
        case ABE_RIGHT:
            abd.rc.left = abd.rc.right - cx;
            break;
        case ABE_TOP:
            abd.rc.bottom = abd.rc.top + cy;
            break;
        case ABE_BOTTOM:
            abd.rc.top = abd.rc.bottom - cy;
            break;
        }

        *lprc = abd.rc;
    }

    void AppBar_QuerySetPos(UINT uEdge, LPRECT lprc, PAPPBARDATA pabd, BOOL fMove)
    {
        pabd->rc = *lprc;
        pabd->uEdge = uEdge;
        m_uSide = uEdge;

        AppBar_QueryPos(pabd->hWnd, &pabd->rc);

        SHAppBarMessage(ABM_SETPOS, pabd);

        if (fMove)
        {
            RECT rc = pabd->rc;
            MoveWindow(pabd->hWnd, rc.left, rc.top,
                       rc.right - rc.left, rc.bottom - rc.top, TRUE);
        }

        if (!m_fAutoHide)
        {
            m_rcAppBar = pabd->rc;
        }
    }

    void AppBar_PosChanged(PAPPBARDATA pabd)
    {
        RECT rc;
        SetRect(&rc, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));

        if (m_fAutoHide)
        {
            m_rcAppBar = rc;
            switch (m_uSide)
            {
            case ABE_TOP:
                m_rcAppBar.bottom = m_rcAppBar.top + m_cySave;
                break;
            case ABE_BOTTOM:
                m_rcAppBar.top = m_rcAppBar.bottom - m_cySave;
                break;
            case ABE_LEFT:
                m_rcAppBar.right = m_rcAppBar.left + m_cxSave;
                break;
            case ABE_RIGHT:
                m_rcAppBar.left = m_rcAppBar.right - m_cxSave;
                break;
            }
        }

        RECT rcWindow;
        GetWindowRect(pabd->hWnd, &rcWindow);
        INT cx = rcWindow.right - rcWindow.left;
        INT cy = rcWindow.bottom - rcWindow.top;
        switch (m_uSide)
        {
        case ABE_TOP:
            rc.bottom = rc.top + cy;
            break;
        case ABE_BOTTOM:
            rc.top = rc.bottom - cy;
            break;
        case ABE_LEFT:
            rc.right = rc.left + cx;
            break;
        case ABE_RIGHT:
            rc.left = rc.right - cx;
            break;
        }
        AppBar_QuerySetPos(m_uSide, &rc, pabd, TRUE);
    }

    BOOL OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
    {
        m_hwnd = hwnd;
        m_fOnTop = TRUE;
        m_uSide = ABE_TOP;

        m_fAppBarRegd = FALSE;
        m_fMoving = FALSE;
        m_cxSave = m_cxWidth;
        m_cySave = m_cyHeight;
        m_bDragged = FALSE;

        AppBar_Register(hwnd);
        AppBar_SetSide(hwnd, ABE_TOP);

        return TRUE;
    }

    void OnActivate(HWND hwnd, UINT state, HWND hwndActDeact, BOOL fMinimized)
    {
        APPBARDATA abd = { sizeof(abd) };
        abd.hWnd = hwnd;
        SHAppBarMessage(ABM_ACTIVATE, &abd);

        switch (state)
        {
            case WA_ACTIVE:
            case WA_CLICKACTIVE:
                AppBar_UnHide(hwnd);
                KillTimer(hwnd, IDT_AUTOHIDE);
                break;

            case WA_INACTIVE:
                AppBar_Hide(hwnd);
                break;
        }
    }

    void OnWindowPosChanged(HWND hwnd, const LPWINDOWPOS lpwpos)
    {
        APPBARDATA abd = { sizeof(abd) };
        abd.hWnd = hwnd;
        SHAppBarMessage(ABM_WINDOWPOSCHANGED, &abd);

        FORWARD_WM_WINDOWPOSCHANGED(hwnd, lpwpos, DefWindowProc);
    }

    void OnSize(HWND hwnd, UINT state, int cx, int cy)
    {
        RECT rcWindow;

        if (m_fMoving || (m_fAutoHide && m_fHiding))
            return;

        if (!m_fHiding)
        {
            if (!m_fAutoHide)
                AppBar_Size(hwnd);

            GetWindowRect(hwnd, &rcWindow);
            m_rcAppBar = rcWindow;

            if (m_uSide == ABE_TOP || m_uSide == ABE_BOTTOM)
            {
                m_cyHeight = m_cySave = rcWindow.bottom - rcWindow.top;
            }
            else
            {
                m_cxWidth = m_cxSave = rcWindow.right - rcWindow.left;
            }
        }

        InvalidateRect(hwnd, NULL, TRUE);
    }

    void OnMove(HWND hwnd, int x, int y)
    {
        if (m_fMoving || m_fAutoHide)
            return;

        if (!m_fHiding)
            AppBar_Size(hwnd);
    }

    void OnNCDestroy(HWND hwnd)
    {
        AppBar_UnRegister(hwnd);

        m_hwnd = NULL;
        SetWindowLongPtr(hwnd, GWLP_USERDATA, 0);
        delete this;
    }

    void OnTimer(HWND hwnd, UINT id)
    {
        POINT pt;
        RECT rc;
        HWND hwndActive;

        switch (id)
        {
        case IDT_AUTOHIDE:
            if (m_fAutoHide && !m_fHiding && !m_fMoving)
            {
                GetCursorPos(&pt);
                GetWindowRect(hwnd, &rc);
                hwndActive = GetForegroundWindow();

                if (!PtInRect(&rc, pt) &&
                    hwndActive != hwnd &&
                    hwndActive != NULL &&
                    GetWindowOwner(hwndActive) != hwnd)
                {
                    KillTimer(hwnd, id);
                    AppBar_Hide(hwnd);
                }
            }
            break;

        case IDT_AUTOUNHIDE:
            KillTimer(hwnd, id);

            if (m_fAutoHide && m_fHiding)
            {
                GetCursorPos(&pt);
                GetWindowRect(hwnd, &rc);
                if (PtInRect(&rc, pt))
                {
                    AppBar_UnHide(hwnd);
                }
            }
            break;
        }
    }

    UINT OnNCHitTest(HWND hwnd, int x, int y)
    {
        AppBar_SetAutoUnhideTimer(hwnd);

        UINT uHitTest = FORWARD_WM_NCHITTEST(hwnd, x, y, DefWindowProc);

        if (m_uSide == ABE_TOP && uHitTest == HTBOTTOM)
            return HTBOTTOM;

        if (m_uSide == ABE_BOTTOM && uHitTest == HTTOP)
            return HTTOP;

        if (m_uSide == ABE_LEFT && uHitTest == HTRIGHT)
            return HTRIGHT;

        if (m_uSide == ABE_RIGHT && uHitTest == HTLEFT)
            return HTLEFT;

        return HTCLIENT;
    }

    void OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)
    {
        m_fMoving = TRUE;
        m_bDragged = FALSE;
        SetCapture(hwnd);
        GetCursorPos(&m_ptDragOn);
    }

    void OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags)
    {
        if (!m_fMoving)
            return;

        POINT pt;
        GetCursorPos(&pt);
        if (labs(pt.x - m_ptDragOn.x) > GetSystemMetrics(SM_CXDRAG) ||
            labs(pt.y - m_ptDragOn.y) > GetSystemMetrics(SM_CYDRAG))
        {
            m_bDragged = TRUE;
        }

        INT cxScreen = GetSystemMetrics(SM_CXSCREEN);
        INT cyScreen = GetSystemMetrics(SM_CYSCREEN);

        DWORD dx, dy;
        UINT ix, iy;
        if (pt.x < cxScreen / 2)
        {
            dx = pt.x;
            ix = ABE_LEFT;
        }
        else
        {
            dx = cxScreen - pt.x;
            ix = ABE_RIGHT;
        }

        if (pt.y < cyScreen / 2)
        {
            dy = pt.y;
            iy = ABE_TOP;
        }
        else
        {
            dy = cyScreen - pt.y;
            iy = ABE_BOTTOM;
        }

        if (cxScreen * dy > cyScreen * dx)
        {
            m_rcDrag.top = 0;
            m_rcDrag.bottom = cyScreen;
            if (ix == ABE_LEFT)
            {
                m_uSide = ABE_LEFT;
                m_rcDrag.left = 0;
                m_rcDrag.right = m_rcDrag.left + m_cxWidth;
            }
            else
            {
                m_uSide = ABE_RIGHT;
                m_rcDrag.right = cxScreen;
                m_rcDrag.left = m_rcDrag.right - m_cxWidth;
            }
        }
        else
        {
            m_rcDrag.left = 0;
            m_rcDrag.right = cxScreen;
            if (iy == ABE_TOP)
            {
                m_uSide = ABE_TOP;
                m_rcDrag.top = 0;
                m_rcDrag.bottom = m_rcDrag.top + m_cyHeight;
            }
            else
            {
                m_uSide = ABE_BOTTOM;
                m_rcDrag.bottom = cyScreen;
                m_rcDrag.top = m_rcDrag.bottom - m_cyHeight;
            }
        }

        AppBar_QueryPos(hwnd, &m_rcDrag);

        if (m_bDragged)
        {
            MoveWindow(hwnd, m_rcDrag.left, m_rcDrag.top,
                       m_rcDrag.right - m_rcDrag.left,
                       m_rcDrag.bottom - m_rcDrag.top,
                       TRUE);
        }
    }

    void OnLButtonUp(HWND hwnd, int x, int y, UINT keyFlags)
    {
        if (!m_fMoving)
            return;

        OnMouseMove(hwnd, x, y, keyFlags);

        m_rcAppBar = m_rcDrag;

        ReleaseCapture();

        if (m_fAutoHide)
        {
            switch (m_uSide)
            {
            case ABE_TOP:
                m_rcDrag.bottom = m_rcDrag.top + 2;
                break;
            case ABE_BOTTOM:
                m_rcDrag.top = m_rcDrag.bottom - 2;
                break;
            case ABE_LEFT:
                m_rcDrag.right = m_rcDrag.left + 2;
                break;
            case ABE_RIGHT:
                m_rcDrag.left = m_rcDrag.right - 2;
                break;
            }
        }

        if (m_bDragged)
        {
            if (m_fAutoHide)
            {
                AppBar_AutoHide(hwnd);
            }
            else
            {
                APPBARDATA abd = { sizeof(abd) };
                abd.hWnd = hwnd;
                AppBar_QuerySetPos(m_uSide, &m_rcDrag, &abd, FALSE);
            }
        }

        m_fMoving = FALSE;
    }

    void GetWorkArea(LPRECT prc) const
    {
        SystemParametersInfoW(SPI_GETWORKAREA, 0, prc, 0);
    }

public:
    void DoAction()
    {
#define INTERVAL 250
        POINT pt;
        RECT rc1, rc2, rcWork;
        DWORD dwTID = GetWindowThreadProcessId(s_hwnd1, NULL);

        GetWindowRect(s_hwnd1, &rc1);
        GetWindowRect(s_hwnd2, &rc2);
        GetWorkArea(&rcWork);
        ok_long(rc1.left, s_rcWorkArea.left);
        ok_long(rc1.top, s_rcWorkArea.top);
        ok_long(rc1.right, s_rcWorkArea.right);
        ok_long(rc1.bottom, s_rcWorkArea.top + 80);
        ok_long(rc2.left, s_rcWorkArea.left);
        ok_long(rc2.top, s_rcWorkArea.top + 80);
        ok_long(rc2.right, s_rcWorkArea.right);
        ok_long(rc2.bottom, s_rcWorkArea.top + 110);
        ok_long(rcWork.left, s_rcWorkArea.left);
        ok_long(rcWork.top, s_rcWorkArea.top + 110);
        ok_long(rcWork.right, s_rcWorkArea.right);
        ok_long(rcWork.bottom, s_rcWorkArea.bottom);
        PostMessage(s_hwnd1, WM_CLOSE, 0, 0);
        Sleep(INTERVAL);

        GetWindowRect(s_hwnd2, &rc2);
        GetWorkArea(&rcWork);
        ok_long(rc2.left, s_rcWorkArea.left);
        ok_long(rc2.top, s_rcWorkArea.top);
        ok_long(rc2.right, s_rcWorkArea.right);
        ok_long(rc2.bottom, s_rcWorkArea.top + 30);
        ok_long(rcWork.left, s_rcWorkArea.left);
        ok_long(rcWork.top, s_rcWorkArea.top + 30);
        ok_long(rcWork.right, s_rcWorkArea.right);
        ok_long(rcWork.bottom, s_rcWorkArea.bottom);
        AppBar_SetSide(s_hwnd2, ABE_LEFT);
        Sleep(INTERVAL);

        GetWindowRect(s_hwnd2, &rc2);
        GetWorkArea(&rcWork);
        ok_long(rc2.left, s_rcWorkArea.left);
        ok_long(rc2.top, s_rcWorkArea.top);
        ok_long(rc2.right, s_rcWorkArea.left + 30);
        ok_long(rcWork.left, s_rcWorkArea.left + 30);
        ok_long(rcWork.top, s_rcWorkArea.top);
        ok_long(rcWork.right, s_rcWorkArea.right);
        ok_long(rcWork.bottom, s_rcWorkArea.bottom);
        AppBar_SetSide(s_hwnd2, ABE_TOP);
        Sleep(INTERVAL);

        GetWindowRect(s_hwnd2, &rc2);
        GetWorkArea(&rcWork);
        ok_long(rc2.left, s_rcWorkArea.left);
        ok_long(rc2.top, s_rcWorkArea.top);
        ok_long(rc2.right, s_rcWorkArea.right);
        ok_long(rc2.bottom, s_rcWorkArea.top + 30);
        ok_long(rcWork.left, s_rcWorkArea.left);
        ok_long(rcWork.top, s_rcWorkArea.top + 30);
        ok_long(rcWork.right, s_rcWorkArea.right);
        ok_long(rcWork.bottom, s_rcWorkArea.bottom);
        AppBar_SetSide(s_hwnd2, ABE_RIGHT);
        Sleep(INTERVAL);

        GetWindowRect(s_hwnd2, &rc2);
        GetWorkArea(&rcWork);
        ok_long(rc2.left, s_rcWorkArea.right - 30);
        ok_long(rc2.top, s_rcWorkArea.top);
        ok_long(rc2.right, s_rcWorkArea.right);
        ok_long(rcWork.left, s_rcWorkArea.left);
        ok_long(rcWork.top, s_rcWorkArea.top);
        ok_long(rcWork.right, s_rcWorkArea.right - 30);
        ok_long(rcWork.bottom, s_rcWorkArea.bottom);
        Sleep(INTERVAL);

        GetWindowRect(s_hwnd2, &rc2);
        pt.x = (rc2.left + rc2.right) / 2;
        pt.y = (rc2.top + rc2.bottom) / 2;
        MOVE(pt.x, pt.y);
        LEFT_DOWN();
        MOVE(pt.x + 64, pt.y + 64);
        Sleep(INTERVAL);

        pt.x = s_rcWorkArea.left + 80;
        pt.y = (s_rcWorkArea.top + s_rcWorkArea.bottom) / 2;
        MOVE(pt.x, pt.y);
        LEFT_UP();
        Sleep(INTERVAL);

        GetWindowRect(s_hwnd2, &rc2);
        GetWorkArea(&rcWork);
        ok_long(rc2.left, s_rcWorkArea.left);
        ok_long(rc2.top, s_rcWorkArea.top);
        ok_long(rc2.right, s_rcWorkArea.left + 30);
        ok_long(rcWork.left, s_rcWorkArea.left + 30);
        ok_long(rcWork.top, s_rcWorkArea.top);
        ok_long(rcWork.right, s_rcWorkArea.right);
        ok_long(rcWork.bottom, s_rcWorkArea.bottom);
        Sleep(INTERVAL);

        GetWindowRect(s_hwnd2, &rc2);
        pt.x = (rc2.left + rc2.right) / 2;
        pt.y = (rc2.top + rc2.bottom) / 2;
        MOVE(pt.x, pt.y);
        LEFT_DOWN();
        MOVE(pt.x + 64, pt.y + 64);
        Sleep(INTERVAL);

        pt.x = s_rcWorkArea.right - 80;
        pt.y = (s_rcWorkArea.top + s_rcWorkArea.bottom) / 2;
        MOVE(pt.x, pt.y);
        LEFT_UP();
        Sleep(INTERVAL);

        GetWindowRect(s_hwnd2, &rc2);
        GetWorkArea(&rcWork);
        ok_long(rc2.left, s_rcWorkArea.right - 30);
        ok_long(rc2.top, s_rcWorkArea.top);
        ok_long(rc2.right, s_rcWorkArea.right);
        ok_long(rcWork.left, s_rcWorkArea.left);
        ok_long(rcWork.top, s_rcWorkArea.top);
        ok_long(rcWork.right, s_rcWorkArea.right - 30);
        ok_long(rcWork.bottom, s_rcWorkArea.bottom);
        Sleep(INTERVAL);

        SendMessage(s_hwnd2, WM_CLOSE, 0, 0);
        Sleep(INTERVAL);

        GetWorkArea(&rcWork);
        ok_long(rcWork.left, s_rcWorkArea.left);
        ok_long(rcWork.top, s_rcWorkArea.top);
        ok_long(rcWork.right, s_rcWorkArea.right);
        ok_long(rcWork.bottom, s_rcWorkArea.bottom);

        PostMessage(s_hwnd2, WM_QUIT, 0, 0);
        PostThreadMessage(dwTID, WM_QUIT, 0, 0);
#undef INTERVAL
    }

    static DWORD WINAPI ActionThreadFunc(LPVOID args)
    {
        Window *this_ = (Window *)args;
        this_->DoAction();
        return 0;
    }
};

START_TEST(SHAppBarMessage)
{
    HINSTANCE hInstance = GetModuleHandle(NULL);

    if (!Window::DoRegisterClass(hInstance))
    {
        skip("Window::DoRegisterClass failed\n");
        return;
    }

    SystemParametersInfo(SPI_GETWORKAREA, 0, &s_rcWorkArea, FALSE);

    HWND hwnd1 = Window::DoCreateMainWnd(hInstance, TEXT("Test1"), 80, 80,
                                         WS_POPUP | WS_THICKFRAME | WS_CLIPCHILDREN);
    if (!hwnd1)
    {
        skip("CreateWindowExW failed\n");
        return;
    }

    HWND hwnd2 = Window::DoCreateMainWnd(hInstance, TEXT("Test2"), 30, 30,
                                         WS_POPUP | WS_BORDER | WS_CLIPCHILDREN);
    if (!hwnd2)
    {
        skip("CreateWindowExW failed\n");
        return;
    }

    s_hwnd1 = hwnd1;
    s_hwnd2 = hwnd2;

    PostMessage(hwnd1, WM_COMMAND, ID_ACTION, 0);

    Window::DoMainLoop();
}
