package player

import (
	"sync"
)

// hungerManager handles the changes in hunger, exhaustion and saturation of a player.
type hungerManager struct {
	mu              sync.RWMutex
	foodLevel       int
	saturationLevel float64
	exhaustionLevel float64
	foodTick        int
}

// newHungerManager returns a new hunger manager with the default values for food level, saturation level and
// exhaustion level.
func newHungerManager() *hungerManager {
	return &hungerManager{foodLevel: 20, saturationLevel: 5, foodTick: 1}
}

// Food returns the current food level of a player. The level returned is guaranteed to always be between 0
// and 20.
func (m *hungerManager) Food() int {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.foodLevel
}

// SetFood sets the food level of a player. The level passed must be in a range of 0-20. If the level passed
// is negative, the food level will be set to 0. If the level exceeds 20, the food level will be set to 20.
func (m *hungerManager) SetFood(level int) {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.foodLevel = max(min(level, 20), 0)
}

// AddFood adds a number of food points to the current food level of a player.
func (m *hungerManager) AddFood(points int) {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.foodLevel = max(min(m.foodLevel+points, 20), 0)
}

// Reset resets the hunger manager to its default values, identical to those set when creating a new manager
// using newHungerManager.
func (m *hungerManager) Reset() {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.foodLevel = 20
	m.saturationLevel = 5
	m.exhaustionLevel = 0
	m.foodTick = 1
}

// ResetExhaustion resets the player's exhaustion level to 0. It prevents the
// player's food level from decreasing immediately after cancelling food loss.
func (m *hungerManager) resetExhaustion() {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.exhaustionLevel = 0
	m.saturationLevel = 0
	m.foodTick = 1
}

// exhaust exhausts the player by the amount of points passed. If the total exhaustion level exceeds 4, a
// saturation point, or food point, if saturation is 0, will be subtracted.
func (m *hungerManager) exhaust(points float64) {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.exhaustionLevel += points
	for m.exhaustionLevel >= 4 {
		// Maximum exhaustion value is 4, so keep removing one saturation point until the exhaustion level
		// is below 4.
		m.exhaustionLevel -= 4
		m.desaturate()
	}
}

// saturate saturates the player's food and saturation by the amount of points passed. Note that the total
// saturation will never exceed the total food value.
func (m *hungerManager) saturate(food int, saturation float64) {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.foodLevel = max(min(m.foodLevel+food, 20), 0)
	m.saturationLevel = max(min(m.saturationLevel+saturation, float64(m.foodLevel)), 0)
}

// desaturate removes one saturation point from the player. If the saturation level of the player is already
// 0, a point will be subtracted from the food level instead. If that level, too, is already 0, nothing will
// happen.
func (m *hungerManager) desaturate() {
	if m.saturationLevel <= 0 && m.foodLevel != 0 {
		m.foodLevel--
	} else if m.saturationLevel > 0 {
		m.saturationLevel = max(m.saturationLevel-1, 0)
	}
}

// canQuicklyRegenerate checks if the player can quickly regenerate. The function returns true if Food() returns 20
// and the player still has saturation left.
// The rate of regeneration is 1/0.5 seconds.
func (m *hungerManager) canQuicklyRegenerate() bool {
	m.mu.RLock()
	defer m.mu.RUnlock()

	return m.foodLevel == 20 && m.saturationLevel > 0
}

// canRegenerate checks if the player with the amount of food levels in the hunger manager can regenerate.
// The function returns true if Food() returns either 18-20.
// The rate of regeneration is 1/4 seconds.
func (m *hungerManager) canRegenerate() bool {
	return m.Food() >= 18
}

// canSprint returns true if the food level of the player is 7 or higher.
func (m *hungerManager) canSprint() bool {
	return m.Food() > 6
}

// starving checks if the player is currently considered to be starving. True is returned if Food() returns 0.
func (m *hungerManager) starving() bool {
	return m.Food() == 0
}

// StarvationDamageSource is the world.DamageSource passed when a player is
// dealt damage from an empty food bar.
type StarvationDamageSource struct{}

func (StarvationDamageSource) ReducedByArmour() bool     { return false }
func (StarvationDamageSource) ReducedByResistance() bool { return false }
func (StarvationDamageSource) Fire() bool                { return false }
func (StarvationDamageSource) IgnoreTotem() bool         { return false }
