-- Constants
local size_min = 20 / 100 -- minimum size, prescaled
local size_max = 59 / 100 -- maximum size, prescaled
local delta_size = (size_max - size_min) / 10 -- Size change for each XP size level
local max_orb_age = 300 -- seconds
local gravity = vector.new(0, -((tonumber(minetest.settings:get("movement_gravity"))) or 9.81), 0)

local size_to_xp = {
	-- min and max XP amount for a given size
	{-32768,     2}, --  1
	{     3,     6}, --  2
	{     7,    16}, --  3
	{    17,    36}, --  4
	{    37,    72}, --  5
	{    73,   148}, --  6
	{   149,   306}, --  7
	{   307,   616}, --  8
	{   617,  1236}, --  9
	{  1237,  2476}, -- 10
	{  2477, 32767}  -- 11
}

local function xp_to_size(xp)
	xp = xp or 0

	-- Find the size for the xp amount
	for i=1,11 do
		local bucket = size_to_xp[i]
		if xp >= bucket[1] and xp <= bucket[2] then
			return (i - 1) * delta_size + size_min
		end
	end

	-- Fallback is the minimum size
	return size_min
end

local function xp_step(self, dtime)
	--if item set to be collected then only execute go to player
	if self.collected == true then
		if not self.collector then
			self.collected = false
			return
		end

		local collector = minetest.get_player_by_name(self.collector)
		if collector and collector:get_hp() > 0 and vector.distance(self.object:get_pos(),collector:get_pos()) < 7.25 then
			self.object:set_acceleration(vector.new(0,0,0))
			self.disable_physics(self)
			--get the variables
			local pos = self.object:get_pos()
			local pos2 = collector:get_pos()

			local player_velocity = collector:get_velocity() or collector:get_player_velocity()

			pos2.y = pos2.y + 0.8

			local direction = vector.direction(pos,pos2)
			local distance = vector.distance(pos2,pos)
			local multiplier = distance
			if multiplier < 1 then
				multiplier = 1
			end
			local currentvel = self.object:get_velocity()

			if distance > 1 then
				multiplier = 20 - distance
				local velocity = vector.multiply(direction, multiplier)
				local acceleration = vector.new(velocity.x - currentvel.x, velocity.y - currentvel.y, velocity.z - currentvel.z)
				self.object:add_velocity(vector.add(acceleration, player_velocity))
			elseif distance < 0.8 then
				mcl_experience.add_xp(collector, self._xp)
				self.object:remove()
			end
			return
		else
			self.collector = nil
			self:enable_physics()
		end
	end

	-- Age orbs
	self.age = self.age + dtime
	if self.age > max_orb_age then
		self.object:remove()
		return
	end

	local pos = self.object:get_pos()
	if not pos then return end

	-- Get the node directly below the XP orb
	local node = minetest.get_node_or_nil({
		x = pos.x,
		y = pos.y - 0.25, -- Orb collision box is +/-0.2, so go a bit below that
		z = pos.z
	})

	-- Remove nodes in 'ignore'
	if node and node.name == "ignore" then
		self.object:remove()
		return
	end

	if not self.physical_state then
		return -- Don't do anything
	end

	-- Slide on slippery nodes
	local vel = self.object:get_velocity()
	local def = node and minetest.registered_nodes[node.name]
	local is_moving = (def and not def.walkable) or
		vel.x ~= 0 or vel.y ~= 0 or vel.z ~= 0
	local is_slippery = false

	if def and def.walkable then
		local slippery = minetest.get_item_group(node.name, "slippery")
		is_slippery = slippery ~= 0
		if is_slippery and (math.abs(vel.x) > 0.2 or math.abs(vel.z) > 0.2) then
			-- Horizontal deceleration
			local slip_factor = 4.0 / (slippery + 4)
			self.object:set_acceleration({
				x = -vel.x * slip_factor,
				y = 0,
				z = -vel.z * slip_factor
			})
		elseif vel.y == 0 then
			is_moving = false
		end
	end

	if self.moving_state == is_moving and self.slippery_state == is_slippery then
		-- Do not update anything until the moving state changes
		return
	end

	self.moving_state = is_moving
	self.slippery_state = is_slippery

	if is_moving then
		self.object:set_acceleration(gravity)
	else
		self.object:set_acceleration({x = 0, y = 0, z = 0})
		self.object:set_velocity({x = 0, y = 0, z = 0})
	end
end

minetest.register_entity("mcl_experience:orb", {
	initial_properties = {
		hp_max = 1,
		physical = true,
		collide_with_objects = false,
		collisionbox = {-0.2, -0.2, -0.2, 0.2, 0.2, 0.2},
		visual = "sprite",
		visual_size = {x = 0.4, y = 0.4},
		textures = {"mcl_experience_orb.png"},
		spritediv = {x = 1, y = 14},
		initial_sprite_basepos = {x = 0, y = 0},
		is_visible = true,
		pointable = false,
	},
	moving_state = true,
	slippery_state = false,
	physical_state = true,
	-- Item expiry
	age = 0,
	-- Pushing item out of solid nodes
	force_out = nil,
	force_out_start = nil,
	--Collection Variables
	collectable = false,
	try_timer = 0,
	collected = false,
	delete_timer = 0,
	radius = 4,

	on_activate = function(self, staticdata, dtime_s)
		self.object:set_velocity(vector.new(
			math.random(-2,2)*math.random(),
			math.random(2,5),
			math.random(-2,2)*math.random()
		))
		self.object:set_armor_groups({immortal = 1})
		self.object:set_velocity({x = 0, y = 2, z = 0})
		self.object:set_acceleration(gravity)

		-- Assign 0 xp in case the entity was persisted even though it should not have been (static_save = false)
		-- This was a minetest bug for a while: https://github.com/minetest/minetest/issues/14420
		local xp = tonumber(staticdata) or 0
		self._xp = xp
		local size = xp_to_size(xp)

		self.object:set_properties({
			visual_size = {x = size, y = size},
			glow = 14,
		})
		self.object:set_sprite({x=1,y=math.random(1,14)}, 14, 0.05, false)
	end,
	get_staticdata = function(self)
		return tostring(self._xp or 0)
	end,

	enable_physics = function(self)
		if not self.physical_state then
			self.physical_state = true
			self.object:set_properties({physical = true})
			self.object:set_velocity({x=0, y=0, z=0})
			self.object:set_acceleration(gravity)
		end
	end,

	disable_physics = function(self)
		if self.physical_state then
			self.physical_state = false
			self.object:set_properties({physical = false})
			self.object:set_velocity({x=0, y=0, z=0})
			self.object:set_acceleration({x=0, y=0, z=0})
		end
	end,
	on_step = function(self, dtime)
		xp_step(self, dtime)
	end,
})