Simulation = inherited("Simulation", SimEntity)

function Simulation:new(sync, primary, alwaysInterpolate)
	local s = instance(self)
	s.ents = {}
	s.entParams = {}
	s.entAssigns = {}
	s.interp = {}
	s.sentLogs = {}
	s.sends = {}
	s.lastInterpolation = 0
	s.interpolationStep = 1
	s.eid = 1
	s.map = nil
	s.sync = sync
	s.assigned = {}
	s.created = {}
	s.createdParams = {}
	s.removeQue = {}
	s.removed = {}
	s.delayedRemove = {}
	s:register(s)
	s.primary = primary
	s.alwaysInterpolate = alwaysInterpolate
	s.time = 0
	return s
end

--[[
                                                                               
88888888ba             88                                                       
88      "8b            ""                                                       
88      ,8P                                                                     
88aaaaaa8P' 8b,dPPYba, 88 88,dPYba,,adPYba,  ,adPPYYba, 8b,dPPYba, 8b       d8  
88""""""'   88P'   "Y8 88 88P'   "88"    "8a ""     `Y8 88P'   "Y8 `8b     d8'  
88          88         88 88      88      88 ,adPPPPP88 88          `8b   d8'   
88          88         88 88      88      88 88,    ,88 88           `8b,d8'    
88          88         88 88      88      88 `"8bbdP"Y8 88             Y88'     
                                                                       d8'      
                                                                      d8'       
]]

function Simulation:remove(ent)
	if DEBUG.showDebug and DEBUG.showGraphs then
		debug.addToGraph("remove", 1)
	end
	if self.primary then
		table.insert(self.removeQue, ent.eid)
		ent._remove = true
	else
		ent:onFakeEvent("remove")
	end	
end

function Simulation:broadcast()
	if self.sync then
		
		if #self.created > 0 then
			self.sync:broadcast("createEnts", self.created, self.createdParams)
		end
		if #self.assigned > 0 then
			self.sync:broadcast("assignEnts", self.assigned)
		end

		for index, ent in pairs(self.ents) do
			if ent ~= self then
				if ent._remove or self:isSendDue(ent) and #self.sends < 50 and ent:getDiffingAmount("sync") > 0 then
					table.insert(self.sends, ent)
				end
			end
		end

		if #self.sends > 0 then
			self.sync:broadcast("syncEnts", self.sends)
			if DEBUG.showDebug and DEBUG.showGraphs then
				debug.addToGraph("sync", #self.sends)
			end
			for index, ent in pairs(self.sends) do
				self:entSent(ent)
			end
			for i=1,#self.sends do
				table.remove(self.sends, 1)
			end
		end

		if #self.removed > 0 then
			self.sync:broadcast("removeEnts", self.removed)
		end

		for index, player in pairs(self.sync.players) do
			if not player.team then
				self.sync:request(player, "giveOptions", "Select Team", self.map:getTeamOptions())
			else
				if not player.pilot then
					for index, pilot in pairs(player.team.pilots) do
						if pilot.playerId == player.id then
							self.sync:broadcast("updatePlayer", player.guid, {pilot = pilot})
						end
					end
				end
			end
		end
	end
end


function GameState:selectOption(player, optionsId, option)
	if self.sync then
		if optionsId == "Select Team" then
			self.sync:broadcast("updatePlayer",player.guid,{team = option.ent})
		else
			print(tostring(optionsId), "unsupported option id")
		end
	end
end

function GameState:assignPilot(player, pilot)

end

function Simulation:wasAssigned(parent, index, child)
	if self.sync then
		if not self.entAssigns[child.eid] then
			self.entAssigns[child.eid] = {}
		end
		local assignment = {parent = parent, index = index, child = child}
		table.insert(self.entAssigns[child.eid], assignment)
	end
end

function Simulation:entSent(ent)
	ent:saveVariables("sync")
	self.sentLogs[ent.eid] = self.time
end

function Simulation:isSendDue(ent)
	if not self.sentLogs[ent.eid] then
		return true
	else
		if self.time - self.sentLogs[ent.eid] > ent:getStepFrequency() then
			return true
		end
	end
end

function Simulation:sendTo(player)
	if self.sync then
		local created = {}
		local createdParams = {}
		local assigns = {}

		local eids = {}
		for index, ent in pairs(self.ents) do
			table.insert(eids, ent.eid)
		end
		shellsort(eids, "<")

		for index, eid in ipairs(eids) do
			if eid ~= self.eid then
				table.insert(created, eid)
				table.insert(createdParams, self.entParams[eid])
				--print("SENDING", eid)
				if self.entAssigns[eid] ~= nil then
					for index, assign in ipairs(self.entAssigns[eid]) do
						table.insert(assigns, assign)
					end
				end
			end
		end

		if #created > 0 then
			self.sync:sendTo(player, "createEnts", created, createdParams)
		end
		if #assigns > 0 then
			self.sync:sendTo(player, "assignEnts", assigns)
		end
	end
end

function Simulation:clearRemoved()
	if self.primary then
		for i=1, #self.removed do
			table.remove(self.removed, 1)
		end
	end
end

function Simulation:clearAssigned()
	if self.primary then
		for i=1, #self.assigned do
			table.remove(self.assigned, 1)
		end
	end
end

function Simulation:create(classRef, ...)
	if self.sync then
		local inst = classRef:new(...)
		if inst then
			inst.eid = self.sync:generateEid()
			self.ents[inst.eid] = inst

			local params = {...}
			table.insert(params, 1, classRef)
			table.insert(params, 2, inst.eid)

			table.insert(self.created, inst.eid)
			table.insert(self.createdParams, params)
			self.entParams[inst.eid] = params
			return inst
		else
			print("Failed to create new of ", getClassName(classRef))
		end
	else
		print("Cannot create new instances as interpolated or client:", getClassName(classRef))
	end
end

function Simulation:clearCreated()
	for i=1, #self.created do
		table.remove(self.created, 1)
		table.remove(self.createdParams, 1)
	end
end

function Simulation:createProxy(sim, eid)
	if self.ents[eid] then
		local proxy = self.ents[eid]:newProxy(sim)
		proxy.eid = eid

		return proxy
	else
		print(eid, "is not a valid entity id")
	end
end

function Simulation:setMap(width,height,seed)
	local map = self:create(TerrainMap,self,width,height,seed)
	self:assignEnt(self, "map", map)

	for i=1,2 do
		local x,y = self.map:getStartingLocation(i)
		local team = self:create(Team, self.map, i, x, y)
		team:spawnBuildings()
	end
end

function Simulation:update(time)
	self.time = self.time + time
	self:processRemoveQue()

	

	if self.sync then
		for index, ent in pairs(self.ents) do
			ent:step(time)
			if DEBUG.showDebug and DEBUG.showGraphs then
				debug.addToGraph("ent", 1)
			end
		end

		for index, player in pairs(self.sync.players) do
			if player.team then
				if not player.pilot then
					local pilot = self:create(Pilot, player.team, player.nick, player.id)
				end
			end
		end
	end

	if self.map then
		self.map:updateFx(time)
	end
end

function Simulation:step(time)
	if self.map and false then
		if math.random() < 55.5*time then
			self:create(Particle, self.map, 0, 0, math.randomGaussian()*135, math.randomGaussian()*135)
		end
	end
end

function Simulation:clearChanges()
	self:clearRemoved()
	self:clearCreated()
	self:clearAssigned()
end

--[[


                                                                                                                                     
  ,ad8888ba,  88 88                                  88888888ba             88                                                       
 d8"'    `"8b 88 ""                         ,d       88      "8b            ""                                                       
d8'           88                            88       88      ,8P                                                                     
88            88 88  ,adPPYba, 8b,dPPYba, MM88MMM    88aaaaaa8P' 8b,dPPYba, 88 88,dPYba,,adPYba,  ,adPPYYba, 8b,dPPYba, 8b       d8  
88            88 88 a8P_____88 88P'   `"8a  88       88""""""'   88P'   "Y8 88 88P'   "88"    "8a ""     `Y8 88P'   "Y8 `8b     d8'  
Y8,           88 88 8PP""""""" 88       88  88       88          88         88 88      88      88 ,adPPPPP88 88          `8b   d8'   
 Y8a.    .a8P 88 88 "8b,   ,aa 88       88  88,      88          88         88 88      88      88 88,    ,88 88           `8b,d8'    
  `"Y8888Y"'  88 88  `"Ybbd8"' 88       88  "Y888    88          88         88 88      88      88 `"8bbdP"Y8 88             Y88'     
                                                                                                                            d8'      
                                                                                                                           d8'       

]]

function Simulation:processRemoveQue()
	for i=1,#self.removeQue do
		local removeEid = self.removeQue[1]
		self.ents[removeEid] = nil
		self.entParams[removeEid] = nil
		self.entAssigns[removeEid] = nil
		table.insert(self.removed, removeEid)
		table.remove(self.removeQue, 1)
	end
end

function Simulation:implement(eid, classRef, ...)
	local inst = classRef:new(...)
	if inst then
		inst.eid = eid
		self.ents[inst.eid] = inst
		table.insert(self.created, inst.eid)
		return inst
	else
		print("Failed to implement new of ", getClassName(classRef))
	end
end

function Simulation:assignEnt(parent, index, child)
	if child then
		parent[index] = child
		self:wasAssigned(parent, index, child)
	else
		parent[index] = nil
	end
	table.insert(self.assigned, {parent = parent, index = index, child = child or false})
end

--[[

                                                                                                                
88                                                                 88                                       88  
88              ,d                                                 88              ,d                       88  
88              88                                                 88              88                       88  
88 8b,dPPYba, MM88MMM ,adPPYba, 8b,dPPYba, 8b,dPPYba,   ,adPPYba,  88 ,adPPYYba, MM88MMM ,adPPYba,  ,adPPYb,88  
88 88P'   `"8a  88   a8P_____88 88P'   "Y8 88P'    "8a a8"     "8a 88 ""     `Y8   88   a8P_____88 a8"    `Y88  
88 88       88  88   8PP""""""" 88         88       d8 8b       d8 88 ,adPPPPP88   88   8PP""""""" 8b       88  
88 88       88  88,  "8b,   ,aa 88         88b,   ,a8" "8a,   ,a8" 88 88,    ,88   88,  "8b,   ,aa "8a,   ,d88  
88 88       88  "Y888 `"Ybbd8"' 88         88`YbbdP"'   `"YbbdP"'  88 `"8bbdP"Y8   "Y888 `"Ybbd8"'  `"8bbdP"Y8  
                                           88                                                                   
                                           88                                                               

]]

function Simulation:hasEid(eid)
	return self.ents[eid]
end

function Simulation:getProxy(forEnt)
	if forEnt then
		local prox = self:getEnt(forEnt.eid)
		if prox then
			return prox
		else
			print("cannot find proxy for ", getClassNameOf(forEnt), forEnt.eid)
		end
	else
		debug.printtrace()
		print("cannot get proxy!")
	end
end

function Simulation:register(entity)
	if entity.eid then
		self.ents[entity.eid] = entity
	else
		print("cannot register entity without eid")
		printtable(entity)
	end
end

function Simulation:interpolateEnt(interpolationEnt, goalEnt)
	if interpolationEnt then
		local interp = interpolationEnt:getInterpolation(self.time)
		interpolationEnt:become(goalEnt, self.time, interp)
	end
end

function Simulation:updateTowards(to, time)
	self.time = self.time + time

	if to.removed then
		for index, rem in pairs(to.removed) do
			table.insert(self.delayedRemove, rem)
		end
	end

	local removed = true
	while removed do
		removed = false
		for index, eid in ipairs(self.delayedRemove) do
			if self:hasEid(eid) then
				local ent = self:getEnt(eid)
				if ent then
					local interp = ent:getInterpolation(self.time)
					if interp >= 1 then
						ent:remove()
						self.ents[eid] = nil
						table.remove(self.delayedRemove, index)
						removed = true
						if DEBUG.showDebug and DEBUG.showGraphs then
							debug.addToGraph("remove", 1)
						end
						break
					end
				end
			else
				table.remove(self.delayedRemove, index)
				removed = true
			end
			--print("REMOVED PROXY of",getClassNameOf(ent))
		end
	end
	
	if self.map then
		self.map:updateFx(time)
	end

	for index, ent in pairs(self.ents) do
		if DEBUG.showDebug and DEBUG.showGraphs then
			debug.addToGraph("fakestep", 1)
		end
		ent:fakeStep(time)
	end

	----IF TO CHANGED
	if self.alwaysInterpolate then
		for index, ent in pairs(self.ents) do
			if DEBUG.showDebug and DEBUG.showGraphs then
				debug.addToGraph("entInt", 1)
			end
			if to:hasEid(ent.eid) then
				local t = to:getProxy(ent)
				if t then
					self:interpolateEnt(ent, t)
				end
			end
		end
	end
	
	for index, eid in ipairs(to.created) do
		local proxy = to:createProxy(self, eid)
		if proxy then
			if not self.primary then
				proxy:onFakeEvent("create")
			end
			self:register(proxy)
			self:interpolateEnt(proxy, to:getEnt(eid))
			--print("CREATED PROXY of",getClassNameOf(proxy))
		end
	end
	

	for index, def in ipairs(to.assigned) do
		if def.child then
			self:getProxy(def.parent)[def.index] = self:getProxy(def.child)
			--printtable(self:getProxy(def.parent))
			--printtable(self:getProxy(def.child))
			--print("ASSIGNED ", def.index, " of type ", getClassNameOf(def.child))
		else
			self:getProxy(def.parent)[def.index] = nil
			--print("DEASSIGNED ", index)
		end
	end
	
	self.interpolationStep = math.max(APP.stepTime, self.time - self.lastInterpolation)
	self.lastInterpolation = self.time
end

function Simulation:become(target)
end




--[[
                                                                          
  ,ad8888ba,                                                          88  
 d8"'    `"8b                                                         88  
d8'                                                                   88  
88             ,adPPYba, 8b,dPPYba,   ,adPPYba, 8b,dPPYba, ,adPPYYba, 88  
88      88888 a8P_____88 88P'   `"8a a8P_____88 88P'   "Y8 ""     `Y8 88  
Y8,        88 8PP""""""" 88       88 8PP""""""" 88         ,adPPPPP88 88  
 Y8a.    .a88 "8b,   ,aa 88       88 "8b,   ,aa 88         88,    ,88 88  
  `"Y88888P"   `"Ybbd8"' 88       88  `"Ybbd8"' 88         `"8bbdP"Y8 88  
                                                                          

]]

function Simulation:getEnt(eid)
	local ent = self.ents[eid]
	if ent then
		return ent
	else
		debug.printtrace()
		print("primary: ", self.primary)
		print(eid, "ent could not be found")
	end
end

--[[

                                                                     
88888888ba                                 88                        
88      "8b                                88                        
88      ,8P                                88                        
88aaaaaa8P' ,adPPYba, 8b,dPPYba,   ,adPPYb,88  ,adPPYba, 8b,dPPYba,  
88""""88'  a8P_____88 88P'   `"8a a8"    `Y88 a8P_____88 88P'   "Y8  
88    `8b  8PP""""""" 88       88 8b       88 8PP""""""" 88          
88     `8b "8b,   ,aa 88       88 "8a,   ,d88 "8b,   ,aa 88          
88      `8b `"Ybbd8"' 88       88  `"8bbdP"Y8  `"Ybbd8"' 88          
                                                                     

]]

function Simulation:renderAt(camera,x,y,scale, angle, a, r, g, b)
	if self.map then
		self.map:renderAt(camera,x-camera.x*camera.zoom, y-camera.y*camera.zoom, scale*camera.zoom, angle, a, r, g, b)
	end

	for index, ent in pairs(self.ents) do
		video.renderTextSprites(index.." "..getClassNameOf(ent), canvas.w -10, 10 + index*15, 2, "small", 255, 255, 255, 255, nil, 0.7)
	end
end

function Simulation:renderInterpolatedAt(camera,x,y,scale, angle, a, r, g, b)
	if self.map then
		self.map:renderBackgroundAt(camera,x-camera.x*camera.zoom, y-camera.y*camera.zoom, scale*camera.zoom, angle, a, r, g, b)
		self.map:renderInterpolatedRawAt(camera,x, y, scale*camera.zoom, angle, a, r, g, b)
	end

	--for index, ent in pairs(self.ents) do
	--	video.renderTextSprites(index.." "..getClassNameOf(ent), 10, 10 + index*15, 0, "small", 255, 255, 255, 255, nil, 0.7)
	--end
end