package entity

import (
	"fmt"
	"math"
)

// ExperienceManager manages experience and levels for entities, and provides functions to add, remove, and calculate
// experience needed for upcoming levels.
type ExperienceManager struct {
	experience int
	dec        float64
}

// NewExperienceManager returns a new ExperienceManager with no experience.
func NewExperienceManager() *ExperienceManager {
	return &ExperienceManager{}
}

// Experience returns the amount of experience the manager currently has.
func (e *ExperienceManager) Experience() int {
	return e.experience
}

// Add adds experience to the total experience and recalculates the level and progress if necessary. Passing a negative
// value is valid. If the new experience would otherwise drop below 0, it is set to 0.
func (e *ExperienceManager) Add(amount int) (level int, progress float64) {
	if e.experience += amount; e.experience < 0 {
		e.Reset()
	}
	return progressFromExperience(e.total())
}

// total returns the total amount of experience including the extra decimals provided for more accuracy.
func (e *ExperienceManager) total() float64 {
	return float64(e.experience) + e.dec
}

// Level returns the current experience level.
func (e *ExperienceManager) Level() int {
	level, _ := progressFromExperience(e.total())
	return level
}

// SetLevel sets the level of the manager.
func (e *ExperienceManager) SetLevel(level int) {
	if level < 0 || level > math.MaxInt32 {
		panic(fmt.Sprintf("level must be between 0 and 2,147,483,647, got %v", level))
	}
	_, progress := progressFromExperience(e.total())
	e.experience = experienceForLevels(level) + int(float64(experienceForLevel(level))*progress)
}

// Progress returns the progress towards the next level.
func (e *ExperienceManager) Progress() float64 {
	_, progress := progressFromExperience(e.total())
	return progress
}

// SetProgress sets the progress of the manager.
func (e *ExperienceManager) SetProgress(progress float64) {
	if progress < 0 || progress > 1 {
		panic(fmt.Sprintf("progress must be between 0 and 1, got %f", progress))
	}
	currentLevel, _ := progressFromExperience(e.total())
	progressExp := float64(experienceForLevel(currentLevel)) * progress
	e.experience = experienceForLevels(currentLevel) + int(progressExp)
	e.dec = progressExp - math.Trunc(progressExp)
}

// Reset resets the total experience, level, and progress of the manager to zero.
func (e *ExperienceManager) Reset() {
	e.experience, e.dec = 0, 0
}

// progressFromExperience returns the level and progress from the total experience given.
func progressFromExperience(experience float64) (level int, progress float64) {
	var a, b, c float64

	switch {
	case experience <= float64(experienceForLevels(16)):
		a, b = 1.0, 6.0
	case experience <= float64(experienceForLevels(31)):
		a, b, c = 2.5, -40.5, 360.0
	default:
		a, b, c = 4.5, -162.5, 2220.0
	}

	var sol float64
	if d := b*b - 4*a*(c-experience); d > 0 {
		s := math.Sqrt(d)
		sol = math.Max((-b+s)/(2*a), (-b-s)/(2*a))
	} else if d == 0 {
		sol = -b / (2 * a)
	}
	return int(sol), sol - math.Trunc(sol)
}

// experienceForLevels calculates the amount of experience needed in total to reach a certain level.
func experienceForLevels(level int) int {
	if level <= 16 {
		return level*level + level*6
	} else if level <= 31 {
		return int(float64(level*level)*2.5 - 40.5*float64(level) + 360)
	}
	return int(float64(level*level)*4.5 - 162.5*float64(level) + 2220)
}

// experienceForLevel returns the amount experience needed to reach level + 1.
func experienceForLevel(level int) int {
	if level <= 15 {
		return 2*level + 7
	} else if level <= 30 {
		return 5*level - 38
	}
	return 9*level - 158
}
