
local S = technic.getter

local cable_tier = {}

function technic.is_tier_cable(name, tier)
	return cable_tier[name] == tier
end

function technic.get_cable_tier(name)
	return cable_tier[name]
end

function technic.register_cable_tier(name, tier)
	assert(technic.machines[tier], "Tier does not exist")
	assert(type(name) == "string", "Invalid node name")

	cable_tier[name] = tier
end

local function check_connections(pos)
	-- Build a table of all machines
	local machines = {}
	for tier,list in pairs(technic.machines) do
		for k,v in pairs(list) do
			machines[k] = v
		end
	end
	local connections = {}
	local positions = {
		{x=pos.x+1, y=pos.y,   z=pos.z},
		{x=pos.x-1, y=pos.y,   z=pos.z},
		{x=pos.x,   y=pos.y+1, z=pos.z},
		{x=pos.x,   y=pos.y-1, z=pos.z},
		{x=pos.x,   y=pos.y,   z=pos.z+1},
		{x=pos.x,   y=pos.y,   z=pos.z-1}}
	for _,connected_pos in pairs(positions) do
		local name = minetest.get_node(connected_pos).name
		if machines[name] or technic.get_cable_tier(name) then
			table.insert(connections,connected_pos)
		end
	end
	return connections
end

local function clear_networks(pos)
	local node = minetest.get_node(pos)
	local meta = minetest.get_meta(pos)
	local placed = node.name ~= "air"
	local positions = check_connections(pos)
	if #positions < 1 then return end
	local dead_end = #positions == 1
	for _,connected_pos in pairs(positions) do
		local net = technic.cables[minetest.hash_node_position(connected_pos)]
		if net and technic.networks[net] then
			if dead_end and placed then
				-- Dead end placed, add it to the network
				-- Get the network
				local network_id = technic.cables[minetest.hash_node_position(positions[1])]
				if not network_id then
					-- We're evidently not on a network, nothing to add ourselves to
					return
				end
				local sw_pos = minetest.get_position_from_hash(network_id)
				sw_pos.y = sw_pos.y + 1
				local network = technic.networks[network_id]
				local tier = network.tier

				-- Actually add it to the (cached) network
				-- !! IMPORTANT: ../switching_station.lua -> check_node_subp() must be kept in sync
				technic.cables[minetest.hash_node_position(pos)] = network_id
				pos.visited = 1
				if technic.is_tier_cable(node.name, tier) then
					-- Found a cable
					table.insert(network.all_nodes,pos)
				elseif technic.machines[tier][node.name] then
					-- Found a machine
					local eu_type = technic.machines[tier][node.name]
					meta:set_string(tier.."_network", string.format("%.20g", network_id))
					if     eu_type == technic.producer then
						table.insert(network.PR_nodes, pos)
					elseif eu_type == technic.receiver then
						table.insert(network.RE_nodes, pos)
					elseif eu_type == technic.producer_receiver then
						table.insert(network.PR_nodes, pos)
						table.insert(network.RE_nodes, pos)
					elseif eu_type == technic.battery then
						table.insert(network.BA_nodes, pos)
					end
					-- Note: SPECIAL (i.e. switching station) is not traversed!
				end
			elseif dead_end and not placed then
				-- Dead end removed, remove it from the network
				-- Get the network
				local network_id = technic.cables[minetest.hash_node_position(positions[1])]
				if not network_id then
					-- We're evidently not on a network, nothing to add ourselves to
					return
				end
				local network = technic.networks[network_id]

				-- Search for and remove machine
				technic.cables[minetest.hash_node_position(pos)] = nil
				for tblname,table in pairs(network) do
					if tblname ~= "tier" then
						for machinenum,machine in pairs(table) do
							if machine.x == pos.x
							and machine.y == pos.y
							and machine.z == pos.z then
								table[machinenum] = nil
							end
						end
					end
				end
			else
				-- Not a dead end, so the whole network needs to be recalculated
				for _,v in pairs(technic.networks[net].all_nodes) do
					local pos1 = minetest.hash_node_position(v)
					technic.cables[pos1] = nil
				end
				technic.networks[net] = nil
			end
		end
	end
end

local function item_place_override_node(itemstack, placer, pointed, node)
	-- Call the default on_place function with a fake itemstack
	local temp_itemstack = ItemStack(itemstack)
	temp_itemstack:set_name(node.name)
	local original_count = temp_itemstack:get_count()
	temp_itemstack =
		minetest.item_place(temp_itemstack, placer, pointed, node.param2) or
		temp_itemstack
	-- Remove the same number of items from the real itemstack
	itemstack:take_item(original_count - temp_itemstack:get_count())
	return itemstack
end

function technic.register_cable(tier, size)
	local ltier = string.lower(tier)
	cable_tier["technic:"..ltier.."_cable"] = tier

	local groups = {snappy=2, choppy=2, oddly_breakable_by_hand=2,
			["technic_"..ltier.."_cable"] = 1}

	local node_box = {
		type = "connected",
		fixed          = {-size, -size, -size, size,  size, size},
		connect_top    = {-size, -size, -size, size,  0.5,  size}, -- y+
		connect_bottom = {-size, -0.5,  -size, size,  size, size}, -- y-
		connect_front  = {-size, -size, -0.5,  size,  size, size}, -- z-
		connect_back   = {-size, -size,  size, size,  size, 0.5 }, -- z+
		connect_left   = {-0.5,  -size, -size, size,  size, size}, -- x-
		connect_right  = {-size, -size, -size, 0.5,   size, size}, -- x+
	}

	minetest.register_node("technic:"..ltier.."_cable", {
		description = S("%s Cable"):format(tier),
		tiles = {"technic_"..ltier.."_cable.png"},
		inventory_image = "technic_"..ltier.."_cable_wield.png",
		wield_image = "technic_"..ltier.."_cable_wield.png",
		groups = groups,
		sounds = default.node_sound_wood_defaults(),
		drop = "technic:"..ltier.."_cable",
		paramtype = "light",
		sunlight_propagates = true,
		drawtype = "nodebox",
		node_box = node_box,
		connects_to = {"group:technic_"..ltier.."_cable",
			"group:technic_"..ltier, "group:technic_all_tiers"},
		on_construct = clear_networks,
		on_destruct = clear_networks,
	})

	local xyz = {
		["-x"] = 1,
		["-y"] = 2,
		["-z"] = 3,
		["x"] = 4,
		["y"] = 5,
		["z"] = 6,
	}
	local notconnects = {
		[1] = "left",
		[2] = "bottom",
		[3] = "front",
		[4] = "right",
		[5] = "top",
		[6] = "back",
	}
	local function s(p)
		if p:find("-") then
			return p:sub(2)
		else
			return "-"..p
		end
	end
	for p, i in pairs(xyz) do
		local def = {
			description = S("%s Cable Plate"):format(tier),
			tiles = {"technic_"..ltier.."_cable.png"},
			groups = table.copy(groups),
			sounds = default.node_sound_wood_defaults(),
			drop = "technic:"..ltier.."_cable_plate_1",
			paramtype = "light",
			sunlight_propagates = true,
			drawtype = "nodebox",
			node_box = table.copy(node_box),
			connects_to = {"group:technic_"..ltier.."_cable",
				"group:technic_"..ltier, "group:technic_all_tiers"},
			on_construct = clear_networks,
			on_destruct = clear_networks,
		}
		def.node_box.fixed = {
			{-size, -size, -size, size, size, size},
			{-0.5, -0.5, -0.5, 0.5, 0.5, 0.5}
		}
		def.node_box.fixed[1][xyz[p]] = 7/16 * (i-3.5)/math.abs(i-3.5)
		def.node_box.fixed[2][xyz[s(p)]] = 3/8 * (i-3.5)/math.abs(i-3.5)
		def.node_box["connect_"..notconnects[i]] = nil
		if i == 1 then
			def.on_place = function(itemstack, placer, pointed_thing)
				local pointed_thing_diff = vector.subtract(pointed_thing.above, pointed_thing.under)
				local num = 1
				local changed
				for k, v in pairs(pointed_thing_diff) do
					if v ~= 0 then
						changed = k
						num = xyz[s(tostring(v):sub(-2, -2)..k)]
						break
					end
				end
				local crtl = placer:get_player_control()
				if (crtl.aux1 or crtl.sneak) and not (crtl.aux1 and crtl.sneak) and changed then
					local fine_pointed = minetest.pointed_thing_to_face_pos(placer, pointed_thing)
					fine_pointed = vector.subtract(fine_pointed, pointed_thing.above)
					fine_pointed[changed] = nil
					local ps = {}
					for p, _ in pairs(fine_pointed) do
						ps[#ps+1] = p
					end
					local bigger = (math.abs(fine_pointed[ps[1]]) > math.abs(fine_pointed[ps[2]]) and ps[1]) or ps[2]
					if math.abs(fine_pointed[bigger]) < 0.3 then
						num = num + 3
						num = (num <= 6 and num) or num - 6
					else
						num = xyz[((fine_pointed[bigger] < 0 and "-") or "") .. bigger]
					end
				end
				return item_place_override_node(
					itemstack, placer, pointed_thing,
					{name = "technic:"..ltier.."_cable_plate_"..num}
				)
			end
		else
			def.groups.not_in_creative_inventory = 1
		end
		def.on_rotate = function(pos, node, user, mode, new_param2)
			local dir = 0
			if mode == screwdriver.ROTATE_FACE then -- left-click
				dir = 1
			elseif mode == screwdriver.ROTATE_AXIS then -- right-click
				dir = -1
			end
			local num = tonumber(node.name:sub(-1))
			num = num + dir
			num = (num >= 1 and num) or num + 6
			num = (num <= 6 and num) or num - 6
			minetest.swap_node(pos, {name = "technic:"..ltier.."_cable_plate_"..num})
		end
		minetest.register_node("technic:"..ltier.."_cable_plate_"..i, def)
		cable_tier["technic:"..ltier.."_cable_plate_"..i] = tier
	end

	local c = "technic:"..ltier.."_cable"
	minetest.register_craft({
		output = "technic:"..ltier.."_cable_plate_1 5",
		recipe = {
			{"", "", c},
			{c , c , c},
			{"", "", c},
		}
	})

	minetest.register_craft({
		output = c,
		recipe = {
			{"technic:"..ltier.."_cable_plate_1"},
		}
	})
end


local function clear_nets_if_machine(pos, node)
	for tier, machine_list in pairs(technic.machines) do
		if machine_list[node.name] ~= nil then
			return clear_networks(pos)
		end
	end
end

minetest.register_on_placenode(clear_nets_if_machine)
minetest.register_on_dignode(clear_nets_if_machine)

