package entity

import (
	"time"

	"edgaru089.ml/go/gl01/internal/util"
	"edgaru089.ml/go/gl01/internal/util/itype"
	"edgaru089.ml/go/gl01/internal/world"
)

// EntityBehaviour describes the behaviour of a type of entity with the same Name.
type EntityBehaviour interface {
	// Name returns the type Name of the behaviour.
	Name() string

	// Hitbox gets the size of the hitbox of the entity.
	//
	// The hitbox is positioned with the position coordinates pointing to
	// the center of the bottom of the box.
	Hitbox(pos itype.Vec3d, dataset itype.Dataset) itype.Vec3d

	// EyeHeight gets the height of the eye of the entity.
	EyeHeight(pos itype.Vec3d, dataset itype.Dataset) float64

	// Update is called on every frame.
	// It should be used to update physics.
	Update(pos itype.Vec3d, dataset itype.Dataset, world *world.World, deltaTime time.Duration)
}

var behaviour = make(map[string]EntityBehaviour)

// RegisterEntityBehaviour registers behaviour with the name of b.Name().
//
// If the name is already taken, false is returned and nothing is done.
// Otherwise, true is returned and the entity is registered.
func RegisterEntityBehaviour(b EntityBehaviour) bool {
	if _, ok := behaviour[b.Name()]; ok {
		return false
	}

	behaviour[b.Name()] = b
	return true
}

// Entity is a struct holding a Behaviour, a Position and a Speed.
type Entity struct {
	b          EntityBehaviour
	pos, speed itype.Vec3d // pos has a origin of the **center of the bottom** of the hitbox
	ds         itype.Dataset

	name string // a shortcut to b.Name(), the typename

	onGround bool
	worldbox []itype.Boxd
	hp       []itype.Vec3d
}

func NewEntity(typename string, pos itype.Vec3d) *Entity {
	var b EntityBehaviour
	var ok bool
	if b, ok = behaviour[typename]; !ok {
		return nil
	}
	return &Entity{
		b:    b,
		pos:  pos,
		name: typename,
	}
}

func (e *Entity) Position() itype.Vec3d {
	return e.pos
}

func (e *Entity) SetPosition(pos itype.Vec3d) {
	e.pos = pos
}

func (e *Entity) Speed() itype.Vec3d {
	return e.speed
}

func (e *Entity) SetSpeed(speed itype.Vec3d) {
	e.speed = speed
}

func (e *Entity) Hitbox() itype.Vec3d {
	return e.b.Hitbox(e.pos, e.ds)
}

func (e *Entity) Accelerate(x, y, z float64) {
	/*e.speed[0] += x
	e.speed[1] += y
	e.speed[2] += z*/
	vec := itype.Vec3d{x, y, z}
	e.speed = util.BunnyhopAccelerate(vec, e.speed, vec.Length(), 8)
}

func (e *Entity) OnGround() bool {
	return e.onGround
}

func (e *Entity) EyeHeight() float64 {
	return e.b.EyeHeight(e.pos, e.ds)
}

func (e *Entity) EyePosition() itype.Vec3d {
	return e.pos.Addv(0, e.EyeHeight(), 0)
}