package item

import (
	"github.com/df-mc/dragonfly/server/item/potion"
	"github.com/df-mc/dragonfly/server/world"
	"github.com/df-mc/dragonfly/server/world/sound"
	"math"
	"time"
)

// Bow is a ranged weapon that fires arrows.
type Bow struct{}

// MaxCount always returns 1.
func (Bow) MaxCount() int {
	return 1
}

// DurabilityInfo ...
func (Bow) DurabilityInfo() DurabilityInfo {
	return DurabilityInfo{
		MaxDurability: 385,
		BrokenItem:    simpleItem(Stack{}),
	}
}

// FuelInfo ...
func (Bow) FuelInfo() FuelInfo {
	return newFuelInfo(time.Second * 10)
}

// Release ...
func (Bow) Release(releaser Releaser, tx *world.Tx, ctx *UseContext, duration time.Duration) {
	creative := releaser.GameMode().CreativeInventory()
	ticks := duration.Milliseconds() / 50
	if ticks < 3 {
		// The player must hold the bow for at least three ticks.
		return
	}

	d := float64(ticks) / 20
	force := math.Min((d*d+d*2)/3, 1)
	if force < 0.1 {
		// The force must be at least 0.1.
		return
	}

	arrow, ok := ctx.FirstFunc(func(stack Stack) bool {
		_, ok := stack.Item().(Arrow)
		return ok
	})
	if !ok && !creative {
		// No arrows in inventory and not in creative mode.
		return
	}

	var tip potion.Potion
	if !arrow.Empty() {
		// Arrow is empty if not found in the creative inventory.
		tip = arrow.Item().(Arrow).Tip
	}

	held, _ := releaser.HeldItems()
	damage, punchLevel, burnDuration, consume := 2.0, 0, time.Duration(0), !creative
	for _, enchant := range held.Enchantments() {
		if f, ok := enchant.Type().(interface{ BurnDuration() time.Duration }); ok {
			burnDuration = f.BurnDuration()
		}
		if _, ok := enchant.Type().(interface{ KnockBackMultiplier() float64 }); ok {
			punchLevel = enchant.Level()
		}
		if p, ok := enchant.Type().(interface{ PowerDamage(int) float64 }); ok {
			damage += p.PowerDamage(enchant.Level())
		}
		if i, ok := enchant.Type().(interface{ ConsumesArrows() bool }); ok && !i.ConsumesArrows() {
			consume = false
		}
	}

	create := tx.World().EntityRegistry().Config().Arrow
	opts := world.EntitySpawnOpts{
		Position: eyePosition(releaser),
		Velocity: releaser.Rotation().Vec3().Mul(force * 5),
		Rotation: releaser.Rotation().Neg(),
	}
	projectile := tx.AddEntity(create(opts, world.ArrowSpawnConfig{
		Damage:              damage,
		Owner:               releaser,
		Critical:            force >= 1,
		ObtainArrowOnPickup: !creative && consume,
		PunchLevel:          punchLevel,
		Tip:                 tip,
	}))
	if f, ok := projectile.(interface{ SetOnFire(duration time.Duration) }); ok {
		f.SetOnFire(burnDuration)
	}

	ctx.DamageItem(1)
	if consume {
		ctx.Consume(arrow.Grow(-arrow.Count() + 1))
	}

	tx.PlaySound(releaser.Position(), sound.BowShoot{})
}

// EnchantmentValue ...
func (Bow) EnchantmentValue() int {
	return 1
}

// Requirements returns the required items to release this item.
func (Bow) Requirements() []Stack {
	return []Stack{NewStack(Arrow{}, 1)}
}

// EncodeItem ...
func (Bow) EncodeItem() (name string, meta int16) {
	return "minecraft:bow", 0
}
