local S = minetest.get_translator("lzr_treasure")
local NS = function(s) return s end

-- Time in seconds until the chest lid entity despawns
local CHEST_LID_LIFETIME = 1.0
-- Time in seconds until the gold block entity (=the thing
-- flying out of the chest on level completion) despawns
local GOLD_BLOCK_ENTITY_LIFETIME = 6.0
-- How fast the gold block entity rotates.
-- Must be a positive number
local GOLD_BLOCK_ENTITY_ROTATE_SPEED = 3.0
-- How many particles to spawn for the gold block entity
-- This is a relative value and will be scaled over the
-- entity's lifetime
local GOLD_BLOCK_ENTITY_PARTICLE_AMOUNT = 150

local victory_job

lzr_treasure = {}
local registered_after_found_treasures = {}
-- Register a function callback(pos, player) that is called
-- when player found a treasure by opening a chest.
-- pos is position of treasure chest that contains treasure.
-- player is player who found it.
lzr_treasure.register_after_found_treasure = function(callback)
	table.insert(registered_after_found_treasures, callback)
end

-- Register treasure blocks --
minetest.register_node("lzr_treasure:gold_block", {
	description = S("Gold Block"),
	tiles = {"lzr_treasure_gold_block.png"},
	-- In Lazarr!, the gold block is only for decoration for the
	-- treasure stash in the main menu ship. It MUST NOT
	-- be used in levels. Use the chests instead to add
	-- gold blocks in levels.
	groups = { breakable = 1, not_in_creative_inventory = 1 },
	sounds = lzr_sounds.node_sound_metal_defaults(),
})

-- Moving chest list; should spawn when the player opens the chest
minetest.register_entity("lzr_treasure:chest_lid", {
	initial_properties = {
		visual = "mesh",
		mesh = "lzr_treasure_chest_lid.obj",
		visual_size = { x=10, y=10, z=10 },
		textures = {
			"lzr_treasure_chest_top.png",
			"lzr_treasure_chest_top.png",
			"lzr_treasure_chest_side.png",
			"lzr_treasure_chest_front.png",
		},
		backface_culling = true,
		static_save = false,
		physical = false,
		collide_with_objects = false,
		selectionbox = {
			-0.5, 2/16, -0.5, 0.5, 0.5, 0.5,
		},
		pointable = false,
	},
	_lifetimer = 0,
	on_activate = function(self)
		self.object:set_armor_groups({immortal=1})
		self.object:set_velocity({x=0,y=3,z=0})
	end,
	on_step = function(self, dtime)
		local vel = self.object:get_velocity()
		if vel.y > 0 then
			vel.y = vel.y * 0.9
			if vel.y < 0.001 then
				vel.y = 0
			end
			self.object:set_velocity(vel)
		end
		self._lifetimer = self._lifetimer + dtime
		if self._lifetimer > CHEST_LID_LIFETIME then
			if self._chest_node_name then
				local cpos = self.object:get_pos()
				minetest.add_particlespawner({
					amount = 16,
					time = 0.001,
					minpos = vector.add(cpos, vector.new(-0.5, 7/16, -0.5)),
					maxpos = vector.add(cpos, vector.new(0.5, 0.5, 0.5)),
					minvel = vector.new(-0.9, -0.1, -0.9),
					maxvel = vector.new(0.9, 0.6, 0.9),
					minacc = vector.new(0, -lzr_globals.GRAVITY, 0),
					maxacc = vector.new(0, -lzr_globals.GRAVITY, 0),
					minsize = 0.6,
					maxsize = 0.6,
					minexptime = 0.5,
					maxexptime = 0.55,
					node = {name = self._chest_node_name},
				})
			end
			self.object:remove()
			return
		end
	end,
})


-- Flying gold block from the chest. Meant to "fly out"
-- of the chest on level completion.
minetest.register_entity("lzr_treasure:gold_block_from_chest", {
	initial_properties = {
		visual = "cube",
		-- This is the size of the "mini gold block" in the
		-- open chest with gold block, minus a tiny value to
		-- work around Z-fighting with empty chest nodes.
		visual_size = { x=14/16-0.01,y=14/16-0.01,z=14/16-0.01 },
		textures = {
			"lzr_treasure_gold_block_entity.png",
			"lzr_treasure_gold_block_entity.png",
			"lzr_treasure_gold_block_entity.png",
			"lzr_treasure_gold_block_entity.png",
			"lzr_treasure_gold_block_entity.png",
			"lzr_treasure_gold_block_entity.png",
		},
		backface_culling = true,
		static_save = false,
		physical = false,
		collide_with_objects = false,
		selectionbox = {
			-7/16, -7/16, -7/16, 7/16, 7/16, 7/16
		},
		pointable = false,
		automatic_rotate = 0,
	},
	_lifetimer = 0,
	_start_y = nil,
	on_activate = function(self)
		self.object:set_armor_groups({immortal=1})
		self.object:set_velocity({x=0,y=3,z=0})
		self.object:set_acceleration({x=0,y=3,z=0})
		-- Continously spawn gold block particles at the bottom of the entity
		minetest.add_particlespawner({
			time = GOLD_BLOCK_ENTITY_LIFETIME,
			amount = GOLD_BLOCK_ENTITY_PARTICLE_AMOUNT * GOLD_BLOCK_ENTITY_LIFETIME,
			time = 0.001,
			minpos = vector.new(-7/16, -7/16, -7/16),
			maxpos = vector.new(7/16, -8/16, 7/16),
			minvel = vector.new(-0.2, -0.2, -0.2),
			maxvel = vector.new(0.2, 0.2, 0.2),
			minsize = 0.5,
			maxsize = 0.5,
			node = {name="lzr_treasure:gold_block"},
			attached = self.object,
		})
		local pos = self.object:get_pos()
		-- Remember to Y height where we spawned in
		self._start_y = pos.y
	end,
	on_step = function(self, dtime)
		self._lifetimer = self._lifetimer + dtime
		-- Remove after the lifetimer expired
		if self._lifetimer > GOLD_BLOCK_ENTITY_LIFETIME then
			self.object:remove()
			return
		end

		-- Start to rotate once we have moved upwards a little
		local props = self.object:get_properties()
		if math.abs(props.automatic_rotate) > 0.1 then
			return
		end
		local pos = self.object:get_pos()
		if self._start_y then
			local ydiff = pos.y - self._start_y
			-- Slightly enough movement after we got out of the chest
			if ydiff > 0.625 then
				-- Rotate clockwise or counter-clockwise,
				-- depending on the gold block's X/Z coords
				local rot = GOLD_BLOCK_ENTITY_ROTATE_SPEED
				local px = math.floor(pos.x)
				local pz = math.floor(pos.z)
				if (px + pz) % 2 == 1 then
					rot = -rot
				end
				self.object:set_properties({automatic_rotate = rot})
			end
		end
	end,
})


-- Register chests --

local register_chest = function(id, def)
	-- Every chest comes in 4 states:
	-- * Locked
	-- * Unlocked
	-- * Open with treasure
	-- * Open and empty

	-- The locked chests break their lock open. One chest variant can also lock itself when in
	-- unlocked state.

	local treasure_id = "gold"
	local sound_open = def.sound_open or "lzr_treasure_chest_open"
	local sound_open_fail = def.sound_open_fail or "lzr_treasure_chest_open_fail"
	local sound_lock_break = def.sound_lock_break or "lzr_treasure_chest_lock_break"
	local sound_lock_regen = def.sound_lock_regen or "lzr_treasure_chest_lock_regen"

	local lock_chest = function(pos, node, check_gamestate)
		local add_lock = true
		local gs = lzr_gamestate.get_state()
		if check_gamestate and gs ~= lzr_gamestate.LEVEL and gs ~= lzr_gamestate.LEVEL_TEST and not def.regen_lock then
			add_lock = false
		end
		if add_lock then
			local actions = {
				{ type = "swap_node", node = {name="lzr_treasure:chest_"..id.."_locked", param2=node.param2} },
				{ type = "sound_play", sound = {name=sound_lock_regen, gain=0.8} },
			}

			local dir = minetest.fourdir_to_dir(node.param2)
			local w = 3/16
			local k = 9/16
			local l = 8/16
			local minoff, maxoff
			if dir.x > 0 then
				minoff = vector.new(-k, -w, -w)
				maxoff = vector.new(-l, w, w)
			elseif dir.x < 0 then
				minoff = vector.new(l, -w, -w)
				maxoff = vector.new(k, w, w)
			elseif dir.z > 0 then
				minoff = vector.new(-w, -w, -k)
				maxoff = vector.new(w, w, -l)
			elseif dir.z < 0 then
				minoff = vector.new(-w, -w, l)
				maxoff = vector.new(w, w, k)
			end
			local avgoff = vector.new()
			avgoff.x = (minoff.x + maxoff.x) / 2
			avgoff.y = (minoff.y + maxoff.y) / 2
			avgoff.z = (minoff.z + maxoff.z) / 2
			if minoff then
				table.insert(actions, { type = "add_particlespawner", particlespawner = {
					amount = 42,
					time = 0.001,
					minpos = vector.add(pos, minoff),
					maxpos = vector.add(pos, maxoff),
					minsize = 0.5,
					maxsize = 0.5,
					minexptime = 1.60,
					maxexptime = 1.65,
					radius = {
						min = 0.4,
						max = 0.5,
						bias = 0.5,
					},
					attract = {
						kind = "point",
						strength = 4,
						origin = vector.add(pos, avgoff),
					},
					texture = "lzr_treasure_particle_lock.png",
				}})
			else
				minetest.log("error", "[lzr_treasure] Could not find correct position to spawn treasure chest lock particles")
			end
			return actions
		end
	end

	local on_toggle_unlocked
	local g_unlocked_receiver
	local g_orphan_receiver
	local g_unlocked_element
	local g_locked_element
	local tt_help_unlocked = S("Contains a gold block")
	local tt_help_locked = S("Contains a gold block")
	local element_group = "chest_element"
	local after_place_node
	local after_dig_node
	if def.regen_lock then
		on_toggle_unlocked = function(pos, node)
			return lock_chest(pos, node, true)
		end
		g_unlocked_receiver = 1
		g_unlocked_element = 2
		g_locked_element = 1
		tt_help_unlocked = tt_help_unlocked .. "\n"..S("Gets locked when triggered off")
		tt_help_locked = tt_help_locked .. "\n"..S("Lock breaks when triggered on, but it re-appears when triggered off")
		after_place_node = lzr_laser.trigger_after_place_node
		after_dig_node = lzr_laser.trigger_after_dig_node
	else
		tt_help_locked = tt_help_locked .. "\n"..S("Lock breaks when triggered on")
		g_orphan_receiver = 1
	end

	-- Unlocked chest: Player can punch it to open the chest and reveal a gold block.
	-- If all chests have been opened, the level is won.
	minetest.register_node("lzr_treasure:chest_"..id.."_unlocked", {
		description = def.description_unlocked,
		_tt_help = tt_help_unlocked,
		paramtype2 = "4dir",
		tiles = { def.tile_top, def.tile_bottom, def.tile_side, def.tile_side, def.tile_side, def.tile_front },
		groups = { breakable = 1, chest = 1, chest_closed = 1, rotatable = 3, receiver = g_unlocked_receiver, orphan_receiver = 1, chest_element = g_unlocked_element },
		sounds = def.node_sounds,
		on_punch = function(pos, node, puncher)
			-- Open chest lid
			local gs = lzr_gamestate.get_state()
			if gs ~= lzr_gamestate.LEVEL and gs ~= lzr_gamestate.LEVEL_TEST then
				return
			end
			minetest.log("action", "[lzr_treasure] "..puncher:get_player_name().." opens "..node.name.." at "..minetest.pos_to_string(pos))
			minetest.set_node(pos, {name="lzr_treasure:chest_"..id.."_open_"..treasure_id, param2=node.param2})
			minetest.sound_play({name=sound_open, gain=0.5}, {pos=pos}, true)

			local pos_above = vector.offset(pos, 0, 1, 0)
			local node_above = minetest.get_node(pos_above)
			local def_above = minetest.registered_nodes[node_above.name]
			if not def_above or not def_above.walkable then
				-- Spawn chest lid
				local lidpos = vector.offset(pos, 0, 0.01, 0)
				local obj = minetest.add_entity(lidpos, "lzr_treasure:chest_lid")
				if obj then
					obj:set_properties({
						textures = {
							def.tile_top, def.tile_bottom, def.tile_side, def.tile_front,
						},
					})
					local yaw = (3-((node.param2-1) % 4)) * (math.pi/2)
					obj:set_yaw(yaw)
					local ent = obj:get_luaentity()
					if ent then
						ent._chest_node_name = "lzr_treasure:chest_"..id.."_unlocked"
					end
				end
			end

			-- call callbacks
			for c=1, #registered_after_found_treasures do
				registered_after_found_treasures[c](pos, puncher)
			end

			-- Check for level victory after 1 second
			if victory_job then
				-- cancel old job, restart timer
				victory_job:cancel()
			end
			victory_job = minetest.after(1, function()
				local done = lzr_laser.check_level_won()
				if done then
					lzr_levels.level_complete()
				end
				victory_job = nil
			end)
		end,
		after_place_node = after_place_node,
		after_dig_node = after_dig_node,
		_lzr_on_toggle = on_toggle_unlocked,
		_lzr_element_group = element_group,
	})

	local unlock_chest = function(pos, node, check_gamestate)
		local break_lock = true
		local gs = lzr_gamestate.get_state()
		if check_gamestate and gs ~= lzr_gamestate.LEVEL and gs ~= lzr_gamestate.LEVEL_TEST and not def.regen_lock then
			break_lock = false
		end
		local actions = {}
		if break_lock then
			table.insert(actions, { type = "swap_node", node = {name="lzr_treasure:chest_"..id.."_unlocked", param2=node.param2}})
		end

		table.insert(actions, { type = "sound_play", sound = {name=sound_lock_break, gain=0.8}})

		local dir = minetest.fourdir_to_dir(node.param2)
		local w = 3/16
		local k = 9/16
		local l = 8/16
		local minoff, maxoff
		if dir.x > 0 then
			minoff = vector.new(-k, -w, -w)
			maxoff = vector.new(-l, w, w)
		elseif dir.x < 0 then
			minoff = vector.new(l, -w, -w)
			maxoff = vector.new(k, w, w)
		elseif dir.z > 0 then
			minoff = vector.new(-w, -w, -k)
			maxoff = vector.new(w, w, -l)
		elseif dir.z < 0 then
			minoff = vector.new(-w, -w, l)
			maxoff = vector.new(w, w, k)
		end
		if minoff then
			table.insert(actions, { type = "add_particlespawner", particlespawner = {
				amount = 12,
				time = 0.001,
				minpos = vector.add(pos, minoff),
				maxpos = vector.add(pos, maxoff),
				minvel = vector.new(-0.5, -0.2, -0.5),
				maxvel = vector.new(0.5, 0.5, 0.5),
				minacc = vector.new(0, -lzr_globals.GRAVITY, 0),
				maxacc = vector.new(0, -lzr_globals.GRAVITY, 0),
				minsize = 0.5,
				maxsize = 0.5,
				minexptime = 0.60,
				maxexptime = 0.65,
				texture = "lzr_treasure_particle_lock.png",
			}})
		else
			minetest.log("error", "[lzr_treasure] Could not find correct position to spawn treasure chest lock particles")
		end
		return actions
	end

	-- Locked chest: Unlocks itself when receiving an ON signal.
	-- One chest variant may re-lock itself again when receiving an OFF signal.
	minetest.register_node("lzr_treasure:chest_"..id.."_locked", {
		description = def.description_locked,
		_tt_help = tt_help_locked,
		paramtype2 = "4dir",
		tiles = { def.tile_top, def.tile_bottom, def.tile_side, def.tile_side, def.tile_side, def.tile_front_lock },
		groups = { breakable = 1, chest = 2, chest_closed = 1, rotatable = 3, receiver = 1, chest_element = g_locked_element },
		sounds = def.node_sounds,
		on_punch = function(pos, node, puncher)
			local gs = lzr_gamestate.get_state()
			if gs ~= lzr_gamestate.LEVEL and gs ~= lzr_gamestate.LEVEL_TEST then
				return
			end
			minetest.sound_play({name=sound_open_fail, gain=0.3}, {pos=pos}, true)
		end,
		after_place_node = lzr_laser.trigger_after_place_node,
		after_dig_node = lzr_laser.trigger_after_dig_node,
		-- Unlocks the chest, but only if playing a level
		_lzr_on_toggle = function(pos, node)
			return unlock_chest(pos, node, true)
		end,
		_lzr_element_group = element_group,
	})

	-- Open empty chest. For the player, this is just
	-- an obstacle.
	-- This element is laser-compatible. A laser can be fired
	-- into it from above for a visual-only effect.
	lzr_laser.register_element("lzr_treasure:chest_"..id.."_open", {
		description = def.description_open,
		paramtype = "light",
		drawtype = "mesh",
		__mesh_off = "lzr_treasure_chest_open.obj",
		__mesh_on = "lzr_treasure_chest_open_laser.obj",
		__tiles_off = {
			{ name = def.tile_top, backface_culling = true },
			{ name = def.tile_bottom, backface_culling = true },
			{ name = def.tile_side, backface_culling = true },
			{ name = def.tile_front, backface_culling = true },
		},
		__tiles_on = {
			{ name = def.tile_top, backface_culling = true },
			{ name = def.tile_bottom, backface_culling = true },
			{ name = def.tile_side, backface_culling = true },
			{ name = def.tile_front, backface_culling = true },
			lzr_laser.LASER_TILE,
		},
		__use_texture_alpha_off = "clip",
		__use_texture_alpha_on = lzr_laser.ALPHA_LASER,
		__light_source_on = lzr_globals.LASER_GLOW,

		collision_box = {
			type = "fixed",
			fixed = { -0.5, -0.5, -0.5, 0.5, 2/16, 0.5 },
		},
		selection_box = {
			type = "fixed",
			fixed = { -0.5, -0.5, -0.5, 0.5, 2/16, 0.5 },
		},

		paramtype2 = "4dir",
		groups = { breakable = 1, chest = 3, laser_block = 1, rotatable = 3, orphan_receiver = 1 },
		sounds = def.node_sounds,
	}, { group = "chest_open" })


	-- Open chest with treasure (gold block).
	-- Appears when the player opened an unlocked chest.
	-- This block does nothing on its own, but when all chests
	-- in the level have been opened and reached this
	-- state (ignoring empty chests), the level counts as won.
	local treasure = "lzr_treasure:gold_block"
	local treasure_tile_top = "lzr_treasure_gold_block_in_chest_top.png"
	local treasure_tile_side = "lzr_treasure_gold_block_in_chest_side.png"

	local treasure_def = minetest.registered_nodes[treasure]
	local sounds_open_treasure = table.copy(def.node_sounds)
	sounds_open_treasure.footstep = table.copy(treasure_def.sounds.footstep)
	sounds_open_treasure.dig = nil
	sounds_open_treasure.dug = table.copy(treasure_def.sounds.dug)

	minetest.register_node("lzr_treasure:chest_"..id.."_open_"..treasure_id, {
		description = S(def.description_open_with, treasure_def.description),
		drawtype = "nodebox",
		paramtype = "light",
		node_box = {
			type = "fixed",
			fixed = {
				{ -0.5, -0.5, -0.5, 0.5, 2/16, 0.5 },
				{ -7/16, 2/16, -7/16, 7/16, 0.5, 7/16 },
			},
		},
		tiles = {
			"("..def.tile_top..")^("..treasure_tile_top.."^[mask:lzr_treasure_mask_block_in_chest_top.png)",
			def.tile_bottom,
			"("..def.tile_side..")^("..treasure_tile_side.."^[mask:lzr_treasure_mask_block_in_chest_side.png)",
			"("..def.tile_side..")^("..treasure_tile_side.."^[mask:lzr_treasure_mask_block_in_chest_side.png)",
			"("..def.tile_side..")^("..treasure_tile_side.."^[mask:lzr_treasure_mask_block_in_chest_side.png)",
			"("..def.tile_front..")^("..treasure_tile_side.."^[mask:lzr_treasure_mask_block_in_chest_side.png)",
		},
		-- Removes the treasure from the chest (turning it into an open empty chest)
		-- and spawns a gold block entity (for level finish)
		_lzr_send_treasure = function(pos, node, delay)
			local send_treasure = function(pos, node)
				local oldnode = minetest.get_node(pos)
				-- Check if there’s still the same node at pos
				if oldnode.name ~= "lzr_treasure:chest_"..id.."_open_"..treasure_id then
					return
				end
				-- make the chest node empty
				minetest.set_node(pos, {name="lzr_treasure:chest_"..id.."_open", param2=node.param2})
				-- spawn gold block entity EXACTLY where normally the gold block of the chest node
				-- was; this change should be visually seamless
				local epos = vector.offset(pos, 0, 1/16, 0)
				minetest.add_entity(epos, "lzr_treasure:gold_block_from_chest")
			end
			if delay then
				minetest.after(delay, send_treasure, pos, node)
			else
				send_treasure(pos, node)
			end
		end,
		paramtype2 = "4dir",
		groups = { breakable = 1, chest = 4, chest_open = 1, chest_open_treasure = 1, rotatable = 3, orphan_receiver = 1, not_in_creative_inventory = 1, parrot_compatible = 1 },
		sounds = sounds_open_treasure,
	})
end

register_chest("wood", {
	--~ Block description
	description_unlocked = S("Wooden Chest"),
	--~ Block description
	description_locked = S("Locked Wooden Chest"),
	--~ Block description
	description_open = S("Open Wooden Chest"),
	--~ Block description
	description_open_laser = S("Open Wooden Chest with Laser"),
	--~ Block description. @1 = a treasure
	description_open_with = NS("Open Wooden Chest with @1"),
	tile_top = "lzr_treasure_chest_top.png",
	tile_bottom = "lzr_treasure_chest_top.png",
	tile_side = "lzr_treasure_chest_side.png",
	tile_front = "lzr_treasure_chest_front.png",
	tile_front_lock = "lzr_treasure_chest_lock.png",
	node_sounds = lzr_sounds.node_sound_wood_defaults(),
})

register_chest("dark", {
	--~ Block description
	description_unlocked = S("Onyx Chest"),
	--~ Block description
	description_locked = S("Locked Onyx Chest"),
	--~ Block description
	description_open = S("Open Onyx Chest"),
	--~ Block description
	description_open_laser = S("Open Onyx Chest with Laser"),
	--~ Block description. @1 = a treasure
	description_open_with = NS("Open Onyx Chest with @1"),
	tile_top = "lzr_treasure_dark_chest_top.png",
	tile_bottom = "lzr_treasure_dark_chest_top.png",
	tile_side = "lzr_treasure_dark_chest_side.png",
	tile_front = "lzr_treasure_dark_chest_front.png",
	tile_front_lock = "lzr_treasure_dark_chest_lock.png",
	node_sounds = lzr_sounds.node_sound_stone_defaults(),
	-- The dark chest can regenerate its lock when toggled off
	regen_lock = true,
})

minetest.register_alias("lzr_treasure:chest_wood_open", "lzr_treasure:chest_wood_open_fixed")
minetest.register_alias("lzr_treasure:chest_dark_open", "lzr_treasure:chest_dark_open_fixed")
minetest.register_alias("lzr_treasure:chest_wood_open_laser", "lzr_treasure:chest_wood_open_fixed_on_1")
minetest.register_alias("lzr_treasure:chest_dark_open_laser", "lzr_treasure:chest_dark_open_fixed_on_1")
