-- Global namespace for functions

mcl_fire = {}

local S = minetest.get_translator("mcl_fire")

--
-- Items
--

-- Flame nodes

local fire_help = S("Fire is a damaging and destructive but short-lived kind of block. It will destroy and spread towards near flammable blocks, but fire will disappear when there is nothing to burn left. It will be extinguished by nearby water and rain. Fire can be destroyed safely by punching it, but it is hurtful if you stand directly in it. If a fire is started above netherrack or a magma block, it will immediately turn into an eternal fire.")
local eternal_fire_help = S("Eternal fire is a damaging and destructive block. It will create fire around it when flammable blocks are nearby. Eternal fire can be extinguished by punches and nearby water blocks. Other than (normal) fire, eternal fire does not get extinguished on its own and also continues to burn under rain. Punching eternal fire is safe, but it hurts if you stand inside.")
local fire_death_messages = {
	S("%s has been cooked crisp."),
	S("%s felt the burn."),
	S("%s died in the flames."),
	S("%s died in a fire."),
}

minetest.register_node("mcl_fire:fire", {
	description = S("Fire"),
	_doc_items_longdesc = fire_help,
	drawtype = "firelike",
	tiles = {
		{
			name = "fire_basic_flame_animated.png",
			animation = {
				type = "vertical_frames",
				aspect_w = 16,
				aspect_h = 16,
				length = 1
			},
		},
	},
	inventory_image = "fire_basic_flame.png",
	paramtype = "light",
	-- Real light level: 15 (but Minetest caps at 14)
	light_source = 14,
	walkable = false,
	buildable_to = true,
	sunlight_propagates = true,
	damage_per_second = 1,
	_mcl_node_death_message = fire_death_messages,
	groups = {fire = 1, dig_immediate = 3, not_in_creative_inventory = 1, dig_by_piston=1, destroys_items=1 },
	floodable = true,
	on_flood = function(pos, oldnode, newnode)
		if minetest.get_item_group(newnode.name, "water") ~= 0 then
			minetest.sound_play("fire_extinguish_flame", {pos = pos, gain = 0.25, max_hear_distance = 16})
		end
	end,
	on_timer = function(pos)
		local airs = minetest.find_nodes_in_area({x=pos.x-1, y=pos.y-1, z=pos.z-1}, {x=pos.x+1, y=pos.y+4, z=pos.z+1}, {"air"})
		if #airs == 0 then
			minetest.remove_node(pos)
			return
		end
		local burned = false
		if math.random(1,2) == 1 then
			while #airs > 0 do
				local r = math.random(1, #airs)
				if minetest.find_node_near(airs[r], 1, {"group:flammable"}) then
					minetest.set_node(airs[r], {name="mcl_fire:fire"})
					burned = true
					break
				else
					table.remove(airs, r)
				end
			end
		end
		if not burned then
			if math.random(1,3) == 1 then
				minetest.remove_node(pos)
				return
			end
		end
		-- Restart timer
		minetest.get_node_timer(pos):start(math.random(3, 7))
	end,
	drop = "",
	sounds = {},
	-- Turn into eternal fire on special blocks, light Nether portal (if possible), start burning timer
	on_construct = function(pos)
		local bpos = {x=pos.x, y=pos.y-1, z=pos.z}
		local under = minetest.get_node(bpos).name

		local dim = mcl_worlds.pos_to_dimension(bpos)
		if under == "mcl_nether:magma" or under == "mcl_nether:netherrack" or (under == "mcl_core:bedrock" and dim == "end") then
			minetest.swap_node(pos, {name = "mcl_fire:eternal_fire"})
		end

		if minetest.get_modpath("mcl_portals") then
			mcl_portals.light_nether_portal(pos)
		end

		minetest.get_node_timer(pos):start(math.random(3, 7))
	end,
	_mcl_blast_resistance = 0,
})

minetest.register_node("mcl_fire:eternal_fire", {
	description = S("Eternal Fire"),
	_doc_items_longdesc = eternal_fire_help,
	drawtype = "firelike",
	tiles = {
		{
			name = "fire_basic_flame_animated.png",
			animation = {
				type = "vertical_frames",
				aspect_w = 16,
				aspect_h = 16,
				length = 1
			},
		},
	},
	inventory_image = "fire_basic_flame.png",
	paramtype = "light",
	-- Real light level: 15 (but Minetest caps at 14)
	light_source = 14,
	walkable = false,
	buildable_to = true,
	sunlight_propagates = true,
	damage_per_second = 1,
	_mcl_node_death_message = fire_death_messages,
	groups = {fire = 1, dig_immediate = 3, not_in_creative_inventory = 1, dig_by_piston = 1, destroys_items = 1},
	floodable = true,
	on_flood = function(pos, oldnode, newnode)
		if minetest.get_item_group(newnode.name, "water") ~= 0 then
			minetest.sound_play("fire_extinguish_flame", {pos = pos, gain = 0.25, max_hear_distance = 16})
		end
	end,
	on_timer = function(pos)
		local airs = minetest.find_nodes_in_area({x=pos.x-1, y=pos.y-1, z=pos.z-1}, {x=pos.x+1, y=pos.y+4, z=pos.z+1}, {"air"})
		while #airs > 0 do
			local r = math.random(1, #airs)
			if minetest.find_node_near(airs[r], 1, {"group:flammable"}) then
				minetest.set_node(airs[r], {name="mcl_fire:fire"})
				break
			else
				table.remove(airs, r)
			end
		end
		-- Restart timer
		minetest.get_node_timer(pos):start(math.random(3, 7))
	end,
	-- Start burning timer and light Nether portal (if possible)
	on_construct = function(pos)
		minetest.get_node_timer(pos):start(math.random(3, 7))

		if minetest.get_modpath("mcl_portals") then
			mcl_portals.light_nether_portal(pos)
		end
	end,
	sounds = {},
	drop = "",
	_mcl_blast_resistance = 0,
})

-- Also make lava set fire to air blocks above
minetest.override_item("mcl_core:lava_source", {
	on_timer = function(pos)
		local function try_ignite(airs)
			while #airs > 0 do
				local r = math.random(1, #airs)
				if minetest.find_node_near(airs[r], 1, {"group:flammable", "group:flammable_lava"}) then
					minetest.set_node(airs[r], {name="mcl_fire:fire"})
					return true
				else
					table.remove(airs, r)
				end
			end
			return false
		end
		local airs1 = minetest.find_nodes_in_area({x=pos.x-1, y=pos.y+1, z=pos.z-1}, {x=pos.x+1, y=pos.y+1, z=pos.z+1}, {"air"})
		local ok = try_ignite(airs1)
		if not ok then
			local airs2 = minetest.find_nodes_in_area({x=pos.x-2, y=pos.y+2, z=pos.z-2}, {x=pos.x+2, y=pos.y+2, z=pos.z+2}, {"air"})
			try_ignite(airs2)
		end

		-- Restart timer
		minetest.get_node_timer(pos):start(math.random(5, 10))
	end,
	on_construct = function(pos)
		minetest.get_node_timer(pos):start(math.random(5, 10))
	end
})

--
-- Sound
--

local flame_sound = minetest.settings:get_bool("flame_sound")
if flame_sound == nil then
	-- Enable if no setting present
	flame_sound = true
end

if flame_sound then

	local handles = {}
	local timer = 0

	-- Parameters

	local radius = 8 -- Flame node search radius around player
	local cycle = 3 -- Cycle time for sound updates

	-- Update sound for player

	function mcl_fire.update_player_sound(player)
		local player_name = player:get_player_name()
		-- Search for flame nodes in radius around player
		local ppos = player:get_pos()
		local areamin = vector.subtract(ppos, radius)
		local areamax = vector.add(ppos, radius)
		local fpos, num = minetest.find_nodes_in_area(
			areamin,
			areamax,
			{"mcl_fire:fire", "mcl_fire:eternal_fire"}
		)
		-- Total number of flames in radius
		local flames = (num["mcl_fire:fire"] or 0) +
			(num["mcl_fire:eternal_fire"] or 0)
		-- Stop previous sound
		if handles[player_name] then
			minetest.sound_stop(handles[player_name])
			handles[player_name] = nil
		end
		-- If flames
		if flames > 0 then
			-- Find centre of flame positions
			local fposmid = fpos[1]
			-- If more than 1 flame
			if #fpos > 1 then
				local fposmin = areamax
				local fposmax = areamin
				for i = 1, #fpos do
					local fposi = fpos[i]
					if fposi.x > fposmax.x then
						fposmax.x = fposi.x
					end
					if fposi.y > fposmax.y then
						fposmax.y = fposi.y
					end
					if fposi.z > fposmax.z then
						fposmax.z = fposi.z
					end
					if fposi.x < fposmin.x then
						fposmin.x = fposi.x
					end
					if fposi.y < fposmin.y then
						fposmin.y = fposi.y
					end
					if fposi.z < fposmin.z then
						fposmin.z = fposi.z
					end
				end
				fposmid = vector.divide(vector.add(fposmin, fposmax), 2)
			end
			-- Play sound
			local handle = minetest.sound_play(
				"fire_fire",
				{
					pos = fposmid,
					to_player = player_name,
					gain = math.min(0.06 * (1 + flames * 0.125), 0.18),
					max_hear_distance = 32,
					loop = true, -- In case of lag
				}
			)
			-- Store sound handle for this player
			if handle then
				handles[player_name] = handle
			end
		end
	end

	-- Cycle for updating players sounds

	minetest.register_globalstep(function(dtime)
		timer = timer + dtime
		if timer < cycle then
			return
		end

		timer = 0
		local players = minetest.get_connected_players()
		for n = 1, #players do
			mcl_fire.update_player_sound(players[n])
		end
	end)

	-- Stop sound and clear handle on player leave

	minetest.register_on_leaveplayer(function(player)
		local player_name = player:get_player_name()
		if handles[player_name] then
			minetest.sound_stop(handles[player_name])
			handles[player_name] = nil
		end
	end)
end


--
-- ABMs
--

-- Extinguish all flames quickly with water and such

minetest.register_abm({
	label = "Extinguish flame",
	nodenames = {"mcl_fire:fire", "mcl_fire:eternal_fire"},
	neighbors = {"group:puts_out_fire"},
	interval = 3,
	chance = 1,
	catch_up = false,
	action = function(pos, node, active_object_count, active_object_count_wider)
		minetest.remove_node(pos)
		minetest.sound_play("fire_extinguish_flame",
			{pos = pos, max_hear_distance = 16, gain = 0.15})
	end,
})


-- Enable the following ABMs according to 'enable fire' setting

local fire_enabled = minetest.settings:get_bool("enable_fire")
if fire_enabled == nil then
	-- New setting not specified, check for old setting.
	-- If old setting is also not specified, 'not nil' is true.
	fire_enabled = not minetest.settings:get_bool("disable_fire")
end

if not fire_enabled then

	-- Remove fire only if fire disabled
	minetest.register_abm({
		label = "Remove disabled fire",
		nodenames = {"mcl_fire:fire"},
		interval = 7,
		chance = 1,
		catch_up = false,
		action = minetest.remove_node,
	})

	-- Set fire to air nodes (inverse pyramid pattern) above lava source
	minetest.register_abm({
		label = "Ignite fire by lava",
		nodenames = {"mcl_core:lava_source"},
		interval = 7,
		chance = 2,
		catch_up = false,
		action = function(pos)
			local function try_ignite(airs)
				while #airs > 0 do
					local r = math.random(1, #airs)
					if minetest.find_node_near(airs[r], 1, {"group:flammable", "group:flammable_lava"}) then
						minetest.set_node(airs[r], {name="mcl_fire:fire"})
						return true
					else
						table.remove(airs, r)
					end
				end
				return false
			end
			local airs1 = minetest.find_nodes_in_area({x=pos.x-1, y=pos.y+1, z=pos.z-1}, {x=pos.x+1, y=pos.y+1, z=pos.z+1}, {"air"})
			local ok = try_ignite(airs1)
			if not ok then
				local airs2 = minetest.find_nodes_in_area({x=pos.x-2, y=pos.y+2, z=pos.z-2}, {x=pos.x+2, y=pos.y+2, z=pos.z+2}, {"air"})
				try_ignite(airs2)
			end
		end,
	})

else -- Fire enabled

	-- Turn flammable nodes around fire into fire
	minetest.register_abm({
		label = "Remove flammable nodes",
		nodenames = {"group:fire"},
		neighbors = {"group:flammable"},
		interval = 5,
		chance = 18,
		catch_up = false,
		action = function(pos, node, active_object_count, active_object_count_wider)
			local p = minetest.find_node_near(pos, 1, {"group:flammable"})
			if p then
				local flammable_node = minetest.get_node(p)
				local def = minetest.registered_nodes[flammable_node.name]
				if def.on_burn then
					def.on_burn(p)
				else
					minetest.set_node(p, {name="mcl_fire:fire"})
					minetest.check_for_falling(p)
				end
			end
		end,
	})

end

-- Set pointed_thing on (normal) fire.
-- * pointed_thing: Pointed thing to ignite
-- * player: Player who sets fire or nil if nobody
mcl_fire.set_fire = function(pointed_thing, player)
	local pname
	if player == nil then
		pname = ""
	else
		pname = player:get_player_name()
	end
	local n = minetest.get_node(pointed_thing.above)
	if minetest.is_protected(pointed_thing.above, pname) then
		minetest.record_protection_violation(pointed_thing.above, pname)
		return
	end
	if n.name == "air" then
		minetest.add_node(pointed_thing.above, {name="mcl_fire:fire"})
	end
end

minetest.register_alias("mcl_fire:basic_flame", "mcl_fire:fire")
minetest.register_alias("fire:basic_flame", "mcl_fire:fire")
minetest.register_alias("fire:permanent_flame", "mcl_fire:eternal_flame")

dofile(minetest.get_modpath(minetest.get_current_modname()).."/flint_and_steel.lua")
dofile(minetest.get_modpath(minetest.get_current_modname()).."/fire_charge.lua")