-- global callbacks
trail = {}

-- Parameters

local FOO = true -- Enable footprints.
local FUNCYC = 0.2 -- Function cycle in seconds.

local TRACHA = minetest.settings:get("trail_hardpack_chance") or 0.1 -- Chance walked dirt/grass is worn and compacted to trail:trail.
local ICECHA = minetest.settings:get("trail_ice_chance") or 0.05 -- Chance walked snowblock is compacted to ice.
local EROSION = minetest.settings:get_bool("trail_erosion", true) -- Enable footprint erosion.
local TRAIL_EROSION = minetest.settings:get_bool("trail_trail_erosion", true) -- Allow hard-packed soil to erode back to dirt
local EROINT = minetest.settings:get("trail_erosion_interval") or 16 -- Erosion interval.
local EROCHA = minetest.settings:get("trail_erosion_chance") or 128 -- Erosion 1/x chance.


-- Utility

local function deep_copy(input)
	if type(input) ~= "table" then
		return input
	end
	local output = {}
	for index, value in pairs(input) do
		output[index] = deep_copy(value)
	end
	return output
end

-- Player positions

local player_pos_previous = {}

minetest.register_on_joinplayer(function(player)
	player_pos_previous[player:get_player_name()] = {x = 0, y = 0, z = 0}
end)

minetest.register_on_leaveplayer(function(player)
	player_pos_previous[player:get_player_name()] = nil
end)

local trails = {}
local erosion = {}

trail.register_trample_node = function(trampleable_node_name, trample_def)
	if trails[trampleable_node_name] then
		minetest.log("error", "[trail] Attempted to call trail.register_trample_node to register trampleable node "
			.. trampleable_node_name ..", which has already been registered as trampleable.")
		return
	end
	local trampleable_node_def = minetest.registered_nodes[trampleable_node_name]
	if trampleable_node_def == nil then
		minetest.log("error", "[trail] Attempted to call trail.register_trample_node with the trampleable node "
			.. trampleable_node_name ..", which has not yet been registered as a node.")
		return
	end

	local trampled_node_name = trample_def.trampled_node_name or trampleable_node_name.."_trampled"
	if not minetest.registered_nodes[trampled_node_name] then
		local trampled_node_def = deep_copy(trampleable_node_def) -- start with a deep copy of the source node's definition
		if trample_def.trampled_node_def_override then -- override any values that need to be overridden explicitly
			for key, value in pairs(trample_def.trampled_node_def_override) do
				trampled_node_def[key] = value
			end
		end

		-- Set up the erosion ABM group
		if EROSION and trample_def.erodes ~= false then
			local groups = trampled_node_def.groups or {}
			groups.trail_erodes = 1
			trampled_node_def.groups = groups
			erosion[trampled_node_name] = trampleable_node_name
		end

		-- If the source node doesn't have a special drop, then set drop to drop a source node rather than dropping a node with a footstep.
		if trampled_node_def.drop == nil then
			trampled_node_def.drop = trampleable_node_name
		end

		-- Modify the +Y tile with a footprint overlay
		if trample_def.add_footprint_overlay ~= false then
			local tiles = trampled_node_def.tiles
			local first_tile = tiles[1]
			local second_tile = tiles[2]
			if second_tile == nil then
				-- The provided node def only has one tile for all sides. We need to only modify the +Y tile,
				-- so we need to copy the original first tile into the second tile slot
				tiles[2] = deep_copy(first_tile)
			end
			if type(first_tile) == "table" then
				first_tile.name = first_tile.name .. "^default_footprint.png"
			elseif type(first_tile) == "string" then
				first_tile = first_tile .. "^default_footprint.png"
			end
			trampled_node_def.tiles[1] = first_tile
		end

		minetest.register_node(":"..trampled_node_name, trampled_node_def)

		-- If hard pack has been defined for this trail type, add it
		local hard_pack_node_name = trample_def.hard_pack_node_name
		if hard_pack_node_name then
			local hard_pack_probability = trample_def.hard_pack_probability or 0.1
			trails[trampled_node_name] = {name=hard_pack_node_name, probability=hard_pack_probability}
		end
	end

	local probability = trample_def.probability or 1
	trails[trampleable_node_name] = {name=trampled_node_name, probability=probability, randomize_trampled_param2 = trample_def.randomize_trampled_param2,}
end

trail.register_erosion = function(source_node_name, destination_node_name)
	if not EROSION then
		return
	end

	if minetest.registered_nodes[source_node_name] == nil then
		minetest.log("error", "[trail] attempted to call trail.register_erosion with unregistered source node "
			.. source_node_name)
		return
	end
	if minetest.registered_nodes[destination_node_name] == nil then
		minetest.log("error", "[trail] attempted to call trail.register_erosion with unregistered destination node "
			.. destination_node_name)
		return
	end
	if minetest.get_item_group(source_node_name, "trail_erodes") == 0 then
		minetest.log("error", "[trail] attempted to call trail.register_erosion with source node "
			.. destination_node_name .. " that wasn't in group trail_erodes.")
		return
	end

	erosion[source_node_name] = destination_node_name
end

-- Nodes

-- hard-packed soil
local trail_trail_def = {
	tiles = {"trail_trailtop.png", "default_dirt.png",
		"default_dirt.png^trail_trailside.png"},
	groups = {crumbly = 2},
	drop = "default:dirt",
	sounds = default.node_sound_dirt_defaults(),
}
if TRAIL_EROSION then
	trail_trail_def.groups.trail_erodes = 1
end
minetest.register_node("trail:trail", trail_trail_def)
if TRAIL_EROSION then
	trail.register_erosion("trail:trail", "default:dirt")
end

-- Default dirt

trail.register_trample_node("default:dirt", {
	trampled_node_name = "trail:dirt",
	trampled_node_def_override = {description = "Dirt with Footprint",},
	hard_pack_node_name = "trail:trail",
	hard_pack_probability = TRACHA,
})

trail.register_trample_node("default:dirt_with_grass", {
	trampled_node_name = "trail:dirt_with_grass",
	trampled_node_def_override = {description = "Dirt with Grass and Footprint",},
	hard_pack_node_name = "trail:trail",
	hard_pack_probability = TRACHA,
})

trail.register_trample_node("default:dirt_with_dry_grass", {
	trampled_node_name = "trail:dirt_with_dry_grass",
	trampled_node_def_override = {description = "Dirt with Dry Grass and Footprint",},
	hard_pack_node_name = "trail:trail",
	hard_pack_probability = TRACHA,
})

trail.register_trample_node("default:dirt_with_snow", {
	trampled_node_name = "trail:dirt_with_snow",
	trampled_node_def_override = {description = "Dirt with Snow and Footprint",},
	hard_pack_node_name = "trail:trail",
	hard_pack_probability = TRACHA,
})

trail.register_trample_node("default:dirt_with_rainforest_litter", {
	trampled_node_name = "trail:dirt_with_rainforest_litter",
	trampled_node_def_override = {description = "Dirt with Rainforest Litter and Footprint",},
	hard_pack_node_name = "trail:trail",
	hard_pack_probability = TRACHA,
})

trail.register_trample_node("default:dirt_with_coniferous_litter", {
	trampled_node_name = "trail:dirt_with_coniferous_litter",
	trampled_node_def_override = {description = "Dirt with Coniferous Litter and Footprint",},
	hard_pack_node_name = "trail:trail",
	hard_pack_probability = TRACHA,
})

-- Default sand

trail.register_trample_node("default:sand", {
	trampled_node_name = "trail:sand",
	trampled_node_def_override = {description = "Sand with Footprint",},
})

trail.register_trample_node("default:desert_sand", {
	trampled_node_name = "trail:desert_sand",
	trampled_node_def_override = {description = "Desert Sand with Footprint",},
})

trail.register_trample_node("default:silver_sand", {
	trampled_node_name = "trail:silver_sand",
	trampled_node_def_override = {description = "Silver Sand with Footprint",},
})

trail.register_trample_node("default:gravel", {
	trampled_node_name = "trail:gravel",
	trampled_node_def_override = {description = "Gravel with Footprint",},
})

-- Default snow

trail.register_trample_node("default:snowblock", {
	trampled_node_name = "trail:snowblock",
	trampled_node_def_override = {description = "Snow Block with Footprint",},
	hard_pack_node_name = "default:ice",
	hard_pack_probability = ICECHA,
})

trail.register_trample_node("default:snow", {
	trampled_node_name = "trail:snow",
	trampled_node_def_override = {description = "Snow with Footprint",},
})


--[[if minetest.get_modpath("farming") then
	-- Flattened wheat
	minetest.register_node("trail:wheat", {
		description = "Flattened Wheat",
		tiles = {"trail_flat_wheat.png"},
		inventory_image = "trail_flat_wheat.png",
		drawtype = "nodebox",
		paramtype = "light",
		paramtype2 = "facedir",
		buildable_to = true,
		node_box = {
			type = "fixed",
			fixed = {
				{-0.5, -0.5, -0.5, 0.5, -3 / 8, 0.5}
			},
		},
		groups = {snappy = 3, flammable = 2, attached_node = 1},
		drop = "",
		sounds = default.node_sound_leaves_defaults(),
	})

	trail.register_trample_node("farming:wheat_5", {
		trampled_node_name = "trail:wheat",
		randomize_trampled_param2 = true,
	})
	trail.register_trample_node("farming:wheat_6", {
		trampled_node_name = "trail:wheat",
		randomize_trampled_param2 = true,
	})
	trail.register_trample_node("farming:wheat_7", {
		trampled_node_name = "trail:wheat",
		randomize_trampled_param2 = true,
	})
	trail.register_trample_node("farming:wheat_8", {
		trampled_node_name = "trail:wheat",
		randomize_trampled_param2 = true,
	})
end
--]]
-- Globalstep function

if FOO then
	local timer = 0

	local get_param2 = function(trail_def)
		if trail_def.randomize_trampled_param2 then
			return math.random(0,3)
		end
		return 0
	end

	minetest.register_globalstep(function(dtime)
		timer = timer + dtime
		if timer > FUNCYC then
			timer = 0
			for _, player in ipairs(minetest.get_connected_players()) do
				local pos = player:get_pos()
				local player_name = player:get_player_name()
				local current_player_pos = {
					x = math.floor(pos.x + 0.5),
					y = math.floor(pos.y + 0.2),
					z = math.floor(pos.z + 0.5)
				}

				--if player_pos_previous[player_name] == nil then
					--break
				--end

				if current_player_pos.x ~= player_pos_previous[player_name].x or
					current_player_pos.y < player_pos_previous[player_name].y or
					current_player_pos.z ~= player_pos_previous[player_name].z then

					local p_snow = {
						x = math.floor(pos.x + 0.5),
						y = math.floor(pos.y + 1.2),
						z = math.floor(pos.z + 0.5)
					}
					local n_snow = minetest.get_node(p_snow).name

					-- test ground cover first (snow, wheat)
					local trail_def = trails[n_snow]
					if trail_def then
						if math.random() <= trail_def.probability then
							local p_snowpl = {
								x = math.floor(pos.x + 0.5),
								y = math.floor(pos.y + 0.5),
								z = math.floor(pos.z + 0.5)
							}
							minetest.set_node(p_snowpl, {name = trail_def.name, param2 = get_param2(trail_def)})
						end
					else
						local p_ground = {
							x = math.floor(pos.x + 0.5),
							y = math.floor(pos.y + 0.4),
							z = math.floor(pos.z + 0.5)
						}
						local n_ground = minetest.get_node(p_ground).name
						trail_def = trails[n_ground]
						if trail_def and math.random() <= trail_def.probability then
							local p_groundpl = {
								x = math.floor(pos.x + 0.5),
								y = math.floor(pos.y - 0.25),
								z = math.floor(pos.z + 0.5)
							}
							minetest.set_node(p_groundpl, {name = trail_def.name, param2 = get_param2(trail_def)})
						end
					end
				end

				player_pos_previous[player_name] = current_player_pos
			end
		end
	end)
end

-- ABM

if EROSION then
	minetest.register_abm({
		nodenames = {"group:trail_erodes"},
		interval = EROINT,
		chance = EROCHA,
		catch_up = true,
		action = function(pos, node, _, _)
			local nodename = node.name
			local erodes_to = erosion[nodename]
			if erodes_to then
				minetest.set_node(pos, {name = erodes_to})
			else
				minetest.log("error", "[trail] The node " .. nodename .. " is in group trail_erodes but "
					.. " didn't have an erosion target node defined.")
			end
		end
	})
end
