PyuTest.util = {
	tablecopy = table.copy, -- Used to have implementation before I realized table.copy existed.

	tableconcat = function(t1, t2)
		local nt = PyuTest.util.tablecopy(t1)
		for k, v in pairs(t2) do
			nt[k] = v
		end
		return nt
	end,

	tableconcat2 = function(t1, t2)
		local nt = PyuTest.util.tablecopy(t1)
		for i = 1, #t2 do
			nt[#nt + i] = t2[i]
		end
		return nt
	end,
}

PyuTest.dorange = function(origin, range, action)
	for dx = -range, range do
		for dz = -range, range do
			for dy = -range, range do
				action(vector.new(origin.x + dx, origin.y + dy, origin.z + dz))
			end
		end
	end
end

PyuTest.get_full_neighbours = function(pos)
	return {
		vector.new(pos.x + 1, pos.y, pos.z),
		vector.new(pos.x - 1, pos.y, pos.z),
		vector.new(pos.x + 1, pos.y + 1, pos.z),
		vector.new(pos.x + 1, pos.y - 1, pos.z),
		vector.new(pos.x - 1, pos.y - 1, pos.z),
		vector.new(pos.x - 1, pos.y + 1, pos.z),

		vector.new(pos.x, pos.y, pos.z + 1),
		vector.new(pos.x, pos.y, pos.z - 1),
		vector.new(pos.x, pos.y + 1, pos.z + 1),
		vector.new(pos.x, pos.y - 1, pos.z + 1),
		vector.new(pos.x, pos.y + 1, pos.z - 1),
		vector.new(pos.x, pos.y - 1, pos.z - 1),

		vector.new(pos.x, pos.y + 1, pos.z),
		vector.new(pos.x, pos.y - 1, pos.z),
	}
end

PyuTest.node_beside_node = function(pos, name)
	local n = {
		pos + vector.new(1, 0, 0),
		pos - vector.new(1, 0, 0),
		pos + vector.new(0, 0, 1),
		pos - vector.new(0, 0, 1),
	}

	for _, v in pairs(n) do
		local node = core.get_node(v)
		if node.name == name then
			return true
		end
	end

	return false
end

PyuTest.node_beside_group = function(pos, group)
	local n = {
		pos + vector.new(1, 0, 0),
		pos - vector.new(1, 0, 0),
		pos + vector.new(0, 0, 1),
		pos - vector.new(0, 0, 1),
	}

	for _, v in pairs(n) do
		local node = core.get_node(v)
		if core.get_item_group(node.name, group) ~= 0 then
			return true
		end
	end

	return false
end

PyuTest.create_explosion = function(pos, range, rm_pos, dmg, damage_whitelist, max_blast_resistance)
	local max_resist = max_blast_resistance or 3

	if rm_pos then
		core.remove_node(pos)
	end

	PyuTest.dorange(pos, range, function(p)
		if not core.settings:get_bool("explosion_damage", true) then
			return
		end

		local node = core.get_node(p)
		local def = core.registered_nodes[node.name]
		local resist = def._pyutest_blast_resistance or 1

		if resist > max_resist then
			return
		end

		if core.get_node(p).name == "pyutest_blocks:tnt" then
			core.after(0.8, function()
				core.remove_node(p)
				PyuTest.create_explosion(p, range, rm_pos, dmg, damage_whitelist, max_blast_resistance)
			end)
		else
			core.dig_node(p)
		end
	end)

	for v in core.objects_inside_radius(pos, range) do
		local function damage()
			if v:is_valid() then
				PyuTest.deal_damage(v, dmg, PyuTest.DAMAGE_TYPES.explosion(range))
			end
		end

		if damage_whitelist ~= nil then
			local unsafe = true

			for _, v2 in pairs(damage_whitelist) do
				if v == v2 then
					unsafe = false
					break
				end
			end

			if unsafe then
				damage()
			end
		else
			damage()
		end
	end


	local r = range
	local minpos = { x = pos.x - r, y = pos.y - r, z = pos.z - r }
	local maxpos = { x = pos.x + r, y = pos.y + r, z = pos.z + r }

	core.add_particlespawner({
		amount = range * 8,
		time = 0.4,
		minexptime = 0.4,
		maxexptime = 1.4,
		minsize = 16,
		maxsize = 32,
		vertical = false,
		glow = 8,

		collisiondetection = false,
		texture = "pyutest-blast.png",

		minpos = minpos,
		maxpos = maxpos,
		minvel = vector.new(-1, -1, 1),
		maxvel = vector.new(1, 1, 1),
	})

	core.add_particlespawner({
		amount = range * 18,
		time = 0.4,
		minexptime = 0.4,
		maxexptime = 1.4,
		minsize = 1,
		maxsize = 2,
		vertical = false,
		glow = 4,

		collisiondetection = false,
		texture = "pyutest-blast2.png",

		minpos = minpos,
		maxpos = maxpos,
		minvel = vector.new(-6, -6, -6),
		maxvel = vector.new(6, 6, 6),
	})

	core.add_particlespawner({
		amount = range * 18,
		time = 0.4,
		minexptime = 0.4,
		maxexptime = 1.4,
		minsize = 1,
		maxsize = 2,
		vertical = false,
		glow = 4,

		collisiondetection = false,
		texture = "pyutest-blast3.png",

		minpos = minpos,
		maxpos = maxpos,
		minvel = vector.new(-6, -6, -6),
		maxvel = vector.new(6, 6, 6),
	})

	core.sound_play("pyutest_block_break", {
		pos = pos,
		gain = 2.5
	})
end

PyuTest.tool_caps = function(options)
	local default_uses = 100
	local groupcaps = {}

	for k, v in pairs(options.groupcaps or {}) do
		groupcaps[k] = {
			maxlevel = v.maxlevel or options.maxlevel,
			times = v.times or {
				[PyuTest.BLOCK_FAST] = options.time or 3,
				[PyuTest.BLOCK_NORMAL] = options.time or 3,
				[PyuTest.BLOCK_SLOW] = options.time or 3
			},
			uses = v.uses or options.uses or default_uses
		}
	end

	return {
		groupcaps = groupcaps,
		punch_attack_uses = options.attack_uses,
		damage_groups = options.damage_groups or { fleshy = 3 },
		full_punch_interval = options.full_punch_interval
	}
end

-- https://github.com/minetest/minetest_game/blob/master/mods/stairs/init.lua#L27
PyuTest.rotate_and_place = function(itemstack, placer, pointed_thing)
	local p0 = pointed_thing.under
	local p1 = pointed_thing.above
	local param2 = 0

	if placer then
		local placer_pos = placer:get_pos()
		if placer_pos then
			local diff = vector.subtract(p1, placer_pos)
			param2 = core.dir_to_facedir(diff)
			-- The player places a node on the side face of the node he is standing on
			if p0.y == p1.y and math.abs(diff.x) <= 0.5 and math.abs(diff.z) <= 0.5 and diff.y < 0 then
				-- reverse node direction
				param2 = (param2 + 2) % 4
			end
		end

		local finepos = core.pointed_thing_to_face_pos(placer, pointed_thing)
		local fpos = finepos.y % 1

		if p0.y - 1 == p1.y or (fpos > 0 and fpos < 0.5)
			or (fpos < -0.5 and fpos > -0.999999999) then
			param2 = param2 + 20
			if param2 == 21 then
				param2 = 23
			elseif param2 == 23 then
				param2 = 21
			end
		end
	end
	return core.item_place(itemstack, placer, pointed_thing, param2)
end

PyuTest.register_interval = function(fn, time)
	local first_run = true

	local function interval()
		if first_run then
			first_run = false
		else
			fn()
		end

		core.after(time, fn)
	end

	interval()
end

PyuTest.give_item_or_drop = function(stack, inventory, listname, pos)
	local leftover = inventory:add_item(listname, stack)

	if leftover then
		core.add_item(pos, leftover)
	end
end

PyuTest.get_biome_def = function(pos)
	local data = core.get_biome_data(pos)
	if not data then return end

	return core.registered_biomes[core.get_biome_name(data.biome)]
end

-- https://github.com/minetest/minetest_game/blob/master/mods/default/functions.lua#L184
function PyuTest.get_inventory_drops(pos, inventory, drops)
	local inv = core.get_meta(pos):get_inventory()
	local n = #drops
	for i = 1, inv:get_size(inventory) do
		local stack = inv:get_stack(inventory, i)
		if stack:get_count() > 0 then
			drops[n + 1] = stack:to_table()
			n = n + 1
		end
	end
end

PyuTest.drop_item = function(pos, name)
	local o = core.add_item(pos, name)

	if o then
		o:add_velocity(vector.new(math.random(-2, 2), 5, math.random(-2, 2)))
	end
end

PyuTest.get_foliage_type = function (pos, name)
	local t = PyuTest.get_biome_def(pos) or {}
	local index = t._pyutest_foliage_index or 0

	return {name = name, param2 = index}
end
