Simulation = {}

function Simulation:new()
	local o = {
		definitions = {},
		wires = {},
		gates = {},

		nwires = 0,
		ngates = 0,
		ninports = 0,
		noutports = 0,

		groupqueue = {},
		groupfxqueue = {},
		gatequeue = {},
		initqueue = {},
		inputqueue = {},
		tickqueue = {},

		callbacks = {},

		currenttick = 0
	}
	setmetatable(o, self)
	self.__index = self
	return o
end

function Simulation:addtoworld(obj, x, y, z)
	if self[x] == nil then
		self[x] = {}
	end

	if self[x][y] == nil then
		self[x][y] = {}
	end

	if self[x][y][z] == nil then
		self[x][y][z] = {}
	end

	self[x][y][z][obj] = obj
end

function Simulation:getfromworld(x, y, z)
	if self[x] == nil or self[x][y] == nil or self[x][y][z] == nil then
		return {}
	else
		return self[x][y][z]
	end
end

function Simulation:getdefinitionbyref(objref)
	return self.definitions[objref]
end

function Simulation:getgatebyref(objref)
	return self.gates[objref]
end

function Simulation:getwirebyref(objref)
	return self.wires[objref]
end

function Simulation:addgatedefinition(definition)
	self.definitions[definition.objref] = definition
end

function Simulation:addwire(wire)
	self.wires[wire.objref] = wire

	for x = wire.bounds[1]+1, wire.bounds[4]-1, 2 do
		for z = wire.bounds[3]+1, wire.bounds[6]-1, 2 do
			self:addtoworld(wire, x, wire.bounds[2], z)
			self:addtoworld(wire, x, wire.bounds[5], z)
		end
	end

	for y = wire.bounds[2]+1, wire.bounds[5]-1, 2 do
		for z = wire.bounds[3]+1, wire.bounds[6]-1, 2 do
			self:addtoworld(wire, wire.bounds[1], y, z)
			self:addtoworld(wire, wire.bounds[4], y, z)
		end
	end

	for x = wire.bounds[1]+1, wire.bounds[4]-1, 2 do
		for y = wire.bounds[2]+1, wire.bounds[5]-1, 2 do
			self:addtoworld(wire, x, y, wire.bounds[3])
			self:addtoworld(wire, x, y, wire.bounds[6])
		end
	end

	self.nwires = self.nwires + 1
	self:connectwire(wire)
end

function Simulation:addgate(gate)
	self.gates[gate.objref] = gate

	for k, port in pairs(gate.ports) do
		local offset = port:getconnectionposition()
		self:addtoworld(port, offset[1], offset[2], offset[3])
		self:connectport(port)

		if port.type == PortTypes.input then
			self.ninports = self.ninports + 1
		elseif port.type == PortTypes.output then
			self.noutports = self.noutports + 1
		end
	end

	self.ngates = self.ngates + 1
end

function Simulation:removewire(objref)
	local wire = self.wires[objref]
	if wire ~= nil then
		self.wires[objref] = nil

		for x = wire.bounds[1]+1, wire.bounds[4]-1, 2 do
			for z = wire.bounds[3]+1, wire.bounds[6]-1, 2 do
				sim[x][wire.bounds[2]][z][wire] = nil
				sim[x][wire.bounds[5]][z][wire] = nil
			end
		end
	
		for y = wire.bounds[2]+1, wire.bounds[5]-1, 2 do
			for z = wire.bounds[3]+1, wire.bounds[6]-1, 2 do
				sim[wire.bounds[1]][y][z][wire] = nil
				sim[wire.bounds[4]][y][z][wire] = nil
			end
		end
	
		for x = wire.bounds[1]+1, wire.bounds[4]-1, 2 do
			for y = wire.bounds[2]+1, wire.bounds[5]-1, 2 do
				sim[x][y][wire.bounds[3]][wire] = nil
				sim[x][y][wire.bounds[6]][wire] = nil
			end
		end

		self.nwires = self.nwires - 1
		wire.group:removewire(wire)
	end
end

function Simulation:removegate(objref)
	local gate = self.gates[objref]
	if gate ~= nil then
		for k, port in pairs(gate.ports) do
			local pos = port:getconnectionposition()
			self[pos[1]][pos[2]][pos[3]][port] = nil
			port.group:removeport(port)

			if port.type == PortTypes.input then
				self.ninports = self.ninports - 1
			elseif port.type == PortTypes.output then
				self.noutports = self.noutports - 1
			end
		end
	end

	self.gates[objref] = nil
	self.ngates = self.ngates - 1
end

function Simulation:connectwireat(wire, x, y, z)
	local objs = self:getfromworld(x, y, z)
	for k, obj in pairs(objs) do
		if obj ~= wire and obj.group ~= nil then
			if obj.logictype == 0 and obj.layer == wire.layer then
				if obj.layer == wire.layer then
					obj.group:addwire(wire)
				end
			elseif obj.logictype == 1 then
				obj.group:addwire(wire)
			end
		end
	end
end

function Simulation:connectwire(wire)
	for x = wire.bounds[1]+1, wire.bounds[4]-1, 2 do
		for z = wire.bounds[3]+1, wire.bounds[6]-1, 2 do
			self:connectwireat(wire, x, wire.bounds[2], z)
			self:connectwireat(wire, x, wire.bounds[5], z)
		end
	end

	for y = wire.bounds[2]+1, wire.bounds[5]-1, 2 do
		for z = wire.bounds[3]+1, wire.bounds[6]-1, 2 do
			self:connectwireat(wire, wire.bounds[1], y, z)
			self:connectwireat(wire, wire.bounds[4], y, z)
		end
	end

	for x = wire.bounds[1]+1, wire.bounds[4]-1, 2 do
		for y = wire.bounds[2]+1, wire.bounds[5]-1, 2 do
			self:connectwireat(wire, x, y, wire.bounds[3])
			self:connectwireat(wire, x, y, wire.bounds[6])
		end
	end
	
	if wire.group == nil then
		Group:new():addwire(wire)
	end
end

function Simulation:connectport(port)
	local connpos = port:getconnectionposition()
	local objs = self:getfromworld(connpos[1], connpos[2], connpos[3])
	for k, obj in pairs(objs) do
		if obj ~= port and obj.group ~= nil then
			obj.group:addport(port)
		end
	end

	if port.group == nil then
		Group:new():addport(port)
	end
end

function Simulation:queuegate(gate)
	self.gatequeue[gate] = gate
end

function Simulation:queuegatelater(gate, delay)
	local tick = self.currenttick + delay
	if self.tickqueue[tick] == nil then
		self.tickqueue[tick] = {}
	end
	table.insert(self.tickqueue[tick], gate)
end

function Simulation:queuegateinput(gate, argv)
	self.inputqueue[gate] = self.inputqueue[gate] or {}
	table.insert(self.inputqueue[gate], argv)
end

function Simulation:queuegateinit(gate)
	self.initqueue[gate] = gate
end

function Simulation:queuegroup(group)
	self.groupqueue[group] = group
end

function Simulation:queuegroupfx(group)
	self.groupfxqueue[group] = group
end

function Simulation:queuecallback(gate, ...)
	self.callbacks[gate.objref] = {...}
end

function Simulation:tick()
	for k, group in pairs(self.groupqueue) do
		local newstate = false
		for j, port in pairs(group.out_ports) do
			newstate = newstate or port.state
			if newstate then
				break
			end
		end
		
		group:setstate(newstate)
	end
	self.groupqueue = {}

	for k, gate in pairs(self.initqueue) do
		gate.definition.init(gate)
	end
	self.initqueue = {}

	for gate, inputs in pairs(self.inputqueue) do
		for inputidx, argv in ipairs(inputs) do
			gate.definition.input(gate, argv)
		end
	end
	self.inputqueue = {}

	if self.tickqueue[self.currenttick] ~= nil then
		for i, gate in ipairs(self.tickqueue[self.currenttick]) do
			self:queuegate(gate)
		end
		self.tickqueue[self.currenttick] = nil
	end

	for k, gate in pairs(self.gatequeue) do
		gate.definition.logic(gate)
	end
	self.gatequeue = {}

	self.currenttick = self.currenttick + 1
end

function Simulation:sendfxupdate()
	for k, group in pairs(self.groupfxqueue) do
		if group.state ~= group.fxstate then
			group.fxstate = group.state
			
			local data = bool_to_int[group.state]
			
			for i, wire in pairs(group.wires) do
				data = data .. "\t" .. wire.objref
			end
			
			client:send("WU\t" .. data .. "\n")
		end
	end
	
	self.groupfxqueue = {}
end

function Simulation:sendcallbacks()
	if next(self.callbacks) ~= nil then
		local data = "CB"
		
		for objref, args in pairs(self.callbacks) do
			local escargs = {}
			for argidx, argv in ipairs(args) do
				table.insert(escargs, expandescape(tostring(argv)))
			end
			data = data .. "\t" .. objref .. "\t"..(#escargs)..(#escargs>0 and ("\t"..table.concat(escargs, "\t")) or "")
		end
		
		client:send(data .. "\n")
		self.callbacks = {}
	end
end