local ffi = FFI or require("ffi")

Group = {}

ffi.cdef [[
	struct Gate;
	struct Net {
		int* state;
		int* state_num;
		int* in_queue;
		int* update_tick;
		int* num_gates_update;
		struct Gate** gates_update_c;
		int id;
	};
]]

local last_net_id = 1
function Group.new()
	local net = {
		-- Logic Critical
		state = ffi.new("int[1]"),
		state_num = ffi.new("int[1]"),
		in_queue = ffi.new("int[1]"),
		update_tick = ffi.new("int[1]"),
		gates_update = {},
		num_gates_update = ffi.new("int[1]"),
		gates_update_c = nil,
		
		fxstate = 0,
		
		wires = {},
		out_ports = {},
		in_ports = {},
		nwires = 0,
		nout_ports = 0,
		nin_ports = 0,
		
		id = last_net_id,
	}
	last_net_id = last_net_id + 1
	
	net.state[0] = 0
	net.state_num[0] = 0
	net.in_queue[0] = 0
	net.update_tick[0] = 0
	net.gates_update_c = ffi.cast("struct Gate**", 0)
	
	net.c = ffi.new("struct Net")
	net.c.state = net.state
	net.c.state_num = net.state_num
	net.c.in_queue = net.in_queue
	net.c.update_tick = net.update_tick
	net.c.num_gates_update = net.num_gates_update
	net.c.gates_update_c = net.gates_update_c
	
	net.c.id = net.id
	
	return net
end

function Group.getsize(group)
	return group.nwires + group.nout_ports + group.nin_ports
end

function Group.addwire(group, wire)
	if Wire.getgroup(wire) ~= group then
		if Wire.getgroup(wire) ~= nil then
			Group.mergewith(group, Wire.getgroup(wire))
		else
			group.wires[wire] = wire
			group.nwires = group.nwires + 1
			
			Wire.setgroup(wire, group)
			Wire.update(wire)
			
			Simulation.queuegroup_safe(GSim, group)
		end
	end
end

function Group.removewire(group, wire)
	Wire.setgroup(wire, nil)
	group.wires[wire] = nil
	
	local sim = GSim
	
	for k, wire in pairs(group.wires) do
		Wire.setgroup(wire, nil)
	end
	
	for k, port in pairs(group.out_ports) do
		Port.setgroup(port, nil)
	end
	
	for k, port in pairs(group.in_ports) do
		Port.setgroup(port, nil)
	end
	
	for k, wire in pairs(group.wires) do
		Simulation.connectwire(sim, wire)
	end
	
	for k, port in pairs(group.out_ports) do
		Simulation.connectport(sim, port)
	end
	
	for k, port in pairs(group.in_ports) do
		Simulation.connectport(sim, port)
	end
	
	group.wires = {}
	group.out_ports = {}
	group.in_ports = {}
	
	group.nwires = 0
	group.nout_ports = 0
	group.nin_ports = 0
	
	Simulation.remove_net(GSim, group)
	Simulation.dequeuegroup(GSim, group)
end

function Group.addport(group, port)
	if port.group~=nil then error("port already has group") end
	Port.setgroup(port, group)
	
	if port.type == PortTypes.output then
		if group.out_ports[port] then error("port already in group") end
		
		group.out_ports[port] = port
		group.nout_ports = group.nout_ports + 1
		group.state_num[0] = group.state_num[0] + Port.getstate(port)
		
		Simulation.queuegroup_safe(GSim, group)
		
	elseif port.type == PortTypes.input then
		if group.in_ports[port] then error("port already in group") end
		
		group.in_ports[port] = port
		group.nin_ports = group.nin_ports + 1
		
		Simulation.queuegate_safe(GSim, Port.getgate(port))
		
	end
	
	Group.rebuild_ports(group)
end

function Group.removeport(group, port)
	if port.group~=group then error("port does not have group") end
	Port.setgroup(port, nil)
	
	if port.type == PortTypes.output then
		if not group.out_ports[port] then error("port not in group") end
		group.out_ports[port] = nil
		group.nout_ports = group.nout_ports - 1
		
		group.state_num[0] = group.state_num[0] - Port.getstate(port)
		
		Simulation.queuegroup_safe(GSim, group)
		
	elseif port.type == PortTypes.input then
		if not group.in_ports[port] then error("port not in group") end
		
		group.in_ports[port] = nil
		group.nin_ports = group.nin_ports - 1
		
		Simulation.queuegate_safe(GSim, Port.getgate(port))
	end
	
	Group.rebuild_ports(group)
end

function Group.mergewith(group, group2)
	if Group.getsize(group) >= Group.getsize(group2) then
		Group.mergeinto(group2, group)
		return group
	else
		Group.mergeinto(group, group2)
		return group2
	end
end

function Group.mergeinto(group, group2)
	for k, wire in pairs(group.wires) do
		Wire.setgroup(wire, nil)
		Group.addwire(group2, wire)
	end

	for k, port in pairs(group.out_ports) do
		Port.setgroup(port, nil)
		Group.addport(group2, port)
	end

	for k, port in pairs(group.in_ports) do
		Port.setgroup(port, nil)
		Group.addport(group2, port)
	end
	
	group.wires = {}
	group.out_ports = {}
	group.in_ports = {}

	group.nwires = 0
	group.nout_ports = 0
	group.nin_ports = 0
	
	Simulation.remove_net(GSim, group)
	Simulation.dequeuegroup(GSim, group)
end

-- Logic Critical
-- Now implemented in C
--function Group.update_c(cnet, tick)
--	local state = cnet.state_num[0]>0 and 1 or 0
--	if state ~= cnet.state[0] then
--		cnet.state[0] = state
--		cnet.update_tick[0] = tick
--		
--		local len = cnet.num_gates_update[0]-1
--		for i = 0, len do
--			local cgate = cnet.gates_update_c[i]
--			if cgate.in_queue[0]==0 then
--				Simulation.queuegate_c(GSim, cgate)
--			end
--		end
--		
--		--Simulation.queuegroupfx(GSim, net)
--	end
--end

function Group.rebuild_ports(net)
	net.gates_update = {}
	net.num_gates_update[0] = 0
	local gates_seen = {}
	for k, port in pairs(net.in_ports) do
		if port.causeupdate then
			local gate = Port.getgate(port)
			if not gates_seen[gate] then
				gates_seen[gate] = true
				net.gates_update[net.num_gates_update[0]+1] = gate
				net.num_gates_update[0] = net.num_gates_update[0] + 1
			end
		end
	end
	net.gates_update_c = ffi.new("struct Gate*["..(net.num_gates_update[0]+1).."]")
	for i = 0, net.num_gates_update[0]-1 do
		net.gates_update_c[i] = net.gates_update[i+1].c
	end
	net.c.gates_update_c = net.gates_update_c
end