// 24 march 2014

package ui

import (
	"fmt"
	"syscall"
	"unsafe"
	"image"
)

const (
	areastyle = _WS_HSCROLL | _WS_VSCROLL | controlstyle
	areaxstyle = 0 | controlxstyle
)

var (
	areaWndClass = toUTF16("gouiarea")
)

func getScrollPos(hwnd _HWND) (xpos int32, ypos int32) {
	var si _SCROLLINFO

	si.cbSize = uint32(unsafe.Sizeof(si))
	si.fMask = _SIF_POS | _SIF_TRACKPOS
	r1, _, err := _getScrollInfo.Call(
		uintptr(hwnd),
		uintptr(_SB_HORZ),
		uintptr(unsafe.Pointer(&si)))
	if r1 == 0 {		// failure
		panic(fmt.Errorf("error getting horizontal scroll position for Area: %v", err))
	}
	xpos = si.nPos
	si.cbSize = uint32(unsafe.Sizeof(si))			// MSDN example code reinitializes this each time, so we'll do it too just to be safe
	si.fMask = _SIF_POS | _SIF_TRACKPOS
	r1, _, err = _getScrollInfo.Call(
		uintptr(hwnd),
		uintptr(_SB_VERT),
		uintptr(unsafe.Pointer(&si)))
	if r1 == 0 {		// failure
		panic(fmt.Errorf("error getting vertical scroll position for Area: %v", err))
	}
	ypos = si.nPos
	return xpos, ypos
}

var (
	_alphaBlend = msimg32.NewProc("AlphaBlend")
	_beginPaint = user32.NewProc("BeginPaint")
	_bitBlt = gdi32.NewProc("BitBlt")
	_createCompatibleBitmap = gdi32.NewProc("CreateCompatibleBitmap")
	_createCompatibleDC = gdi32.NewProc("CreateCompatibleDC")
	_createDIBSection = gdi32.NewProc("CreateDIBSection")
	_deleteDC = gdi32.NewProc("DeleteDC")
	_deleteObject = gdi32.NewProc("DeleteObject")
	_endPaint = user32.NewProc("EndPaint")
	_fillRect = user32.NewProc("FillRect")
	_getUpdateRect = user32.NewProc("GetUpdateRect")
	// _selectObject in prefsize_windows.go
)

const (
	areaBackgroundBrush = _HBRUSH(_COLOR_BTNFACE + 1)
)

func paintArea(s *sysData) {
	var xrect _RECT
	var ps _PAINTSTRUCT

	r1, _, _ := _getUpdateRect.Call(
		uintptr(s.hwnd),
		uintptr(unsafe.Pointer(&xrect)),
		uintptr(_TRUE))		// erase the update rect with the background color
	if r1 == 0 {			// no update rect; do nothing
		return
	}

	hscroll, vscroll := getScrollPos(s.hwnd)

	cliprect := image.Rect(int(xrect.left), int(xrect.top), int(xrect.right), int(xrect.bottom))
	cliprect = cliprect.Add(image.Pt(int(hscroll), int(vscroll)))			// adjust by scroll position
	// make sure the cliprect doesn't fall outside the size of the Area
	cliprect = cliprect.Intersect(image.Rect(0, 0, s.areawidth, s.areaheight))
	if cliprect.Empty() {		// still no update rect
		return
	}

	// TODO don't do the above, but always draw the background color?

	r1, _, err := _beginPaint.Call(
		uintptr(s.hwnd),
		uintptr(unsafe.Pointer(&ps)))
	if r1 == 0 {		// failure
		panic(fmt.Errorf("error beginning Area repaint: %v", err))
	}
	hdc := _HANDLE(r1)

	// very big thanks to Ninjifox for suggesting this technique and helping me go through it

	// first let's create the destination image, which we fill with the windows background color
	// this is how we fake drawing the background; see also http://msdn.microsoft.com/en-us/library/ms969905.aspx
	r1, _, err = _createCompatibleDC.Call(uintptr(hdc))
	if r1 == 0 {		// failure
		panic(fmt.Errorf("error creating off-screen rendering DC: %v", err))
	}
	rdc := _HANDLE(r1)
	// the bitmap has to be compatible with the window
	// if we create a bitmap compatible with the DC we just created, it'll be monochrome
	// thanks to David Heffernan in http://stackoverflow.com/questions/23033636/winapi-gdi-fillrectcolor-btnface-fills-with-strange-grid-like-brush-on-window
	r1, _, err = _createCompatibleBitmap.Call(
		uintptr(hdc),
		uintptr(xrect.right - xrect.left),
		uintptr(xrect.bottom - xrect.top))
	if r1 == 0 {		// failure
		panic(fmt.Errorf("error creating off-screen rendering bitmap: %v", err))
	}
	rbitmap := _HANDLE(r1)
	r1, _, err = _selectObject.Call(
		uintptr(rdc),
		uintptr(rbitmap))
	if r1 == 0 {		// failure
		panic(fmt.Errorf("error connecting off-screen rendering bitmap to off-screen rendering DC: %v", err))
	}
	prevrbitmap := _HANDLE(r1)
	rrect := _RECT{
		left:		0,
		right:	xrect.right - xrect.left,
		top:		0,
		bottom:	xrect.bottom - xrect.top,
	}
	r1, _, err = _fillRect.Call(
		uintptr(rdc),
		uintptr(unsafe.Pointer(&rrect)),
		uintptr(areaBackgroundBrush))
	if r1 == 0 {		// failure
		panic(fmt.Errorf("error filling off-screen rendering bitmap with the system background color: %v", err))
	}

	i := s.handler.Paint(cliprect)
	// don't convert to BRGA just yet; see below

	// now we need to shove realbits into a bitmap
	// technically bitmaps don't know about alpha; they just ignore the alpha byte
	// AlphaBlend(), however, sees it - see http://msdn.microsoft.com/en-us/library/windows/desktop/dd183352%28v=vs.85%29.aspx
	bi := _BITMAPINFO{}
	bi.bmiHeader.biSize = uint32(unsafe.Sizeof(bi.bmiHeader))
	bi.bmiHeader.biWidth = int32(i.Rect.Dx())
	bi.bmiHeader.biHeight = -int32(i.Rect.Dy())		// negative height to force top-down drawing
	bi.bmiHeader.biPlanes = 1
	bi.bmiHeader.biBitCount = 32
	bi.bmiHeader.biCompression = _BI_RGB
	bi.bmiHeader.biSizeImage = uint32(i.Rect.Dx() * i.Rect.Dy() * 4)
	// this is all we need, but because this confused me at first, I will say the two pixels-per-meter fields are unused (see http://blogs.msdn.com/b/oldnewthing/archive/2013/05/15/10418646.aspx and page 581 of Charles Petzold's Programming Windows, Fifth Edition)
	ppvBits := uintptr(0)		// now for the trouble: CreateDIBSection() allocates the memory for us...
	r1, _, err = _createDIBSection.Call(
		uintptr(_NULL),		// Ninjifox does this, so do some wine tests (http://source.winehq.org/source/dlls/gdi32/tests/bitmap.c#L725, thanks vpovirk in irc.freenode.net/#winehackers) and even Raymond Chen (http://blogs.msdn.com/b/oldnewthing/archive/2006/11/16/1086835.aspx), so.
		uintptr(unsafe.Pointer(&bi)),
		uintptr(_DIB_RGB_COLORS),
		uintptr(unsafe.Pointer(&ppvBits)),
		uintptr(0),		// we're not dealing with hSection or dwOffset
		uintptr(0))
	if r1 == 0 {		// failure
		panic(fmt.Errorf("error creating HBITMAP for image returned by AreaHandler.Paint(): %v", err))
	}
	ibitmap := _HANDLE(r1)

	// now we have to do TWO MORE things before we can finally do alpha blending
	// first, we need to load the bitmap memory, because Windows makes it for us
	// the pixels are arranged in RGBA order, but GDI requires BGRA
	// this turns out to be just ARGB in little endian; let's convert into this memory
	// the bitmap Windows gives us has a stride == width
	toARGB(i, ppvBits, i.Rect.Dx() * 4)

	// the second thing is... make a device context for the bitmap :|
	// Ninjifox just makes another compatible DC; we'll do the same
	r1, _, err = _createCompatibleDC.Call(uintptr(hdc))
	if r1 == 0 {		// failure
		panic(fmt.Errorf("error creating HDC for image returned by AreaHandler.Paint(): %v", err))
	}
	idc := _HANDLE(r1)
	r1, _, err = _selectObject.Call(
		uintptr(idc),
		uintptr(ibitmap))
	if r1 == 0 {		// failure
		panic(fmt.Errorf("error connecting HBITMAP for image returned by AreaHandler.Paint() to its HDC: %v", err))
	}
	previbitmap := _HANDLE(r1)

	// AND FINALLY WE CAN DO THE ALPHA BLENDING!!!!!!111
	blendfunc := _BLENDFUNCTION{
		BlendOp:				_AC_SRC_OVER,
		BlendFlags:			0,
		SourceConstantAlpha:	255,					// only use per-pixel alphas
		AlphaFormat:			_AC_SRC_ALPHA,		// premultiplied
	}
	r1, _, err = _alphaBlend.Call(
		uintptr(rdc),	// destination
		uintptr(0),		// origin and size
		uintptr(0),
		uintptr(i.Rect.Dx()),
		uintptr(i.Rect.Dy()),
		uintptr(idc),	// source image
		uintptr(0),
		uintptr(0),
		uintptr(i.Rect.Dx()),
		uintptr(i.Rect.Dy()),
		blendfunc.arg())
	if r1 == _FALSE {		// failure
		panic(fmt.Errorf("error alpha-blending image returned by AreaHandler.Paint() onto background: %v", err))
	}

	// and finally we can just blit that into the window
	r1, _, err = _bitBlt.Call(
		uintptr(hdc),
		uintptr(xrect.left),
		uintptr(xrect.top),
		uintptr(xrect.right - xrect.left),
		uintptr(xrect.bottom - xrect.top),
		uintptr(rdc),
		uintptr(0),			// from the rdc's origin
		uintptr(0),
		uintptr(_SRCCOPY))
	if r1 == 0 {		// failure
		panic(fmt.Errorf("error blitting Area image to Area: %v", err))
	}

	// now to clean up
	r1, _, err = _selectObject.Call(
		uintptr(idc),
		uintptr(previbitmap))
	if r1 == 0 {		// failure
		panic(fmt.Errorf("error reverting HDC for image returned by AreaHandler.Paint() to original HBITMAP: %v", err))
	}
	r1, _, err = _selectObject.Call(
		uintptr(rdc),
		uintptr(prevrbitmap))
	if r1 == 0 {		// failure
		panic(fmt.Errorf("error reverting HDC for off-screen rendering to original HBITMAP: %v", err))
	}
	r1, _, err = _deleteObject.Call(uintptr(ibitmap))
	if r1 == 0 {		// failure
		panic(fmt.Errorf("error deleting HBITMAP for image returned by AreaHandler.Paint(): %v", err))
	}
	r1, _, err = _deleteObject.Call(uintptr(rbitmap))
	if r1 == 0 {		// failure
		panic(fmt.Errorf("error deleting HBITMAP for off-screen rendering: %v", err))
	}
	r1, _, err = _deleteDC.Call(uintptr(idc))
	if r1 == 0 {		// failure
		panic(fmt.Errorf("error deleting HDC for image returned by AreaHandler.Paint(): %v", err))
	}
	r1, _, err = _deleteDC.Call(uintptr(rdc))
	if r1 == 0 {		// failure
		panic(fmt.Errorf("error deleting HDC for off-screen rendering: %v", err))
	}

	// return value always nonzero according to MSDN
	_endPaint.Call(
		uintptr(s.hwnd),
		uintptr(unsafe.Pointer(&ps)))
}

func getAreaControlSize(hwnd _HWND) (width int, height int) {
	var rect _RECT

	r1, _, err := _getClientRect.Call(
		uintptr(hwnd),
		uintptr(unsafe.Pointer(&rect)))
	if r1 == 0 {		// failure
		panic(fmt.Errorf("error getting size of actual Area control: %v", err))
	}
	return int(rect.right - rect.left),
		int(rect.bottom - rect.top)
}

func scrollArea(s *sysData, wparam _WPARAM, which uintptr) {
	var si _SCROLLINFO

	cwid, cht := getAreaControlSize(s.hwnd)
	pagesize := int32(cwid)
	maxsize := int32(s.areawidth)
	if which == uintptr(_SB_VERT) {
		pagesize = int32(cht)
		maxsize = int32(s.areaheight)
	}

	si.cbSize = uint32(unsafe.Sizeof(si))
	si.fMask = _SIF_POS | _SIF_TRACKPOS
	r1, _, err := _getScrollInfo.Call(
		uintptr(s.hwnd),
		which,
		uintptr(unsafe.Pointer(&si)))
	if r1 == 0 {		// failure
		panic(fmt.Errorf("error getting current scroll position for scrolling: %v", err))
	}

	newpos := si.nPos
	switch wparam & 0xFFFF {
	case _SB_LEFT:			// also _SB_TOP but Go won't let me
		newpos = 0
	case _SB_RIGHT:		// also _SB_BOTTOM
		// see comment in adjustAreaScrollbars() below
		newpos = maxsize - pagesize
	case _SB_LINELEFT:		// also _SB_LINEUP
		newpos--
	case _SB_LINERIGHT:	// also _SB_LINEDOWN
		newpos++
	case _SB_PAGELEFT:		// also _SB_PAGEUP
		newpos -= pagesize
	case _SB_PAGERIGHT:	// also _SB_PAGEDOWN
		newpos += pagesize
	case _SB_THUMBPOSITION:
		// raymond chen says to just set the newpos to the SCROLLINFO nPos for this message; see http://blogs.msdn.com/b/oldnewthing/archive/2003/07/31/54601.aspx and http://blogs.msdn.com/b/oldnewthing/archive/2003/08/05/54602.aspx
		// do nothing here; newpos already has nPos
	case _SB_THUMBTRACK:
		newpos = si.nTrackPos
	}		// otherwise just keep the current position (that's what MSDN example code says, anyway)

	// make sure we're not out of range
	if newpos < 0 {
		newpos = 0
	}
	if newpos > (maxsize - pagesize) {
		newpos = maxsize - pagesize
	}

	// this would be where we would put a check to not scroll if the scroll position changed, but see the note about SB_THUMBPOSITION above: Raymond Chen's code always does the scrolling anyway in this case

	delta := -(newpos - si.nPos)	// negative because ScrollWindowEx() scrolls in the opposite direction
	dx := delta
	dy := int32(0)
	if which == uintptr(_SB_VERT) {
		dx = int32(0)
		dy = delta
	}
	r1, _, err = _scrollWindowEx.Call(
		uintptr(s.hwnd),
		uintptr(dx),
		uintptr(dy),
		uintptr(0),			// these four change what is scrolled and record info about the scroll; we're scrolling the whole client area and don't care about the returned information here
		uintptr(0),
		uintptr(0),
		uintptr(0),
		uintptr(_SW_INVALIDATE | _SW_ERASE))					// mark the remaining rect as needing redraw and erase...
	if r1 == _ERROR {		// failure
		panic(fmt.Errorf("error scrolling Area: %v", err))
	}
	// ...but don't redraw the window yet; we need to apply our scroll changes

	// we actually have to commit the change back to the scrollbar; otherwise the scroll position will merely reset itself
	si.cbSize = uint32(unsafe.Sizeof(si))
	si.fMask = _SIF_POS
	si.nPos = newpos
	_setScrollInfo.Call(
		uintptr(s.hwnd),
		which,
		uintptr(unsafe.Pointer(&si)))

	// NOW redraw it
	r1, _, err = _updateWindow.Call(uintptr(s.hwnd))
	if r1 == 0 {		// failure
		panic(fmt.Errorf("error updating Area after scrolling: %v", err))
	}
}

func adjustAreaScrollbars(s *sysData) {
	var si _SCROLLINFO

	cwid, cht := getAreaControlSize(s.hwnd)

	// the trick is we want a page to be the width/height of the visible area
	// so the scroll range would go from [0..image_dimension - control_dimension]
	// but judging from the sample code on MSDN, we don't need to do this; the scrollbar will do it for us
	// we DO need to handle it when scrolling, though, since the thumb can only go up to this upper limit

	// have to do horizontal and vertical separately
	si.cbSize = uint32(unsafe.Sizeof(si))
	si.fMask = _SIF_RANGE | _SIF_PAGE
	si.nMin = 0
	si.nMax = int32(s.areawidth - 1)		// the max point is inclusive, so we have to pass in the last valid value, not the first invalid one (see http://blogs.msdn.com/b/oldnewthing/archive/2003/07/31/54601.aspx); if we don't, we get weird things like the scrollbar sometimes showing one extra scroll position at the end that you can never scroll to
	si.nPage = uint32(cwid)
	_setScrollInfo.Call(
		uintptr(s.hwnd),
		uintptr(_SB_HORZ),
		uintptr(unsafe.Pointer(&si)),
		uintptr(_TRUE))			// redraw the scroll bar

	si.cbSize = uint32(unsafe.Sizeof(si))			// MSDN sample code does this a second time; let's do it too to be safe
	si.fMask = _SIF_RANGE | _SIF_PAGE
	si.nMin = 0
	si.nMax = int32(s.areaheight - 1)
	si.nPage = uint32(cht)
	_setScrollInfo.Call(
		uintptr(s.hwnd),
		uintptr(_SB_VERT),
		uintptr(unsafe.Pointer(&si)),
		uintptr(_TRUE))			// redraw the scroll bar
}

var (
	_invalidateRect = user32.NewProc("InvalidateRect")
)

func repaintArea(s *sysData) {
	r1, _, err := _invalidateRect.Call(
		uintptr(s.hwnd),
		uintptr(0),			// the whole area
		uintptr(_TRUE))		// have Windows erase if possible
	if r1 == 0 {		// failure
		panic(fmt.Errorf("error flagging Area as needing repainting after event (last error: %v)", err))
	}
	r1, _, err = _updateWindow.Call(uintptr(s.hwnd))
	if r1 == 0 {		// failure
		panic(fmt.Errorf("error repainting Area after event: %v", err))
	}
}

var (
	_getKeyState = user32.NewProc("GetKeyState")
)

func getModifiers() (m Modifiers) {
	down := func(x uintptr) bool {
		// GetKeyState() gets the key state at the time of the message, so this is what we want
		r1, _, _ := _getKeyState.Call(x)
		return (r1 & 0x80) != 0
	}

	if down(_VK_CONTROL) {
		m |= Ctrl
	}
	if down(_VK_MENU) {
		m |= Alt
	}
	if down(_VK_SHIFT) {
		m |= Shift
	}
	if down(_VK_LWIN) || down(_VK_RWIN) {
		m |= Super
	}
	return m
}

var (
	_getMessageTime = user32.NewProc("GetMessageTime")
	_getDoubleClickTime = user32.NewProc("GetDoubleClickTime")
	_getSystemMetrics = user32.NewProc("GetSystemMetrics")
)

func areaMouseEvent(s *sysData, button uint, up bool, wparam _WPARAM, lparam _LPARAM) {
	var me MouseEvent

	xpos, ypos := getScrollPos(s.hwnd)		// mouse coordinates are relative to control; make them relative to Area
	xpos += lparam.X()
	ypos += lparam.Y()
	me.Pos = image.Pt(int(xpos), int(ypos))
	if !me.Pos.In(image.Rect(0, 0, s.areawidth, s.areaheight)) {		// outside the actual Area; no event
		return
	}
	if up {
		me.Up = button
	} else if button != 0 {			// don't run the click counter if the mouse was only moved
		me.Down = button
		// this returns a LONG, which is int32, but we don't need to worry about the signedness because for the same bit widths and two's complement arithmetic, s1-s2 == u1-u2 if bits(s1)==bits(s2) and bits(u1)==bits(u2) (and Windows requires two's complement: http://blogs.msdn.com/b/oldnewthing/archive/2005/05/27/422551.aspx)
		// TODO actually will this break with negative numbers on systems where uintptr is 64 bits wide? only if the ABI doesn't sign-extend...
		time, _, _ := _getMessageTime.Call()
		// this returns a UINT, which is uint32; don't worry about the smaller size as sign extension won't matter here (see the above)
		maxTime, _, _ := _getDoubleClickTime.Call()
		// ignore zero returns and errors; MSDN says zero will be returned on error but that GetLastError() is meaningless
		// these should be unsigned... TODO MSDN doesn't say and GetSystemMetrics() returns an int (int32)
		xdist, _, _ := _getSystemMetrics.Call(_SM_CXDOUBLECLK)
		ydist, _, _ := _getSystemMetrics.Call(_SM_CYDOUBLECLK)
		me.Count = s.clickCounter.click(button, me.Pos.X, me.Pos.Y,
			time, maxTime, int(xdist / 2), int(ydist / 2))
	}
	// though wparam will contain control and shift state, let's use just one function to get modifiers for both keyboard and mouse events; it'll work the same anyway since we have to do this for alt and windows key (super)
	me.Modifiers = getModifiers()
	if button != 1 && (wparam & _MK_LBUTTON) != 0 {
		me.Held = append(me.Held, 1)
	}
	if button != 2 && (wparam & _MK_MBUTTON) != 0 {
		me.Held = append(me.Held, 2)
	}
	if button != 3 && (wparam & _MK_RBUTTON) != 0 {
		me.Held = append(me.Held, 3)
	}
	if button != 4 && (wparam & _MK_XBUTTON1) != 0 {
		me.Held = append(me.Held, 4)
	}
	if button != 5 && (wparam & _MK_XBUTTON2) != 0 {
		me.Held = append(me.Held, 5)
	}
	repaint := s.handler.Mouse(me)
	if repaint {
		repaintArea(s)
	}
}

func areaKeyEvent(s *sysData, up bool, wparam _WPARAM, lparam _LPARAM) {
	var ke KeyEvent

	scancode := byte((lparam >> 16) & 0xFF)
	ke.Modifiers = getModifiers()
	if wparam == _VK_RETURN && (lparam & 0x01000000) != 0 {
		// the above is special handling for numpad enter
		// bit 24 of LPARAM (0x01000000) indicates right-hand keys
		ke.ExtKey = NEnter
	} else if extkey, ok := extkeys[wparam]; ok {
		ke.ExtKey = extkey
	} else if mod, ok := modonlykeys[wparam]; ok {
		ke.Modifier = mod
		// don't include the modifier in ke.Modifiers
		ke.Modifiers &^= mod
	} else if xke, ok := fromScancode(uintptr(scancode)); ok {
		// one of these will be nonzero
		ke.Key = xke.Key
		ke.ExtKey = xke.ExtKey
	} else if ke.Modifiers == 0 {
		// no key, extkey, or modifiers; do nothing
		return
	}
	ke.Up = up
	repaint := s.handler.Key(ke)
	if repaint {
		repaintArea(s)
	}
}

var extkeys = map[_WPARAM]ExtKey{
	_VK_ESCAPE:		Escape,
	_VK_INSERT:		Insert,
	_VK_DELETE:		Delete,
	_VK_HOME:		Home,
	_VK_END:			End,
	_VK_PRIOR:		PageUp,
	_VK_NEXT:		PageDown,
	_VK_UP:			Up,
	_VK_DOWN:		Down,
	_VK_LEFT:		Left,
	_VK_RIGHT:		Right,
	_VK_F1:			F1,
	_VK_F2:			F2,
	_VK_F3:			F3,
	_VK_F4:			F4,
	_VK_F5:			F5,
	_VK_F6:			F6,
	_VK_F7:			F7,
	_VK_F8:			F8,
	_VK_F9:			F9,
	_VK_F10:			F10,
	_VK_F11:			F11,
	_VK_F12:			F12,
	// numpad numeric keys and . are handled in events_notdarwin.go
	// numpad enter is handled in code above
	_VK_ADD:		NAdd,
	_VK_SUBTRACT:	NSubtract,
	_VK_MULTIPLY:	NMultiply,
	_VK_DIVIDE:		NDivide,
}

// sanity check
func init() {
	included := make([]bool, _nextkeys)
	for _, v := range extkeys {
		included[v] = true
	}
	for i := 1; i < int(_nextkeys); i++ {
		if i >= int(N0) && i <= int(N9) {		// skip numpad numbers, ., and enter
			continue
		}
		if i == int(NDot) || i == int(NEnter) {
			continue
		}
		if !included[i] {
			panic(fmt.Errorf("error: not all ExtKeys defined on Windows (missing %d)", i))
		}
	}
}

var modonlykeys = map[_WPARAM]Modifiers{
	// even if the separate left/right aren't necessary, have them here anyway, just to be safe
	_VK_CONTROL:	Ctrl,
	_VK_LCONTROL:	Ctrl,
	_VK_RCONTROL:	Ctrl,
	_VK_MENU:		Alt,
	_VK_LMENU:		Alt,
	_VK_RMENU:		Alt,
	_VK_SHIFT:		Shift,
	_VK_LSHIFT:		Shift,
	_VK_RSHIFT:		Shift,
	// there's no combined Windows key virtual-key code as there is with the others
	_VK_LWIN:		Super,
	_VK_RWIN:		Super,
}

var (
	_setFocus = user32.NewProc("SetFocus")
)

func areaWndProc(hwnd _HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM) _LRESULT {
	s := getSysData(hwnd)
	if s == nil {		// not yet saved
		return storeSysData(hwnd, uMsg, wParam, lParam)
	}
	switch uMsg {
	case _WM_PAINT:
		paintArea(s)
		return 0
	case _WM_ERASEBKGND:
		// don't draw a background; we'll do so when painting
		// this is to make things flicker-free; see http://msdn.microsoft.com/en-us/library/ms969905.aspx
		return 1
	case _WM_HSCROLL:
		scrollArea(s, wParam, _SB_HORZ)
		return 0
	case _WM_VSCROLL:
		scrollArea(s, wParam, _SB_VERT)
		return 0
	case _WM_SIZE:
		adjustAreaScrollbars(s)
		return 0
	case _WM_ACTIVATE:
		// don't keep the double-click timer running if the user switched programs in between clicks
		s.clickCounter.reset()
		return 0
	case _WM_MOUSEACTIVATE:
		// this happens on every mouse click (apparently), so DON'T reset the click counter, otherwise it will always be reset (not an issue, as MSDN says WM_ACTIVATE is sent alongside WM_MOUSEACTIVATE when necessary)
		// transfer keyboard focus to our Area on an activating click
		// (see http://www.catch22.net/tuts/custom-controls)
		r1, _, err := _setFocus.Call(uintptr(s.hwnd))
		if r1 == 0 {		// failure
			panic(fmt.Errorf("error giving Area keyboard focus: %v", err))
			return _MA_ACTIVATE		// TODO eat the click?
		}
		return defWindowProc(hwnd, uMsg, wParam, lParam)
	case _WM_MOUSEMOVE:
		areaMouseEvent(s, 0, false, wParam, lParam)
		return 0
	case _WM_LBUTTONDOWN:
		areaMouseEvent(s, 1, false, wParam, lParam)
		return 0
	case _WM_LBUTTONUP:
		areaMouseEvent(s, 1, true, wParam, lParam)
		return 0
	case _WM_MBUTTONDOWN:
		areaMouseEvent(s, 2, false, wParam, lParam)
		return 0
	case _WM_MBUTTONUP:
		areaMouseEvent(s, 2, true, wParam, lParam)
		return 0
	case _WM_RBUTTONDOWN:
		areaMouseEvent(s, 3, false, wParam, lParam)
		return 0
	case _WM_RBUTTONUP:
		areaMouseEvent(s, 3, true, wParam, lParam)
		return 0
	case _WM_XBUTTONDOWN:
		which := uint((wParam >> 16) & 0xFFFF) + 3		// values start at 1; we want them to start at 4
		areaMouseEvent(s, which, false, wParam, lParam)
		return _LRESULT(_TRUE)		// XBUTTON messages are different!
	case _WM_XBUTTONUP:
		which := uint((wParam >> 16) & 0xFFFF) + 3
		areaMouseEvent(s, which, true, wParam, lParam)
		return _LRESULT(_TRUE)
	case _WM_KEYDOWN:
		areaKeyEvent(s, false, wParam, lParam)
		return 0
	case _WM_KEYUP:
		areaKeyEvent(s, true, wParam, lParam)
		return 0
	// Alt+[anything] and F10 send these instead and require us to return to DefWindowProc() so global keystrokes such as Alt+Tab can be processed
	case _WM_SYSKEYDOWN:
		areaKeyEvent(s, false, wParam, lParam)
		return defWindowProc(hwnd, uMsg, wParam, lParam)
	case _WM_SYSKEYUP:
		areaKeyEvent(s, true, wParam, lParam)
		return defWindowProc(hwnd, uMsg, wParam, lParam)
	case msgSetAreaSize:
		s.areawidth = int(wParam)		// see setAreaSize() in sysdata_windows.go
		s.areaheight = int(lParam)
		adjustAreaScrollbars(s)
		repaintArea(s)					// this calls for an update
		return 0
	default:
		return defWindowProc(hwnd, uMsg, wParam, lParam)
	}
	panic(fmt.Sprintf("areaWndProc message %d did not return: internal bug in ui library", uMsg))
}

func registerAreaWndClass() (err error) {
	wc := &_WNDCLASS{
		style:			_CS_HREDRAW | _CS_VREDRAW,		// no CS_DBLCLKS because do that manually
		lpszClassName:	utf16ToArg(areaWndClass),
		lpfnWndProc:		syscall.NewCallback(areaWndProc),
		hInstance:		hInstance,
		hIcon:			icon,
		hCursor:			cursor,
		hbrBackground:	_HBRUSH(_NULL),		// no brush; we handle WM_ERASEBKGND
	}
	r1, _, err := _registerClass.Call(uintptr(unsafe.Pointer(wc)))
	if r1 == 0 {		// failure
		return err
	}
	return nil
}

type _BITMAPINFO struct {
	bmiHeader	_BITMAPINFOHEADER
	bmiColors	[32]uintptr	// we don't use it; make it an arbitrary number that wouldn't cause issues
}

type _BITMAPINFOHEADER struct {
	biSize			uint32
	biWidth			int32
	biHeight			int32
	biPlanes			uint16
	biBitCount		uint16
	biCompression		uint32
	biSizeImage		uint32
	biXPelsPerMeter	int32
	biYPelsPerMeter	int32
	biClrUsed			uint32
	biClrImportant		uint32
}

type _BLENDFUNCTION struct {
	BlendOp				byte
	BlendFlags			byte
	SourceConstantAlpha	byte
	AlphaFormat			byte
}

// AlphaBlend() takes a BLENDFUNCTION value
func (b _BLENDFUNCTION) arg() (x uintptr) {
	// little endian
	x = uintptr(b.AlphaFormat) << 24
	x |= uintptr(b.SourceConstantAlpha) << 16
	x |= uintptr(b.BlendFlags) << 8
	x |= uintptr(b.BlendOp)
	return x
}

type _PAINTSTRUCT struct {
	hdc			_HANDLE
	fErase		int32		// originally BOOL
	rcPaint		_RECT
	fRestore		int32		// originally BOOL
	fIncUpdate	int32		// originally BOOL
	rgbReserved	[32]byte
}