package bot

import (
	"errors"
	"strconv"

	"github.com/Tnze/go-mc/data"
	pk "github.com/Tnze/go-mc/net/packet"
)

// SwingArm swing player's arm.
// hand could be one of 0: main hand, 1: off hand.
// It's just animation.
func (c *Client) SwingArm(hand int) error {
	return c.conn.WritePacket(pk.Marshal(
		data.AnimationServerbound,
		pk.VarInt(hand),
	))
}

// Respawn the player when it was dead.
func (c *Client) Respawn() error {
	return c.conn.WritePacket(pk.Marshal(
		data.ClientStatus,
		pk.VarInt(0),
	))
}

// UseItem use the item player handing.
// hand could be one of 0: main hand, 1: off hand
func (c *Client) UseItem(hand int) error {
	return c.conn.WritePacket(pk.Marshal(
		data.UseItem,
		pk.VarInt(hand),
	))
}

// UseEntity used by player to right-clicks another entity.
// hand could be one of 0: main hand, 1: off hand.
// A Notchian server only accepts this packet if
// the entity being attacked/used is visible without obstruction
// and within a 4-unit radius of the player's position.
func (c *Client) UseEntity(entityID int32, hand int) error {
	return c.conn.WritePacket(pk.Marshal(
		data.UseEntity,
		pk.VarInt(entityID),
		pk.VarInt(0),
		pk.VarInt(hand),
	))
}

// AttackEntity used by player to left-clicks another entity.
// The attack version of UseEntity. Has the same limit.
func (c *Client) AttackEntity(entityID int32, hand int) error {
	return c.conn.WritePacket(pk.Marshal(
		data.UseEntity,
		pk.VarInt(entityID),
		pk.VarInt(1),
	))
}

// UseEntityAt is a variety of UseEntity with target location
func (c *Client) UseEntityAt(entityID int32, x, y, z float32, hand int) error {
	return c.conn.WritePacket(pk.Marshal(
		data.UseEntity,
		pk.VarInt(entityID),
		pk.VarInt(2),
		pk.Float(x), pk.Float(y), pk.Float(z),
		pk.VarInt(hand),
	))
}

// Chat send chat as chat message or command at textbox.
func (c *Client) Chat(msg string) error {
	if len(msg) > 256 {
		return errors.New("message too long")
	}

	return c.conn.WritePacket(pk.Marshal(
		data.ChatMessageServerbound,
		pk.String(msg),
	))
}

// PluginMessage is used by mods and plugins to send their data.
func (c *Client) PluginMessage(channal string, msg []byte) error {
	return c.conn.WritePacket(pk.Marshal(
		data.PluginMessageServerbound,
		pk.Identifier(channal),
		pluginMessageData(msg),
	))
}

// UseBlock is used to place or use a block.
// hand is the hand from which the block is placed; 0: main hand, 1: off hand.
// face is the face on which the block is placed.
//
// Cursor position is the position of the crosshair on the block:
// cursorX, from 0 to 1 increasing from west to east;
// cursorY, from 0 to 1 increasing from bottom to top;
// cursorZ, from 0 to 1 increasing from north to south.
//
// insideBlock is true when the player's head is inside of a block's collision.
func (c *Client) UseBlock(hand, locX, locY, locZ, face int, cursorX, cursorY, cursorZ float32, insideBlock bool) error {
	return c.conn.WritePacket(pk.Marshal(
		data.PlayerBlockPlacement,
		pk.VarInt(hand),
		pk.Position{X: locX, Y: locY, Z: locZ},
		pk.VarInt(face),
		pk.Float(cursorX), pk.Float(cursorY), pk.Float(cursorZ),
		pk.Boolean(insideBlock),
	))
}

// SelectItem used to change the slot selection in hotbar.
// slot should from 0 to 8
func (c *Client) SelectItem(slot int) error {
	if slot < 0 || slot > 8 {
		return errors.New("invalid slot: " + strconv.Itoa(slot))
	}

	return c.conn.WritePacket(pk.Marshal(
		data.HeldItemChangeServerbound,
		pk.Short(slot),
	))
}

// PickItem used to swap out an empty space on the hotbar with the item in the given inventory slot.
// The Notchain client uses this for pick block functionality (middle click) to retrieve items from the inventory.
//
// The server will first search the player's hotbar for an empty slot,
// starting from the current slot and looping around to the slot before it.
// If there are no empty slots, it will start a second search from the
// current slot and find the first slot that does not contain an enchanted item.
// If there still are no slots that meet that criteria, then the server will
// use the currently selected slot. After finding the appropriate slot,
// the server swaps the items and then change player's selected slot (cause the HeldItemChange event).
func (c *Client) PickItem(slot int) error {
	return c.conn.WritePacket(pk.Marshal(
		data.PickItem,
		pk.VarInt(slot),
	))
}

func (c *Client) playerAction(status, locX, locY, locZ, face int) error {
	return c.conn.WritePacket(pk.Marshal(
		data.PlayerDigging,
		pk.VarInt(status),
		pk.Position{X: locX, Y: locY, Z: locZ},
		pk.Byte(face),
	))
}

// Dig used to start, end or cancel a digging
// status is 0 for start digging, 1 for cancel and 2 if client think it done.
// To digging a block without cancel, use status 0 and 2 once each.
func (c *Client) Dig(status, locX, locY, locZ, face int) error {
	return c.playerAction(status, locX, locY, locZ, face)
}

// DropItemStack drop the entire selected stack
func (c *Client) DropItemStack() error {
	return c.playerAction(3, 0, 0, 0, 0)
}

// DropItem drop one item in selected stack
func (c *Client) DropItem() error {
	return c.playerAction(4, 0, 0, 0, 0)
}

// UseItemEnd used to finish UseItem, like eating food, pulling back bows.
func (c *Client) UseItemEnd() error {
	return c.playerAction(5, 0, 0, 0, 0)
}

// SwapItem used to swap the items in hands.
func (c *Client) SwapItem() error {
	return c.playerAction(6, 0, 0, 0, 0)
}

// Disconnect disconnect the server.
// Server will close the connection.
func (c *Client) Disconnect() error {
	return c.conn.Close()
}

// SendPacket send the packet to server.
func (c *Client) SendPacket(packet pk.Packet) error {
	return c.conn.WritePacket(packet)
}
