
--proc/windows/window: Windows (as in HWND) API
--Written by Cosmin Apreutesei. Public Domain.

setfenv(1, require'winapi')
require'winapi.winuser'
require'winapi.windowclasses'
require'winapi.gdi'

--creation

ffi.cdef[[
HWND CreateWindowExW(
     DWORD dwExStyle,
     LPCWSTR lpClassName,
     LPCWSTR lpWindowName,
     DWORD dwStyle,
     int X,
     int Y,
     int nWidth,
     int nHeight,
     HWND hWndParent,
     HMENU hMenu,
     HINSTANCE hInstance,
     LPVOID lpParam);

BOOL DestroyWindow(HWND hWnd);
]]

WS_OVERLAPPED    = 0x00000000 --no bits!
WS_POPUP         = 0x80000000
WS_CHILD         = 0x40000000
WS_MINIMIZE      = 0x20000000
WS_VISIBLE       = 0x10000000
WS_DISABLED      = 0x08000000
WS_CLIPSIBLINGS  = 0x04000000
WS_CLIPCHILDREN  = 0x02000000
WS_MAXIMIZE      = 0x01000000
WS_CAPTION       = 0x00C00000 --WS_BORDER + WS_DLGFRAME (always set on creation)
WS_BORDER        = 0x00800000
WS_DLGFRAME      = 0x00400000
WS_VSCROLL       = 0x00200000
WS_HSCROLL       = 0x00100000
WS_SYSMENU       = 0x00080000
WS_THICKFRAME    = 0x00040000
WS_GROUP         = 0x00020000
WS_TABSTOP       = 0x00010000 --same value as WS_MAXIMIZEBOX
WS_MINIMIZEBOX   = 0x00020000
WS_MAXIMIZEBOX   = 0x00010000
WS_TILED         = WS_OVERLAPPED
WS_ICONIC        = WS_MINIMIZE
WS_SIZEBOX       = WS_THICKFRAME
WS_OVERLAPPEDWINDOW = bit.bor(
	WS_OVERLAPPED,
	WS_CAPTION,
	WS_SYSMENU,
	WS_THICKFRAME,
	WS_MINIMIZEBOX,
	WS_MAXIMIZEBOX)
WS_TILEDWINDOW   = WS_OVERLAPPEDWINDOW
WS_CHILDWINDOW   = WS_CHILD

WS_EX_DLGMODALFRAME   = 0x00000001
WS_EX_NOPARENTNOTIFY  = 0x00000004
WS_EX_TOPMOST         = 0x00000008
WS_EX_ACCEPTFILES     = 0x00000010
WS_EX_TRANSPARENT     = 0x00000020
WS_EX_MDICHILD        = 0x00000040
WS_EX_TOOLWINDOW      = 0x00000080
WS_EX_WINDOWEDGE      = 0x00000100 --always set on creation
WS_EX_CLIENTEDGE      = 0x00000200
WS_EX_CONTEXTHELP     = 0x00000400
WS_EX_RIGHT           = 0x00001000
WS_EX_LEFT            = 0x00000000 --no bits!
WS_EX_RTLREADING      = 0x00002000
WS_EX_LTRREADING      = 0x00000000 --no bits!
WS_EX_LEFTSCROLLBAR   = 0x00004000
WS_EX_RIGHTSCROLLBAR  = 0x00000000 --no bits!
WS_EX_CONTROLPARENT   = 0x00010000
WS_EX_STATICEDGE      = 0x00020000
WS_EX_APPWINDOW       = 0x00040000
WS_EX_LAYERED         = 0x00080000
WS_EX_NOINHERITLAYOUT = 0x00100000
WS_EX_LAYOUTRTL       = 0x00400000
WS_EX_COMPOSITED      = 0x02000000
WS_EX_NOACTIVATE      = 0x08000000 --only works with frameless windows

WS_POPUPWINDOW         = bit.bor(WS_POPUP, WS_BORDER, WS_SYSMENU)
WS_EX_OVERLAPPEDWINDOW = bit.bor(WS_EX_WINDOWEDGE, WS_EX_CLIENTEDGE)
WS_EX_PALETTEWINDOW    = bit.bor(WS_EX_WINDOWEDGE, WS_EX_TOOLWINDOW, WS_EX_TOPMOST)

CW_USEDEFAULT = 0x80000000 --if used for x, then y must be a SW_* flag

--NOTE: Only windows without the WS_SIZEBOX style can be created off-screen.
function CreateWindow(info)
	local class = wcs(MAKEINTRESOURCE(info.class))
	local text = wcs(info.text)
	local hwnd = checkh(C.CreateWindowExW(
								flags(info.style_ex),
								ffi.cast('LPCWSTR', class),
								wcs(info.text),
								flags(info.style),
								info.x, info.y, info.w, info.h,
								info.parent,
								info.menu, info.instance, info.param))
	if not info.parent then own(hwnd, DestroyWindow) end
	return hwnd
end

function DestroyWindow(hwnd)
	if not hwnd then return end
	checknz(C.DestroyWindow(hwnd))
	disown(hwnd)
end

--commands

ffi.cdef[[
BOOL ShowWindow(HWND hWnd, int nCmdShow);
BOOL ShowWindowAsync(HWND hWnd, int nCmdShow);
]]

SW_HIDE             =  0 --hide and remember the show state
SW_SHOWNORMAL       =  1 --revert to normal state and activate (do nothing if already in normal state)
SW_SHOWMINIMIZED    =  2 --minimize but do not deactivate (do nothing if already minimized)
SW_SHOWMAXIMIZED    =  3 --maximize and activate (do nothing if already maximized)
SW_SHOWNOACTIVATE   =  4 --revert to normal state but do not activate (do nothing if alread in normal state)
SW_SHOW             =  5 --show in current state and activate (activate even if minimized; do nothing if already visible)
SW_MINIMIZE         =  6 --minimize and deactivate (do nothing if already minimized); same as SW_SHOWMINNOACTIVE
SW_SHOWMINNOACTIVE  =  7 --minimize and deactivate; same as SW_MINIMIZE
SW_SHOWNA           =  8 --show in current state but do not activate
SW_RESTORE          =  9 --restore to last state (minimized -> normal or maximized; maximized -> normal) and activate
SW_SHOWDEFAULT      = 10 --show per STARTUPINFO
SW_FORCEMINIMIZE    = 11 --minimize from a different thread

function ShowWindow(hwnd, SW)
	return C.ShowWindow(hwnd, flags(SW)) ~= 0
end

function ShowWindowAsync(hwnd, SW)
	return C.ShowWindowAsync(hwnd, flags(SW)) ~= 0
end

ffi.cdef[[
int GetWindowTextLengthW(HWND hWnd);

int  GetWindowTextW(
     HWND hWnd,
     LPWSTR lpString,
     int nMaxCount);

BOOL SetWindowTextW(
     HWND hWnd,
     LPCWSTR lpString);
]]

GetWindowTextLength = C.GetWindowTextLengthW

function GetWindowText(hwnd, buf)
	local ws, sz = WCS(buf or C.GetWindowTextLengthW(hwnd))
	C.GetWindowTextW(hwnd, ws, sz+1)
	return buf or mbs(ws)
end

function SetWindowText(hwnd, text)
	checknz(C.SetWindowTextW(hwnd, wcs(text)))
end

ffi.cdef[[
BOOL SetWindowPos(
     HWND hWnd,
     HWND hWndInsertAfter,
     int X,
     int Y,
     int cx,
     int cy,
     UINT uFlags);
]]

HWND_TOP        = ffi.cast('HWND', 0)
HWND_BOTTOM     = ffi.cast('HWND', 1)
HWND_TOPMOST    = ffi.cast('HWND', -1)
HWND_NOTOPMOST  = ffi.cast('HWND', -2)

SWP_NOSIZE           = 0x0001
SWP_NOMOVE           = 0x0002
SWP_NOZORDER         = 0x0004
SWP_NOREDRAW         = 0x0008
SWP_NOACTIVATE       = 0x0010
SWP_FRAMECHANGED     = 0x0020  --the frame changed: send WM_NCCALCSIZE
SWP_SHOWWINDOW       = 0x0040
SWP_HIDEWINDOW       = 0x0080
SWP_NOCOPYBITS       = 0x0100
SWP_NOOWNERZORDER    = 0x0200  --don't do owner Z ordering
SWP_NOSENDCHANGING   = 0x0400  --don't send WM_WINDOWPOSCHANGING
SWP_DRAWFRAME        = SWP_FRAMECHANGED
SWP_NOREPOSITION     = SWP_NOOWNERZORDER
SWP_DEFERERASE       = 0x2000
SWP_ASYNCWINDOWPOS   = 0x4000
SWP_STATECHANGED     = 0x8000  --undocumented
SWP_FRAMECHANGED_ONLY = bit.bor(SWP_NOZORDER, SWP_NOOWNERZORDER, SWP_NOACTIVATE,
											SWP_NOSIZE, SWP_NOMOVE, SWP_FRAMECHANGED)
SWP_ZORDER_CHANGED_ONLY = bit.bor(SWP_NOMOVE, SWP_NOSIZE, SWP_NOACTIVATE)

--NOTE: Windows can't be moved off-screen with this function.
function SetWindowPos(hwnd, back_hwnd, x, y, cx, cy, SWP)
	checknz(C.SetWindowPos(hwnd, back_hwnd, x, y, cx, cy, flags(SWP)))
end

ffi.cdef[[
BOOL MoveWindow(HWND hWnd, int X, int Y, int nWidth, int nHeight, BOOL bRepaint);
BOOL UpdateWindow(HWND hWnd);
BOOL EnableWindow(HWND hWnd, BOOL bEnable);
BOOL GetWindowRect(HWND hWnd, LPRECT lpRect);
BOOL GetClientRect(HWND hWnd, LPRECT lpRect);
HWND SetParent(HWND hWndChild, HWND hWndNewParent);
HWND GetParent(HWND hWnd);
BOOL IsWindowVisible(HWND hWnd);
BOOL IsWindowEnabled(HWND hWnd);
HWND GetActiveWindow();
HWND SetActiveWindow(HWND hWnd);
BOOL BringWindowToTop(HWND hWnd);
BOOL IsIconic(HWND hWnd);
BOOL IsZoomed(HWND hWnd);
HWND GetFocus();
HWND SetFocus(HWND hWnd);
HWND GetWindow(HWND hWnd, UINT uCmd);
HWND GetTopWindow(HWND hWnd);
BOOL IsWindowUnicode(HWND hWnd);
HWND GetDesktopWindow();
HWND GetForegroundWindow();
BOOL SetForegroundWindow(HWND hWnd);
BOOL LockSetForegroundWindow(UINT uLockCode);
]]

function MoveWindow(hwnd, x, y, w, h, repaint)
	checknz(C.MoveWindow(hwnd, x, y, w, h, repaint))
end

function UpdateWindow(hwnd) --send WM_PAINT _if_ current update region is not empty
	checknz(C.UpdateWindow(hwnd))
end

function EnableWindow(hwnd, enable)
	return C.EnableWindow(hwnd, enable) ~= 0
end

function GetWindowRect(hwnd, rect)
	rect = RECT(rect)
	checknz(C.GetWindowRect(hwnd, rect))
	return rect
end

function GetClientRect(hwnd, rect)
	rect = RECT(rect)
	checknz(C.GetClientRect(hwnd, rect))
	return rect
end

GetParent = C.GetParent

function SetParent(hwnd, parent)
	local prev_parent = checkh(C.SetParent(hwnd, parent))
	if parent == nil then own(hwnd, DestroyWindow) else disown(hwnd) end
	return prev_parent
end

function IsWindowVisible(hwnd)
	return C.IsWindowVisible(hwnd) ~= 0
end

function IsWindowEnabled(hwnd)
	return C.IsWindowEnabled(hwnd) ~= 0
end

function GetActiveWindow()
	return ptr(C.GetActiveWindow())
end

--NOTE: SetActiveWindow() triggers WM_ACTIVATEAPP even when the app doesn't activate!
--An app (and thus one of its windows) doesn't activate with SetActiveWindow(),
--but with SetForegroundWindow(), and even then it might not activate immediately,
--but instead blink the window in the taskbar waiting for the user to activate it.
--WM_ACTIVATE is sent immediately and in all cases, while WM_NCACTIVATE is sent
--if and after the user clicks on the flashing taskbar button.
SetActiveWindow = C.SetActiveWindow

function BringWindowToTop(hwnd)
	checknz(C.BringWindowToTop(hwnd))
end

function IsIconic(hwnd)
	return C.IsIconic(hwnd) ~= 0
end

function IsZoomed(hwnd)
	return C.IsZoomed(hwnd) ~= 0
end

function GetFocus()
	return ptr(C.GetFocus())
end

SetFocus = C.SetFocus

GW_HWNDFIRST        = 0
GW_HWNDLAST         = 1
GW_HWNDNEXT         = 2
GW_HWNDPREV         = 3
GW_OWNER            = 4
GW_CHILD            = 5
GW_ENABLEDPOPUP     = 6

function GetWindow(hwnd, GW)   return ptr(C.GetWindow(hwnd, flags(GW))) end
function GetOwner(hwnd)        return callh2(GetWindow, hwnd, GW_OWNER) end
function GetFirstChild(hwnd)   return callh2(GetWindow, hwnd, GW_CHILD) end
function GetFirstSibling(hwnd) return callh2(GetWindow, hwnd, GW_HWNDFIRST) end
function GetLastSibling(hwnd)  return callh2(GetWindow, hwnd, GW_HWNDLAST) end
function GetNextSibling(hwnd)  return callh2(GetWindow, hwnd, GW_HWNDNEXT) end --from top to bottom of the z-order
function GetPrevSibling(hwnd)  return callh2(GetWindow, hwnd, GW_HWNDPREV) end --from bottom to top of the z-order

local function nextchild(parent, sibling)
	if not sibling then return GetFirstChild(parent) end
	return GetNextSibling(sibling)
end

--returns a stateless iterator iterating from top to bottom of the z-order.
--NOTE: you can get infinite loops if the z-order of the windows involved changes while iterating.
function GetChildWindows(hwnd)
	return nextchild, hwnd
end

GetTopWindow = C.GetTopWindow --same semantics as GetFirstChild ?

function IsWindowUnicode(hwnd) --for outside windows; ours are always unicode
	return C.IsWindowUnicode(hwnd) ~= 0
end

GetDesktopWindow = C.GetDesktopWindow

function GetForegroundWindow()
	return ptr(C.GetForegroundWindow())
end

function SetForegroundWindow(hwnd)
	return C.SetForegroundWindow(hwnd) ~= 0
end
jit.off(SetForegroundWindow) --important!

LSFW_LOCK   = 1
LSFW_UNLOCK = 2

function LockSetForegroundWindow(hwnd, LSFW)
	checknz(C.LockSetForegroundWindow(hwnd, flags(LSFW)))
end

--enum windows

ffi.cdef[[
typedef __stdcall BOOL (* WNDENUMPROC)(HWND, LPARAM);
BOOL EnumChildWindows(
     HWND hWndParent,
     WNDENUMPROC lpEnumFunc,
     LPARAM lParam);
]]

--NOTE: for a not null hwnd you can use GetChildWindows (no callback, no table).
function EnumChildWindows(hwnd) --note: front-to-back order
	local t = {}
	local cb = ffi.cast('WNDENUMPROC', function(hwnd, lparam)
		t[#t+1] = hwnd
		return 1 --continue
	end)
	C.EnumChildWindows(hwnd, cb, 0)
	cb:free()
	return t
end

WPF_ASYNCWINDOWPLACEMENT = 0x0004
WPF_RESTORETOMAXIMIZED   = 0x0002
WPF_SETMINPOSITION       = 0x0001

ffi.cdef[[
typedef struct tagWINDOWPLACEMENT {
    UINT  length;
    UINT  _flags;
    UINT  showCmd;
    POINT ptMinPosition;
    POINT ptMaxPosition;
    RECT  rcNormalPosition;
} WINDOWPLACEMENT;
typedef WINDOWPLACEMENT *PWINDOWPLACEMENT, *LPWINDOWPLACEMENT;

BOOL GetWindowPlacement(
     HWND hWnd,
     WINDOWPLACEMENT *lpwndpl);

BOOL SetWindowPlacement(
     HWND hWnd,
     const WINDOWPLACEMENT *lpwndpl);
]]

WINDOWPLACEMENT = struct{
	ctype = 'WINDOWPLACEMENT', size = 'length',
	fields = sfields{
		'flags', '_flags', flags, pass, --WPF_*
	},
}

function GetWindowPlacement(hwnd, wpl)
	wpl = WINDOWPLACEMENT(wpl)
	checknz(C.GetWindowPlacement(hwnd, wpl))
	return wpl
end

function SetWindowPlacement(hwnd, wpl)
	wpl = WINDOWPLACEMENT(wpl)
	checknz(C.SetWindowPlacement(hwnd, wpl))
end

ffi.cdef[[
LRESULT DefWindowProcW(
     HWND hWnd,
     UINT Msg,
     WPARAM wParam,
     LPARAM lParam);

LRESULT CallWindowProcW(
     WNDPROC lpPrevWndFunc,
	  HWND hWnd,
     UINT Msg,
     WPARAM wParam,
     LPARAM lParam);
]]

DefWindowProc = C.DefWindowProcW
CallWindowProc = C.CallWindowProcW

--set/get window long

GWL_WNDPROC        = -4
GWL_HINSTANCE      = -6
GWL_HWNDPARENT     = -8 --this gets/sets the owner, not the parent!
GWL_STYLE          = -16
GWL_EXSTYLE        = -20
GWL_USERDATA       = -21
GWL_ID             = -12

if ffi.abi'64bit' then
	ffi.cdef[[
	LONG_PTR SetWindowLongPtrW(HWND hWnd, int nIndex, LONG_PTR dwNewLong);
	LONG_PTR GetWindowLongPtrW(HWND hWnd, int nIndex);
	]]
	SetWindowLongW = C.SetWindowLongPtrW
	GetWindowLongW = C.GetWindowLongPtrW
else --32bit
	ffi.cdef[[
	LONG SetWindowLongW(HWND hWnd, int nIndex, LONG dwNewLong);
	LONG GetWindowLongW(HWND hWnd, int nIndex);
	]]
	SetWindowLongW = C.SetWindowLongW
	GetWindowLongW = C.GetWindowLongW
end

function SetWindowLong(hwnd, GWL, long)
	return callnz2(SetWindowLongW, hwnd, flags(GWL), ffi.cast('LONG', long))
end

function GetWindowLong(hwnd, GWL)
	return GetWindowLongW(hwnd, flags(GWL))
end

--Get/SetWindowLong wrappers (don't look them up in the docs)

function GetWindowStyle(hwnd) return tonumber(GetWindowLong(hwnd, GWL_STYLE)) end
function SetWindowStyle(hwnd, style) SetWindowLong(hwnd, GWL_STYLE, flags(style)) end

function GetWindowExStyle(hwnd) return tonumber(GetWindowLong(hwnd, GWL_EXSTYLE)) end
function SetWindowExStyle(hwnd, style) SetWindowLong(hwnd, GWL_EXSTYLE, flags(style)) end

function GetWindowInstance(hwnd) return ffi.cast('HMODULE', GetWindowLong(hwnd, GWL_HINSTANCE)) end
function SetWindowInstance(hwnd, hinst) SetWindowLong(hwnd, GWL_HINSTANCE, hinst) end

function IsRestored(hwnd) return bit.band(GetWindowStyle(hwnd), WS_MINIMIZE + WS_MAXIMIZE) == 0 end
function IsVisible(hwnd) return bit.band(GetWindowStyle(hwnd), WS_VISIBLE) == WS_VISIBLE end

function GetWindowOwner(hwnd) return ffi.cast('HWND', GetWindowLong(hwnd, GWL_HWNDPARENT)) end --GetOwner() is another way
function SetWindowOwner(hwnd, owner_hwnd) SetWindowLong(hwnd, GWL_HWNDPARENT, owner_hwnd) end

--window geometry

ffi.cdef[[
int MapWindowPoints(
     HWND hWndFrom,
     HWND hWndTo,
     LPPOINT lpPoints,
     UINT cPoints);

HWND WindowFromPoint(POINT Point);
HWND ChildWindowFromPoint(HWND hWndParent, POINT Point);
HWND RealChildWindowFromPoint(HWND hWndParent, POINT Point);

BOOL AdjustWindowRectEx(
     LPRECT lpRect,
     DWORD dwStyle,
     BOOL bMenu,
     DWORD dwExStyle);
]]

function MapWindowPoints(hwndFrom, hwndTo, points)
	local points, sz = arrays.POINT(points)
	callnz2(C.MapWindowPoints, hwndFrom, hwndTo, points, sz)
	return points
end

function MapWindowPoint(hwndFrom, hwndTo, ...) --changes and returns the same passed point
	local p = POINT(...)
	callnz2(C.MapWindowPoints, hwndFrom, hwndTo, ffi.cast('POINT*', p), 1)
	return p
end

function MapWindowRect(hwndFrom, hwndTo, ...) --changes and returns the same passed rect
	local r = RECT(...)
	callnz2(C.MapWindowPoints, hwndFrom, hwndTo, ffi.cast('POINT*', r), 2)
	return r
end

function WindowFromPoint(...)
	return ptr(C.WindowFromPoint(POINT(...)))
end

function ChildWindowFromPoint(hwnd, ...)
	return ptr(C.ChildWindowFromPoint(hwnd, POINT(...)))
end

function RealChildWindowFromPoint(hwnd, ...)
	return ptr(C.RealChildWindowFromPoint(hwnd, POINT(...)))
end

function AdjustWindowRect(crect, style, style_ex, has_menu, rect)
	rect = RECT(rect)
	rect.x1 = crect.x1
	rect.y1 = crect.y1
	rect.x2 = crect.x2
	rect.y2 = crect.y2
	checknz(C.AdjustWindowRectEx(rect, style, has_menu, style_ex))
	return rect
end

-- layered windows

ULW_COLORKEY            = 0x00000001
ULW_ALPHA               = 0x00000002
ULW_OPAQUE              = 0x00000004
ULW_EX_NORESIZE         = 0x00000008

ffi.cdef[[
BOOL UpdateLayeredWindow(
	HWND hwnd,
	HDC hdcDst,
	POINT *pptDst,
	SIZE *psize,
	HDC hdcSrc,
	POINT *pptSrc,
	COLORREF crKey,
	BLENDFUNCTION *pblend,
	DWORD dwFlags
);

BOOL SetLayeredWindowAttributes(
	HWND     hwnd,
	COLORREF crKey,
	BYTE     bAlpha,
	DWORD    dwFlags
);
]]

--NOTE: this fails under Remote Desktop and doesn't set an error in GetLastError().
function UpdateLayeredWindow(hwnd, dst_hdc, dst_ppt, psize, src_hdc, src_ppt, key, pblend, ULW)
	return C.UpdateLayeredWindow(hwnd, dst_hdc, dst_ppt, psize, src_hdc, src_ppt, key, pblend, flags(ULW)) == 1
end

LWA_COLORKEY = 1
LWA_ALPHA    = 2

function SetLayeredWindowAttributes(hwnd, key_color, alpha, LWA)
	return C.SetLayeredWindowAttributes(hwnd, key_color, alpha, flags(LWA)) == 1
end

-- timers

ffi.cdef[[
typedef void (* TIMERPROC)(HWND, UINT, UINT_PTR, DWORD);
UINT_PTR SetTimer(
     HWND hWnd,
     UINT_PTR nIDEvent,
     UINT uElapse,
     TIMERPROC lpTimerFunc);

BOOL KillTimer(
     HWND hWnd,
     UINT_PTR uIDEvent);
]]

--NOTE: calling error() in callback is not supported!
function SetTimer(hwnd, id, timeout, callback)
	return checknz(C.SetTimer(hwnd, id, timeout, callback))
end

function KillTimer(hwnd, id)
	checknz(C.KillTimer(hwnd, id))
end

-- messages

ffi.cdef[[
typedef struct tagMSG {
	 HWND        hwnd;
	 UINT        message;
	 union {
		WPARAM      wParam;
		LPARAM      signed_wParam;
	 };
	 LPARAM      lParam;
	 DWORD       time;
	 POINT       pt;
} MSG, *PMSG, *NPMSG, *LPMSG;

BOOL GetMessageW(
	  LPMSG lpMsg,
	  HWND hWnd,
	  UINT wMsgFilterMin,
	  UINT wMsgFilterMax);

BOOL TranslateMessage(const MSG *lpMsg);

int TranslateAcceleratorW(
     HWND hWnd,
     HACCEL hAccTable,
     LPMSG lpMsg);

LRESULT DispatchMessageW(const MSG *lpMsg);

BOOL IsDialogMessageW(
     HWND hDlg,
     LPMSG lpMsg);

void PostQuitMessage(int nExitCode);

LRESULT SendMessageW(
	  HWND hWnd,
	  UINT Msg,
	  WPARAM wParam,
	  LPARAM lParam);

BOOL PeekMessageW(
     LPMSG lpMsg,
     HWND hWnd,
     UINT wMsgFilterMin,
     UINT wMsgFilterMax,
     UINT wRemoveMsg);

BOOL PostMessageW(
     HWND hWnd,
     UINT Msg,
     WPARAM wParam,
     LPARAM lParam);

LONG GetMessageTime(void);

UINT RegisterWindowMessageW(LPCWSTR lpString);

]]

local msg0 = types.MSG()
function GetMessage(hwnd, WMmin, WMmax, msg)
	msg = msg and types.MSG(msg) or msg0
	checkpoz(C.GetMessageW(msg, hwnd, flags(WMmin), flags(WMmax)))
	return msg
end

function DispatchMessage(msg)
	return C.DispatchMessageW(msg)
end

function TranslateAccelerator(hwnd, haccel, msg)
	return C.TranslateAcceleratorW(hwnd, haccel, msg) ~= 0
end

function TranslateMessage(msg)
	return C.TranslateMessage(msg)
end

--NOTE: IsDialogMessage() is filtering out WM_CHAR messages.
function IsDialogMessage(hwnd, msg)
	return C.IsDialogMessageW(hwnd, msg) ~= 0
end

HWND_BROADCAST = ffi.cast('HWND', 0xffff)
HWND_MESSAGE   = ffi.cast('HWND', -3)

function PostQuitMessage(exitcode)
	C.PostQuitMessage(exitcode or 0)
end

function RegisterWindowMessage(name)
	local code = checknz(C.RegisterWindowMessageW(wcs(name)))
	register_message(name, code)
	return code
end

function SendMessagePtr(hwnd, WM, wParam, lParam)
	if wParam == nil then wParam = 0 end
	if lParam == nil then lParam = 0 end
	return C.SendMessageW(hwnd, flags(WM),
		ffi.cast('WPARAM', wParam),
		ffi.cast('LPARAM', lParam))
end
if ffi.abi'64bit' then
	local uint32_ct = ffi.typeof'uint32_t'
	local int32_ct  = ffi.typeof'int32_t'
	function SendMessage(hwnd, WM, wParam, lParam)
		--WPARAM sometimes accepts negative values and those must be 32bit zero-extended.
		if type(wParam) == 'number' and wParam < 0 then
			wParam = ffi.cast(uint32_ct, wParam) --zero-extend negative numbers.
		end
		--LRESULT and LPARAM are sign-extended 32bit integers.
		return tonumber(ffi.cast(int32_ct, SendMessagePtr(hwnd, WM, wParam, lParam)))
	end
else
	SendMessage = SendMessagePtr
end
SNDMSG = SendMessage
SNDMSG_PTR = SendMessagePtr --use this when the return value is a pointer.

function PostMessage(hwnd, WM, wParam, lParam)
	if wParam == nil then wParam = 0 end
	if lParam == nil then lParam = 0 end
	return C.PostMessageW(hwnd, WM,
		ffi.cast('WPARAM', wParam),
		ffi.cast('LPARAM', lParam))
end

GetMessageTime = C.GetMessageTime

-- Queue status flags for GetQueueStatus() and MsgWaitForMultipleObjects()
QS_KEY              = 0x0001
QS_MOUSEMOVE        = 0x0002
QS_MOUSEBUTTON      = 0x0004
QS_POSTMESSAGE      = 0x0008
QS_TIMER            = 0x0010
QS_PAINT            = 0x0020
QS_SENDMESSAGE      = 0x0040
QS_HOTKEY           = 0x0080
QS_ALLPOSTMESSAGE   = 0x0100
QS_RAWINPUT         = 0x0400
QS_MOUSE            = bit.bor(QS_MOUSEMOVE, QS_MOUSEBUTTON)
QS_INPUT            = bit.bor(QS_MOUSE, QS_KEY, QS_RAWINPUT)
QS_ALLEVENTS        = bit.bor(QS_INPUT, QS_POSTMESSAGE, QS_TIMER, QS_PAINT, QS_HOTKEY)
QS_ALLINPUT         = bit.bor(QS_INPUT, QS_POSTMESSAGE, QS_TIMER, QS_PAINT, QS_HOTKEY, QS_SENDMESSAGE)

local STATUS_ABANDONED_WAIT_0 = 0x00000080

--flags for MsgWaitForMultipleObjectsEx()
MWMO_WAITALL        = 0x0001
MWMO_ALERTABLE      = 0x0002
MWMO_INPUTAVAILABLE = 0x0004

--return values for MsgWaitForMultipleObjectsEx()
WAIT_FAILED         = 0xFFFFFFFF
WAIT_OBJECT_0       = 0
WAIT_ABANDONED      = STATUS_ABANDONED_WAIT_0
WAIT_ABANDONED_0    = STATUS_ABANDONED_WAIT_0
WAIT_TIMEOUT        = 258
WAIT_IO_COMPLETION  = 0x000000C0

ffi.cdef[[
DWORD GetQueueStatus(UINT flags);
DWORD MsgWaitForMultipleObjectsEx(
	DWORD  nCount,
	HANDLE *pHandles,
	DWORD  dwMilliseconds,
	DWORD  dwWakeMask,
	DWORD  dwFlags
);
]]

--returns:
	-- true: input in queue
	-- false: timeout
	-- number[, abandoned]: index in handles array (and mutex abandoned status)
	-- 'io': WAIT_IO_COMPLETION
function MsgWaitForMultipleObjectsEx(count, handles, wait_ms, QS, MWMO)
	if wait_ms == 1/0 then
		wait_ms = 0xFFFFFFFF
	end

	local ret = C.MsgWaitForMultipleObjectsEx(
		count, handles, wait_ms, flags(QS), flags(MWMO))

	check(ret ~= WAIT_FAILED)
	if ret >= WAIT_OBJECT_0 and ret < WAIT_OBJECT_0 + count then
		return ret - WAIT_OBJECT_0
	elseif ret == WAIT_OBJECT_0 + count then
		return true
	elseif ret >= WAIT_ABANDONED_0 and ret < WAIT_ABANDONED_0 + count then
		return ret - WAIT_ABANDONED_0, true
	elseif ret == WAIT_TIMEOUT then
		return false
	elseif ret == WAIT_IO_COMPLETION then
		return 'io'
	else
		assert(false)
	end
end

PM_NOREMOVE         = 0x0000
PM_REMOVE           = 0x0001
PM_NOYIELD          = 0x0002
PM_QS_INPUT         = bit.lshift(QS_INPUT, 16)
PM_QS_POSTMESSAGE   = bit.lshift(bit.bor(QS_POSTMESSAGE, QS_HOTKEY, QS_TIMER), 16)
PM_QS_PAINT         = bit.lshift(QS_PAINT, 16)
PM_QS_SENDMESSAGE   = bit.lshift(QS_SENDMESSAGE, 16)

local msg0 = types.MSG()
function PeekMessage(hwnd, WMmin, WMmax, PM, msg)
	msg = msg and types.MSG(msg) or msg0
	return C.PeekMessageW(msg, hwnd, flags(WMmin), flags(WMmax), flags(PM)) ~= 0, msg
end

--message-based commands

function SetWindowFont(hwnd, font)
	SNDMSG(hwnd, WM_SETFONT, font, true) --no result
end

function GetWindowFont(hwnd)
	return ptr(ffi.cast('HFONT', SNDMSG(hwnd, WM_GETFONT)))
end

function CloseWindow(hwnd) --the winapi CloseWindow() has nothing to do with closing the window
	checkz(SNDMSG(hwnd, WM_CLOSE))
end

function SetRedraw(hwnd, allow) --adds WS_VISIBLE to the window!
	SNDMSG(hwnd, WM_SETREDRAW, allow)
end

UIS_SET         = 1
UIS_CLEAR       = 2
UIS_INITIALIZE  = 3

UISF_HIDEFOCUS  = 0x1
UISF_HIDEACCEL  = 0x2
UISF_ACTIVE     = 0x4

function ChangeUIState(hwnd, UIS, UISF)
	SNDMSG(hwnd, WM_CHANGEUISTATE, MAKEWPARAM(flags(UIS), flags(UISF)))
end

-- message names and decoders

--wm ranges (for filtering)
WM_KEYFIRST                      = 0x0100
WM_KEYLAST                       = 0x0109
WM_IME_KEYLAST                   = 0x010F
WM_MOUSEFIRST                    = 0x0200
WM_MOUSELAST                     = 0x020D
WM_TABLET_FIRST                  = 0x02c0
WM_TABLET_LAST                   = 0x02df
WM_HANDHELDFIRST                 = 0x0358
WM_HANDHELDLAST                  = 0x035F
WM_AFXFIRST                      = 0x0360
WM_AFXLAST                       = 0x037F
WM_PENWINFIRST                   = 0x0380
WM_PENWINLAST                    = 0x038F

WM_APP                           = 0x8000 --tip: see the wmapp module on how to manage those.
WM_USER                          = 0x0400

--dev note: make a comment on obsolete messages but keep them anyway,
--so that you don't see unknown messages when debugging.

WM_NAMES = constants{
	WM_NULL                          = 0x0000,
	WM_CREATE                        = 0x0001,
	WM_DESTROY                       = 0x0002,
	WM_MOVE                          = 0x0003,
	WM_SIZE                          = 0x0005,
	WM_ACTIVATE                      = 0x0006,
	WM_SETFOCUS                      = 0x0007,
	WM_KILLFOCUS                     = 0x0008,
	WM_ENABLE                        = 0x000A,
	WM_SETREDRAW                     = 0x000B,
	WM_SETTEXT                       = 0x000C,
	WM_GETTEXT                       = 0x000D,
	WM_GETTEXTLENGTH                 = 0x000E,
	WM_PAINT                         = 0x000F,
	WM_CLOSE                         = 0x0010,
	WM_QUERYENDSESSION               = 0x0011,
	WM_QUERYOPEN                     = 0x0013,
	WM_ENDSESSION                    = 0x0016,
	WM_QUIT                          = 0x0012,
	WM_ERASEBKGND                    = 0x0014,
	WM_SYSCOLORCHANGE                = 0x0015,
	WM_SHOWWINDOW                    = 0x0018,
	WM_WININICHANGE                  = 0x001A, --obsolete
	WM_SETTINGCHANGE                 = 0x001A,
	WM_DEVMODECHANGE                 = 0x001B,
	WM_ACTIVATEAPP                   = 0x001C,
	WM_FONTCHANGE                    = 0x001D,
	WM_TIMECHANGE                    = 0x001E,
	WM_CANCELMODE                    = 0x001F,
	WM_SETCURSOR                     = 0x0020,
	WM_MOUSEACTIVATE                 = 0x0021,
	WM_CHILDACTIVATE                 = 0x0022,
	WM_QUEUESYNC                     = 0x0023,
	WM_GETMINMAXINFO                 = 0x0024,
	WM_PAINTICON                     = 0x0026,
	WM_ICONERASEBKGND                = 0x0027,
	WM_NEXTDLGCTL                    = 0x0028,
	WM_SPOOLERSTATUS                 = 0x002A,
	WM_DRAWITEM                      = 0x002B,
	WM_MEASUREITEM                   = 0x002C,
	WM_DELETEITEM                    = 0x002D,
	WM_VKEYTOITEM                    = 0x002E,
	WM_CHARTOITEM                    = 0x002F,
	WM_SETFONT                       = 0x0030,
	WM_GETFONT                       = 0x0031,
	WM_SETHOTKEY                     = 0x0032,
	WM_GETHOTKEY                     = 0x0033,
	WM_QUERYDRAGICON                 = 0x0037,
	WM_COMPAREITEM                   = 0x0039,
	WM_GETOBJECT                     = 0x003D,
	WM_COMPACTING                    = 0x0041,
	WM_COMMNOTIFY                    = 0x0044, --obsolete
	WM_WINDOWPOSCHANGING             = 0x0046,
	WM_WINDOWPOSCHANGED              = 0x0047,
	WM_POWER                         = 0x0048, --obsolete
	WM_COPYDATA                      = 0x004A,
	WM_CANCELJOURNAL                 = 0x004B,
	WM_NOTIFY                        = 0x004E,
	WM_INPUTLANGCHANGEREQUEST        = 0x0050,
	WM_INPUTLANGCHANGE               = 0x0051,
	WM_TCARD                         = 0x0052,
	WM_HELP                          = 0x0053,
	WM_USERCHANGED                   = 0x0054,
	WM_NOTIFYFORMAT                  = 0x0055,
	WM_CONTEXTMENU                   = 0x007B,
	WM_STYLECHANGING                 = 0x007C,
	WM_STYLECHANGED                  = 0x007D,
	WM_DISPLAYCHANGE                 = 0x007E,
	WM_GETICON                       = 0x007F,
	WM_SETICON                       = 0x0080,
	WM_NCCREATE                      = 0x0081,
	WM_NCDESTROY                     = 0x0082,
	WM_NCCALCSIZE                    = 0x0083,
	WM_NCHITTEST                     = 0x0084,
	WM_NCPAINT                       = 0x0085,
	WM_NCACTIVATE                    = 0x0086,
	WM_GETDLGCODE                    = 0x0087,
	WM_SYNCPAINT                     = 0x0088,
	WM_NCMOUSEMOVE                   = 0x00A0,
	WM_NCLBUTTONDOWN                 = 0x00A1,
	WM_NCLBUTTONUP                   = 0x00A2,
	WM_NCLBUTTONDBLCLK               = 0x00A3,
	WM_NCRBUTTONDOWN                 = 0x00A4,
	WM_NCRBUTTONUP                   = 0x00A5,
	WM_NCRBUTTONDBLCLK               = 0x00A6,
	WM_NCMBUTTONDOWN                 = 0x00A7,
	WM_NCMBUTTONUP                   = 0x00A8,
	WM_NCMBUTTONDBLCLK               = 0x00A9,
	WM_NCXBUTTONDOWN                 = 0x00AB,
	WM_NCXBUTTONUP                   = 0x00AC,
	WM_NCXBUTTONDBLCLK               = 0x00AD,
	WM_INPUT_DEVICE_CHANGE           = 0x00FE,
	WM_INPUT                         = 0x00FF,
	WM_KEYDOWN                       = 0x0100,
	WM_KEYUP                         = 0x0101,
	WM_CHAR                          = 0x0102,
	WM_DEADCHAR                      = 0x0103,
	WM_SYSKEYDOWN                    = 0x0104,
	WM_SYSKEYUP                      = 0x0105,
	WM_SYSCHAR                       = 0x0106,
	WM_SYSDEADCHAR                   = 0x0107,
	WM_UNICHAR                       = 0x0109,
	WM_IME_STARTCOMPOSITION          = 0x010D,
	WM_IME_ENDCOMPOSITION            = 0x010E,
	WM_IME_COMPOSITION               = 0x010F,
	WM_INITDIALOG                    = 0x0110,
	WM_COMMAND                       = 0x0111,
	WM_SYSCOMMAND                    = 0x0112,
	WM_TIMER                         = 0x0113, --id, callback
	WM_HSCROLL                       = 0x0114,
	WM_VSCROLL                       = 0x0115,
	WM_INITMENU                      = 0x0116,
	WM_INITMENUPOPUP                 = 0x0117,
	WM_MENUSELECT                    = 0x011F,
	WM_MENUCHAR                      = 0x0120,
	WM_ENTERIDLE                     = 0x0121,
	WM_MENURBUTTONUP                 = 0x0122,
	WM_MENUDRAG                      = 0x0123,
	WM_MENUGETOBJECT                 = 0x0124,
	WM_UNINITMENUPOPUP               = 0x0125,
	WM_MENUCOMMAND                   = 0x0126,
	WM_CHANGEUISTATE                 = 0x0127,
	WM_UPDATEUISTATE                 = 0x0128,
	WM_QUERYUISTATE                  = 0x0129,
	WM_CTLCOLORMSGBOX                = 0x0132,
	WM_CTLCOLOREDIT                  = 0x0133,
	WM_CTLCOLORLISTBOX               = 0x0134,
	WM_CTLCOLORBTN                   = 0x0135,
	WM_CTLCOLORDLG                   = 0x0136,
	WM_CTLCOLORSCROLLBAR             = 0x0137,
	WM_CTLCOLORSTATIC                = 0x0138,
	MN_GETHMENU                      = 0x01E1, --MN_ not a typo
	WM_MOUSEMOVE                     = 0x0200,
	WM_LBUTTONDOWN                   = 0x0201,
	WM_LBUTTONUP                     = 0x0202,
	WM_LBUTTONDBLCLK                 = 0x0203,
	WM_RBUTTONDOWN                   = 0x0204,
	WM_RBUTTONUP                     = 0x0205,
	WM_RBUTTONDBLCLK                 = 0x0206,
	WM_MBUTTONDOWN                   = 0x0207,
	WM_MBUTTONUP                     = 0x0208,
	WM_MBUTTONDBLCLK                 = 0x0209,
	WM_MOUSEWHEEL                    = 0x020A,
	WM_XBUTTONDOWN                   = 0x020B,
	WM_XBUTTONUP                     = 0x020C,
	WM_XBUTTONDBLCLK                 = 0x020D,
	WM_MOUSEHWHEEL                   = 0x020E,
	WM_PARENTNOTIFY                  = 0x0210,
	WM_ENTERMENULOOP                 = 0x0211,
	WM_EXITMENULOOP                  = 0x0212,
	WM_NEXTMENU                      = 0x0213,
	WM_SIZING                        = 0x0214,
	WM_CAPTURECHANGED                = 0x0215,
	WM_MOVING                        = 0x0216,
	WM_POWERBROADCAST                = 0x0218,
	WM_DEVICECHANGE                  = 0x0219,
	WM_MDICREATE                     = 0x0220,
	WM_MDIDESTROY                    = 0x0221,
	WM_MDIACTIVATE                   = 0x0222,
	WM_MDIRESTORE                    = 0x0223,
	WM_MDINEXT                       = 0x0224,
	WM_MDIMAXIMIZE                   = 0x0225,
	WM_MDITILE                       = 0x0226,
	WM_MDICASCADE                    = 0x0227,
	WM_MDIICONARRANGE                = 0x0228,
	WM_MDIGETACTIVE                  = 0x0229,
	WM_MDISETMENU                    = 0x0230,
	WM_ENTERSIZEMOVE                 = 0x0231,
	WM_EXITSIZEMOVE                  = 0x0232,
	WM_DROPFILES                     = 0x0233,
	WM_MDIREFRESHMENU                = 0x0234,
	WM_IME_SETCONTEXT                = 0x0281,
	WM_IME_NOTIFY                    = 0x0282,
	WM_IME_CONTROL                   = 0x0283,
	WM_IME_COMPOSITIONFULL           = 0x0284,
	WM_IME_SELECT                    = 0x0285,
	WM_IME_CHAR                      = 0x0286,
	WM_IME_REQUEST                   = 0x0288,
	WM_IME_KEYDOWN                   = 0x0290,
	WM_IME_KEYUP                     = 0x0291,
	WM_MOUSEHOVER                    = 0x02A1,
	WM_MOUSELEAVE                    = 0x02A3,
	WM_NCMOUSEHOVER                  = 0x02A0,
	WM_NCMOUSELEAVE                  = 0x02A2,
	WM_WTSSESSION_CHANGE             = 0x02B1,
	WM_DPICHANGED                    = 0x02E0, --Win8.1+
	WM_CUT                           = 0x0300,
	WM_COPY                          = 0x0301,
	WM_PASTE                         = 0x0302,
	WM_CLEAR                         = 0x0303,
	WM_UNDO                          = 0x0304,
	WM_RENDERFORMAT                  = 0x0305,
	WM_RENDERALLFORMATS              = 0x0306,
	WM_DESTROYCLIPBOARD              = 0x0307,
	WM_DRAWCLIPBOARD                 = 0x0308,
	WM_PAINTCLIPBOARD                = 0x0309,
	WM_VSCROLLCLIPBOARD              = 0x030A,
	WM_SIZECLIPBOARD                 = 0x030B,
	WM_ASKCBFORMATNAME               = 0x030C,
	WM_CHANGECBCHAIN                 = 0x030D,
	WM_HSCROLLCLIPBOARD              = 0x030E,
	WM_QUERYNEWPALETTE               = 0x030F,
	WM_PALETTEISCHANGING             = 0x0310,
	WM_PALETTECHANGED                = 0x0311,
	WM_HOTKEY                        = 0x0312,
	WM_PRINT                         = 0x0317,
	WM_PRINTCLIENT                   = 0x0318,
	WM_APPCOMMAND                    = 0x0319,
	WM_THEMECHANGED                  = 0x031A,
}

require'winapi.wmapp'

--message sent to the thread (thus the message loop) to unregister a window class after a window is destroyed.
register_message'WM_UNREGISTER_CLASS'

--default message routed by BaseWindowClass through the NotifyIcons tracker.
register_message'WM_NOTIFYICON'

--message sent to the thread to raise an error (see WM_PAINT handling in BaseWindowClass).
register_message'WM_EXCEPTION'

--decode a message based on registered decoders from various submodules.
function DecodeMessage(WM_, wParam, lParam) --returns decoded results...
	local decoder = WM[WM_NAMES[WM_]] or pass
	return decoder(wParam, lParam)
end

--window state

SW_OTHERUNZOOM    = 4 --The window is being uncovered because a maximize window was restored or minimized.
SW_OTHERZOOM      = 2 --The window is being covered by another window that has been maximized.
SW_PARENTCLOSING  = 1 --The window's owner window is being minimized.
SW_PARENTOPENING  = 3 --The window's owner window is being restored.

local show_status = {'owner_minimized', 'other_maximized', 'owner_restored', 'other_restored'}

function WM.WM_SHOWWINDOW(wParam, lParam) --shown/hidden, show_status (nil if ShowWindow was called)
	return wParam == 1, show_status[tonumber(lParam)]
end

function WM.WM_ENABLE(wParam)
	return wParam == 1
end

-- window activation

local activate_flags = {[0] = 'inactive', 'active', 'clickactive'}

function WM.WM_ACTIVATE(wParam, lParam)
	local WA, minimized = splitlong(wParam)
	return activate_flags[WA], minimized ~= 0, ptr(ffi.cast('HWND', lParam)) --flag, minimized, other_window
end

function WM.WM_ACTIVATEAPP(wParam, lParam)
	return activate_flags[tonumber(wParam)], tonumber(lParam) --flag, other_thread_id
end

function WM.WM_NCACTIVATE(wParam, lParam)
	return activate_flags[tonumber(wParam)], tonumber(lParam) --flag, update_hrgn
end

-- window sizing

ffi.cdef[[
typedef struct tagMINMAXINFO {
    POINT ptReserved;
    SIZE  ptMaxSize;
	 POINT ptMaxPosition;
	 SIZE  ptMinTrackSize;
	 SIZE  ptMaxTrackSize;
} MINMAXINFO, *PMINMAXINFO, *LPMINMAXINFO;

typedef struct tagWINDOWPOS {
    HWND    hwnd;
    HWND    hwndInsertAfter;
    int     x;
    int     y;
	 int     w;
    int     h;
    UINT    flags;
} WINDOWPOS, *LPWINDOWPOS, *PWINDOWPOS;
]]

do
	local wp_fields = {}
	for k,v in pairs(_M) do
		if k:match'^SWP_' then
			wp_fields[k] = v
		end
	end
	local wp_flags = bitmask(wp_fields)
	local function wp_flags_get(flags)
		local t = {}
		wp_flags:get(flags, t)
		return t
	end
	local function wp_flags_set(flags, value)
		wp_flags:set(flags, t)
	end

	WINDOWPOS = struct{
		ctype = 'WINDOWPOS',
		fields = sfields{
			'flagbits', 'flags', wp_flags_set, wp_flags_get, --SWP_*
		}
	}
end

--NOTE: only sent to top-level windows. Not sent initially, so a resize
--must be forced for the constraints to be applied.
function WM.WM_GETMINMAXINFO(wParam, lParam)
	return ffi.cast('MINMAXINFO*', lParam)
end

--NOTE: sent when the window position or size is about to change, programatically or by user.
function WM.WM_WINDOWPOSCHANGING(wParam, lParam)
	return ffi.cast('WINDOWPOS*', lParam)
end

--NOTE: sent after the window position or size changed, programatically or by user.
WM.WM_WINDOWPOSCHANGED = WM.WM_WINDOWPOSCHANGING

function WM.WM_MOVING(wParam, lParam) --RECT (frame rect, not client rect)
	return ffi.cast('RECT*', lParam)
end

local sizing_flags = {'left', 'right', 'top', 'topleft', 'topright', 'bottom', 'bottomleft', 'bottomright'}

--NOTE: only sent when resizing by user.
function WM.WM_SIZING(wParam, lParam) --flag, RECT (frame rect, not client rect)
	return sizing_flags[tonumber(wParam)], ffi.cast('RECT*', lParam)
end

local size_flags = {[0] = 'restored', 'minimized', 'maximized', 'other_restored', 'other_maximized'}

--NOTE: WM_SIZE gives the size of the client rect, not of the frame rect!
--NOTE: WM_SIZE is sent by the default proc for WM_WINDOWPOSCHANGED.
function WM.WM_SIZE(wParam, lParam) --flag, cw, ch
	return size_flags[tonumber(wParam)], splitlong(lParam)
end

--NOTE: WM_MOVE gives the coordinates of the client rect, not of the frame rect!
--NOTE: WM_MOVE is sent by the default proc for WM_WINDOWPOSCHANGED.
function WM.WM_MOVE(wParam, lParam) --cx, cy
	return splitlong(lParam)
end

function WM.WM_DISPLAYCHANGE(wParam, lParam) --w, h, bpp
	local w, h = splitlong(lParam)
	return w, h, tonumber(wParam)
end

-- controls/wm_*command

function WM.WM_COMMAND(wParam, lParam)
	local id, command = splitlong(wParam)
	if lParam == 0 then
		if command == 0 then --menu
			return 'menu', id
		elseif command == 1 then --accelerator
			return 'accelerator', id
		else
			assert(false)
		end
	else
		return 'control', id, command, checkh(ffi.cast('HWND', lParam))
	end
end

function WM.WM_MENUCOMMAND(wParam, lParam)
	return checkh(ffi.cast('HMENU', lParam)), countfrom1(tonumber(wParam))
end

SC_SIZE          = 0xF000
SC_MOVE          = 0xF010
SC_DRAGMOVE      = 0xF012
SC_MINIMIZE      = 0xF020
SC_MAXIMIZE      = 0xF030
SC_NEXTWINDOW    = 0xF040
SC_PREVWINDOW    = 0xF050
SC_CLOSE         = 0xF060
SC_VSCROLL       = 0xF070
SC_HSCROLL       = 0xF080
SC_MOUSEMENU     = 0xF090
SC_KEYMENU       = 0xF100
SC_ARRANGE       = 0xF110
SC_RESTORE       = 0xF120
SC_TASKLIST      = 0xF130
SC_SCREENSAVE    = 0xF140
SC_HOTKEY        = 0xF150
SC_DEFAULT       = 0xF160
SC_MONITORPOWER  = 0xF170
SC_CONTEXTHELP   = 0xF180
SC_SEPARATOR     = 0xF00F
SCF_ISSECURE     = 0x00000001

function WM.WM_SYSCOMMAND(wParam, lParam)
	local SC = bit.band(tonumber(wParam), 0xfff0)
	if SC == SC_KEYMENU then
		return SC, tonumber(lParam) --SC, char_code
	else
		return SC, splitsigned(lParam) --SC, x, y
	end
end

--controls/wm_compareitem

ffi.cdef[[
typedef struct tagCOMPAREITEMSTRUCT {
    UINT        CtlType;
    UINT        id;
    HWND        hwnd;
    UINT        i1;
    ULONG_PTR   item1_user_data;
    UINT        i2;
    ULONG_PTR   item2_user_data;
    DWORD       dwLocaleId;
} COMPAREITEMSTRUCT,  *PCOMPAREITEMSTRUCT,  *LPCOMPAREITEMSTRUCT;
]]

function WM.WM_COMPAREITEM(wParam, lParam) --must return -1 if a < b, 1 if a > b and 0 if a == b
	return checkh(ffi.cast('HWND', wParam)), checkh(ffi.cast('COMPAREITEMSTRUCT*', lParam))
end

--controls/wm_notify

NM_FIRST = 2^32

WM_NOTIFY_NAMES = constants{ --{code_number = code_name}
	NM_CUSTOMDRAW = NM_FIRST-12,
}

ffi.cdef[[
typedef struct tagNMHDR
{
    HWND      hwndFrom;
    UINT_PTR  idFrom;
    UINT      code;
}   NMHDR;
typedef NMHDR *LPNMHDR;
]]

function WM.WM_NOTIFY(wParam, lParam) --return hwnd, code, decoded message...
	local hdr = ffi.cast('NMHDR*', lParam)
	local code_name = WM_NOTIFY_NAMES[hdr.code]
	local decoder = NM[code_name] or pass
	return hdr.hwndFrom, hdr.code, decoder(hdr, wParam)
end

ffi.cdef[[
typedef struct tagNMCUSTOMDRAWINFO
{
    NMHDR hdr;
    DWORD stage;
    HDC hdc;
    RECT rc;
    DWORD_PTR dwItemSpec;
    UINT  uItemState;
    LPARAM lItemlParam;
} NMCUSTOMDRAW, *LPNMCUSTOMDRAW;
]]

--stage flags
CDDS_PREPAINT           = 0x00000001
CDDS_POSTPAINT          = 0x00000002
CDDS_PREERASE           = 0x00000003
CDDS_POSTERASE          = 0x00000004
CDDS_ITEM               = 0x00010000 --individual item specific
CDDS_ITEMPREPAINT       = bit.bor(CDDS_ITEM, CDDS_PREPAINT)
CDDS_ITEMPOSTPAINT      = bit.bor(CDDS_ITEM, CDDS_POSTPAINT)
CDDS_ITEMPREERASE       = bit.bor(CDDS_ITEM, CDDS_PREERASE)
CDDS_ITEMPOSTERASE      = bit.bor(CDDS_ITEM, CDDS_POSTERASE)
CDDS_SUBITEM            = 0x00020000

--return flags
CDRF_DODEFAULT          = 0x00000000
CDRF_NEWFONT            = 0x00000002
CDRF_SKIPDEFAULT        = 0x00000004
CDRF_DOERASE            = 0x00000008 -- draw the background
CDRF_SKIPPOSTPAINT      = 0x00000100 -- don't draw the focus rect
CDRF_NOTIFYPOSTPAINT    = 0x00000010
CDRF_NOTIFYITEMDRAW     = 0x00000020
CDRF_NOTIFYSUBITEMDRAW  = 0x00000020 --flags are the same, we can distinguish by context
CDRF_NOTIFYPOSTERASE    = 0x00000040

function NM.NM_CUSTOMDRAW(hdr)
	return ffi.cast('NMCUSTOMDRAW*', hdr)
end

-- NOTE: a FFI callback cannot safely be called from a C function which is
-- itself called via the FFI from JIT-compiled code. This means we must disable
-- jitting for all functions that could trigger a FFI callback.
jit.off(SendMessage)
jit.off(PeekMessage)
jit.off(GetMessage)
jit.off(DispatchMessage)
jit.off(TranslateAccelerator)
jit.off(TranslateMessage)
jit.off(IsDialogMessage)

