// 8 february 2014

package ui

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

const (
	stdWndClass = "gouiwnd"
)

var (
	_defWindowProc = user32.NewProc("DefWindowProcW")
)

func defWindowProc(hwnd _HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM) _LRESULT {
	r1, _, _ := _defWindowProc.Call(
		uintptr(hwnd),
		uintptr(uMsg),
		uintptr(wParam),
		uintptr(lParam))
	return _LRESULT(r1)
}

// don't worry about error returns from GetWindowLongPtr()/SetWindowLongPtr()
// see comments of http://blogs.msdn.com/b/oldnewthing/archive/2014/02/03/10496248.aspx

func getWindowLongPtr(hwnd _HWND, what uintptr) uintptr {
	r1, _, _ := _getWindowLongPtr.Call(
		uintptr(hwnd),
		what)
	return r1
}

func setWindowLongPtr(hwnd _HWND, what uintptr, value uintptr) {
	_setWindowLongPtr.Call(
		uintptr(hwnd),
		what,
		value)
}

// we can store a pointer in extra space provided by Windows
// we'll store sysData there
// see http://blogs.msdn.com/b/oldnewthing/archive/2005/03/03/384285.aspx

func getSysData(hwnd _HWND) *sysData {
	return (*sysData)(unsafe.Pointer(getWindowLongPtr(hwnd, negConst(_GWLP_USERDATA))))
}

func storeSysData(hwnd _HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM) _LRESULT {
	// we can get the lpParam from CreateWindowEx() in WM_NCCREATE and WM_CREATE
	// we can freely skip any messages that come prior
	// see http://blogs.msdn.com/b/oldnewthing/archive/2005/04/22/410773.aspx and http://blogs.msdn.com/b/oldnewthing/archive/2014/02/03/10496248.aspx (note the date on the latter one!)
	if uMsg == _WM_NCCREATE {
		// the lpParam to CreateWindowEx() is the first uintptr of the CREATESTRUCT
		// so rather than create that whole structure, we'll just grab the uintptr at the address pointed to by lParam
		cs := (*uintptr)(unsafe.Pointer(lParam))
		saddr := *cs
		setWindowLongPtr(hwnd, negConst(_GWLP_USERDATA), saddr)
		// also set s.hwnd here so it can be used by other window messages right away
		s := (*sysData)(unsafe.Pointer(saddr))
		s.hwnd = hwnd
	}
	// TODO is this correct for WM_NCCREATE? I think the above link does it but I'm not entirely sure...
	return defWindowProc(hwnd, uMsg, wParam, lParam)
}

func stdWndProc(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_COMMAND:
		id := _HMENU(wParam.LOWORD())
		s.childrenLock.Lock()
		ss := s.children[id]
		s.childrenLock.Unlock()
		switch ss.ctype {
		case c_button:
			if wParam.HIWORD() == _BN_CLICKED {
				ss.signal()
			}
		}
		return 0
	case _WM_GETMINMAXINFO:
		mm := lParam.MINMAXINFO()
		// ... minimum size
		_ = mm
		return 0
	case _WM_SIZE:
		if s.resize != nil {
			var r _RECT

			r1, _, err := _getClientRect.Call(
				uintptr(hwnd),
				uintptr(unsafe.Pointer(&r)))
			if r1 == 0 {
				panic("GetClientRect failed: " + err.Error())
			}
			// top-left corner is (0,0) so no need for winheight
			s.doResize(int(r.left), int(r.top), int(r.right), int(r.bottom), 0)
			// TODO use the Defer movement functions here?
			// TODO redraw window and all children here?
		}
		return 0
	case _WM_CLOSE:
		s.signal()
		return 0
	default:
		return defWindowProc(hwnd, uMsg, wParam, lParam)
	}
	panic(fmt.Sprintf("stdWndProc message %d did not return: internal bug in ui library", uMsg))
}

type _WNDCLASS struct {
	style				uint32
	lpfnWndProc		uintptr
	cbClsExtra		int32		// originally int
	cbWndExtra		int32		// originally int
	hInstance			_HANDLE
	hIcon			_HANDLE
	hCursor			_HANDLE
	hbrBackground	_HBRUSH
	lpszMenuName	*uint16
	lpszClassName		uintptr
}

var (
	icon, cursor _HANDLE
)

var (
	_registerClass = user32.NewProc("RegisterClassW")
)

func registerStdWndClass() (err error) {
	wc := &_WNDCLASS{
		lpszClassName:	uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(stdWndClass))),
		lpfnWndProc:		syscall.NewCallback(stdWndProc),
		hInstance:		hInstance,
		hIcon:			icon,
		hCursor:			cursor,
		hbrBackground:	_HBRUSH(_COLOR_BTNFACE + 1),
	}
	r1, _, err := _registerClass.Call(uintptr(unsafe.Pointer(wc)))
	if r1 == 0 {		// failure
		return err
	}
	return nil
}

// no need to use/recreate MAKEINTRESOURCE() here as the Windows constant generator already took care of that because Microsoft's headers do already
func initWndClassInfo() (err error) {
	r1, _, err := user32.NewProc("LoadIconW").Call(
		uintptr(_NULL),
		uintptr(_IDI_APPLICATION))
	if r1 == 0 {		// failure
		return fmt.Errorf("error getting window icon: %v", err)
	}
	icon = _HANDLE(r1)

	r1, _, err = user32.NewProc("LoadCursorW").Call(
		uintptr(_NULL),
		uintptr(_IDC_ARROW))
	if r1 == 0 {		// failure
		return fmt.Errorf("error getting window cursor: %v", err)
	}
	cursor = _HANDLE(r1)

	return nil
}