package world

import (
	"encoding/binary"
	"io"
	"maps"
	"slices"
	"sync"
	"sync/atomic"
	"time"

	"github.com/df-mc/dragonfly/server/block/cube"
	"github.com/go-gl/mathgl/mgl64"
	"github.com/google/uuid"
)

// EntityType is the type of Entity. It specifies the name, encoded Entity
// ID and bounding box of an Entity.
type EntityType interface {
	// Open returns an Entity implementation in the context of a transaction.
	Open(tx *Tx, handle *EntityHandle, data *EntityData) Entity

	// EncodeEntity converts the Entity to its encoded representation: It
	// returns the type of the Minecraft Entity, for example
	// 'minecraft:falling_block'.
	EncodeEntity() string
	// BBox returns the bounding box of an Entity with this EntityType.
	BBox(e Entity) cube.BBox
	// DecodeNBT reads the fields from the NBT data map passed and converts it
	// to an Entity of the same EntityType.
	DecodeNBT(m map[string]any, data *EntityData)
	// EncodeNBT encodes the Entity of the same EntityType passed to a map of
	// properties that can be encoded to NBT.
	EncodeNBT(data *EntityData) map[string]any
}

// EntityConfig is used to configure the initial settings of an Entity upon
// creation using NewEntity.
type EntityConfig interface {
	Apply(data *EntityData)
}

// EntityHandle is a persistent identifier of an entity. It holds data of the
// entity that can be transformed into an Entity implementation in the context
// of a transaction.
type EntityHandle struct {
	id uuid.UUID
	t  EntityType

	cond         *sync.Cond
	worldless    *atomic.Bool
	weakTxActive bool
	w            *World

	data EntityData

	// TODO Handler? Handle world change here?
}

// EntitySpawnOpts holds spawning related options for entities created.
type EntitySpawnOpts struct {
	// Position is the position that an Entity should be spawned at.
	Position mgl64.Vec3
	// Rotation is the rotation that an Entity should be spawned with.
	Rotation cube.Rotation
	// Velocity specifies the initial velocity of the Entity.
	Velocity mgl64.Vec3
	// ID specifies the UUID of an entity. This field should usually be left
	// empty, as a valid UUID is generated when not set. Non-player entities
	// only have the last 8 bytes of the UUID set.
	ID uuid.UUID
	// NameTag is the name tag that the entity is spawned with.
	NameTag string
}

// New creates an EntityHandle using an EntityType and EntityConfig passed. The
// EntityHandle may be added to a world by calling Tx.AddEntity().
// The spawn conditions depend on the options set in opts.
func (opts EntitySpawnOpts) New(t EntityType, conf EntityConfig) *EntityHandle {
	if opts.ID == uuid.Nil {
		// Generate a new UUID with only the upper 8 bytes filled. This UUID
		// needs to be translatable to an int64.
		opts.ID = uuid.New()
		clear(opts.ID[:8])
	}
	handle := &EntityHandle{id: opts.ID, t: t, cond: sync.NewCond(&sync.Mutex{}), worldless: &atomic.Bool{}}
	handle.worldless.Store(true)
	handle.data.Pos, handle.data.Rot, handle.data.Vel = opts.Position, opts.Rotation, opts.Velocity
	handle.data.Name = opts.NameTag
	conf.Apply(&handle.data)
	return handle
}

// NewEntity creates an EntityHandle using an EntityType and EntityConfig
// passed. The EntityHandle may be added to a world by calling Tx.AddEntity().
// NewEntity uses the zero value for EntitySpawnOpts.
func NewEntity(t EntityType, conf EntityConfig) *EntityHandle {
	var opts EntitySpawnOpts
	return opts.New(t, conf)
}

// entityFromData reads an entity from the decoded NBT data passed and returns
// an EntityHandle.
func entityFromData(t EntityType, id int64, data map[string]any) *EntityHandle {
	handle := &EntityHandle{t: t, cond: sync.NewCond(&sync.Mutex{}), worldless: &atomic.Bool{}}
	binary.LittleEndian.PutUint64(handle.id[8:], uint64(id))
	handle.decodeNBT(data)
	t.DecodeNBT(data, &handle.data)
	return handle
}

// Type returns the EntityType of the EntityHandle.
func (e *EntityHandle) Type() EntityType {
	return e.t
}

// Entity attempts to convert an EntityHandle to an Entity using the Tx passed.
// A non-nil Entity is returned only if the entity's world matches the world of
// the Tx. If they do not match, false is returned.
func (e *EntityHandle) Entity(tx *Tx) (Entity, bool) {
	if e == nil || e.w != tx.World() {
		return nil, false
	}
	return e.t.Open(tx, e, &e.data), true
}

// mustEntity calls Entity but panics if the worlds do not match.
func (e *EntityHandle) mustEntity(tx *Tx) Entity {
	if ent, ok := e.Entity(tx); ok {
		return ent
	}
	panic("can't load entity with Tx of different world")
}

// UUID returns the identifier of the EntityHandle.
func (e *EntityHandle) UUID() uuid.UUID {
	return e.id
}

// Close closes the EntityHandle. Any subsequent call to ExecWorld will return
// immediately without the transaction function being called. Close always
// returns nil.
func (e *EntityHandle) Close() error {
	e.setAndUnlockWorld(closeWorld)
	return nil
}

// ExecWorld obtains the EntityHandle's World in a thread-safe way and opens a
// transaction in it when it does. If the EntityHandle has not been added to a
// world, ExecWorld will block until the EntityHandle is added to a World and
// run the transaction function once it is. If the Entity is closed before
// ExecWorld is called, ExecWorld will return false immediately without running
// the transaction function.
func (e *EntityHandle) ExecWorld(f func(tx *Tx, e Entity)) bool {
	return e.execWorld(f, false)
}

// execWorld uses a sync.Cond to synchronise access to the handler's world. We
// are dealing with a rather complicated synchronisation pattern here. The goal
// for ExecWorld is to block until e.w becomes accessible. Meanwhile, World.Exec
// may also affect e.w, which execWorld needs to deal with.
func (e *EntityHandle) execWorld(f func(tx *Tx, e Entity), weak bool) bool {
	e.cond.L.Lock()
	for e.w == nil || (!weak && e.weakTxActive) {
		// Wait suspends the current goroutine and unlocks e.cond.L, until
		// e.cond.Broadcast() is called. After this, one of the goroutines
		// waiting will acquire a lock of e.cond.L again. This means that only
		// one goroutine will run the code after this simultaneously.
		e.cond.Wait()
	}
	// If a goroutine manages to exit the for loop, it will have acquired a lock
	// on e.cond.L. This also means that e.w can be assumed to not be nil here.
	// Because of the lock on e.cond.L, no other transaction will be able to
	// change e.w until we finish. e.worldless is set to true in
	// e.unsetAndLockWorld(), where the entity's world is removed.
	e.worldless.Store(false)
	if e.w == closeWorld {
		// EntityHandle was closed. No need to continue.
		e.cond.L.Unlock()
		return false
	}
	// We now arrive at the more complicated part. When we call e.w.Exec(), our
	// transaction must await earlier transactions in the world. If one of those
	// earlier transactions tries to change e.w (through e.unsetAndLockWorld()
	// or e.setAndUnlockWorld()), it must lock e.cond.L. This would lead to a
	// deadlock, because we already have e.cond.L locked here.
	// We work around this with so-called "weak transactions". This is a
	// transaction that may be invalidated before it is executed. In this case,
	// this invalidation happens by setting e.worldless to true. If the
	// transaction turns out to be invalidated (ret == false), we simply try
	// again, this time with e.execWorld(f, true) to make this goroutine bypass
	// any goroutines still awaiting e.cond.
	ret := e.weakExec(func(tx *Tx) { f(tx, e.mustEntity(tx)) })
	e.cond.L.Unlock()

	if !ret {
		// Our weak transaction was suspended. We try again, this time with
		// e.execWorld(f, true) to make this goroutine bypass any goroutines
		// still awaiting e.cond.
		return e.execWorld(f, true)
	}
	return true
}

// weakExec performs a "weak transaction". It adds a transaction to the world
// that is invalidated when e.worldless is set to true. In this case, weakExec
// returns false. If the weak transaction is successfully executed, it returns
// true, and any calls to ExecWorld waiting on e.cond are awakened. The goal of
// weakExec is to suspend the current goroutine and unlock e.cond.L while
// waiting for previous transactions to finish.
func (e *EntityHandle) weakExec(f ExecFunc) bool {
	e.weakTxActive = true

	// We create a weak transaction and start a for loop to listen for the
	// length of the channel. This might look weird, but the crucial part here
	// is the call to e.cond.Wait(), which unlocks e.cond.L. This is required
	// to prevent a deadlock if an earlier transaction tries to change e.w.
	c := e.w.weakExec(e.worldless, e.cond, f)
	for len(c) == 0 && e.w != closeWorld {
		// Calling e.cond.Wait() here will free the lock on e.cond.L until our
		// transaction finishes. e.w.weakExec() ensures that e.cond.Broadcast()
		// is called once the transaction finished/is suspended, so we can
		// continue after that.
		e.cond.Wait()
	}
	// If the EntityHandle was closed (e.w == closeWorld), we treat the
	// transaction as successful, because all transactions must be cancelled.
	if e.w != closeWorld && !<-c {
		// Weak transaction was suspended. Return false and try again.
		return false
	}
	// After setting e.weakTxActive back to false, we must Broadcast to make
	// sure any goroutines waiting in e.execWorld as a result of the
	// e.weakTxActive condition can continue.
	e.weakTxActive = false
	e.cond.Broadcast()
	return true
}

var closeWorld = &World{}

// unsetAndLockWorld sets e.w to nil, causing any subsequent calls to ExecWorld
// to block until e.w is set to a non-nil value.
func (e *EntityHandle) unsetAndLockWorld() {
	e.cond.L.Lock()
	defer e.cond.L.Unlock()

	e.worldless.Store(true)
	e.w = nil
}

// setAndUnlockWorld sets e.w to a World passed and broadcasts e.cond, so that
// any goroutines waiting for a non-nil world are awoken.
func (e *EntityHandle) setAndUnlockWorld(w *World) {
	e.cond.L.Lock()
	defer e.cond.L.Unlock()

	if e.w != nil {
		panic("cannot add entity to new world before removing from old world")
	}
	e.w = w
	e.cond.Broadcast()
}

// decodeNBT decodes the position, velocity, rotation, age, on-fire duration and
// name tag of an entity.
func (e *EntityHandle) decodeNBT(m map[string]any) {
	e.data.Pos = readVec3(m, "Pos")
	e.data.Vel = readVec3(m, "Motion")
	e.data.Rot = readRotation(m)
	e.data.Age = time.Duration(readInt16(m, "Age")) * (time.Second / 20)
	e.data.FireDuration = time.Duration(readInt16(m, "Fire")) * time.Second / 20
	e.data.Name, _ = m["NameTag"].(string)
}

// encodeNBT encodes the position, velocity, rotation, age, on-fire duration and
// name tag of an entity.
func (e *EntityHandle) encodeNBT() map[string]any {
	return map[string]any{
		"Pos":     []float32{float32(e.data.Pos[0]), float32(e.data.Pos[1]), float32(e.data.Pos[2])},
		"Motion":  []float32{float32(e.data.Vel[0]), float32(e.data.Vel[1]), float32(e.data.Vel[2])},
		"Yaw":     float32(e.data.Rot[0]),
		"Pitch":   float32(e.data.Rot[1]),
		"Fire":    int16(e.data.FireDuration.Seconds() * 20),
		"Age":     int16(e.data.Age / (time.Second * 20)),
		"NameTag": e.data.Name,
	}
}

// EntityData holds data shared by every entity. It is kept in an EntityHandle.
type EntityData struct {
	Pos, Vel     mgl64.Vec3
	Rot          cube.Rotation
	Name         string
	FireDuration time.Duration
	Age          time.Duration

	Data any
}

// Entity represents an Entity in the world, typically an object that may be moved around and can be
// interacted with by other entities.
// Viewers of a world may view an Entity when near it.
type Entity interface {
	io.Closer
	// H returns the EntityHandle that points to the entity.
	H() *EntityHandle
	// Position returns the current position of the Entity in the world.
	Position() mgl64.Vec3
	// Rotation returns the yaw (horizontal rotation) and pitch (vertical
	// rotation) of the entity in degrees.
	Rotation() cube.Rotation
}

// TickerEntity represents an Entity that has a Tick method which should be called every time the Entity is
// ticked every 20th of a second.
type TickerEntity interface {
	Entity
	// Tick ticks the Entity with the current World and tick passed.
	Tick(tx *Tx, current int64)
}

// EntityAction represents an action that may be performed by an Entity. Typically, these actions are sent to
// viewers in a world so that they can see these actions.
type EntityAction interface {
	EntityAction()
}

// DamageSource represents the source of the damage dealt to an Entity. This
// source may be passed to the Hurt() method of an Entity in order to deal
// damage to an Entity with a specific source.
type DamageSource interface {
	// ReducedByArmour checks if the source of damage may be reduced if the
	// receiver of the damage is wearing armour.
	ReducedByArmour() bool
	// ReducedByResistance specifies if the Source is affected by the resistance
	// effect. If false, damage dealt to an Entity with this source will not be
	// lowered if the Entity has the resistance effect.
	ReducedByResistance() bool
	// Fire specifies if the Source is fire related and should be ignored when
	// an Entity has the fire resistance effect.
	Fire() bool
	// IgnoreTotem specifies whether the totem will be ignored if the damage is lethal.
	IgnoreTotem() bool
}

// HealingSource represents a source of healing for an Entity. This source may
// be passed to the Heal() method of a living Entity.
type HealingSource interface {
	HealingSource()
}

// EntityRegistry is a mapping that EntityTypes may be registered to. It is used
// for loading entities from disk in a World's Provider.
type EntityRegistry struct {
	conf EntityRegistryConfig
	ent  map[string]EntityType
}

// EntityRegistryConfig holds functions used by the block and item packages to
// create entities as a result of their behaviour. ALL functions of
// EntityRegistryConfig must be filled out for the behaviour of these blocks and
// items not to fail.
type EntityRegistryConfig struct {
	Item               func(opts EntitySpawnOpts, it any) *EntityHandle
	FallingBlock       func(opts EntitySpawnOpts, bl Block) *EntityHandle
	TNT                func(opts EntitySpawnOpts, fuse time.Duration) *EntityHandle
	BottleOfEnchanting func(opts EntitySpawnOpts, owner Entity) *EntityHandle
	Arrow              func(opts EntitySpawnOpts, conf ArrowSpawnConfig) *EntityHandle
	Egg                func(opts EntitySpawnOpts, owner Entity) *EntityHandle
	EnderPearl         func(opts EntitySpawnOpts, owner Entity) *EntityHandle
	Firework           func(opts EntitySpawnOpts, firework Item, owner Entity, sidewaysVelocityMultiplier, upwardsAcceleration float64, attached bool) *EntityHandle
	LingeringPotion    func(opts EntitySpawnOpts, t any, owner Entity) *EntityHandle
	Snowball           func(opts EntitySpawnOpts, owner Entity) *EntityHandle
	SplashPotion       func(opts EntitySpawnOpts, t any, owner Entity) *EntityHandle
	Lightning          func(opts EntitySpawnOpts) *EntityHandle
}

// ArrowSpawnConfig holds the options used to spawn an arrow entity.
type ArrowSpawnConfig struct {
	// Damage specifies the base damage dealt by the arrow.
	Damage float64
	// Owner is the entity that fired the arrow.
	Owner Entity
	// Critical specifies if the arrow should deal critical damage.
	Critical bool
	// DisablePickup specifies if picking up the arrow should be disabled.
	DisablePickup bool
	// ObtainArrowOnPickup specifies if the arrow should be returned as an item when picked up.
	ObtainArrowOnPickup bool
	// PunchLevel specifies the level of punch knockback applied to the arrow.
	PunchLevel int
	// PiercingLevel is the crossbow Piercing enchantment level. The arrow passes
	// through PiercingLevel entities and damages PiercingLevel+1 in total. A
	// value of 0 means no piercing.
	PiercingLevel int
	// Tip specifies the potion tip carried by the arrow.
	Tip any
}

// New creates an EntityRegistry using conf and the EntityTypes passed.
func (conf EntityRegistryConfig) New(ent []EntityType) EntityRegistry {
	m := make(map[string]EntityType, len(ent))
	for _, e := range ent {
		name := e.EncodeEntity()
		if _, ok := m[name]; ok {
			panic("cannot register the same entity (" + name + ") twice")
		}
		m[name] = e
	}
	return EntityRegistry{conf: conf, ent: m}
}

// Config returns the EntityRegistryConfig that was used to create the
// EntityRegistry.
func (reg EntityRegistry) Config() EntityRegistryConfig {
	return reg.conf
}

// Lookup looks up an EntityType by its name. If found, the EntityType is
// returned and the bool is true. The bool is false otherwise.
func (reg EntityRegistry) Lookup(name string) (EntityType, bool) {
	t, ok := reg.ent[name]
	return t, ok
}

// Types returns all EntityTypes passed upon construction of the EntityRegistry.
func (reg EntityRegistry) Types() []EntityType {
	return slices.Collect(maps.Values(reg.ent))
}

func readVec3(x map[string]any, k string) mgl64.Vec3 {
	if i, ok := x[k].([]any); ok {
		if len(i) != 3 {
			return mgl64.Vec3{}
		}
		var v mgl64.Vec3
		for index, f := range i {
			f32, _ := f.(float32)
			v[index] = float64(f32)
		}
		return v
	} else if i, ok := x[k].([]float32); ok {
		if len(i) != 3 {
			return mgl64.Vec3{}
		}
		return mgl64.Vec3{float64(i[0]), float64(i[1]), float64(i[2])}
	}
	return mgl64.Vec3{}
}

func readFloat32(m map[string]any, k string) float32 {
	v, _ := m[k].(float32)
	return v
}

func readRotation(m map[string]any) cube.Rotation {
	return cube.Rotation{float64(readFloat32(m, "Yaw")), float64(readFloat32(m, "Pitch"))}
}

func readInt16(m map[string]any, k string) int16 {
	v, _ := m[k].(int16)
	return v
}
