lzr_util = {}

local max_value = 255

-- Convert a 6-character color hexcode string in format RRGGBB
-- to 3 numbers red, green, blue.
lzr_util.hexcode_to_rgb = function(hexcode)
	if not hexcode or string.len(hexcode) ~= 6 then
		return
	end
	local rhex = string.sub(hexcode, 1, 2)
	local ghex = string.sub(hexcode, 3, 4)
	local bhex = string.sub(hexcode, 5, 6)
	local r = tonumber(rhex, 16)
	local g = tonumber(ghex, 16)
	local b = tonumber(bhex, 16)
	if r and g and b then
		r = math.min(255, math.max(0, r))
		g = math.min(255, math.max(0, g))
		b = math.min(255, math.max(0, b))
		return r, g, b
	end
end

-- Convert 3 numbers for red, green and blue (0-255 each)
-- to a hexadecical code of length 6 in format RRGGBB.
lzr_util.rgb_to_hexcode = function(r, g, b)
	return string.format("%02X%02X%02X", r, g, b)
end

-- Converts RGB values (0..255 each) to HSV values (0..1 each)
lzr_util.rgb_to_hsv = function(r, g, b)
	r = r / max_value
	g = g / max_value
	b = b / max_value

	local max = math.max(r, g, b)
	local min = math.min(r, g, b)
	local h, s, v = 0, 0, (max + min) / 2

	local d = max - min
	v = max
	s = max == 0 and 0 or d / max

	if max ~= min then
		if max == r then
			h = (g - b) / d + (g < b and 6 or 0)
		elseif max == g then
			h = (b - r) / d + 2
		elseif max == b then
			h = (r - g) / d + 4
		end
		h = h / 6
	end
	return h, s, v
end

-- Converts HSV values (0..1 each) to RGB values (0..255 each)
lzr_util.hsv_to_rgb = function(h, s, v)
	local r, g, b

	local i = math.floor(h * 6)
	local f = h * 6 - i
	local p = v * (1 - s)
	local q = v * (1 - f * s)
	local t = v * (1 - (1 - f) * s)

	i = i % 6

	if i == 0 then
		r, g, b = v, t, p
	elseif i == 1 then
		r, g, b = q, v, p
	elseif i == 2 then
		r, g, b = p, v, t
	elseif i == 3 then
		r, g, b = p, q, v
	elseif i == 4 then
		r, g, b = t, p, v
	elseif i == 5 then
		r, g, b = v, p, q
	end

        return math.floor(r * max_value), math.floor(g * max_value), math.floor(b * max_value)
end

-- Copies an area of the map and returns
-- a table of nodes and param2 nodes. Node metadata is not copied.
-- This output can be used as parameters
-- for lzr_util.paste_area.
-- * source_pos: Where the area begins (lower X, Y, Z position)
-- * size: area size. Must equal the size used by lzr_util.copy_area
lzr_util.copy_area = function(source_pos, size)
	if size.x <= 0 or size.y <= 0 or size.z <= 0 then
		return
	end
	local size_m = vector.offset(size, -1, -1, -1)
	local source_maxpos = vector.add(source_pos, size_m)

	-- Load area from source
	local vmanip = minetest.get_voxel_manip(source_pos, source_maxpos)
	local emin, emax = vmanip:get_emerged_area()
	local varea_source = VoxelArea(emin, emax)
	local vdata_source = vmanip:get_data()
	local vdatap2_source = vmanip:get_param2_data()
	local nodes = {}
	local param2s = {}
	local iter = varea_source:iterp(source_pos, source_maxpos)
	for idx in iter do
		table.insert(nodes, vdata_source[idx])
		table.insert(param2s, vdatap2_source[idx])
	end
	return nodes, param2s
end

-- Pastes an area that was previously copied
-- by lzr_util.copy_area.
-- * target_pos: Where to paste the area
-- * size: area size. Must equal the size used by lzr_util.copy_area
-- * nodes: Table of nodes returned by lzr_util.copy_area
-- * param2s: Table of param2 values returned by lzr_util.copy_area
lzr_util.paste_area = function(target_pos, size, nodes, param2s)
	if size.x <= 0 or size.y <= 0 or size.z <= 0 then
		minetest.log("info", "[lzr_util] Didn't paste area to "..minetest.pos_to_string(target_pos)..". Size is 0")
		return
	end
	local size_m = vector.offset(size, -1, -1, -1)
	local target_maxpos = vector.add(target_pos, size_m)

	-- Write area to target
	local vmanip = minetest.get_voxel_manip(target_pos, target_maxpos)
	local emin, emax = vmanip:get_emerged_area()
	local vdata_target = vmanip:get_data()
	local vdatap2_target = vmanip:get_param2_data()
	local varea_target = VoxelArea(emin, emax)
	local iter = varea_target:iterp(target_pos, target_maxpos)
	local i = 1
	for idx in iter do
		vdata_target[idx] = nodes[i]
		vdatap2_target[idx] = param2s[i]
		i = i + 1
	end
	vmanip:set_data(vdata_target)
	vmanip:set_param2_data(vdatap2_target)
	vmanip:write_to_map()
	minetest.log("action", "[lzr_util] Pasted area to "..minetest.pos_to_string(target_pos))
end

-- Returns true if the given file exists, false otherwise.
-- * path: Path to file (without file name)
-- * filename: File name of file (without path)
lzr_util.file_exists = function(path, filename)
	local levels = minetest.get_dir_list(path, false)
	for l=1, #levels do
		if levels[l] == filename then
			return true
		end
	end
	return false
end

-- Takes an index of the data field of a schematic table (as returned
-- by minetest.read_schematic) and the schematic size,
-- and returns the position as a vector.
-- index 0 corresponds to (0,0,0).
lzr_util.schematic_index_to_pos = function(index, size)
	local d = index-1
	local x = d % size.x
	local y = math.floor((d / size.x) % size.y)
	local z = math.floor((d / (size.x*size.y)) % size.z)
	return vector.new(x,y,z)
end

-- Show the inventory formspec for the given player, if the client
-- of the player supports it.
-- If it’s not supported, does nothing.
lzr_util.show_inventory_formspec_if_supported = function(player)
	if lzr_util.is_show_inventory_formspec_supported(player) then
		minetest.show_formspec(player:get_player_name(), "", player:get_inventory_formspec())
	end
end

-- Returns true if the inventory formspec can be shown to the player
-- (supported by the Luanti version)
lzr_util.is_show_inventory_formspec_supported = function(player)
	local pinfo = minetest.get_player_information(player:get_player_name())
	if not pinfo or not pinfo.protocol_version then
		return false
	end
	-- Showing the inventory formspec with show_formspec is
	-- supported since Luanti 5.13.0 (protocol version 49).
	if pinfo.protocol_version < 49 then
		return false
	end
	return true
end
