assert(getmetatable(_G)==nil, "_G already has a metatable")
setmetatable(_G, {
	__index = function(t, i) error("attempt to access nil variable "..i, 2) end
})

OPT_SAVE_DIR = arg[1] or error("must specify save location")
OPT_SAVE_DIR = OPT_SAVE_DIR:gsub("\\", "/")
OPT_SAVE_DIR = OPT_SAVE_DIR:gsub("/$", "")
print("Save location set to \""..OPT_SAVE_DIR.."\"")

local socket = require("socket")
local ffi = require("ffi")

dofile("iosafe.lua")

FFI = ffi
dofile("utility.lua")
dofile("simulation.lua")
dofile("group.lua")
dofile("wire.lua")
dofile("gatedef.lua")
dofile("port.lua")
dofile("gate.lua")
dofile("save.lua")
dofile("compile.lua")
FFI = nil

OPT_TICK_ENABLED = true
OPT_TICK_TIME = 0.032
OPT_TICK_MULT = 1
OPT_FX_UPDATES = true
OPT_FX_TIME = 0.032

OPT_TICK_INF = 29

local tickdelay = 0
local ticksperinterval = 0

bool_to_int = {[false] = 0, [true] = 1}

local lastticktime = 0
local ticks = 0
local tickrate = 0
local lastmeasuretime = 0
local lastfxtime = 0

local avgticks = {}
local totalticks = 0

local sim = Simulation.new(Simulation)
GSim = sim

local units = {
	"uHz",
	"mHz",
	"Hz",
	"kHz",
	"MHz",
	"GHz",
}

local function round(x)
	return math.floor(x+0.5)
end

local function unitize(v)
	local unit = 1
	v = v*1000000
	
	while v >= 1000 do
		v = v/1000
		unit = unit+1
	end
	
	local s
	if v >= 100 then
		s = "" .. round(v/10)*10
	elseif v >= 10 then
		s = "" .. round(v)
	elseif v >= 1 then
		s = "" .. round(v*10)/10
		if #s == 1 then s = s .. ".0" end
	else
		s = 0
	end
	
	return s .. " " .. units[unit]
end

function vectotable(vec)
	local tbl = {}
	for comp in string.gmatch(vec, "([^%s]+)") do
		tbl[#tbl+1] = tonumber(comp)
	end
	return tbl
end

function tabletostring(table)
	local str = tostring(table[1])
	for i = 2, #table do
		str = str .. " " .. tostring(table[i])
	end
	return str
end

function toboolean(value)
	local num = tonumber(value)
	if num == 1 then
		return true
	else
		return false
	end
end

local function acceptclient()
	client = server:accept()
	client:settimeout(0)
	local ip, port = client:getsockname()
	print("Connection from " .. ip .. ":" .. port)
end

server = assert(socket.bind("*", 25000))
client = nil

local ip, port = server:getsockname()
print("Server listening on " .. ip .. ":" .. port)

acceptclient()

while 1 do
	local line, err = client:receive()
	
	if not err then
		local data = {}
		local i = 1
		line = line:gsub(";;", "; ;")
		line = line:gsub(";$", "; ")
		
		for str in string.gmatch(line, "([^;]+)") do
			data[i] = str or ""
			i = i + 1
		end
		
		local i = 1
		while i <= #data do
			if data[i] == "W" then
				local min = vectotable(data[i+3])
				local max = vectotable(data[i+4])
				local bounds = {min[1], min[2], min[3], max[1], max[2], max[3]}
				
				local wire = Wire.new(Wire, tonumber(data[i+1]), tonumber(data[i+2]), bounds, sim)
				Simulation.addwire(sim, wire)
				
				i = i + 4
			elseif data[i] == "G" then
				local objref = tonumber(data[i+1])
				local definition = Simulation.getdefinitionbyref(sim, tonumber(data[i+2]))
				
				assert(definition, "No gate definition for objref "..objref)
				
				local position = vectotable(data[i+3])
				local rotation = tonumber(data[i+4])
				local gate = GateDefinition.constructgate(definition, objref, position, rotation, sim)
				
				Simulation.addgate(sim, gate)
				--print(gate.objref)
				Gate.init(gate)
				Gate.logic(gate)
				
				i = i + 4
			elseif data[i] == "RW" then
				Simulation.removewire(sim, tonumber(data[i+1]))
				i = i + 1
			elseif data[i] == "RG" then
				Simulation.removegate(sim, tonumber(data[i+1]))
				i = i + 1
			elseif data[i] == "GD" then
				--print("---------------------------------------[[[[")
				--print(table.concat(data, "]]]]\n[[[[", i, math.min(#data, i+100)))
				--print("]]]]---------------------------------------")
				local objref = tonumber(data[i+1])
				local name = data[i+2]
				local desc = data[i+3]
				local init = data[i+4]
				local logic = data[i+5]
				local input = data[i+6]
				local global = data[i+7]
				local numports = tonumber(data[i+8])
				local ports = {}
				
				for a = i+9, numports*5+i+8, 5 do
					local portd = {
						type = tonumber(data[a]),
						position = vectotable(data[a+1]),
						direction = tonumber(data[a+2]),
						causeupdate = toboolean(data[a+3]),
						name = data[a+4],
					}
					ports[#ports+1] = portd
					
					if not portd.direction then print(line) end
				end
				
				local definition = GateDefinition.new(GateDefinition, objref, name, desc, init, logic, input, global, ports)
				Simulation.addgatedefinition(sim, definition)
				
				i = i + 8 + numports*5
			elseif data[i] == "SL" then
				local wire = Simulation.getwirebyref(sim, tonumber(data[i+1]))
				if wire ~= nil then
					Wire.setlayer(wire, tonumber(data[i+2]))
				end
				
				i = i + 2
			elseif data[i] == "SP" then
				local gate = Simulation.getgatebyref(sim, tonumber(data[i+1]))
				if gate ~= nil then
					Port.setstate(gate.ports[tonumber(data[i+2])], toboolean(data[i+3]))
				end
				
				i = i + 3
			elseif data[i] == "SG" then
				local wire = Simulation.getwirebyref(sim, tonumber(data[i+1]))
				if wire ~= nil then
					Group.setstate(Wire.getgroup(wire), toboolean(data[i+2]))
				end
				
				i = i + 2
			elseif data[i] == "OPT" then
				local option = data[i+1]
				local value = tonumber(data[i+2])
				
				if option == "TICK_ENABLED" then
					OPT_TICK_ENABLED = toboolean(value)
				elseif option == "TICK_TIME" then
					if value < 0 or value > 999999 then
						value = 0
					end
					if value<=0.001 then value = 0.0001 end
					OPT_TICK_TIME = value
				elseif option == "FX_UPDATES" then
					OPT_FX_UPDATES = toboolean(value)
				elseif option == "FX_TIME" then
					if value < 0 or value > 999999 then
						value = 0
					end
					OPT_FX_TIME = value
				elseif option=="TICK_MULT" then
					OPT_TICK_MULT = value
				end
				
				i = i + 2
			elseif data[i] == "GINFO" then
				local userid = data[i+1]
				local objref = tonumber(data[i+2])
				
				local info = ""
				
				local wire = Simulation.getwirebyref(sim, objref)
				if wire then
					local numportsi = 0; for k, wire2 in pairs(Wire.getgroup(wire).in_ports ) do numportsi = numportsi+1 end
					local numportso = 0; for k, wire2 in pairs(Wire.getgroup(wire).out_ports) do numportso = numportso+1 end
					local numwires  = 0; for k, wire2 in pairs(Wire.getgroup(wire).wires    ) do numwires  = numwires +1 end
					
					info = "\\c5Net " .. tostring(wire.group):match("table: 0x(.+)"):upper() .. "\n" .. (Wire.getgroup(wire).state and "\\c2On" or "\\c0Off") .. "\n" ..
						"Wires: "..numwires.."\n"..
						"In Ports: " ..numportsi.."\n"..
						"Out Ports: "..numportso
					;
				end
				
				local gate = Simulation.getgatebyref(sim, objref)
				if gate then
					local def = Gate.getdefinition(gate)
					info = "\\c5" .. def.name .. "<br>"
					for i = 1, #gate.ports do
						info = info .. (gate.ports[i].state and "\\c2" or "\\c0") .. def.ports[i].name .. (i ~= #gate.ports and " " or "")
					end
				end
				
				if info ~= "" then
					client:send("GINFO\t" .. userid .. "\t" .. expandescape(info) .. "\n")
				end
				
				i = i + 2
			elseif data[i] == "SINFO" then
				client:send("SINFO\t" .. data[i+1] .. "\t" .. sim.nwires .. "\t" .. sim.ngates .. "\t" .. sim.ninports .. "\t" .. sim.noutports .. "\n")
				i = i + 1
			elseif data[i] == "TICK" then
				Simulation.tick(sim)
				ticks = ticks + 1
			elseif data[i] == "IN" then
				local gate = Simulation.getgatebyref(sim, tonumber(data[i+1]))
				local argc = tonumber(data[i+2])
				local argv = {}
				for a = i+3, i+3+argc-1 do
					argv[#argv+1] = collapseescape(data[a])
				end
				if gate then
					Simulation.queuegateinput(sim, gate, argv)
				end
				
				i = i+2+argc
			elseif data[i] == "SAVE" then
				print("saving all data")
				logicsave()
			else
				print("invalid data "..data[i])
			end
			
			i = i + 1
		end
	elseif err == "closed" then
		sim = Simulation.new(Simulation)
		acceptclient()
	end
	
	local time = os.clock()
	
	if OPT_TICK_ENABLED then
		if time-lastticktime >= OPT_TICK_TIME then
			lastticktime = time
			
			if OPT_TICK_TIME==0 then
				for i = 1, OPT_TICK_INF do
					Simulation.tick(sim)
				end
				ticks = ticks+OPT_TICK_INF
			else
				for i = 1, OPT_TICK_MULT do
					Simulation.tick(sim)
					ticks = ticks+1
					
					local elapsed = os.clock()-time
					if elapsed>0.1 then
						break
					end
				end
			end
			
			local sleeptime = time+OPT_TICK_TIME-os.clock()-0.005
			if sleeptime>0 then socket.sleep(sleeptime) end
		end
	else
		socket.sleep(0.05)
	end
	
	if time-lastfxtime >= OPT_FX_TIME then
		if OPT_FX_UPDATES then
			Simulation.sendfxupdate(sim)
		end
		Simulation.sendcallbacks(sim)
		lastfxtime = time
	end
	
	if time-lastmeasuretime >= 0.1 then
		if #avgticks >= 10 then
			totalticks = totalticks - table.remove(avgticks, 1)
		end
		
		table.insert(avgticks, ticks)
		totalticks = totalticks + ticks
		
		ticks = 0
		
		client:send("TPS\t" .. unitize((totalticks/#avgticks)/0.1) .. "\n")
		lastmeasuretime = os.clock()
	end
end