local ffi = FFI or require("ffi")

Group = {}

ffi.cdef [[
	struct Gate;
	struct Net {
		int* state;
		int* state_num;
		int* in_queue;
		int* update_tick;
		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.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.gates_update_c = ffi.cast("struct Gate**", 0)
	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
function Group.update_net_c(netc, tick)
	local state = netc.state_num[0]>0 and 1 or 0
	if state ~= netc.state[0] then
		netc.state[0] = state
		netc.update_tick[0] = tick
		
		local len = netc.num_gates_update[0]
		for i = 1, len do
			local gatec = netc.gates_update_c[i]
			if gatec and gatec.in_queue[0]==0 then
				Simulation.queue_gate_c(GSim, gatec)
			end
		end
		
		local net = Simulation.net_from_netc(GSim, netc)
		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]).."]")
	for i = 0, net.num_gates_update[0]-1 do
		net.gates_update_c[i] = net.gates_update[i+1].c
	end
end