/* Unit test suite for Button control.
 *
 * Copyright 1999 Ove Kaaven
 * Copyright 2003 Dimitrie O. Paun
 * Copyright 2004, 2005 Dmitry Timoshkov
 * Copyright 2014 Nikolay Sivov 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
 */

#include <windows.h>
#include <commctrl.h>

#include "wine/test.h"
#include "v6util.h"
#include "msg.h"

#define IS_WNDPROC_HANDLE(x) (((ULONG_PTR)(x) >> 16) == (~0u >> 16))

static BOOL (WINAPI *pSetWindowSubclass)(HWND, SUBCLASSPROC, UINT_PTR, DWORD_PTR);
static BOOL (WINAPI *pRemoveWindowSubclass)(HWND, SUBCLASSPROC, UINT_PTR);
static LRESULT (WINAPI *pDefSubclassProc)(HWND, UINT, WPARAM, LPARAM);

/****************** button message test *************************/
#define ID_BUTTON 0x000e

#define COMBINED_SEQ_INDEX 0
#define NUM_MSG_SEQUENCES  1

static struct msg_sequence *sequences[NUM_MSG_SEQUENCES];

struct wndclass_redirect_data
{
    ULONG size;
    DWORD res;
    ULONG name_len;
    ULONG name_offset;
    ULONG module_len;
    ULONG module_offset;
};

/* returned pointer is valid as long as activation context is alive */
static WCHAR* get_versioned_classname(const WCHAR *name)
{
    BOOL (WINAPI *pFindActCtxSectionStringW)(DWORD,const GUID *,ULONG,LPCWSTR,PACTCTX_SECTION_KEYED_DATA);
    struct wndclass_redirect_data *wnddata;
    ACTCTX_SECTION_KEYED_DATA data;
    BOOL ret;

    pFindActCtxSectionStringW = (void*)GetProcAddress(GetModuleHandleA("kernel32"), "FindActCtxSectionStringW");

    memset(&data, 0, sizeof(data));
    data.cbSize = sizeof(data);

    ret = pFindActCtxSectionStringW(0, NULL,
                                    ACTIVATION_CONTEXT_SECTION_WINDOW_CLASS_REDIRECTION,
                                    name, &data);
    ok(ret, "got %d, error %u\n", ret, GetLastError());
    wnddata = (struct wndclass_redirect_data*)data.lpData;
    return (WCHAR*)((BYTE*)wnddata + wnddata->name_offset);
}

static void init_functions(void)
{
    HMODULE hmod = GetModuleHandleA("comctl32.dll");
    ok(hmod != NULL, "got %p\n", hmod);

#define MAKEFUNC_ORD(f, ord) (p##f = (void*)GetProcAddress(hmod, (LPSTR)(ord)))
    MAKEFUNC_ORD(SetWindowSubclass, 410);
    MAKEFUNC_ORD(RemoveWindowSubclass, 412);
    MAKEFUNC_ORD(DefSubclassProc, 413);
#undef MAKEFUNC_ORD
}

/* try to make sure pending X events have been processed before continuing */
static void flush_events(void)
{
    MSG msg;
    int diff = 200;
    int min_timeout = 100;
    DWORD time = GetTickCount() + diff;

    while (diff > 0)
    {
        if (MsgWaitForMultipleObjects( 0, NULL, FALSE, min_timeout, QS_ALLINPUT ) == WAIT_TIMEOUT) break;
        while (PeekMessageA( &msg, 0, 0, 0, PM_REMOVE )) DispatchMessageA( &msg );
        diff = time - GetTickCount();
    }
}

static BOOL ignore_message( UINT message )
{
    /* these are always ignored */
    return (message >= 0xc000 ||
            message == WM_GETICON ||
            message == WM_GETOBJECT ||
            message == WM_TIMECHANGE ||
            message == WM_DISPLAYCHANGE ||
            message == WM_DEVICECHANGE ||
            message == WM_DWMNCRENDERINGCHANGED ||
            message == WM_GETTEXTLENGTH ||
            message == WM_GETTEXT);
}

static LRESULT CALLBACK button_subclass_proc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR id, DWORD_PTR ref_data)
{
    static LONG defwndproc_counter = 0;
    LRESULT ret;
    struct message msg;

    if (ignore_message( message )) return pDefSubclassProc(hwnd, message, wParam, lParam);

    switch (message)
    {
    case WM_SYNCPAINT:
        break;
    case BM_SETSTATE:
        if (GetCapture())
            ok(GetCapture() == hwnd, "GetCapture() = %p\n", GetCapture());
        /* fall through */
    default:
        msg.message = message;
        msg.flags = sent|wparam|lparam;
        if (defwndproc_counter) msg.flags |= defwinproc;
        msg.wParam = wParam;
        msg.lParam = lParam;
        add_message(sequences, COMBINED_SEQ_INDEX, &msg);
    }

    if (message == WM_NCDESTROY)
        pRemoveWindowSubclass(hwnd, button_subclass_proc, 0);

    defwndproc_counter++;
    ret = pDefSubclassProc(hwnd, message, wParam, lParam);
    defwndproc_counter--;

    return ret;
}

static LRESULT WINAPI test_parent_wndproc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static LONG defwndproc_counter = 0;
    static LONG beginpaint_counter = 0;
    LRESULT ret;
    struct message msg;

    if (ignore_message( message )) return 0;

    if (message == WM_PARENTNOTIFY || message == WM_CANCELMODE ||
        message == WM_SETFOCUS || message == WM_KILLFOCUS ||
        message == WM_ENABLE || message == WM_ENTERIDLE ||
        message == WM_DRAWITEM || message == WM_COMMAND ||
        message == WM_IME_SETCONTEXT)
    {
        switch (message)
        {
            /* ignore */
            case WM_NCHITTEST:
                return HTCLIENT;
            case WM_SETCURSOR:
            case WM_MOUSEMOVE:
            case WM_NCMOUSEMOVE:
                return 0;
        }

        msg.message = message;
        msg.flags = sent|parent|wparam|lparam;
        if (defwndproc_counter) msg.flags |= defwinproc;
        if (beginpaint_counter) msg.flags |= beginpaint;
        msg.wParam = wParam;
        msg.lParam = lParam;
        add_message(sequences, COMBINED_SEQ_INDEX, &msg);
    }

    if (message == WM_PAINT)
    {
        PAINTSTRUCT ps;
        beginpaint_counter++;
        BeginPaint( hwnd, &ps );
        beginpaint_counter--;
        EndPaint( hwnd, &ps );
        return 0;
    }

    defwndproc_counter++;
    ret = DefWindowProcA(hwnd, message, wParam, lParam);
    defwndproc_counter--;

    return ret;
}

static const struct message setfocus_seq[] =
{
    { WM_IME_SETCONTEXT, sent|wparam|optional, 1 },
    { WM_IME_NOTIFY, sent|wparam|defwinproc|optional, 2 },
    { BM_GETSTATE, sent|optional }, /* when touchscreen is present */
    { WM_SETFOCUS, sent|wparam },
    { WM_COMMAND, sent|wparam|parent, MAKEWPARAM(ID_BUTTON, BN_SETFOCUS) },
    { WM_APP, sent|wparam|lparam },
    { WM_PAINT, sent },
    { 0 }
};

static const struct message killfocus_seq[] =
{
    { WM_KILLFOCUS, sent|wparam, 0 },
    { WM_COMMAND, sent|wparam|parent, MAKEWPARAM(ID_BUTTON, BN_KILLFOCUS) },
    { WM_IME_SETCONTEXT, sent|wparam|optional, 0 },
    { WM_IME_NOTIFY, sent|wparam|defwinproc|optional, 1 },
    { WM_APP, sent|wparam|lparam, 0, 0 },
    { WM_PAINT, sent },
    { 0 }
};

static const struct message setfocus_static_seq[] =
{
    { WM_IME_SETCONTEXT, sent|wparam|optional, 1 },
    { WM_IME_NOTIFY, sent|wparam|defwinproc|optional, 2 },
    { BM_GETSTATE, sent|optional }, /* when touchscreen is present */
    { WM_SETFOCUS, sent|wparam, 0 },
    { WM_COMMAND, sent|wparam|parent, MAKEWPARAM(ID_BUTTON, BN_SETFOCUS) },
    { WM_COMMAND, sent|wparam|parent|optional, MAKEWPARAM(ID_BUTTON, BN_CLICKED) }, /* radio button */
    { WM_APP, sent|wparam|lparam, 0, 0 },
    { WM_PAINT, sent },
    { 0 }
};

static const struct message setfocus_groupbox_seq[] =
{
    { WM_IME_SETCONTEXT, sent|wparam|optional, 1 },
    { WM_IME_NOTIFY, sent|wparam|defwinproc|optional, 2 },
    { BM_GETSTATE, sent|optional }, /* when touchscreen is present */
    { WM_SETFOCUS, sent|wparam, 0 },
    { WM_COMMAND, sent|wparam|parent, MAKEWPARAM(ID_BUTTON, BN_SETFOCUS) },
    { WM_COMMAND, sent|wparam|parent|optional, MAKEWPARAM(ID_BUTTON, BN_CLICKED) }, /* radio button */
    { WM_APP, sent|wparam|lparam, 0, 0 },
    { WM_PAINT, sent },
    { 0 }
};

static const struct message killfocus_static_seq[] =
{
    { WM_KILLFOCUS, sent|wparam, 0 },
    { WM_COMMAND, sent|wparam|parent, MAKEWPARAM(ID_BUTTON, BN_KILLFOCUS) },
    { WM_IME_SETCONTEXT, sent|wparam|optional, 0 },
    { WM_IME_NOTIFY, sent|wparam|defwinproc|optional, 1 },
    { WM_APP, sent|wparam|lparam, 0, 0 },
    { WM_PAINT, sent },
    { 0 }
};

static const struct message setfocus_ownerdraw_seq[] =
{
    { WM_IME_SETCONTEXT, sent|wparam|optional, 1 },
    { WM_IME_NOTIFY, sent|wparam|defwinproc|optional, 2 },
    { BM_GETSTATE, sent|optional }, /* when touchscreen is present */
    { WM_SETFOCUS, sent|wparam, 0 },
    { WM_DRAWITEM, sent|wparam|parent, ID_BUTTON },
    { WM_COMMAND, sent|wparam|parent, MAKEWPARAM(ID_BUTTON, BN_SETFOCUS) },
    { WM_APP, sent|wparam|lparam, 0, 0 },
    { WM_IME_SETCONTEXT, sent|wparam|optional, 1 },
    { 0 }
};

static const struct message killfocus_ownerdraw_seq[] =
{
    { WM_KILLFOCUS, sent|wparam, 0 },
    { WM_COMMAND, sent|wparam|parent, MAKEWPARAM(ID_BUTTON, BN_KILLFOCUS) },
    { WM_IME_SETCONTEXT, sent|wparam|optional, 0 },
    { WM_IME_NOTIFY, sent|wparam|defwinproc|optional, 1 },
    { WM_APP, sent|wparam|lparam, 0, 0 },
    { WM_PAINT, sent },
    { WM_DRAWITEM, sent|wparam|parent, ID_BUTTON },
    { 0 }
};

static const struct message lbuttondown_seq[] =
{
    { WM_LBUTTONDOWN, sent|wparam|lparam, 0, 0 },
    { WM_IME_SETCONTEXT, sent|wparam|defwinproc|optional, 1 },
    { WM_IME_NOTIFY, sent|wparam|defwinproc|optional, 2 },
    { BM_GETSTATE, sent|defwinproc|optional }, /* when touchscreen is present */
    { WM_SETFOCUS, sent|wparam|defwinproc, 0 },
    { BM_SETSTATE, sent|wparam|defwinproc, TRUE },
    { 0 }
};

static const struct message lbuttonup_seq[] =
{
    { WM_LBUTTONUP, sent|wparam|lparam, 0, 0 },
    { BM_SETSTATE, sent|wparam|defwinproc, FALSE },
    { WM_CAPTURECHANGED, sent|wparam|defwinproc, 0 },
    { WM_COMMAND, sent|wparam|defwinproc, 0 },
    { 0 }
};

static const struct message setfont_seq[] =
{
    { WM_SETFONT, sent },
    { 0 }
};

static const struct message setstyle_seq[] =
{
    { BM_SETSTYLE, sent },
    { WM_STYLECHANGING, sent|wparam|defwinproc, GWL_STYLE },
    { WM_STYLECHANGED, sent|wparam|defwinproc, GWL_STYLE },
    { WM_APP, sent|wparam|lparam, 0, 0 },
    { WM_PAINT, sent },
    { WM_NCPAINT, sent|defwinproc|optional }, /* FIXME: Wine sends it */
    { WM_ERASEBKGND, sent|defwinproc|optional },
    { WM_PAINT, sent|optional },
    { 0 }
};

static const struct message setstyle_static_seq[] =
{
    { BM_SETSTYLE, sent },
    { WM_STYLECHANGING, sent|wparam|defwinproc, GWL_STYLE },
    { WM_STYLECHANGED, sent|wparam|defwinproc, GWL_STYLE },
    { WM_APP, sent|wparam|lparam, 0, 0 },
    { WM_PAINT, sent },
    { WM_NCPAINT, sent|defwinproc|optional }, /* FIXME: Wine sends it */
    { WM_ERASEBKGND, sent|defwinproc|optional },
    { 0 }
};

static const struct message setstyle_user_seq[] =
{
    { BM_SETSTYLE, sent },
    { WM_STYLECHANGING, sent|wparam|defwinproc, GWL_STYLE },
    { WM_STYLECHANGED, sent|wparam|defwinproc, GWL_STYLE },
    { WM_APP, sent|wparam|lparam, 0, 0 },
    { WM_PAINT, sent },
    { WM_NCPAINT, sent|defwinproc|optional }, /* FIXME: Wine sends it */
    { WM_ERASEBKGND, sent|defwinproc|optional },
    { 0 }
};

static const struct message setstyle_ownerdraw_seq[] =
{
    { BM_SETSTYLE, sent },
    { WM_STYLECHANGING, sent|wparam|defwinproc, GWL_STYLE },
    { WM_STYLECHANGED, sent|wparam|defwinproc, GWL_STYLE },
    { WM_APP, sent|wparam|lparam, 0, 0 },
    { WM_PAINT, sent },
    { WM_NCPAINT, sent|optional }, /* FIXME: Wine sends it */
    { WM_ERASEBKGND, sent|defwinproc|optional },
    { WM_DRAWITEM, sent|wparam|parent, ID_BUTTON },
    { 0 }
};

static const struct message setstate_seq[] =
{
    { BM_SETSTATE, sent },
    { WM_APP, sent|wparam|lparam, 0, 0 },
    { WM_PAINT, sent },
    { WM_NCPAINT, sent|optional }, /* FIXME: Wine sends it */
    { WM_ERASEBKGND, sent|defwinproc|optional },
    { WM_PAINT, sent|optional },
    { 0 }
};

static const struct message setstate_static_seq[] =
{
    { BM_SETSTATE, sent },
    { WM_APP, sent|wparam|lparam, 0, 0 },
    { WM_PAINT, sent },
    { WM_NCPAINT, sent|optional }, /* FIXME: Wine sends it */
    { WM_ERASEBKGND, sent|defwinproc|optional },
    { 0 }
};

static const struct message setstate_user_seq[] =
{
    { BM_SETSTATE, sent },
    { WM_COMMAND, sent|wparam|parent, MAKEWPARAM(ID_BUTTON, BN_HILITE) },
    { WM_APP, sent|wparam|lparam, 0, 0 },
    { WM_PAINT, sent },
    { WM_NCPAINT, sent|optional }, /* FIXME: Wine sends it */
    { WM_ERASEBKGND, sent|defwinproc|optional },
    { 0 }
};

static const struct message setstate_ownerdraw_seq[] =
{
    { BM_SETSTATE, sent },
    { WM_APP, sent|wparam|lparam, 0, 0 },
    { WM_PAINT, sent },
    { WM_NCPAINT, sent|optional }, /* FIXME: Wine sends it */
    { WM_ERASEBKGND, sent|defwinproc|optional },
    { WM_DRAWITEM, sent|wparam|parent, ID_BUTTON },
    { 0 }
};

static const struct message clearstate_seq[] =
{
    { BM_SETSTATE, sent },
    { WM_COMMAND, sent|wparam|parent, MAKEWPARAM(ID_BUTTON, BN_UNHILITE) },
    { WM_APP, sent|wparam|lparam, 0, 0 },
    { WM_PAINT, sent },
    { WM_NCPAINT, sent|optional }, /* FIXME: Wine sends it */
    { WM_ERASEBKGND, sent|defwinproc|optional },
    { 0 }
};

static const struct message clearstate_ownerdraw_seq[] =
{
    { BM_SETSTATE, sent },
    { WM_APP, sent|wparam|lparam, 0, 0 },
    { WM_PAINT, sent },
    { WM_NCPAINT, sent|optional }, /* FIXME: Wine sends it */
    { WM_ERASEBKGND, sent|defwinproc|optional },
    { WM_DRAWITEM, sent|wparam|parent, ID_BUTTON },
    { 0 }
};

static const struct message setcheck_ignored_seq[] =
{
    { BM_SETCHECK, sent },
    { WM_APP, sent|wparam|lparam, 0, 0 },
    { WM_PAINT, sent|optional },
    { 0 }
};

static const struct message setcheck_static_seq[] =
{
    { BM_SETCHECK, sent },
    { WM_APP, sent|wparam|lparam, 0, 0 },
    { WM_PAINT, sent },
    { WM_NCPAINT, sent|optional }, /* FIXME: Wine sends it */
    { WM_ERASEBKGND, sent|defwinproc|optional },
    { 0 }
};

static const struct message setcheck_radio_seq[] =
{
    { BM_SETCHECK, sent },
    { WM_STYLECHANGING, sent|wparam|defwinproc, GWL_STYLE },
    { WM_STYLECHANGED, sent|wparam|defwinproc, GWL_STYLE },
    { WM_APP, sent|wparam|lparam, 0, 0 },
    { 0 }
};

static const struct message setcheck_radio_redraw_seq[] =
{
    { BM_SETCHECK, sent },
    { WM_STYLECHANGING, sent|wparam|defwinproc, GWL_STYLE },
    { WM_STYLECHANGED, sent|wparam|defwinproc, GWL_STYLE },
    { WM_APP, sent|wparam|lparam, 0, 0 },
    { WM_PAINT, sent },
    { WM_NCPAINT, sent|optional }, /* FIXME: Wine sends it */
    { WM_ERASEBKGND, sent|defwinproc|optional },
    { 0 }
};

static HWND create_button(DWORD style, HWND parent)
{
    HMENU menuid = 0;
    HWND hwnd;

    if (parent)
    {
        style |= WS_CHILD|BS_NOTIFY;
        menuid = (HMENU)ID_BUTTON;
    }
    hwnd = CreateWindowExA(0, "Button", "test", style, 0, 0, 50, 14, parent, menuid, 0, NULL);
    ok(hwnd != NULL, "failed to create a button, 0x%08x, %p\n", style, parent);
    pSetWindowSubclass(hwnd, button_subclass_proc, 0, 0);
    return hwnd;
}

static void test_button_messages(void)
{
    static const struct
    {
        DWORD style;
        DWORD dlg_code;
        const struct message *setfocus;
        const struct message *killfocus;
        const struct message *setstyle;
        const struct message *setstate;
        const struct message *clearstate;
        const struct message *setcheck;
    } button[] = {
        { BS_PUSHBUTTON, DLGC_BUTTON | DLGC_UNDEFPUSHBUTTON,
          setfocus_seq, killfocus_seq, setstyle_seq,
          setstate_seq, setstate_seq, setcheck_ignored_seq },
        { BS_DEFPUSHBUTTON, DLGC_BUTTON | DLGC_DEFPUSHBUTTON,
          setfocus_seq, killfocus_seq, setstyle_seq,
          setstate_seq, setstate_seq, setcheck_ignored_seq },
        { BS_CHECKBOX, DLGC_BUTTON,
          setfocus_static_seq, killfocus_static_seq, setstyle_static_seq,
          setstate_static_seq, setstate_static_seq, setcheck_static_seq },
        { BS_AUTOCHECKBOX, DLGC_BUTTON,
          setfocus_static_seq, killfocus_static_seq, setstyle_static_seq,
          setstate_static_seq, setstate_static_seq, setcheck_static_seq },
        { BS_RADIOBUTTON, DLGC_BUTTON | DLGC_RADIOBUTTON,
          setfocus_static_seq, killfocus_static_seq, setstyle_static_seq,
          setstate_static_seq, setstate_static_seq, setcheck_radio_redraw_seq },
        { BS_3STATE, DLGC_BUTTON,
          setfocus_static_seq, killfocus_static_seq, setstyle_static_seq,
          setstate_static_seq, setstate_static_seq, setcheck_static_seq },
        { BS_AUTO3STATE, DLGC_BUTTON,
          setfocus_static_seq, killfocus_static_seq, setstyle_static_seq,
          setstate_static_seq, setstate_static_seq, setcheck_static_seq },
        { BS_GROUPBOX, DLGC_STATIC,
          setfocus_groupbox_seq, killfocus_static_seq, setstyle_static_seq,
          setstate_static_seq, setstate_static_seq, setcheck_ignored_seq },
        { BS_USERBUTTON, DLGC_BUTTON | DLGC_UNDEFPUSHBUTTON,
          setfocus_seq, killfocus_seq, setstyle_user_seq,
          setstate_user_seq, clearstate_seq, setcheck_ignored_seq },
        { BS_AUTORADIOBUTTON, DLGC_BUTTON | DLGC_RADIOBUTTON,
          setfocus_static_seq, killfocus_static_seq, setstyle_static_seq,
          setstate_static_seq, setstate_static_seq, setcheck_radio_redraw_seq },
        { BS_OWNERDRAW, DLGC_BUTTON,
          setfocus_ownerdraw_seq, killfocus_ownerdraw_seq, setstyle_ownerdraw_seq,
          setstate_ownerdraw_seq, clearstate_ownerdraw_seq, setcheck_ignored_seq },
    };
    const struct message *seq;
    unsigned int i;
    HWND hwnd, parent;
    DWORD dlg_code;
    HFONT zfont;
    BOOL todo;

    /* selection with VK_SPACE should capture button window */
    hwnd = create_button(BS_CHECKBOX | WS_VISIBLE | WS_POPUP, NULL);
    ok(hwnd != 0, "Failed to create button window\n");
    ReleaseCapture();
    SetFocus(hwnd);
    SendMessageA(hwnd, WM_KEYDOWN, VK_SPACE, 0);
    ok(GetCapture() == hwnd, "Should be captured on VK_SPACE WM_KEYDOWN\n");
    SendMessageA(hwnd, WM_KEYUP, VK_SPACE, 0);
    DestroyWindow(hwnd);

    parent = CreateWindowExA(0, "TestParentClass", "Test parent", WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                             100, 100, 200, 200, 0, 0, 0, NULL);
    ok(parent != 0, "Failed to create parent window\n");

    for (i = 0; i < sizeof(button)/sizeof(button[0]); i++)
    {
        MSG msg;
        DWORD style, state;

        trace("%d: button test sequence\n", i);
        hwnd = create_button(button[i].style, parent);

        style = GetWindowLongA(hwnd, GWL_STYLE);
        style &= ~(WS_CHILD | BS_NOTIFY);
        /* XP turns a BS_USERBUTTON into BS_PUSHBUTTON */
        if (button[i].style == BS_USERBUTTON)
            ok(style == BS_PUSHBUTTON, "expected style BS_PUSHBUTTON got %x\n", style);
        else
            ok(style == button[i].style, "expected style %x got %x\n", button[i].style, style);

        dlg_code = SendMessageA(hwnd, WM_GETDLGCODE, 0, 0);
        ok(dlg_code == button[i].dlg_code, "%u: wrong dlg_code %08x\n", i, dlg_code);

        ShowWindow(hwnd, SW_SHOW);
        UpdateWindow(hwnd);
        SetFocus(0);
        flush_events();
        SetFocus(0);
        flush_sequences(sequences, NUM_MSG_SEQUENCES);

        todo = button[i].style != BS_OWNERDRAW;
        ok(GetFocus() == 0, "expected focus 0, got %p\n", GetFocus());
        SetFocus(hwnd);
        SendMessageA(hwnd, WM_APP, 0, 0); /* place a separator mark here */
        while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) DispatchMessageA(&msg);
        ok_sequence(sequences, COMBINED_SEQ_INDEX, button[i].setfocus, "SetFocus(hwnd) on a button", todo);

        todo = button[i].style == BS_OWNERDRAW;
        SetFocus(0);
        SendMessageA(hwnd, WM_APP, 0, 0); /* place a separator mark here */
        while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) DispatchMessageA(&msg);
        ok_sequence(sequences, COMBINED_SEQ_INDEX, button[i].killfocus, "SetFocus(0) on a button", todo);
        ok(GetFocus() == 0, "expected focus 0, got %p\n", GetFocus());

        SendMessageA(hwnd, BM_SETSTYLE, button[i].style | BS_BOTTOM, TRUE);
        SendMessageA(hwnd, WM_APP, 0, 0); /* place a separator mark here */
        while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) DispatchMessageA(&msg);
        ok_sequence(sequences, COMBINED_SEQ_INDEX, button[i].setstyle, "BM_SETSTYLE on a button", TRUE);

        style = GetWindowLongA(hwnd, GWL_STYLE);
        style &= ~(WS_VISIBLE | WS_CHILD | BS_NOTIFY);
        /* XP doesn't turn a BS_USERBUTTON into BS_PUSHBUTTON here! */
        ok(style == button[i].style, "expected style %04x got %04x\n", button[i].style, style);

        state = SendMessageA(hwnd, BM_GETSTATE, 0, 0);
        ok(state == 0, "expected state 0, got %04x\n", state);

        flush_sequences(sequences, NUM_MSG_SEQUENCES);

        SendMessageA(hwnd, BM_SETSTATE, TRUE, 0);
        SendMessageA(hwnd, WM_APP, 0, 0); /* place a separator mark here */
        while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) DispatchMessageA(&msg);
        ok_sequence(sequences, COMBINED_SEQ_INDEX, button[i].setstate, "BM_SETSTATE/TRUE on a button", TRUE);

        state = SendMessageA(hwnd, BM_GETSTATE, 0, 0);
        ok(state == BST_PUSHED, "expected state 0x0004, got %04x\n", state);

        style = GetWindowLongA(hwnd, GWL_STYLE);
        style &= ~(WS_CHILD | BS_NOTIFY | WS_VISIBLE);
        ok(style == button[i].style, "expected style %04x got %04x\n", button[i].style, style);

        flush_sequences(sequences, NUM_MSG_SEQUENCES);

        SendMessageA(hwnd, BM_SETSTATE, FALSE, 0);
        SendMessageA(hwnd, WM_APP, 0, 0); /* place a separator mark here */
        while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) DispatchMessageA(&msg);
        ok_sequence(sequences, COMBINED_SEQ_INDEX, button[i].clearstate, "BM_SETSTATE/FALSE on a button", TRUE);

        state = SendMessageA(hwnd, BM_GETSTATE, 0, 0);
        ok(state == 0, "expected state 0, got %04x\n", state);

        style = GetWindowLongA(hwnd, GWL_STYLE);
        style &= ~(WS_CHILD | BS_NOTIFY | WS_VISIBLE);
        ok(style == button[i].style, "expected style %04x got %04x\n", button[i].style, style);

        state = SendMessageA(hwnd, BM_GETCHECK, 0, 0);
        ok(state == BST_UNCHECKED, "expected BST_UNCHECKED, got %04x\n", state);

        flush_sequences(sequences, NUM_MSG_SEQUENCES);

        if (button[i].style == BS_RADIOBUTTON ||
            button[i].style == BS_AUTORADIOBUTTON)
        {
            seq = setcheck_radio_seq;
            todo = TRUE;
        }
        else
        {
            seq = setcheck_ignored_seq;
            todo = FALSE;
        }
        SendMessageA(hwnd, BM_SETCHECK, BST_UNCHECKED, 0);
        SendMessageA(hwnd, WM_APP, 0, 0); /* place a separator mark here */
        while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) DispatchMessageA(&msg);
        ok_sequence(sequences, COMBINED_SEQ_INDEX, seq, "BM_SETCHECK on a button", todo);

        state = SendMessageA(hwnd, BM_GETCHECK, 0, 0);
        ok(state == BST_UNCHECKED, "expected BST_UNCHECKED, got %04x\n", state);

        style = GetWindowLongA(hwnd, GWL_STYLE);
        style &= ~(WS_CHILD | BS_NOTIFY | WS_VISIBLE);
        ok(style == button[i].style, "expected style %04x got %04x\n", button[i].style, style);

        flush_sequences(sequences, NUM_MSG_SEQUENCES);

        SendMessageA(hwnd, BM_SETCHECK, BST_CHECKED, 0);
        SendMessageA(hwnd, WM_APP, 0, 0); /* place a separator mark here */
        while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) DispatchMessageA(&msg);

        if (button[i].style == BS_PUSHBUTTON ||
            button[i].style == BS_DEFPUSHBUTTON ||
            button[i].style == BS_GROUPBOX ||
            button[i].style == BS_USERBUTTON ||
            button[i].style == BS_OWNERDRAW)
        {
            ok_sequence(sequences, COMBINED_SEQ_INDEX, button[i].setcheck, "BM_SETCHECK on a button", FALSE);
            state = SendMessageA(hwnd, BM_GETCHECK, 0, 0);
            ok(state == BST_UNCHECKED, "expected check BST_UNCHECKED, got %04x\n", state);
        }
        else
        {
            ok_sequence(sequences, COMBINED_SEQ_INDEX, button[i].setcheck, "BM_SETCHECK on a button", TRUE);
            state = SendMessageA(hwnd, BM_GETCHECK, 0, 0);
            ok(state == BST_CHECKED, "expected check BST_CHECKED, got %04x\n", state);
        }

        style = GetWindowLongA(hwnd, GWL_STYLE);
        style &= ~(WS_CHILD | BS_NOTIFY | WS_VISIBLE);
        if (button[i].style == BS_RADIOBUTTON ||
            button[i].style == BS_AUTORADIOBUTTON)
            ok(style == (button[i].style | WS_TABSTOP), "expected style %04x | WS_TABSTOP got %04x\n", button[i].style, style);
        else
            ok(style == button[i].style, "expected style %04x got %04x\n", button[i].style, style);

        DestroyWindow(hwnd);
    }

    DestroyWindow(parent);

    hwnd = create_button(BS_PUSHBUTTON, NULL);

    SetForegroundWindow(hwnd);
    flush_events();

    SetActiveWindow(hwnd);
    SetFocus(0);
    flush_sequences(sequences, NUM_MSG_SEQUENCES);

    SendMessageA(hwnd, WM_LBUTTONDOWN, 0, 0);
    ok_sequence(sequences, COMBINED_SEQ_INDEX, lbuttondown_seq, "WM_LBUTTONDOWN on a button", FALSE);

    SendMessageA(hwnd, WM_LBUTTONUP, 0, 0);
    ok_sequence(sequences, COMBINED_SEQ_INDEX, lbuttonup_seq, "WM_LBUTTONUP on a button", TRUE);

    flush_sequences(sequences, NUM_MSG_SEQUENCES);
    zfont = GetStockObject(SYSTEM_FONT);
    SendMessageA(hwnd, WM_SETFONT, (WPARAM)zfont, TRUE);
    UpdateWindow(hwnd);
    ok_sequence(sequences, COMBINED_SEQ_INDEX, setfont_seq, "WM_SETFONT on a button", FALSE);

    DestroyWindow(hwnd);
}

static void test_button_class(void)
{
    static const WCHAR testW[] = {'t','e','s','t',0};
    WNDCLASSEXW exW, ex2W;
    WNDCLASSEXA exA;
    char buffA[100];
    WCHAR *nameW;
    HWND hwnd;
    BOOL ret;
    int len;

    ret = GetClassInfoExA(NULL, WC_BUTTONA, &exA);
    ok(ret, "got %d\n", ret);
todo_wine
    ok(IS_WNDPROC_HANDLE(exA.lpfnWndProc), "got %p\n", exA.lpfnWndProc);

    ret = GetClassInfoExW(NULL, WC_BUTTONW, &exW);
    ok(ret, "got %d\n", ret);
    ok(!IS_WNDPROC_HANDLE(exW.lpfnWndProc), "got %p\n", exW.lpfnWndProc);

    /* check that versioned class is also accessible */
    nameW = get_versioned_classname(WC_BUTTONW);
    ok(lstrcmpW(nameW, WC_BUTTONW), "got %s\n", wine_dbgstr_w(nameW));

    ret = GetClassInfoExW(NULL, nameW, &ex2W);
todo_wine
    ok(ret, "got %d\n", ret);
if (ret) /* TODO: remove once Wine is fixed */
    ok(ex2W.lpfnWndProc == exW.lpfnWndProc, "got %p, %p\n", exW.lpfnWndProc, ex2W.lpfnWndProc);

    /* Check reported class name */
    hwnd = create_button(BS_CHECKBOX, NULL);
    len = GetClassNameA(hwnd, buffA, sizeof(buffA));
    ok(len == strlen(buffA), "got %d\n", len);
    ok(!strcmp(buffA, "Button"), "got %s\n", buffA);

    len = RealGetWindowClassA(hwnd, buffA, sizeof(buffA));
    ok(len == strlen(buffA), "got %d\n", len);
    ok(!strcmp(buffA, "Button"), "got %s\n", buffA);
    DestroyWindow(hwnd);

    /* explicitly create with versioned class name */
    hwnd = CreateWindowExW(0, nameW, testW, BS_CHECKBOX, 0, 0, 50, 14, NULL, 0, 0, NULL);
todo_wine
    ok(hwnd != NULL, "failed to create a window %s\n", wine_dbgstr_w(nameW));
if (hwnd)
{
    len = GetClassNameA(hwnd, buffA, sizeof(buffA));
    ok(len == strlen(buffA), "got %d\n", len);
    ok(!strcmp(buffA, "Button"), "got %s\n", buffA);

    len = RealGetWindowClassA(hwnd, buffA, sizeof(buffA));
    ok(len == strlen(buffA), "got %d\n", len);
    ok(!strcmp(buffA, "Button"), "got %s\n", buffA);

    DestroyWindow(hwnd);
}
}

static void register_parent_class(void)
{
    WNDCLASSA cls;

    cls.style = 0;
    cls.lpfnWndProc = test_parent_wndproc;
    cls.cbClsExtra = 0;
    cls.cbWndExtra = 0;
    cls.hInstance = GetModuleHandleA(0);
    cls.hIcon = 0;
    cls.hCursor = LoadCursorA(0, (LPCSTR)IDC_ARROW);
    cls.hbrBackground = GetStockObject(WHITE_BRUSH);
    cls.lpszMenuName = NULL;
    cls.lpszClassName = "TestParentClass";
    RegisterClassA(&cls);
}

START_TEST(button)
{
    ULONG_PTR ctx_cookie;
    HANDLE hCtx;

    if (!load_v6_module(&ctx_cookie, &hCtx))
        return;

    register_parent_class();

    init_functions();
    init_msg_sequences(sequences, NUM_MSG_SEQUENCES);

    test_button_class();
    test_button_messages();

    unload_v6_module(ctx_cookie, hCtx);
}
