// 13 december 2015

package ui

import (
	"unsafe"
)

// #include "pkgui.h"
import "C"

// no need to lock this; only the GUI thread can access it
var areahandlers = make(map[*C.uiAreaHandler]AreaHandler)

// AreaHandler defines the functionality needed for handling events
// from an Area. Each of the methods on AreaHandler is called from
// the GUI thread, and every parameter (other than the Area itself)
// should be assumed to only be valid during the life of the method
// call (so for instance, do not save AreaDrawParams.AreaWidth, as
// that might change without generating an event).
// 
// Coordinates to Draw and MouseEvent are given in points. Points
// are generic, floating-point, device-independent coordinates with
// (0,0) at the top left corner. You never have to worry about the
// mapping between points and pixels; simply draw everything using
// points and you get nice effects like looking sharp on high-DPI
// monitors for free. Proper documentation on the matter is being
// written. In the meantime, there are several referenes to this kind of
// drawing, most notably on Apple's website: https://developer.apple.com/library/mac/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Explained/Explained.html#//apple_ref/doc/uid/TP40012302-CH4-SW1
// 
// For a scrolling Area, points are automatically offset by the scroll
// position. So if the mouse moves to position (5,5) while the
// horizontal scrollbar is at position 10 and the horizontal scrollbar is
// at position 20, the coordinate stored in the AreaMouseEvent
// structure is (15,25). The same applies to drawing.
type AreaHandler interface {
	// Draw is sent when a part of the Area needs to be drawn.
	// dp will contain a drawing context to draw on, the rectangle
	// that needs to be drawn in, and (for a non-scrolling area) the
	// size of the area. The rectangle that needs to be drawn will
	// have been cleared by the system prior to drawing, so you are
	// always working on a clean slate.
	// 
	// If you call Save on the drawing context, you must call Release
	// before returning from Draw, and the number of calls to Save
	// and Release must match. Failure to do so results in undefined
	// behavior.
	Draw(a *Area, dp *AreaDrawParams)

	// MouseEvent is called when the mouse moves over the Area
	// or when a mouse button is pressed or released. See
	// AreaMouseEvent for more details.
	// 
	// If a mouse button is being held, MouseEvents will continue to
	// be generated, even if the mouse is not within the area. On
	// some systems, the system can interrupt this behavior;
	// see DragBroken.
	MouseEvent(a *Area, me *AreaMouseEvent)

	// MouseCrossed is called when the mouse either enters or
	// leaves the Area. It is called even if the mouse buttons are being
	// held (see MouseEvent above). If the mouse has entered the
	// Area, left is false; if it has left the Area, left is true.
	// 
	// If, when the Area is first shown, the mouse is already inside
	// the Area, MouseCrossed will be called with left=false.
	// TODO what about future shows?
	MouseCrossed(a *Area, left bool)

	// DragBroken is called if a mouse drag is interrupted by the
	// system. As noted above, when a mouse button is held,
	// MouseEvent will continue to be called, even if the mouse is
	// outside the Area. On some systems, this behavior can be
	// stopped by the system itself for a variety of reasons. This
	// method is provided to allow your program to cope with the
	// loss of the mouse in this case. You should cope by cancelling
	// whatever drag-related operation you were doing.
	// 
	// Note that this is only generated on some systems under
	// specific conditions. Do not implement behavior that only
	// takes effect when DragBroken is called.
	DragBroken(a *Area)

	// KeyEvent is called when a key is pressed while the Area has
	// keyboard focus (if the Area has been tabbed into or if the
	// mouse has been clicked on it). See AreaKeyEvent for specifics.
	// 
	// Because some keyboard events are handled by the system
	// (for instance, menu accelerators and global hotkeys), you
	// must return whether you handled the key event; return true
	// if you did or false if you did not. If you wish to ignore the
	// keyboard outright, the correct implementation of KeyEvent is
	// 	func (h *MyHandler) KeyEvent(a *ui.Area, ke *ui.AreaKeyEvent) (handled bool) {
	// 		return false
	// 	}
	// DO NOT RETURN TRUE UNCONDITIONALLY FROM THIS
	// METHOD. BAD THINGS WILL HAPPEN IF YOU DO.
	KeyEvent(a *Area, ke *AreaKeyEvent) (handled bool)
}

func registerAreaHandler(ah AreaHandler) *C.uiAreaHandler {
	uah := C.pkguiAllocAreaHandler()
	areahandlers[uah] = ah
	return uah
}

func unregisterAreaHandler(uah *C.uiAreaHandler) {
	delete(areahandlers, uah)
	C.pkguiFreeAreaHandler(uah)
}

// AreaDrawParams provides a drawing context that can be used
// to draw on an Area and tells you where to draw. See AreaHandler
// for introductory information.
type AreaDrawParams struct {
	// Context is the drawing context to draw on. See DrawContext
	// for how to draw.
	Context		*DrawContext

	// AreaWidth and AreaHeight provide the size of the Area for
	// non-scrolling Areas. For scrolling Areas both values are zero.
	// 
	// To reiterate the AreaHandler documentation, do NOT save
	// these values for later; they can change without generating
	// an event.
	AreaWidth	float64
	AreaHeight	float64

	// These four fields define the rectangle that needs to be
	// redrawn. The system will not draw anything outside this
	// rectangle, but you can make your drawing faster if you
	// also stay within the lines.
	ClipX		float64
	ClipY		float64
	ClipWidth		float64
	ClipHeight	float64
}

//export pkguiDoAreaHandlerDraw
func pkguiDoAreaHandlerDraw(uah *C.uiAreaHandler, ua *C.uiArea, udp *C.uiAreaDrawParams) {
	ah := areahandlers[uah]
	a := ControlFromLibui(uintptr(unsafe.Pointer(ua))).(*Area)
	dp := &AreaDrawParams{
		Context:		&DrawContext{udp.Context},
		AreaWidth:	float64(udp.AreaWidth),
		AreaHeight:	float64(udp.AreaHeight),
		ClipX:		float64(udp.ClipX),
		ClipY:		float64(udp.ClipY),
		ClipWidth:		float64(udp.ClipWidth),
		ClipHeight:	float64(udp.ClipHeight),
	}
	ah.Draw(a, dp)
}

// TODO document all these
// 
// TODO note that in the case of a drag, X and Y can be out of bounds, or in the event of a scrolling area, in places that are not visible
type AreaMouseEvent struct {
	X			float64
	Y			float64

	// AreaWidth and AreaHeight provide the size of the Area for
	// non-scrolling Areas. For scrolling Areas both values are zero.
	// 
	// To reiterate the AreaHandler documentation, do NOT save
	// these values for later; they can change without generating
	// an event.
	AreaWidth	float64
	AreaHeight	float64

	Down		uint
	Up			uint
	Count		uint
	Modifiers		Modifiers
	Held			[]uint
}

func appendBits(out []uint, held C.uint64_t) []uint {
	n := uint(1)
	for i := 0; i < 64; i++ {
		if held & 1 != 0 {
			out = append(out, n)
		}
		held >>= 1
		n++
	}
	return out
}

//export pkguiDoAreaHandlerMouseEvent
func pkguiDoAreaHandlerMouseEvent(uah *C.uiAreaHandler, ua *C.uiArea, ume *C.uiAreaMouseEvent) {
	ah := areahandlers[uah]
	a := ControlFromLibui(uintptr(unsafe.Pointer(ua))).(*Area)
	me := &AreaMouseEvent{
		X:			float64(ume.X),
		Y:			float64(ume.Y),
		AreaWidth:	float64(ume.AreaWidth),
		AreaHeight:	float64(ume.AreaHeight),
		Down:		uint(ume.Down),
		Up:			uint(ume.Up),
		Count:		uint(ume.Count),
		Modifiers:		Modifiers(ume.Modifiers),
		Held:		make([]uint, 0, 64),
	}
	me.Held = appendBits(me.Held, ume.Held1To64)
	ah.MouseEvent(a, me)
}

//export pkguiDoAreaHandlerMouseCrossed
func pkguiDoAreaHandlerMouseCrossed(uah *C.uiAreaHandler, ua *C.uiArea, left C.int) {
	ah := areahandlers[uah]
	a := ControlFromLibui(uintptr(unsafe.Pointer(ua))).(*Area)
	ah.MouseCrossed(a, tobool(left))
}

//export pkguiDoAreaHandlerDragBroken
func pkguiDoAreaHandlerDragBroken(uah *C.uiAreaHandler, ua *C.uiArea) {
	ah := areahandlers[uah]
	a := ControlFromLibui(uintptr(unsafe.Pointer(ua))).(*Area)
	ah.DragBroken(a)
}

// TODO document all these
type AreaKeyEvent struct {
	Key		rune
	ExtKey	ExtKey
	Modifier	Modifiers
	Modifiers	Modifiers
	Up		bool
}

//export pkguiDoAreaHandlerKeyEvent
func pkguiDoAreaHandlerKeyEvent(uah *C.uiAreaHandler, ua *C.uiArea, uke *C.uiAreaKeyEvent) C.int {
	ah := areahandlers[uah]
	a := ControlFromLibui(uintptr(unsafe.Pointer(ua))).(*Area)
	ke := &AreaKeyEvent{
		Key:			rune(uke.Key),
		ExtKey:		ExtKey(uke.ExtKey),
		Modifier:		Modifiers(uke.Modifier),
		Modifiers:		Modifiers(uke.Modifiers),
		Up:			tobool(uke.Up),
	}
	return frombool(ah.KeyEvent(a, ke))
}

// TODO document
// 
// Note: these must be numerically identical to their libui equivalents.
type Modifiers uint
const (
	Ctrl Modifiers = 1 << iota
	Alt
	Shift
	Super
)

// TODO document
// 
// Note: these must be numerically identical to their libui equivalents.
type ExtKey int
const (
	Escape ExtKey = iota + 1
	Insert			// equivalent to "Help" on Apple keyboards
	Delete
	Home
	End
	PageUp
	PageDown
	Up
	Down
	Left
	Right
	F1			// F1..F12 are guaranteed to be consecutive
	F2
	F3
	F4
	F5
	F6
	F7
	F8
	F9
	F10
	F11
	F12
	N0			// numpad keys; independent of Num Lock state
	N1			// N0..N9 are guaranteed to be consecutive
	N2
	N3
	N4
	N5
	N6
	N7
	N8
	N9
	NDot
	NEnter
	NAdd
	NSubtract
	NMultiply
	NDivide
)
