local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local mod = mcl_minecarts
local S = minetest.get_translator(modname)
mod.RAIL_GROUPS = {
	STANDARD = 1,
	CURVES = 2,
}

-- Inport functions and constants from elsewhere
local table_merge = mcl_util.table_merge
local check_connection_rules = mod.check_connection_rules
local update_rail_connections = mod.update_rail_connections
local minetest_fourdir_to_dir = minetest.fourdir_to_dir
local minetest_dir_to_fourdir = minetest.dir_to_fourdir
local vector_offset = vector.offset
local vector_equals = vector.equals
local north = mod.north
local south = mod.south
local east = mod.east
local west = mod.west

--- Rail direction Handlers
local function rail_dir_straight(pos, dir, node)
	dir = vector.new(dir)
	dir.y = 0

	if node.param2 == 0 or node.param2 == 2 then
		if vector_equals(dir, north) then
			return north
		else
			return south
		end
	else
		if vector_equals(dir,east) then
			return east
		else
			return west
		end
	end
end
local function rail_dir_sloped(pos, dir, node)
	local uphill = minetest_fourdir_to_dir(node.param2)
	local downhill = minetest_fourdir_to_dir((node.param2+2)%4)
	local up_uphill = vector_offset(uphill,0,1,0)

	if vector_equals(dir, uphill) or vector_equals(dir, up_uphill) then
		return up_uphill
	else
		return downhill
	end
end
-- Fourdir to cardinal direction
-- 0 = north
-- 1 = east
-- 2 = south
-- 3 = west

-- This takes a table `dirs` that has one element for each cardinal direction
-- and which specifies the direction for a cart to continue in when entering
-- a rail node in the direction of the cardinal. This function takes node
-- rotations into account.
local function rail_dir_from_table(pos, dir, node, dirs)
	dir = vector.new(dir)
	dir.y = 0
	local dir_fourdir = (minetest_dir_to_fourdir(dir) - node.param2 + 4) % 4
	local new_fourdir = (dirs[dir_fourdir] + node.param2) % 4
	return minetest_fourdir_to_dir(new_fourdir)
end

local CURVE_RAIL_DIRS = { [0] = 1, 1, 2, 2, }
local function rail_dir_curve(pos, dir, node)
	return rail_dir_from_table(pos, dir, node, CURVE_RAIL_DIRS)
end
local function rail_dir_tee_off(pos, dir, node)
	return rail_dir_from_table(pos, dir, node, CURVE_RAIL_DIRS)
end

local TEE_RAIL_ON_DIRS = { [0] = 0, 1, 1, 0 }
local function rail_dir_tee_on(pos, dir, node)
	return rail_dir_from_table(pos, dir, node, TEE_RAIL_ON_DIRS)
end

local function rail_dir_cross(pos, dir, node)
	dir = vector.new(dir)
	dir.y = 0

	-- Always continue in the same direction. No direction changes allowed
	return dir
end

-- Setup shared text
local railuse = S(
	"Place them on the ground to build your railway, the rails will automatically connect to each other and will"..
	" turn into curves, T-junctions, crossings and slopes as needed."
)
mod.text = mod.text or {}
mod.text.railuse = railuse
local BASE_DEF = {
	drawtype = "mesh",
	mesh = "flat_track.obj",
	paramtype = "light",
	paramtype2 = "4dir",
	stack_max = 64,
	sounds = mcl_sounds.node_sound_metal_defaults(),
	is_ground_content = true,
	paramtype = "light",
	use_texture_alpha = "clip",
	collision_box = {
		type = "fixed",
		fixed = { -8/16, -8/16, -8/16, 8/16, -7/16, 8/15 }
	},
	selection_box = {
		type = "fixed",
		fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
	},
	groups = {
		handy=1, pickaxey=1,
		attached_node=1,
		rail=1,
		connect_to_raillike=minetest.raillike_group("rail"),
		dig_by_water=0,destroy_by_lava_flow=0,
		transport=1
	},
	_tt_help = S("Track for minecarts"),
	_doc_items_usagehelp = railuse,
	_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. Normal rails slightly slow down minecarts due to friction."),
	on_place = function(itemstack, placer, pointed_thing)
		local node = minetest.get_node(pointed_thing.under)
		local node_name = node.name

		-- Don't allow placing rail above rail
		if minetest.get_item_group(node_name,"rail") ~= 0 then
			return itemstack
		end

		-- Handle right-clicking nodes with right-click handlers
		if placer and not placer:get_player_control().sneak then
			local node_def = minetest.registered_nodes[node_name] or {}
			local on_rightclick = node_def and node_def.on_rightclick
			if on_rightclick then
				return on_rightclick(pointed_thing.under, node, placer, itemstack, pointed_thing) or itemstack
			end
		end

		-- Place the rail
		return minetest.item_place_node(itemstack, placer, pointed_thing)
	end,
	after_place_node = function(pos, placer, itemstack, pointed_thing)
		update_rail_connections(pos)
	end,
	_mcl_minecarts = {
		get_next_dir = rail_dir_straight,
	},
	_mcl_blast_resistance = 0.7,
	_mcl_hardness = 0.7,
}

local SLOPED_RAIL_DEF = table.copy(BASE_DEF)
table_merge(SLOPED_RAIL_DEF,{
	drawtype = "mesh",
	mesh = "sloped_track.obj",
	groups = {
		rail_slope = 1,
		not_in_creative_inventory = 1,
	},
	collision_box = {
		type = "fixed",
		fixed = {
			{ -0.5, -0.5, -0.5,  0.5,  0.0,  0.5 },
			{ -0.5,  0.0,  0.0,  0.5,  0.5,  0.5 }
		}
	},
	selection_box = {
		type = "fixed",
		fixed = {
			{ -0.5, -0.5, -0.5,  0.5,  0.0,  0.5 },
			{ -0.5,  0.0,  0.0,  0.5,  0.5,  0.5 }
		}
	},
	_mcl_minecarts = {
		get_next_dir = rail_dir_sloped,
	},
})

function mod.register_rail(itemstring, ndef)
	assert(ndef.tiles)
	assert(ndef.description)

	-- Extract out the craft recipe
	local craft = ndef.craft
	ndef.craft = nil

	-- Add sensible defaults
	if not ndef.inventory_image then ndef.inventory_image = ndef.tiles[1] end
	if not ndef.wield_image then ndef.wield_image = ndef.tiles[1] end

	--print("registering rail "..itemstring.." with definition: "..dump(ndef))

	-- Make registrations
	minetest.register_node(itemstring, ndef)
	if craft then minetest.register_craft(craft) end
end

local function make_mesecons(base_name, suffix, base_mesecons)
	if not base_mesecons then
		if suffix == "_tee_off" or suffix == "_tee_on" then
			base_mesecons = {}
		else
			return
		end
	end

	local mesecons = table.copy(base_mesecons)

	if suffix == "_tee_off" then
		mesecons.effector = base_mesecons.effector and table.copy(base_mesecons.effector) or {}

		local old_action_on = base_mesecons.effector and base_mesecons.effector.action_on
		mesecons.effector.action_on = function(pos, node)
			if old_action_on then old_action_on(pos, node) end

			node.name = base_name.."_tee_on"
			minetest.set_node(pos, node)
		end
		mesecons.effector.rules = mesecons.effector.rules or mesecon.rules.alldirs
	elseif suffix == "_tee_on" then
		mesecons.effector = base_mesecons.effector and table.copy(base_mesecons.effector) or {}

		local old_action_off = base_mesecons.effector and base_mesecons.effector.action_off
		mesecons.effector.action_off = function(pos, node)
			if old_action_off then old_action_off(pos, node) end

			node.name = base_name.."_tee_off"
			minetest.set_node(pos, node)
		end
		mesecons.effector.rules = mesecons.effector.rules or mesecon.rules.alldirs
	end

	if mesecons.conductor then
		mesecons.conductor = table.copy(base_mesecons.conductor)

		if mesecons.conductor.onstate then
			mesecons.conductor.onstate = base_mesecons.conductor.onstate..suffix
		end
		if base_mesecons.conductor.offstate then
			mesecons.conductor.offstate = base_mesecons.conductor.offstate..suffix
		end
	end

	return mesecons
end


function mod.register_straight_rail(base_name, tiles, def)
	def = def or {}
	local base_def = table.copy(BASE_DEF)
	local sloped_def = table.copy(SLOPED_RAIL_DEF)
	local add = {
		tiles = { tiles[1] },
		drop = base_name,
		groups = {
			rail = mod.RAIL_GROUPS.STANDARD,
		},
		_mcl_minecarts = {
			base_name = base_name,
			can_slope = true,
		},
	}
	table_merge(base_def, add); table_merge(sloped_def, add)
	table_merge(base_def, def); table_merge(sloped_def, def)

	-- Register the base node
	mod.register_rail(base_name, base_def)
	base_def.craft = nil; sloped_def.craft = nil
	table_merge(base_def,{
		_mcl_minecarts = {
			railtype = "straight",
			suffix = "",
		},
	})

	-- Sloped variant
	mod.register_rail_sloped(base_name.."_sloped", table_merge(table.copy(sloped_def),{
		_mcl_minecarts = {
			get_next_dir = rail_dir_sloped,
			suffix = "_sloped",
		},
		mesecons = make_mesecons(base_name, "_sloped", def.mesecons),
		tiles = { tiles[1] },
		_mcl_minecarts = {
			railtype = "sloped",
		},
	}))
end

function mod.register_curves_rail(base_name, tiles, def)
	def = def or {}
	local base_def = table.copy(BASE_DEF)
	local sloped_def = table.copy(SLOPED_RAIL_DEF)
	local add = {
		_mcl_minecarts = { base_name = base_name },
		groups = {
			rail = mod.RAIL_GROUPS.CURVES
		},
		drop = base_name,
	}
	table_merge(base_def, add); table_merge(sloped_def, add)
	table_merge(base_def, def); table_merge(sloped_def, def)

	-- Register the base node
	mod.register_rail(base_name, table_merge(table.copy(base_def),{
		tiles = { tiles[1] },
		_mcl_minecarts = {
			get_next_dir = rail_dir_straight,
			railtype = "straight",
			can_slope = true,
			suffix = "",
		},
	}))

	-- Update for other variants
	base_def.craft = nil
	table_merge(base_def, {
		groups = {
			not_in_creative_inventory = 1
		}
	})

	-- Corner variants
	mod.register_rail(base_name.."_corner", table_merge(table.copy(base_def),{
		tiles = { tiles[2] },
		_mcl_minecarts = {
			get_next_dir = rail_dir_curve,
			railtype = "corner",
			suffix = "_corner",
		},
		mesecons = make_mesecons(base_name, "_corner", def.mesecons),
	}))

	-- Tee variants
	mod.register_rail(base_name.."_tee_off", table_merge(table.copy(base_def),{
		tiles = { tiles[3] },
		mesecons = make_mesecons(base_name, "_tee_off", def.mesecons),
		_mcl_minecarts = {
			get_next_dir = rail_dir_tee_off,
			railtype = "tee",
			suffix = "_tee_off",
		},
	}))
	mod.register_rail(base_name.."_tee_on", table_merge(table.copy(base_def),{
		tiles = { tiles[4] },
		_mcl_minecarts = {
			get_next_dir = rail_dir_tee_on,
			railtype = "tee",
			suffix = "_tee_on",
		},
		mesecons = make_mesecons(base_name, "_tee_on", def.mesecons),
	}))

	-- Sloped variant
	mod.register_rail_sloped(base_name.."_sloped", table_merge(table.copy(sloped_def),{
		description = S("Sloped Rail"), -- Temporary name to make debugging easier
		_mcl_minecarts = {
			get_next_dir = rail_dir_sloped,
			railtype = "tee",
			suffix = "_sloped",
		},
		mesecons = make_mesecons(base_name, "_sloped", def.mesecons),
		tiles = { tiles[1] },
	}))

	-- Cross variant
	mod.register_rail(base_name.."_cross", table_merge(table.copy(base_def),{
		tiles = { tiles[5] },
		_mcl_minecarts = {
			get_next_dir = rail_dir_cross,
			railtype = "cross",
			suffix = "_cross",
		},
		mesecons = make_mesecons(base_name, "_cross", def.mesecons),
	}))
end

function mod.register_rail_sloped(itemstring, def)
	assert(def.tiles)

	-- Build the node definition
	local ndef = table.copy(SLOPED_RAIL_DEF)
	table_merge(ndef, def)

	-- Add sensible defaults
	if not ndef.inventory_image then ndef.inventory_image = ndef.tiles[1] end
	if not ndef.wield_image then ndef.wield_image = ndef.tiles[1] end

	--print("registering sloped rail "..itemstring.." with definition: "..dump(ndef))

	-- Make registrations
	minetest.register_node(itemstring, ndef)
end

-- Redstone rules
mod.rail_rules_long =
{{x=-1,  y= 0, z= 0, spread=true},
 {x= 1,  y= 0, z= 0, spread=true},
 {x= 0,  y=-1, z= 0, spread=true},
 {x= 0,  y= 1, z= 0, spread=true},
 {x= 0,  y= 0, z=-1, spread=true},
 {x= 0,  y= 0, z= 1, spread=true},

 {x= 1, y= 1, z= 0},
 {x= 1, y=-1, z= 0},
 {x=-1, y= 1, z= 0},
 {x=-1, y=-1, z= 0},
 {x= 0, y= 1, z= 1},
 {x= 0, y=-1, z= 1},
 {x= 0, y= 1, z=-1},
 {x= 0, y=-1, z=-1}}

dofile(modpath.."/rails/normal.lua")
dofile(modpath.."/rails/activator.lua")
dofile(modpath.."/rails/detector.lua")
dofile(modpath.."/rails/powered.lua")

-- Aliases
if minetest.get_modpath("doc") then
	doc.add_entry_alias("nodes", "mcl_minecarts:golden_rail", "nodes", "mcl_minecarts:golden_rail_on")
end

local CURVY_RAILS_MAP = {
	["mcl_minecarts:rail"] = "mcl_minecarts:rail_v2",
	["mcl_minecarts:golden_rail"] = "mcl_minecarts:golden_rail_v2",
	["mcl_minecarts:golden_rail_on"] = "mcl_minecarts:golden_rail_v2_on",
	["mcl_minecarts:activator_rail"] = "mcl_minecarts:activator_rail_v2",
	["mcl_minecarts:activator_rail_on"] = "mcl_minecarts:activator_rail_v2_on",
	["mcl_minecarts:detector_rail"] = "mcl_minecarts:detector_rail_v2",
	["mcl_minecarts:detector_rail_on"] = "mcl_minecarts:detector_rail_v2_on",
}
local function convert_legacy_curvy_rails(pos, node)
	node.name = CURVY_RAILS_MAP[node.name]
	if node.name then
		minetest.swap_node(pos, node)
		mod.update_rail_connections(pos, { legacy = true, ignore_neighbor_connections = true })
	end
end
for old,new in pairs(CURVY_RAILS_MAP) do
	local new_def = minetest.registered_nodes[new]
	minetest.register_node(old, {
		drawtype = "raillike",
		inventory_image = new_def.inventory_image,
		groups = { rail = 1, legacy = 1 },
		tiles = { new_def.tiles[1], new_def.tiles[1], new_def.tiles[1], new_def.tiles[1] },
		_vl_legacy_convert_node = convert_legacy_curvy_rails
	})
	vl_legacy.register_item_conversion(old, new)
end
local STRAIGHT_RAILS_MAP ={
}
local function convert_legacy_straight_rail(pos, node)
	node.name = STRAIGHT_RAILS_MAP[node.name]
	if node.name then
		local connections = mod.get_rail_connections(pos, { legacy = true, ignore_neighbor_connections = true })
		if not mod.HORIZONTAL_STANDARD_RULES[connections] then
			-- Drop an immortal object at this location
			local item_entity = minetest.add_item(pos, ItemStack(node.name))
			if item_entity then
				item_entity:get_luaentity()._immortal = true
			end

			-- This is a configuration that doesn't exist in the new rail
			-- Replace with a standard rail
			node.name = "mcl_minecarts:rail_v2"
		end
		minetest.swap_node(pos, node)
		mod.update_rail_connections(pos, { legacy = true, ignore_neighbor_connections = true })
	end
end
for old,new in pairs(STRAIGHT_RAILS_MAP) do
	local new_def = minetest.registered_nodes[new]
	minetest.register_node(old, {
		drawtype = "raillike",
		inventory_image = new_def.inventory_image,
		groups = { rail = 1, legacy = 1 },
		tiles = { new_def.tiles[1], new_def.tiles[1], new_def.tiles[1], new_def.tiles[1] },
		_vl_legacy_convert_node = convert_legacy_straight_rail,
	})
	vl_legacy.register_item_conversion(old, new)
end

