package player

import (
	"net"
	"time"

	"github.com/df-mc/dragonfly/server/block/cube"
	"github.com/df-mc/dragonfly/server/cmd"
	"github.com/df-mc/dragonfly/server/event"
	"github.com/df-mc/dragonfly/server/item"
	"github.com/df-mc/dragonfly/server/player/skin"
	"github.com/df-mc/dragonfly/server/session"
	"github.com/df-mc/dragonfly/server/world"
	"github.com/go-gl/mathgl/mgl64"
)

type Context = event.Context[*Player]

// Handler handles events that are called by a player. Implementations of Handler may be used to listen to
// specific events such as when a player chats or moves.
type Handler interface {
	// HandleMove handles the movement of a player. ctx.Cancel() may be called to cancel the movement event.
	// The new position, yaw and pitch are passed.
	HandleMove(ctx *Context, newPos mgl64.Vec3, newRot cube.Rotation)
	// HandleJump handles the player jumping.
	HandleJump(p *Player)
	// HandleTeleport handles the teleportation of a player. ctx.Cancel() may be called to cancel it.
	HandleTeleport(ctx *Context, pos mgl64.Vec3)
	// HandleChangeWorld handles when the player is added to a new world. before may be nil.
	HandleChangeWorld(p *Player, before, after *world.World)
	// HandleToggleSprint handles when the player starts or stops sprinting.
	// After is true if the player is sprinting after toggling (changing their sprinting state).
	HandleToggleSprint(ctx *Context, after bool)
	// HandleToggleSneak handles when the player starts or stops sneaking.
	// After is true if the player is sneaking after toggling (changing their sneaking state).
	HandleToggleSneak(ctx *Context, after bool)
	// HandleChat handles a message sent in the chat by a player. ctx.Cancel() may be called to cancel the
	// message being sent in chat.
	// The message may be changed by assigning to *message.
	HandleChat(ctx *Context, message *string)
	// HandleFoodLoss handles the food bar of a player depleting naturally, for example because the player was
	// sprinting and jumping. ctx.Cancel() may be called to cancel the food points being lost.
	HandleFoodLoss(ctx *Context, from int, to *int)
	// HandleHeal handles the player being healed by a healing source. ctx.Cancel() may be called to cancel
	// the healing.
	// The health added may be changed by assigning to *health.
	HandleHeal(ctx *Context, health *float64, src world.HealingSource)
	// HandleHurt handles the player being hurt by any damage source. ctx.Cancel() may be called to cancel the
	// damage being dealt to the player.
	// The damage dealt to the player may be changed by assigning to *damage.
	// *damage is the final damage dealt to the player. Immune is set to true
	// if the player was hurt during an immunity frame with higher damage than
	// the original cause of the immunity frame. In this case, the damage is
	// reduced but the player is still knocked back.
	HandleHurt(ctx *Context, damage *float64, immune bool, attackImmunity *time.Duration, src world.DamageSource)
	// HandleDeath handles the player dying to a particular damage cause.
	HandleDeath(p *Player, src world.DamageSource, keepInv *bool)
	// HandleRespawn handles the respawning of the player in the world. The spawn position passed may be
	// changed by assigning to *pos. The world.World in which the Player is respawned may be modifying by assigning to
	// *w. This world may be the world the Player died in, but it might also point to a different world (the overworld)
	// if the Player died in the nether or end.
	HandleRespawn(p *Player, pos *mgl64.Vec3, w **world.World)
	// HandleSkinChange handles the player changing their skin. ctx.Cancel() may be called to cancel the skin
	// change.
	HandleSkinChange(ctx *Context, skin *skin.Skin)
	// HandleFireExtinguish handles the player extinguishing a fire at a specific position. ctx.Cancel() may
	// be called to cancel the fire being extinguished.
	// cube.Pos can be used to see where was the fire extinguished, may be used to cancel this on specific positions.
	HandleFireExtinguish(ctx *Context, pos cube.Pos)
	// HandleStartBreak handles the player starting to break a block at the position passed. ctx.Cancel() may
	// be called to stop the player from breaking the block completely.
	HandleStartBreak(ctx *Context, pos cube.Pos)
	// HandleBlockBreak handles a block that is being broken by a player. ctx.Cancel() may be called to cancel
	// the block being broken. A pointer to a slice of the block's drops is passed, and may be altered
	// to change what items will actually be dropped.
	HandleBlockBreak(ctx *Context, pos cube.Pos, drops *[]item.Stack, xp *int)
	// HandleBlockPlace handles the player placing a specific block at a position in its world. ctx.Cancel()
	// may be called to cancel the block being placed.
	HandleBlockPlace(ctx *Context, pos cube.Pos, b world.Block)
	// HandleBlockPick handles the player picking a specific block at a position in its world. ctx.Cancel()
	// may be called to cancel the block being picked.
	HandleBlockPick(ctx *Context, pos cube.Pos, b world.Block)
	// HandleItemUse handles the player using an item in the air. It is called for each item, although most
	// will not actually do anything. Items such as snowballs may be thrown if HandleItemUse does not cancel
	// the context using ctx.Cancel(). It is not called if the player is holding no item.
	HandleItemUse(ctx *Context)
	// HandleItemUseOnBlock handles the player using the item held in its main hand on a block at the block
	// position passed. The face of the block clicked is also passed, along with the relative click position.
	// The click position has X, Y and Z values which are all in the range 0.0-1.0. It is also called if the
	// player is holding no item.
	HandleItemUseOnBlock(ctx *Context, pos cube.Pos, face cube.Face, clickPos mgl64.Vec3)
	// HandleItemUseOnEntity handles the player using the item held in its main hand on an entity passed to
	// the method.
	// HandleItemUseOnEntity is always called when a player uses an item on an entity, regardless of whether
	// the item actually does anything when used on an entity. It is also called if the player is holding no
	// item.
	HandleItemUseOnEntity(ctx *Context, e world.Entity)
	// HandleItemRelease handles the player releasing an item after using it for
	// a particular duration. These include items such as bows.
	HandleItemRelease(ctx *Context, item item.Stack, dur time.Duration)
	// HandleItemConsume handles the player consuming an item. This is called whenever a consumable such as
	// food is consumed.
	HandleItemConsume(ctx *Context, item item.Stack)
	// HandleAttackEntity handles the player attacking an entity using the item held in its hand. ctx.Cancel()
	// may be called to cancel the attack, which will cancel damage dealt to the target and will stop the
	// entity from being knocked back.
	// The entity attacked may not be alive (implements entity.Living), in which case no damage will be dealt
	// and the target won't be knocked back.
	// The entity attacked may also be immune when this method is called, in which case no damage and knock-
	// back will be dealt.
	// The knock back force and height is also provided which can be modified.
	// The attack can be a critical attack, which would increase damage by a factor of 1.5 and
	// spawn critical hit particles around the target entity. These particles will not be displayed
	// if no damage is dealt.
	HandleAttackEntity(ctx *Context, e world.Entity, force, height *float64, critical *bool)
	// HandleExperienceGain handles the player gaining experience. ctx.Cancel() may be called to cancel
	// the gain.
	// The amount is also provided which can be modified.
	HandleExperienceGain(ctx *Context, amount *int)
	// HandlePunchAir handles the player punching air.
	HandlePunchAir(ctx *Context)
	// HandleSignEdit handles the player editing a sign. It is called for every keystroke while editing a sign and
	// has both the old text passed and the text after the edit. This typically only has a change of one character.
	HandleSignEdit(ctx *Context, pos cube.Pos, frontSide bool, oldText, newText string)
	// HandleSleep handles the player beginning the sleep action. ctx.Cancel() may be called to cancel the action.
	HandleSleep(ctx *Context, sendReminder *bool)
	// HandleLecternPageTurn handles the player turning a page in a lectern. ctx.Cancel() may be called to cancel the
	// page turn. The page number may be changed by assigning to *page.
	HandleLecternPageTurn(ctx *Context, pos cube.Pos, oldPage int, newPage *int)
	// HandleItemDamage handles the event wherein the item either held by the player or as armour takes
	// damage through usage.
	// The type of the item may be checked to determine whether it was armour or a tool used. The damage to
	// the item is passed.
	HandleItemDamage(ctx *Context, i item.Stack, damage *int)
	// HandleItemPickup handles the player picking up an item from the ground. The item stack laying on the
	// ground is passed. ctx.Cancel() may be called to prevent the player from picking up the item.
	HandleItemPickup(ctx *Context, i *item.Stack)
	// HandleHeldSlotChange handles the player changing the slot they are currently holding.
	HandleHeldSlotChange(ctx *Context, from, to int)
	// HandleItemDrop handles the player dropping an item on the ground.
	// ctx.Cancel() may be called to prevent the player from dropping the item.Stack passed on the ground.
	HandleItemDrop(ctx *Context, s item.Stack)
	// HandleTransfer handles a player being transferred to another server. ctx.Cancel() may be called to
	// cancel the transfer.
	HandleTransfer(ctx *Context, addr *net.UDPAddr)
	// HandleCommandExecution handles the command execution of a player, who wrote a command in the chat.
	// ctx.Cancel() may be called to cancel the command execution.
	HandleCommandExecution(ctx *Context, command cmd.Command, args []string)
	// HandleQuit handles the closing of a player. It is always called when the player is disconnected,
	// regardless of the reason.
	HandleQuit(p *Player)
	// HandleDiagnostics handles the latest diagnostics data that the player has sent to the server. This is
	// not sent by every client however, only those with the "Creator > Enable Client Diagnostics" setting
	// enabled.
	HandleDiagnostics(p *Player, d session.Diagnostics)
}

// NopHandler implements the Handler interface but does not execute any code when an event is called. The
// default Handler of players is set to NopHandler.
// Users may embed NopHandler to avoid having to implement each method.
type NopHandler struct{}

// Compile time check to make sure NopHandler implements Handler.
var _ Handler = NopHandler{}

func (NopHandler) HandleItemDrop(*Context, item.Stack)                                     {}
func (NopHandler) HandleHeldSlotChange(*Context, int, int)                                 {}
func (NopHandler) HandleMove(*Context, mgl64.Vec3, cube.Rotation)                          {}
func (NopHandler) HandleJump(*Player)                                                      {}
func (NopHandler) HandleTeleport(*Context, mgl64.Vec3)                                     {}
func (NopHandler) HandleChangeWorld(*Player, *world.World, *world.World)                   {}
func (NopHandler) HandleToggleSprint(*Context, bool)                                       {}
func (NopHandler) HandleToggleSneak(*Context, bool)                                        {}
func (NopHandler) HandleCommandExecution(*Context, cmd.Command, []string)                  {}
func (NopHandler) HandleTransfer(*Context, *net.UDPAddr)                                   {}
func (NopHandler) HandleChat(*Context, *string)                                            {}
func (NopHandler) HandleSkinChange(*Context, *skin.Skin)                                   {}
func (NopHandler) HandleFireExtinguish(*Context, cube.Pos)                                 {}
func (NopHandler) HandleStartBreak(*Context, cube.Pos)                                     {}
func (NopHandler) HandleBlockBreak(*Context, cube.Pos, *[]item.Stack, *int)                {}
func (NopHandler) HandleBlockPlace(*Context, cube.Pos, world.Block)                        {}
func (NopHandler) HandleBlockPick(*Context, cube.Pos, world.Block)                         {}
func (NopHandler) HandleSignEdit(*Context, cube.Pos, bool, string, string)                 {}
func (NopHandler) HandleSleep(*Context, *bool)                                             {}
func (NopHandler) HandleLecternPageTurn(*Context, cube.Pos, int, *int)                     {}
func (NopHandler) HandleItemPickup(*Context, *item.Stack)                                  {}
func (NopHandler) HandleItemUse(*Context)                                                  {}
func (NopHandler) HandleItemUseOnBlock(*Context, cube.Pos, cube.Face, mgl64.Vec3)          {}
func (NopHandler) HandleItemUseOnEntity(*Context, world.Entity)                            {}
func (NopHandler) HandleItemRelease(ctx *Context, item item.Stack, dur time.Duration)      {}
func (NopHandler) HandleItemConsume(*Context, item.Stack)                                  {}
func (NopHandler) HandleItemDamage(*Context, item.Stack, *int)                             {}
func (NopHandler) HandleAttackEntity(*Context, world.Entity, *float64, *float64, *bool)    {}
func (NopHandler) HandleExperienceGain(*Context, *int)                                     {}
func (NopHandler) HandlePunchAir(*Context)                                                 {}
func (NopHandler) HandleHurt(*Context, *float64, bool, *time.Duration, world.DamageSource) {}
func (NopHandler) HandleHeal(*Context, *float64, world.HealingSource)                      {}
func (NopHandler) HandleFoodLoss(*Context, int, *int)                                      {}
func (NopHandler) HandleDeath(*Player, world.DamageSource, *bool)                          {}
func (NopHandler) HandleRespawn(*Player, *mgl64.Vec3, **world.World)                       {}
func (NopHandler) HandleQuit(*Player)                                                      {}
func (NopHandler) HandleDiagnostics(*Player, session.Diagnostics)                          {}
