RAILTRACK_WARN_SECTION_LEN = 20
RAILTRACK_ROTATIONS = "FLR"
RAILTRACK_ACCEL_FLAT = -0.5
RAILTRACK_ACCEL_UP = -2
RAILTRACK_ACCEL_DOWN = 2

railtrack = {}

railtrack.default_rail = {
	description = "Rail",
	drawtype = "raillike",
	tiles = {"default_rail.png", "default_rail_curved.png",
		"default_rail_t_junction.png", "default_rail_crossing.png"},
	paramtype = "light",
	sunlight_propagates = true,
	walkable = false,
	is_ground_content = false,
	selection_box = {
		type = "fixed",
		fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
	},
	groups = {bendy = 2, dig_immediate = 2, attached_node = 1,
		connect_to_raillike = minetest.raillike_group("rail")},
	railtype = "rail",
	after_place_node = function(pos, placer, itemstack)
		local meta = minetest.get_meta(pos)
		local def = itemstack:get_definition() or {}
		if def.acceleration then
			meta:set_string("acceleration", def.acceleration)
		end
		local junc = {}
		local contype = meta:get_string("contype") or ""
		local s_cons = meta:get_string("connections") or ""
		if contype == "section" then
			railtrack:warn_section_len(placer, pos, meta)
		elseif s_cons ~= "" then
			local cons = minetest.deserialize(s_cons)
			for _, con in pairs(cons) do
				if railtrack:warn_section_len(placer, con) then
					break
				end
			end
		end
	end,
	on_construct = function(pos)
		railtrack:update_rails(pos)
	end,
	after_destruct = function(pos, oldnode)
		if not minetest.is_singleplayer() then
			if not minetest.check_player_privs(name, {rails=true}) then
				minetest.chat_send_player(name, "Requires rails privilege")
				minetest.set_node(pos, oldnode)
			end
		end
		local cons = railtrack:get_connections(pos)
		for _, p in pairs(cons) do
			railtrack:update_rails(p)
		end
	end,
}

function railtrack:register_rail(name, def)
	for k, v in pairs(railtrack.default_rail) do
		if not def[k] then
			def[k] = v
		end
	end
	def.inventory_image = def.inventory_image or def.tiles[1]
	def.wield_image = def.wield_image or def.tiles[1]
	minetest.register_node(name, def)
end

function railtrack:warn_section_len(player, pos, meta)
	meta = meta or minetest.get_meta(pos)
	local contype = meta:get_string("contype") or ""
	if contype == "section" then
		local s_junc = meta:get_string("junctions") or ""
		if s_junc ~= "" then
			local junc = minetest.deserialize(s_junc)
			if #junc == 2 then
				local dist = railtrack:get_distance(junc[1], junc[2])
				if dist > RAILTRACK_WARN_SECTION_LEN then
					local name = player:get_player_name()
					if name then
						minetest.chat_send_player(name, "Warning, section"
								.." length "..dist.." exceeds the recommended"
								.." maximum of "..RAILTRACK_WARN_SECTION_LEN)
						return true
					end
				end
			end
		end
	end
end

function railtrack:is_railnode(pos)
	local node = minetest.get_node(pos)
	if node then
		return minetest.get_item_group(node.name, "connect_to_raillike") > 0
	end
end

function railtrack:get_sign(z)
	if z == 0 then
		return 0
	else
		return z / math.abs(z)
	end
end

function railtrack:get_rotations(s_rots, dir)
	local rots = {}
	for i = 1, string.len(s_rots) do
		local r = string.sub(s_rots, i, i)
		local rot = nil
		if r == "F" then
			rot = {x=dir.x, z=dir.z}
		elseif r == "L" then
			rot = {x=-dir.z, z=dir.x}
		elseif r == "R" then
			rot = {x=dir.z, z=-dir.x}
		elseif r == "B" then
			rot = {x=-dir.x, z=-dir.z}
		end
		if rot then
			table.insert(rots, rot)
		end
	end
	return rots
end

function railtrack:get_acceleration(pos)
	local meta = minetest.get_meta(pos)
	local accel = meta:get_string("acceleration") or ""
	if accel ~= "" then
		return tonumber(accel)
	end
end

function railtrack:get_direction(p1, p2)
	local v = vector.subtract(p1, p2)
	return {
		x = railtrack:get_sign(v.x),
		y = railtrack:get_sign(v.y),
		z = railtrack:get_sign(v.z),
	}
end

function railtrack:get_distance(p1, p2)
	local dx = p1.x - p2.x
	local dz = p1.z - p2.z
	return math.abs(dx) + math.abs(dz)
end


function railtrack:get_railtype(pos)
	local node = minetest.get_node(pos) or {}
	if node.name then
		local ref = minetest.registered_items[node.name] or {}
		return ref.railtype
	end
end

function railtrack:get_connection_type(pos, cons)
	local railtype = railtrack:get_railtype(pos)
	if #cons == 0 then
		return "single"
	elseif #cons == 1 then
		return "junction"
	elseif #cons == 2 then
		if cons[1].x == cons[2].x or cons[1].z == cons[2].z then
			if (cons[1].y == cons[2].y and cons[1].y == pos.y) or
					(math.abs(cons[1].y - cons[2].y) == 2) then
				if railtype == railtrack:get_railtype(cons[1]) and
						railtype == railtrack:get_railtype(cons[2]) then
					return "section"
				end
			end
		end
	end
	return "junction"
end

function railtrack:get_connections(pos)
	local connections = {}
	for y = -1, 1 do
		for x = -1, 1 do
			for z = -1, 1 do
				if math.abs(x) ~= math.abs(z) then
					local p = vector.add(pos, {x=x, y=y, z=z})
					if railtrack:is_railnode(p) then
						table.insert(connections, p)
					end
				end
			end
		end
	end
	return connections
end

function railtrack:get_junctions(pos, last_pos, junctions)
	junctions = junctions or {}
	local cons = railtrack:get_connections(pos)
	local contype = railtrack:get_connection_type(pos, cons)
	if contype == "junction" then
		table.insert(junctions, pos)
	elseif contype == "section" then
		if last_pos then
			for i, p in pairs(cons) do
				if vector.equals(p, last_pos) then
					cons[i] = nil
				end
			end
		end
		for _, p in pairs(cons) do
			railtrack:get_junctions(p, pos, junctions)
		end
	end
	return junctions
end

function railtrack:set_acceleration(pos, accel)
	local meta = minetest.get_meta(pos)
	local contype = meta:get_string("contype")
	if contype == "section" then
		local s_junc = meta:get_string("junctions") or ""
		local junc = minetest.deserialize(s_junc) or {}
		if #junc == 2 then
			local p = vector.new(junc[2])
			local dir = railtrack:get_direction(junc[1], junc[2])
			local dist = railtrack:get_distance(junc[1], junc[2])
			for i = 0, dist do
				local m = minetest.get_meta(p)
				if m then
					m:set_string("acceleration", tostring(accel))
				end
				p = vector.add(dir, p)
			end
		end
	else
		meta:set_string("acceleration", tostring(accel))
	end
end

function railtrack:update_rails(pos, last_pos, level)
	local connections = {}
	local junctions = {}
	local meta = minetest.get_meta(pos)
	local cons = railtrack:get_connections(pos)
	local contype = railtrack:get_connection_type(pos, cons)
	level = level or 0
	for i, p in pairs(cons) do
		connections[i] = p
	end
	if contype == "junction" then
		level = level + 1
	end
	if contype == "section" or level < 2 then
		if last_pos then
			for i, p in pairs(cons) do
				if vector.equals(p, last_pos) then
					cons[i] = nil
				end
			end
		end
		for _, p in pairs(cons) do
			railtrack:update_rails(p, pos, level)
		end
	end
	if contype == "section" then
		junctions = railtrack:get_junctions(pos)
		connections = {}
	end
	meta:set_string("connections", minetest.serialize(connections))
	meta:set_string("junctions", minetest.serialize(junctions))
	meta:set_string("contype", contype)
end