-- Lazarr! mod: lzr_stairs
-- See README.txt for licensing and other information.

-- Global namespace for functions
lzr_stairs = {}

local S = minetest.get_translator("lzr_stairs")

local function rotate_and_place(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
			param2 = minetest.dir_to_facedir(vector.subtract(p1, placer_pos))
		end

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

		local def = itemstack:get_definition()
		if def.paramtype2 == "facedir" or def.paramtype2 == "colorfacedir" then
			-- Flip node vertically if placed at upper half of node
			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
	end
	return minetest.item_place(itemstack, placer, pointed_thing, param2)
end


-- Register stair.
-- CAN be rotated.
-- Node will be called lzr_stairs:stair_<subname>

function lzr_stairs.register_stair(subname, recipeitem, groups, images, description,
		sounds, worldaligntex, vertical_rotation)
	local src_def = minetest.registered_nodes[recipeitem]

	-- Set backface culling and world-aligned textures
	local stair_images = {}
	for i, image in ipairs(images) do
		if type(image) == "string" then
			stair_images[i] = {
				name = image,
				backface_culling = true,
			}
			if worldaligntex then
				stair_images[i].align_style = "world"
			end
		else
			stair_images[i] = table.copy(image)
			if stair_images[i].backface_culling == nil then
				stair_images[i].backface_culling = true
			end
			if worldaligntex and stair_images[i].align_style == nil then
				stair_images[i].align_style = "world"
			end
		end
	end
	local new_groups = table.copy(groups)
	new_groups.stair = 1
	new_groups.rotatable = 3
	local paramtype2
	if vertical_rotation == false then
		paramtype2 = "4dir"
	else
		paramtype2 = "facedir"
	end
	minetest.register_node(":lzr_stairs:stair_" .. subname, {
		description = description,
		drawtype = "nodebox",
		tiles = stair_images,
		use_texture_alpha = src_def and src_def.use_texture_alpha,
		paramtype = "light",
		paramtype2 = paramtype2,
		is_ground_content = false,
		groups = new_groups,
		sounds = sounds,
		node_box = {
			-- This stair nodebox is unusual and differs from most other Luanti games.
			-- The segments have been made slightly different so that a laser will alway
			-- be completely blocked off from any direction.
			type = "fixed",
			fixed = {
				{-0.5, -0.5, -0.5, 0.5, 1/16, 0.5},
				{-0.5, 1/16, -1/16, 0.5, 0.5, 0.5},
			},
		},
		on_place = function(itemstack, placer, pointed_thing)
			if pointed_thing.type ~= "node" then
				return itemstack
			end

			return rotate_and_place(itemstack, placer, pointed_thing)
		end,
	})
end


-- Register slab. (includes slab for bottom and top part of node)
-- The slab can be rotated to move to the bottom or top of the node.
-- Sideways slabs do not exist.
-- 
-- Registers multiple nodes via lzr_laser.register_element,
-- using the basename `lzr_stairs:slab_<subname>`.
-- In practice, this will register the following nodes:
-- * lzr_stairs:slab_<subname>_fixed for the bottom slab,
-- * lzr_stairs:slab_top_<subname>_fixed for the top slab and
-- * lzr_stairs:slab_<subname>_fixed_on_<colorcode> for the bottom slab with a laser from above
-- * lzr_stairs:slab_top_<subname>_fixed_on_<colorcode> for the top slabs with a laser from below
--
-- (where <colorcode> being the laser colorcode)
function lzr_stairs.register_slab(subname, recipeitem, groups, images, description,
		sounds, worldaligntex, images_top)
	local src_def = minetest.registered_nodes[recipeitem]

	local parse_images = function(images)
		-- Use first tile only, for slab mesh
		local slab_images = table.copy(images)

		-- Set slab_images[2] and [3], if not set
		for i=2,3 do
			if not slab_images[i] then
				if type(slab_images[i-1]) == "table" then
					slab_images[i] = table.copy(slab_images[i-1])
				elseif type(slab_images[i-1]) == "string" then
					slab_images[i] = slab_images[i-1]
				end
			end
		end
		slab_images[4] = "blank.png"
		slab_images[5] = nil
		slab_images[6] = nil
		for i=1, 6 do
			if slab_images[i] and slab_images[i] ~= "blank.png" then
				-- Add backface culling because otherwise z-fighting occurs when
				-- inside the node
				if type(slab_images[i]) == "string" then
					slab_images[i] = { name = slab_images[i], backface_culling = true }
				elseif type(slab_images[i]) == "table" then
					slab_images[i].backface_culling = true
				end
			end
		end
		return slab_images
	end
	local slab_images = parse_images(images)
	local slab_top_images
	if images_top then
		slab_top_images = parse_images(images_top)
	else
		slab_top_images = table.copy(slab_images)
	end

	local laser_slab_images = table.copy(slab_images)
	laser_slab_images[4] = lzr_laser.LASER_TILE

	local laser_slab_top_images = table.copy(slab_top_images)
	laser_slab_top_images[4] = lzr_laser.LASER_TILE

	local new_groups = table.copy(groups)
	new_groups.slab = 1
	new_groups.laser_block = 1
	new_groups.rotatable = 3

	local def_bottom = {
		description = description,
		drawtype = "mesh",
		__mesh_off = "lzr_stairs_slab_laser.obj",
		__mesh_on = "lzr_stairs_slab_laser.obj",
		__tiles_off = slab_images,
		__tiles_on = laser_slab_images,

		__use_texture_alpha_off = (src_def and src_def.use_texture_alpha) or "clip",
		__use_texture_alpha_on = lzr_laser.ALPHA_LASER,
		__light_source_on = lzr_globals.LASER_GLOW,

		paramtype = "light",
		is_ground_content = false,
		groups = new_groups,
		sounds = sounds,
		collision_box = {
			type = "fixed",
			-- The slab nodebox is slightly higher than half of a node
			-- so that an incoming laser from any direction (except the top)
			-- will be visually blocked.
			fixed = {-0.5, -0.5, -0.5, 0.5, 1/16, 0.5},
		},
		selection_box = {
			type = "fixed",
			fixed = {-0.5, -0.5, -0.5, 0.5, 1/16, 0.5},
		},
		__on_place = function(itemstack, placer, pointed_thing)
			-- node's on_rightclick action takes precedence
			if pointed_thing.type == "node" and placer then
				local node = minetest.get_node(pointed_thing.under)
				local def = minetest.registered_nodes[node.name]
				local sneak = placer:get_player_control().sneak
				if def and def.on_rightclick and not sneak then
					def.on_rightclick(pointed_thing.under, node, placer, itemstack, pointed_thing)
					return itemstack
				end
			end

			local function place_slab_top(itemstack, placer, pointed_thing)
				local itemstack_top = ItemStack(itemstack)
				local iname = itemstack_top:get_name()
				iname = string.gsub(iname, "lzr_stairs:slab_", "lzr_stairs:slab_top_")
				itemstack_top:set_name(iname)
				itemstack_top:set_count(1)
				minetest.item_place(itemstack_top, placer, pointed_thing)
				return itemstack
			end

			-- Place at wall: Place "top" slab when pointed at upper 9/16th of node side
			if (pointed_thing.above.y == pointed_thing.under.y) then
				local precise = minetest.pointed_thing_to_face_pos(placer, pointed_thing)
				local h = precise.y % 1
				if h < 0.5 and h >= 0.0625 then
					-- Place "top" slab
					itemstack = place_slab_top(itemstack, placer, pointed_thing)
					return itemstack
				else
					-- Normal placement
					itemstack = minetest.item_place(itemstack, placer, pointed_thing)
					return itemstack
				end
			end

			-- Place at floor or ceiling
			local place_down = pointed_thing.above.y > pointed_thing.under.y
			if place_down then
				itemstack = minetest.item_place(itemstack, placer, pointed_thing)
			else
				itemstack = place_slab_top(itemstack, placer, pointed_thing)
			end
			return itemstack
		end,
		_lzr_on_rotate = function(pos, node, rotate_axis, rotate_dir)
			if rotate_axis ~= "y" then
				node.name = string.gsub(node.name, "lzr_stairs:slab_", "lzr_stairs:slab_top_")
				return true, node
			else
				return true, node
			end
		end,
	}

	lzr_laser.register_element(
		":lzr_stairs:slab_" .. subname,
		def_bottom,
		{ group = "laser_element_slab_"..subname }
	)

	local def_top = table.copy(def_bottom)
	--~ Block description for a top slab. @1 = original slab description
	def_top.description = S("@1 (top)", description)
	def_top.__mesh_off = "lzr_stairs_slab_top_laser.obj"
	def_top.__mesh_on = "lzr_stairs_slab_top_laser.obj"
	def_top.__tiles_off = slab_top_images
	def_top.__tiles_on = laser_slab_top_images
	def_top.__drop = "lzr_stairs:slab_"..subname

	def_top.groups.slab = 2
	def_top.groups.not_in_creative_inventory = 1

	def_top.collision_box = {
		type = "fixed",
		-- Same as bottom slab, but shifted to the top
		fixed = { -0.5, -1/16, -0.5, 0.5, 0.5, 0.5 },
	}
	def_top.selection_box = {
		type = "fixed",
		fixed = { -0.5, -1/16, -0.5, 0.5, 0.5, 0.5 },
	}
	def_top.on_place = nil
	def_top._lzr_on_rotate = function(pos, node, rotate_axis, rotate_dir)
		if rotate_axis ~= "y" then
			node.name = string.gsub(node.name, "lzr_stairs:slab_top_", "lzr_stairs:slab_")
			return true, node
		else
			return true, node
		end
	end

	lzr_laser.register_element(
		":lzr_stairs:slab_top_" .. subname,
		def_top,
		{ group = "laser_element_slab_top_"..subname }
	)
end

-- Register inner stair
-- Node will be called lzr_stairs:stair_inner_<subname>

function lzr_stairs.register_stair_inner(subname, recipeitem, groups, images,
		description, sounds, worldaligntex, full_description, vertical_rotation)
	local src_def = minetest.registered_nodes[recipeitem]

	-- Set backface culling and world-aligned textures
	local stair_images = {}
	for i, image in ipairs(images) do
		if type(image) == "string" then
			stair_images[i] = {
				name = image,
				backface_culling = true,
			}
			if worldaligntex then
				stair_images[i].align_style = "world"
			end
		else
			stair_images[i] = table.copy(image)
			if stair_images[i].backface_culling == nil then
				stair_images[i].backface_culling = true
			end
			if worldaligntex and stair_images[i].align_style == nil then
				stair_images[i].align_style = "world"
			end
		end
	end
	local new_groups = table.copy(groups)
	new_groups.stair = 2
	new_groups.rotatable = 3
	if full_description then
		description = full_description
	else
		description = "Inner " .. description
	end
	local paramtype2
	if vertical_rotation == false then
		paramtype2 = "4dir"
	else
		paramtype2 = "facedir"
	end
	minetest.register_node(":lzr_stairs:stair_inner_" .. subname, {
		description = description,
		drawtype = "nodebox",
		tiles = stair_images,
		use_texture_alpha = src_def and src_def.use_texture_alpha,
		paramtype = "light",
		paramtype2 = paramtype2,
		is_ground_content = false,
		groups = new_groups,
		sounds = sounds,
		node_box = {
			-- Unusual stairs nodebox (see stairs comment)
			type = "fixed",
			fixed = {
				{-0.5, -0.5, -0.5, 0.5, 1/16, 0.5},
				{-0.5, 1/16, -1/16, 0.5, 0.5, 0.5},
				{-0.5, 1/16, -0.5, 1/16, 0.5, -1/16},
			},
		},
		on_place = function(itemstack, placer, pointed_thing)
			if pointed_thing.type ~= "node" then
				return itemstack
			end

			return rotate_and_place(itemstack, placer, pointed_thing)
		end,
	})

end


-- Register outer stair
-- Node will be called lzr_stairs:stair_outer_<subname>

function lzr_stairs.register_stair_outer(subname, recipeitem, groups, images,
		description, sounds, worldaligntex, full_description, vertical_rotation)
	local src_def = minetest.registered_nodes[recipeitem]

	-- Set backface culling and world-aligned textures
	local stair_images = {}
	for i, image in ipairs(images) do
		if type(image) == "string" then
			stair_images[i] = {
				name = image,
				backface_culling = true,
			}
			if worldaligntex then
				stair_images[i].align_style = "world"
			end
		else
			stair_images[i] = table.copy(image)
			if stair_images[i].backface_culling == nil then
				stair_images[i].backface_culling = true
			end
			if worldaligntex and stair_images[i].align_style == nil then
				stair_images[i].align_style = "world"
			end
		end
	end
	local new_groups = table.copy(groups)
	new_groups.stair = 3
	new_groups.rotatable = 3
	if full_description then
		description = full_description
	else
		description = "Outer " .. description
	end
	local paramtype2
	if vertical_rotation == false then
		paramtype2 = "4dir"
	else
		paramtype2 = "facedir"
	end
	minetest.register_node(":lzr_stairs:stair_outer_" .. subname, {
		description = description,
		drawtype = "nodebox",
		tiles = stair_images,
		use_texture_alpha = src_def and src_def.use_texture_alpha,
		paramtype = "light",
		paramtype2 = paramtype2,
		is_ground_content = false,
		groups = new_groups,
		sounds = sounds,
		node_box = {
			-- Unusual stairs nodebox (see stairs comment)
			type = "fixed",
			fixed = {
				{-0.5, -0.5, -0.5, 0.5, 1/16, 0.5},
				{-0.5, 1/16, -1/16, 1/16, 0.5, 0.5},
			},
		},
		on_place = function(itemstack, placer, pointed_thing)
			if pointed_thing.type ~= "node" then
				return itemstack
			end

			return rotate_and_place(itemstack, placer, pointed_thing)
		end,
	})

end


-- Stair/slab registration function.
-- Nodes will be called lzr_stairs:{stair,slab}_<subname>

function lzr_stairs.register_stair_and_slab(subname, recipeitem, groups, images,
		desc_stair, desc_slab, sounds, worldaligntex,
		desc_stair_inner, desc_stair_outer)
	lzr_stairs.register_stair(subname, recipeitem, groups, images, desc_stair,
		sounds, worldaligntex)
	lzr_stairs.register_stair_inner(subname, recipeitem, groups, images,
		desc_stair, sounds, worldaligntex, desc_stair_inner)
	lzr_stairs.register_stair_outer(subname, recipeitem, groups, images,
		desc_stair, sounds, worldaligntex, desc_stair_outer)
	lzr_stairs.register_slab(subname, recipeitem, groups, images, desc_slab,
		sounds, worldaligntex)
end

-- Register default stairs and slabs
dofile(minetest.get_modpath("lzr_stairs").."/register.lua")
-- Node aliases for backwards-compatibility
dofile(minetest.get_modpath("lzr_stairs").."/aliases.lua")
