package x

import "image"
import "image/color"
import "git.tebibyte.media/tomo/tomo"
import "git.tebibyte.media/tomo/tomo/data"
import "git.tebibyte.media/tomo/tomo/input"
import "git.tebibyte.media/tomo/tomo/event"
import "git.tebibyte.media/tomo/tomo/canvas"

type box struct {
	backend *Backend
	parent   parent
	outer    anyBox

	bounds  image.Rectangle
	minSize image.Point

	padding tomo.Inset
	border  []tomo.Border
	color   color.Color

	dndData   data.Data
	dndAccept []data.Mime
	focused   bool
	focusable bool

	canvas canvas.Canvas
	drawer canvas.Drawer

	on struct {
		focusEnter event.FuncBroadcaster
		focusLeave event.FuncBroadcaster
		dndEnter   event.FuncBroadcaster
		dndLeave   event.FuncBroadcaster
		dndDrop    event.Broadcaster[func (data.Data)]
		mouseEnter event.FuncBroadcaster
		mouseLeave event.FuncBroadcaster
		mouseMove  event.FuncBroadcaster
		mouseDown  event.Broadcaster[func (input.Button)]
		mouseUp    event.Broadcaster[func (input.Button)]
		scroll     event.Broadcaster[func (float64, float64)]
		keyDown    event.Broadcaster[func (input.Key, bool)]
		keyUp      event.Broadcaster[func (input.Key, bool)]
	}
}

func (backend *Backend) NewBox() tomo.Box {
	box := &box {
		backend: backend,
		color:   color.White,
	}
	box.drawer = box
	box.outer  = box
	return box
}

func (this *box) Box () tomo.Box {
	return this
}

func (this *box) Window () tomo.Window {
	return this.parent.window()
}

func (this *box) Bounds () image.Rectangle {
	return this.bounds
}

func (this *box) InnerBounds () image.Rectangle {
	return this.padding.Apply(this.innerClippingBounds())
}

func (this *box) MinimumSize () image.Point {
	return this.minSize
}

func (this *box) borderSum () tomo.Inset {
	sum := tomo.Inset { }
	for _, border := range this.border {
		sum[0] += border.Width[0]
		sum[1] += border.Width[1]
		sum[2] += border.Width[2]
		sum[3] += border.Width[3]
	}
	return sum
}

func (this *box) innerClippingBounds () image.Rectangle {
	return this.borderSum().Apply(this.bounds)
}

func (this *box) SetBounds (bounds image.Rectangle) {
	if this.bounds == bounds { return }
	this.bounds = bounds
	this.invalidateLayout()
}

func (this *box) SetColor (c color.Color) {
	if this.color == c { return }
	this.color = c
	this.invalidateDraw()
}

func (this *box) SetBorder (border ...tomo.Border) {
	this.border = border
	this.invalidateLayout()
	this.recalculateMinimumSize()
}

func (this *box) SetMinimumSize (size image.Point) {
	if this.minSize == size { return }
	this.minSize = size
	
	if this.parent != nil {
		this.parent.notifyMinimumSizeChange(this)
	}
}

func (this *box) SetPadding (padding tomo.Inset) {
	if this.padding == padding { return }
	this.padding = padding
	this.invalidateLayout()
	this.recalculateMinimumSize()
}

func (this *box) SetDNDData (dat data.Data) {
	this.dndData = dat
}

func (this *box) SetDNDAccept (types ...data.Mime) {
	this.dndAccept = types
}

func (this *box) SetFocused (focused bool) {
	if this.parent == nil { return }
	window := this.parent.window()
	if window == nil { return }
	if !this.focusable { return }
	
	if this.Focused () && !focused {
		this.parent.window().focus(nil)
	} else if !this.Focused() && focused {
		this.parent.window().focus(this.outer)
	}
}

func (this *box) SetFocusable (focusable bool) {
	if this.focusable == focusable { return }
	this.focusable = focusable
	if !focusable {
		this.SetFocused(false)
	}
}

func (this *box) Focused () bool {
	if this.parent == nil { return false  }
	parentWindow := this.parent.window()
	if parentWindow == nil { return false }
	return this.outer == parentWindow.focused
}

func (this *box) Modifiers () input.Modifiers {
	return this.parent.window().modifiers
}

func (this *box) MousePosition () image.Point {
	return this.parent.window().mousePosition
}

// ----- event handler setters ---------------------------------------------- //
func (this *box) OnFocusEnter (callback func()) event.Cookie {
	return this.on.focusEnter.Connect(callback)
}
func (this *box) OnFocusLeave (callback func()) event.Cookie {
	return this.on.focusLeave.Connect(callback)
}
func (this *box) OnDNDEnter (callback func()) event.Cookie {
	return this.on.dndEnter.Connect(callback)
}
func (this *box) OnDNDLeave (callback func()) event.Cookie {
	return this.on.dndLeave.Connect(callback)
}
func (this *box) OnDNDDrop (callback func(data.Data)) event.Cookie {
	return this.on.dndDrop.Connect(callback)
}
func (this *box) OnMouseEnter (callback func()) event.Cookie {
	return this.on.mouseEnter.Connect(callback)
}
func (this *box) OnMouseLeave (callback func()) event.Cookie {
	return this.on.mouseLeave.Connect(callback)
}
func (this *box) OnMouseMove (callback func()) event.Cookie {
	return this.on.mouseMove.Connect(callback)
}
func (this *box) OnMouseDown (callback func(input.Button)) event.Cookie {
	return this.on.mouseDown.Connect(callback)
}
func (this *box) OnMouseUp (callback func(input.Button)) event.Cookie {
	return this.on.mouseUp.Connect(callback)
}
func (this *box) OnScroll (callback func(deltaX, deltaY float64)) event.Cookie {
	return this.on.scroll.Connect(callback)
}
func (this *box) OnKeyDown (callback func(key input.Key, numberPad bool)) event.Cookie {
	return this.on.keyDown.Connect(callback)
}
func (this *box) OnKeyUp (callback func(key input.Key, numberPad bool)) event.Cookie {
	return this.on.keyUp.Connect(callback)
}
// -------------------------------------------------------------------------- //

func (this *box) Draw (can canvas.Canvas) {
	if can == nil { return }
	pen := can.Pen()
	pen.Fill(this.color)
	pen.Rectangle(this.bounds)
}

func (this *box) drawBorders (can canvas.Canvas) {
	if can == nil { return }
	pen := can.Pen()
	bounds := this.bounds
	for _, border := range this.border {
		pen.Fill(border.Color[tomo.SideTop])
		pen.Rectangle(image.Rect (
			bounds.Min.X,
			bounds.Min.Y,
			bounds.Max.X,
			bounds.Min.Y + border.Width[tomo.SideTop]))
		pen.Fill(border.Color[tomo.SideBottom])
		pen.Rectangle(image.Rect (
			bounds.Min.X,
			bounds.Max.Y - border.Width[tomo.SideBottom],
			bounds.Max.X,
			bounds.Max.Y))
		pen.Fill(border.Color[tomo.SideLeft])
		pen.Rectangle(image.Rect (
			bounds.Min.X,
			bounds.Min.Y + border.Width[tomo.SideTop],
			bounds.Min.X + border.Width[tomo.SideLeft],
			bounds.Max.Y - border.Width[tomo.SideBottom]))
		pen.Fill(border.Color[tomo.SideRight])
		pen.Rectangle(image.Rect (
			bounds.Max.X - border.Width[tomo.SideRight],
			bounds.Min.Y + border.Width[tomo.SideTop],
			bounds.Max.X,
			bounds.Max.Y - border.Width[tomo.SideBottom]))

		bounds = border.Width.Apply(bounds)
	}
}

func (this *box) doDraw () {
	if this.canvas == nil { return }
	if this.drawer != nil {
		this.drawBorders(this.canvas)
		this.drawer.Draw(this.canvas.Clip(this.innerClippingBounds()))
	}
}

func (this *box) doLayout () {
	if this.parent == nil { this.canvas = nil; return  }
	parentCanvas := this.parent.canvas()
	if parentCanvas == nil { this.canvas = nil; return }
	this.canvas = parentCanvas.Clip(this.bounds)
}

func (this *box) setParent (parent parent) {
	if this.parent != parent && this.Focused() {
		this.SetFocused(false)
	}
	this.parent = parent
}

func (this *box) recursiveRedo () {
	this.doLayout()
	this.doDraw()
}

func (this *box) invalidateLayout () {
	if this.parent == nil || this.parent.window() == nil { return }
	this.parent.window().invalidateLayout(this.outer)
}

func (this *box) invalidateDraw () {
	if this.parent == nil || this.parent.window() == nil { return }
	this.parent.window().invalidateDraw(this.outer)
}

func (this *box) recalculateMinimumSize () {
	if this.outer != anyBox(this) {
		this.outer.recalculateMinimumSize()
	}
}

func (this *box) canBeFocused () bool {
	return this.focusable
}

func (this *box) boxUnder (point image.Point) anyBox {
	if point.In(this.bounds) {
		return this.outer
	} else {
		return nil
	}
}

func (this *box) handleFocusEnter () {
	this.on.focusEnter.Broadcast()
}

func (this *box) handleFocusLeave () {
	this.on.focusLeave.Broadcast()
}

func (this *box) handleMouseEnter () {
	this.on.mouseEnter.Broadcast()
}

func (this *box) handleMouseLeave () {
	this.on.mouseLeave.Broadcast()
}

func (this *box) handleMouseMove () {
	this.on.mouseMove.Broadcast()
}

func (this *box) handleMouseDown (button input.Button) {
	if this.focusable {
		this.SetFocused(true)
	} else {
		if this.parent == nil { return }
		window := this.parent.window()
		if window == nil { return }
		window.focus(nil)
	}
	for _, listener := range this.on.mouseDown.Listeners() {
		listener(button)
	}
}

func (this *box) handleMouseUp (button input.Button) {
	for _, listener := range this.on.mouseUp.Listeners() {
		listener(button)
	}
}

func (this *box) handleKeyDown (key input.Key, numberPad bool) {
	for _, listener := range this.on.keyDown.Listeners() {
		listener(key, numberPad)
	}
}

func (this *box) handleKeyUp (key input.Key, numberPad bool) {
	for _, listener := range this.on.keyUp.Listeners() {
		listener(key, numberPad)
	}
}

func (this *box) propagate (callback func (anyBox) bool) bool {
	return callback(this)
}

func (this *box) propagateAlt (callback func (anyBox) bool) bool {
	return callback(this)
}