package ru.dbotthepony.mc.otm.core.util

import it.unimi.dsi.fastutil.longs.LongArrayList
import net.minecraft.world.entity.player.Player

private val levelsTable = LongArrayList()
private val totalLevelsTable = LongArrayList().also { it.add(0L) }

private fun calculate(level: Int): Long {
	return if (level >= 30) {
		112L + (level - 30) * 9L
	} else {
		if (level >= 15) 37L + (level - 15) * 5L else 7L + level * 2L
	}
}

/**
 * Experience points required for leveling **up from** specified [level].
 *
 * Works exactly like [Player.getXpNeededForNextLevel],
 * meaning that to achieve `level + 1` player need to acquire [getXpRequiredForLevelUp] experience points
 */
fun getXpRequiredForLevelUp(level: Int): Long {
	require(level >= 0) { "Negative level: $level" }

	if (levelsTable.size <= level) {
		synchronized(levelsTable) {
			while (levelsTable.size <= level) {
				levelsTable.add(calculate(levelsTable.size))
			}
		}
	}

	return levelsTable.getLong(level)
}

/**
 * Experience points required for achieving **exact** [level] specified.
 *
 * Example:
 * * 0 level -> 0 points (because player starts at level 0)
 * * 1 level -> [getXpRequiredForLevelUp]`(0)`
 * * 2 level -> [getXpRequiredForLevelUp]`(0)` + [getXpRequiredForLevelUp]`(1)`
 * * and so on
 */
fun getTotalXpRequiredForLevel(level: Int): Long {
	require(level >= 0) { "Negative level: $level" }

	if (totalLevelsTable.size <= level) {
		synchronized(totalLevelsTable) {
			while (totalLevelsTable.size <= level) {
				totalLevelsTable.add(totalLevelsTable.getLong(totalLevelsTable.size - 1) + getXpRequiredForLevelUp(totalLevelsTable.size - 1))
			}
		}
	}

	return totalLevelsTable.getLong(level)
}

/**
 * Determines current level from [experience] points provided
 */
fun getLevelFromXp(experience: Long): Int {
	require(experience >= 0L) { "Negative experience: $experience" }

	if (totalLevelsTable.getLong(totalLevelsTable.size - 1) < experience) {
		synchronized(totalLevelsTable) {
			while (totalLevelsTable.getLong(totalLevelsTable.size - 1) < experience) {
				totalLevelsTable.add(totalLevelsTable.getLong(totalLevelsTable.size - 1) + getXpRequiredForLevelUp(totalLevelsTable.size - 1))
			}
		}
	}

	var bottom = 0
	var top = totalLevelsTable.size - 1

	while (top - bottom > 4) {
		val middle = bottom + (top - bottom) / 2
		val middleValue = totalLevelsTable.getLong(middle)

		if (middleValue == experience) {
			return middle
		} else if (middleValue > experience) {
			top = middle - 1
		} else {
			bottom = middle + 1
		}
	}

	for (i in bottom + 1 .. top) {
		if (totalLevelsTable.getLong(i) > experience) {
			return i - 1
		}
	}

	return top
}
