local enable_damage = minetest.settings:get_bool("enable_damage")

-- TODO: when < minetest 5.9 isn't supported anymore, remove this variable check and replace all occurences of [hud_elem_type_field] with type
local hud_elem_type_field = "type"
if not minetest.features.hud_def_type_field then
	hud_elem_type_field = "hud_elem_type"
end

function mcl_burning.get_storage(obj)
	return obj:is_player() and mcl_burning.storage[obj] or obj:get_luaentity()
end

function mcl_burning.is_burning(obj)
	local storage = mcl_burning.get_storage(obj)
	if storage then
		return mcl_burning.get_storage(obj).burn_time
	else
		return false
	end
end

function mcl_burning.is_affected_by_rain(obj)
	return mcl_weather.rain.raining and mcl_weather.is_outdoor(obj:get_pos())
end

function mcl_burning.get_collisionbox(obj, smaller, storage)
	local cache = storage.collisionbox_cache
	if cache then
		local box = cache[smaller and 2 or 1]
		return box[1], box[2]
	else
		local box = obj:get_properties().collisionbox
		local minp, maxp = vector.new(box[1], box[2], box[3]), vector.new(box[4], box[5], box[6])
		local s_vec = vector.new(0.1, 0.1, 0.1)
		local s_minp = vector.add(minp, s_vec)
		local s_maxp = vector.subtract(maxp, s_vec)
		storage.collisionbox_cache = {{minp, maxp}, {s_minp, s_maxp}}
		return minp, maxp
	end
end

function mcl_burning.get_touching_nodes(obj, nodenames, storage)
	local pos = obj:get_pos()
	local minp, maxp = mcl_burning.get_collisionbox(obj, true, storage)
	local nodes = minetest.find_nodes_in_area(vector.add(pos, minp), vector.add(pos, maxp), nodenames)
	return nodes
end

local function get_player_copper_burn_reduction(player)
	if not player or not player.is_player or not player:is_player() then
		return 0
	end
	local meta = player:get_meta()
	if not meta then
		return 0
	end
	local reduction = meta:get_float("mcl_armor:copper_burn_time_reduction") or 0
	return math.max(0, math.min(0.25, reduction))
end

-- Manages the fire animation on a burning player's HUD
--
-- Parameters:
--   player - a valid player object;
--
-- If the player already has a fire HUD, updates the burning animation.
-- If the fire does not have a fire HUD, initializes the HUD.
--
function mcl_burning.update_hud(player)
	local animation_frames = tonumber(minetest.settings:get("fire_animation_frames")) or 8
	local hud_flame_animated = "mcl_burning_hud_flame_animated.png^[opacity:180^[verticalframe:" .. animation_frames .. ":"

	local storage = mcl_burning.get_storage(player)
	if not storage.fire_hud_id then
		storage.animation_frame = 1
		storage.fire_hud_id = player:hud_add({
			[hud_elem_type_field] = "image",
			position = {x = 0.5, y = 0.5},
			scale = {x = -100, y = -100},
			text = hud_flame_animated .. storage.animation_frame,
			z_index = 1000,
		})
	else
		storage.animation_frame = storage.animation_frame + 1
		if storage.animation_frame > animation_frames - 1 then
			storage.animation_frame = 0
		end
		player:hud_change(storage.fire_hud_id, "text", hud_flame_animated .. storage.animation_frame)
	end
end

-- Sets and object state as burning and adds a fire animation to the object.
--
-- Parameters:
--   obj - may be a player or a lua_entity;
--   burn_time - sets the object's burn duration;
--
-- If obj is a player, adds a fire animation to the HUD, if obj is a
-- lua_entity, adds an animated fire entity to obj.
-- The effective burn duration is modified by obj's armor protection.
-- If obj was already burning, its burn duration is updated if the current
-- duration is less than burn_time.
-- If obj is dead, fireproof or enable_damage is disabled, this function does nothing.
--
function mcl_burning.set_on_fire(obj, burn_time)
	if obj:get_hp() < 0 then
		return
	end

	local luaentity = obj:get_luaentity()
	if luaentity and luaentity.fire_resistant then
		return
	end

	if obj:is_player() and not enable_damage then
		return
	end

	if obj:is_player() then
		local burn_time_reduction = get_player_copper_burn_reduction(obj)
		if burn_time_reduction > 0 then
			burn_time = burn_time * (1 - burn_time_reduction)
		end
	end

	local storage = mcl_burning.get_storage(obj)
	if storage.burn_time then
		if burn_time > storage.burn_time then
			storage.burn_time = burn_time
		end
		return
	end
	storage.burn_time = burn_time
	storage.fire_damage_timer = 0

	local minp, maxp = mcl_burning.get_collisionbox(obj, false, storage)
	local size = vector.subtract(maxp, minp)
	size = vector.multiply(size, vector.new(1.1, 1.2, 1.1))
	size = vector.divide(size, obj:get_properties().visual_size)

	local fire_entity = minetest.add_entity(obj:get_pos(), "mcl_burning:fire")
	if fire_entity and fire_entity:get_pos() then
		fire_entity:set_properties({visual_size = size})
		fire_entity:set_attach(obj, "", vector.new(0, size.y * 5, 0), vector.new(0, 0, 0))
	end

	if obj:is_player() then
		mcl_burning.update_hud(obj)
	end
end

function mcl_burning.extinguish(obj)
	if not obj:get_pos() then return end
	if mcl_burning.is_burning(obj) then
		local storage = mcl_burning.get_storage(obj)
		if obj:is_player() then
			if storage.fire_hud_id then
				obj:hud_remove(storage.fire_hud_id)
			end
			mcl_burning.storage[obj] = {}
		else
			storage.burn_time = nil
			storage.fire_damage_timer = nil
		end
	end
end

function mcl_burning.tick(obj, dtime, storage)
	if storage.burn_time then
		storage.burn_time = storage.burn_time - dtime

		if storage.burn_time <= 0 or mcl_burning.is_affected_by_rain(obj) or #mcl_burning.get_touching_nodes(obj, "group:puts_out_fire", storage) > 0 then
			mcl_burning.extinguish(obj)
			return true
		else
			storage.fire_damage_timer = storage.fire_damage_timer + dtime
			local fire_tick_interval = 1
			if obj:is_player() then
				local burn_time_reduction = get_player_copper_burn_reduction(obj)
				if burn_time_reduction > 0 then
					-- While submerged in lava, slow fire damage tick rate.
					local in_lava = #mcl_burning.get_touching_nodes(obj, "group:lava", storage) > 0
					if in_lava then
						fire_tick_interval = 1 + burn_time_reduction
					end
				end
			end

			if storage.fire_damage_timer >= fire_tick_interval then
				storage.fire_damage_timer = storage.fire_damage_timer - fire_tick_interval

				local luaentity = obj:get_luaentity()

				if not luaentity or not luaentity.fire_damage_resistant then
					local mcl_reason =  { type = "on_fire" }
					mcl_damage.finish_reason(mcl_reason)
					mcl_util.deal_damage(obj, 1, mcl_reason)
				end
			end
		end
	end
end
