package land

import (
	"github.com/veandco/go-sdl2/sdl"
	"time"
)

type RootObject struct {
	Children []Object
	Overlays []Object

	Analyzer *PerformanceAnalyzer
}

func NewRootObject() *RootObject {
	in := &RootObject{
		Analyzer: &PerformanceAnalyzer{},
	}

	go in.Analyzer.RunResetter(1 * time.Second)

	return in
}

func (p *RootObject) AddChild(child Object) {
	p.Children = append(p.Children, child)
}

func (p *RootObject) AddOverlay(overlay Object) {
	p.Overlays = append(p.Overlays, overlay)
}

func (p *RootObject) RunEventLoop(tickDuration time.Duration) {
	for {
		startTime := time.Now()

		p.Update()

		elapsed := time.Since(startTime)
		hangDuration := tickDuration - elapsed
		if hangDuration > 0 {
			time.Sleep(hangDuration)
		}
	}
}

func (p *RootObject) ForEachChildren(cb func(child Object)) {
	var caller func(current Object)

	caller = func(current Object) {
		cb(current)
		for _, child := range current.GetChildren() {
			caller(child)
		}
	}

	for _, child := range p.Children {
		caller(child)
	}
	for _, child := range p.Overlays {
		caller(child)
	}
}

func (p *RootObject) HandleUserEvent(event sdl.Event) {
	switch event := event.(type) {
	case *sdl.MouseButtonEvent:
		x, y := float64(event.X), float64(event.Y)
		vec := Vector2D{x, y}
		p.ForEachChildren(func(child Object) {
			if interChild, ok := child.(InteractableObject); ok {
				if IsOverlapWithPoint(child.(PositionedObject), vec) {
					if event.Type == sdl.MOUSEBUTTONDOWN {
						interChild.OnEvent(UserEventMouseDown, vec)
					} else if event.Type == sdl.MOUSEBUTTONUP {
						interChild.OnEvent(UserEventMouseUp, vec)
					}
				}
			}
		})
	case *sdl.MouseMotionEvent:
		x, y := float64(event.X), float64(event.Y)
		vec := Vector2D{x, y}
		p.ForEachChildren(func(child Object) {
			if interChild, ok := child.(InteractableObject); ok {
				isOverlap := IsOverlapWithPoint(child.(PositionedObject), vec)
				interChild.OnEvent(UserEventMouseMove, vec, isOverlap)
			}
		})
	}
}

func (p *RootObject) Update() {
	p.ForEachChildren(func(child Object) {
		child.Update()
	})

	// Check collision
	p.ForEachChildren(func(child Object) {
		p.ForEachChildren(func(other Object) {
			if child == other {
				return
			}
			collidableChild, ok := child.(CollidableObject)
			if !ok {
				return
			}
			collidableOther, ok := other.(CollidableObject)
			if !ok {
				return
			}
			if checkCollisionBetweenObject(collidableChild, collidableOther) {
				collidableChild.OnCollide(collidableOther)
				collidableOther.OnCollide(collidableChild)
			}
		})
	})

	p.Analyzer.Tick()
}

func (p *RootObject) Draw(pen *sdl.Renderer) {
	// Render background and clear previous state
	pen.SetDrawColor(77, 77, 77, 255)
	pen.Clear()

	// Render each child
	p.ForEachChildren(func(child Object) {
		if drawableChild, ok := child.(DrawableObject); ok {
			drawableChild.Draw(pen)
		}
	})

	p.Analyzer.Draw()
}
