package x

import "image"
import "git.tebibyte.media/tomo/tomo"
import "git.tebibyte.media/tomo/tomo/input"
import "git.tebibyte.media/tomo/tomo/canvas"

type boxSet map[anyBox] struct { }

func (set boxSet) Empty () bool {
	return set == nil || len(set) == 0
}

func (set boxSet) Has (box anyBox) bool {
	if set == nil { return false }
	_, ok := set[box]
	return ok
}

func (set *boxSet) Add (box anyBox) {
	if *set == nil {
		*set = make(boxSet)
	}
	(*set)[box] = struct { } { }
}

func (set *boxSet) Pop () anyBox {
	for box := range *set {
		delete(*set, box)
		return box
	}
	return nil
}

type parent interface {
	window                  () *window
	canvas                  () canvas.Canvas
	notifyMinimumSizeChange (anyBox)
	drawBackgroundPart      (canvas.Canvas)
	captures                (eventCategory) bool
}

type anyBox interface {
	tomo.Box
	canvas.Drawer

	doDraw           ()
	doLayout         ()
	doMinimumSize    ()
	contentMinimum   () image.Point
	setParent        (parent)
	getParent        () parent
	flushActionQueue ()
	recursiveRedo    ()
	canBeFocused     () bool
	boxUnder         (image.Point, eventCategory) anyBox
	transparent      () bool

	propagate    (func (anyBox) bool) bool
	propagateAlt (func (anyBox) bool) bool

	handleFocusEnter ()
	handleFocusLeave ()
	// handleDndEnter   ()
	// handleDndLeave   ()
	// handleDndDrop    (data.Data)
	handleMouseEnter ()
	handleMouseLeave ()
	handleMouseMove  ()
	handleMouseDown  (input.Button)
	handleMouseUp    (input.Button)
	handleScroll     (float64, float64)
	handleKeyDown    (input.Key, bool)
	handleKeyUp      (input.Key, bool)
}

func assertAnyBox (unknown tomo.Box) anyBox {
	if box, ok := unknown.(anyBox); ok {
		return box
	} else {
		panic("foregin box implementation, i did not make this!")
	}
}

func (window *window) SetRoot (root tomo.Object) {
	if window.root != nil {
		window.root.setParent(nil)
	}
	if root == nil {
		window.root = nil
	} else {
		box := assertAnyBox(root.GetBox())
		box.setParent(window)
		box.flushActionQueue()
		window.invalidateLayout(box)
		window.root = box
	}
	window.minimumClean = false
}

func (window *window) window () *window {
	return window
}

func (window *window) canvas () canvas.Canvas {
	return window.xCanvas
}

func (window *window) notifyMinimumSizeChange (anyBox) {
	window.minimumClean = false
}

func (window *window) invalidateMinimum (box anyBox) {
	window.needMinimum.Add(box)
}

func (window *window) invalidateDraw (box anyBox) {
	window.needDraw.Add(box)
}

func (window *window) invalidateLayout (box anyBox) {
	window.needLayout.Add(box)
	window.invalidateDraw(box)
}

func (window *window) focus (box anyBox) {
	if window.focused == box { return }

	previous := window.focused
	window.focused = box

	if previous != nil {
		previous.handleFocusLeave()
	}
	if box != nil && box.canBeFocused() {
		box.handleFocusEnter()
	}
}

func (window *window) hover (box anyBox) {
	if window.hovered == box { return }

	previous := window.hovered
	window.hovered = box

	if previous != nil {
		previous.handleMouseLeave()
	}
	if box != nil {
		box.handleMouseEnter()
	}
}

func (window *window) anyFocused () bool {
	return window.focused != nil
}

type eventCategory int; const (
	eventCategoryDND eventCategory = iota
	eventCategoryMouse
	eventCategoryScroll
	eventCategoryKeyboard
)

func (this *window) boxUnder (point image.Point, category eventCategory) anyBox {
	if this.root == nil { return nil }
	return this.root.boxUnder(point, category)
}

func (this *window) captures (eventCategory) bool {
	return false
}

func (this *window) keyboardTarget () anyBox {
	focused := this.window().focused
	if focused == nil { return nil }
	parent := focused.getParent()
	for {
		parentBox, ok := parent.(anyBox)
		if !ok { break }
		if parent.captures(eventCategoryKeyboard) {
			return parentBox
		}
		parent = parentBox.getParent()
	}

	return focused
}

func (this *window) focusNext () {
	found   := !this.anyFocused()
	focused := false
	this.propagateAlt (func (box anyBox) bool {
		if found {
			// looking for the next box to select
			if box.canBeFocused() {
				// found it
				this.focus(box)
				focused = true
				return false
			}
		} else {
			// looking for the current focused element
			if box == this.focused {
				// found it
				found = true
			}
		}
		return true
	})

	if !focused { this.focus(nil) }
}

func (this *window) focusPrevious () {
	var behind anyBox
	this.propagate (func (box anyBox) bool {
		if box == this.focused {
			return false
		}
		if box.canBeFocused() { behind = box }
		return true
	})
	this.focus(behind)
}

func (this *window) propagate (callback func (box anyBox) bool) {
	if this.root == nil { return }
	this.root.propagate(callback)
}

func (this *window) propagateAlt (callback func (box anyBox) bool) {
	if this.root == nil { return }
	this.root.propagateAlt(callback)
}

func (window *window) afterEvent () {
	if window.xCanvas == nil { return }

	if window.needRedo {
		// set child bounds
		childBounds := window.metrics.bounds
		childBounds = childBounds.Sub(childBounds.Min)
		window.root.SetBounds(childBounds)

		// full relayout/redraw
		if window.root != nil {
			window.root.recursiveRedo()
		}
		window.pushAll()
		window.needRedo = false
		return
	}

	for len(window.needMinimum) > 0 {
		window.needMinimum.Pop().doMinimumSize()
	}
	if !window.minimumClean {
		window.doMinimumSize()
	}
	for len(window.needLayout) > 0 {
		window.needLayout.Pop().doLayout()
	}
	var toPush image.Rectangle
	for len(window.needDraw) > 0 {
		box := window.needDraw.Pop()
		box.doDraw()
		toPush = toPush.Union(box.Bounds())
	}
	if !toPush.Empty() {
		window.pushRegion(toPush)
	}
}