
function startTimeFrame()
	bench.push("frame")
	game.lastTime  = ltimer.getTime()
	game.frameGarbageStart = collectgarbage("count")
end
function endTimeFrame()
	game.frameTimeIter = (game.frameTimeIter and game.frameTimeIter<5) and game.frameTimeIter+1 or 0
	game.frameTimes[game.frameTimeIter] = ltimer.getTime()-(game.lastTime or 0)
	game.frameGarbage= collectgarbage("count")-game.frameGarbageStart
	bench.pop("frame")
end


function game.update(dt)
	traceReset()
	bench.run()
	manualGC(0.1,120,false)
	startTimeFrame()
	bench.push("update")

	if cmdDown and keypressed("f") then
		lwindow.setFullscreen(not lwindow.getFullscreen(),"desktop")
	end

	gfx.setDefaultFilter( "nearest", "nearest" )
	mouse.set(nothing)
	controls:keysUpdate()
	toggleDeveloper()
	resetDraws()
	pollWaits()
	effects.updateTweens()
	effects.toggleResolution()

	if 		  game.state == "Load" 	then 	loader:loadGame ()
	elseif 	game.state == "Menu" 	then 	menu:updateMenu ()
	elseif 	game.state == "Game" 	then	game.runGame	(dt)
	elseif 	game.state == "Edit" 	then 	game.runEditor	(dt)	 	
	end

	mouse.reset()
	keyboard:reset()

	bench.pop("update")
	screenBox = {x=0,y=0,w=screenWidth,h=screenHeight}
end

function game.draw()
	bench.push("draw")
	

		updateShaders()
		drawSetBackground()
		renderViews()
		renderOther()
		renderDebug() 
		renderGame()
		renderLightDebug()
		clearCanvases()
		changeRenderRes()

	-- _test_line = _test_line or {}
	-- _test_line2 = _test_line2 or {}
	-- if keyDown("i") and mf(time*5)~=mf(time*5+main_dt*5) then 
	-- 	table.insert(_test_line,mouse.x)
	-- 	table.insert(_test_line,mouse.y)
	-- end
	-- if keyDown("o") and mf(time*5)~=mf(time*5+main_dt*5) then 
	-- 	table.insert(_test_line2,mouse.x)
	-- 	table.insert(_test_line2,mouse.y)
	-- end
	-- gfx.setColor(1,0,0,1)
	-- if #_test_line>3  then 
	-- 	gfx.line(_test_line)
	-- 	if #_test_line2>3 then 
	-- 		gfx.line(_test_line2)
	-- 	end
	-- 	gfx.push()
	-- 	gfx.translate(300,0)
	-- 	if #_test_line>5 and #_test_line2>5 then 
	-- 		gfx.polygon("line",fuseLines({_test_line,_test_line2}))
	-- 	end
	-- 	gfx.pop()
	-- end
	if editor.colorTip and editor.colorTip.sliders and editor.colorTip.sliders.h then 
		print("grad")
		gfx.draw(editor.colorTip.sliders.h)
	end
	bench.pop("draw")
	endTimeFrame()

end

local tempShadowMap;
shadowMapTester = shadowMapTester or newCanvas(512,512)
shadowMapTester:renderTo(function()gfx.clear(1,1,1,1)end)

function game.enterGame(key)
	CONFIG_FILE = (LINK_DIR.."/.config"):gsub("//","/")
	print(CONFIG_FILE)
	local cfg = lfs.read(CONFIG_FILE)
	print("cfg",cfg)
	game.config =Tserial.unpack( cfg)
	
	tempShadowMap = newCanvas(256,256,{format = "depth16",readable=true})
	shaders.forwardRender:send ('alpha' , 1 )
	
	print("loading level")
	
	level = levels[key];
	
	if developer then 
		editor.start()
	else 
		game.start()
	end
	
	setResolution()
	scene.setPatterns()
	
	game.reloadLevelData()

	level.connections 	=level.connections or {}
	level.sets[level.defaultSet].views = level.sets[level.defaultSet].views or level.sets[level.defaultSet].rooms
	level.sets[level.defaultSet].rooms=nil
	game.currentRooms   = level.sets[level.defaultSet].views
	game.currentRoom    = game.currentRooms[1]

	editor.defineEditor ()

	scene.load()
	setCanvases()
	game.unPause()
	
	if game.config.isAnim then 
		level.isAnim 		= true
	end
	
	if level.isAnim then 
		editor.createAnimation(key)
	else 
		if not _entered  then 
			_entered=true
			if config.roomsView then 
				print("OPEN SET")
				game.openSet(config.set)
			else
				print("OPEN ROOM")
				game.openRoom(config.room)
				_entered=true
			end
		end
	end
	
	local f =0
	for k,v in pairs(_G) do 
		if type(v)~="function" and type(v)~="table" then 
			-- print(k,type(v))
			f= f+1
		end
	end
	print("globals:",lume.count(_G),f)
end


function game.openRoom(key)
	editor.pushUndoState 	("Change Room")
	game.enterStackRoom 	(0)
	waitFor(function()
		if #roomStack==0 then
			game.enterRoom 		(level.rooms[key],nil,nil,nil,true)
			return true
		end
	end)
end

function game.openSet(key)
	editor.pushUndoState 	("Change Set")
	game.enterStackRoom 	( -1 )
	roomZoom 				= 1
	skipUIclear 			= true
	editor.loadSet 			( key )
end

function game.exit()
	scene.destroy()

	contextMenu 	= {}
	drawFuncs 	 = {}
	editingLevel 	= false
	pauseFade 		= 0
	time 			= 0
	renderWidth 	= launchWidth
	renderHeight 	= launchHeight
	screenWidth 	= launchWidth
	screenHeight 	= launchHeight
	canvasMain  	 	= newCanvas( renderWidth, 		renderHeight )

	setLaunchRes 	()
	game.unPause 	()
	namer:close 	()

	game.state 		= "Load"
	lmouse.setVisible( true )	
	CURRENT_PROJECT=nil

	print("release")
	print(gfx.getStats().images)
	for i,v in pairs(allSymbols) do
		if v.image then 
			releaseImage(v.image)
		end
		--if v.data then v.data:release() end
	end
	allSymbols={}
	print(gfx.getStats().images)

	--init()
end

function rawLevel(key)
	return deepcopy(levels[key])
end

function game.reloadLevelData()
	level  			= rawLevel(level.key)
	game.level 	= level
	game.world 	= game.getWorld(level)
end


function game.setResolution(n)
	game.newRes = n
end

function game.start()
	editingLevel 	= false
	game.state 	 	= "Game"
	game.setResolution(game.playRes)
end


function game.changeRooms(rooms,effect)
	currentRooms = game.getRooms(rooms)
end

function game.addRoom(room)
	if type(game.getRoom(room))~="string" or level.rooms[room] then 
		local room =  type(game.getRoom(room))~="string" and game.getRoom(room) or level.rooms[room]
		room.cameras = room.cameras or {camera.setup()}
		table.insert(currentRooms,{key=room.key,room=room,viewportID=1,cameraID=1,camera=room.cameras[1],viewport=room.viewports[1],depth=1})
	else
		table.insert(currentRooms,room)
	end
	return currentRooms[#currentRooms]
end

function game.addRooms(rooms)
	currentRooms = mergeIndexed(currentRooms,game.getRooms(rooms))
end

function game.duplicateRoom(room,props)
	local dupe = game.addRoom(room.room.name)
	dupe.viewportID = #room.room.viewports+1
	dupe.viewport 	= deepcopy(dupe.viewport)
	if props.offset then 
		for i,p in ipairs(dupe.viewport.polys) do 
			for j,q in ipairs(p) do 
				dupe.viewport.polys[i][j]=dupe.viewport.polys[i][j]+ ((j%2==0) and props.offset.x/renderWidth or props.offset.y/renderHeight) 
			end
		end
	end
end

function game.removeRoom(room)
	lume.remove(currentRooms,room)
end

function game.removeRooms(rooms)
	for i,r in rooms do
		lume.remove(currentRooms,game.getRoom(r))
	end
end
function game.getRooms(...)
	return lume.map(rooms, function(x) return game.getRoom(x) end)
end

function game.getRoom(room)
	return type(room) == "string" and (lume.match(level.rooms,function(x) return (x.name==room) end) or level.rooms[room]) or room
end

function game.getSet(set)
	return type(set) == "string" and lume.match(level.sets,function(x) return (x.name==set) end) or set
end

function game.findSet(room)
	local rset = currentSet.key
	for s,set in pairs(level.sets) do 
		local match = lume.match(game.getSet(set).rooms,function(x) return x.key==room end)
		if match then 
			rset = s
		end
	end
	if rset == currentSet.key then 
		log("found same set")
	end
	return rset
end

function game.getWorld(Level)
	local rworld = 0
	for w,world in ipairs(worlds) do
		for l,level in ipairs(world.levels) do 
			if level.key==Level.key then
				rworld = w
			end
		end
	end
	return rworld
end


function game.loadSet(key)
	if not level.sets[key] then 
		_,key = lume.match(level.sets,function(x) return x.name==key end)
		if not key then 
			log("set not found")
			return false
		end
	end

	if not level.sets[key]  then 
		log("set not found 2")
		return false
	end
	
	log(key.." -> "..currentSet.key)

	if key == currentSet.key then
		log("you are already in this set")
		return false
	end 

	loadSet(key)
	
	game.currentRooms = currentSet.views
	return true
end

function game.reloadSet()

end

function game.setContains(set,r)
	local r = game.getRoom(r) or room
	return lume.match(game.getSet(set).views,function(x) return x.key==r.key end)
end

function loadSet(key)
	local from 			= currentRoom and currentRoom.key or nil
	currentSet  		= level.sets[key] or {
		views={
			{
				viewportID=1,
				cameraID=1,
				depth=1,
				key=room.key
			}
		},
		key="temp",
		name="temp",
		dateCreated=0
	}
	currentSet.sky = currentSet.sky or {
		image = {
			key 	= "",
			frame 	= 1,
			play 	= false,
			color 	= {r=1,g=1,b=1,a=1}
		},
		color = {r=0.8,g=0.8,b=0.8,a=1}
	}
	game.set 			= currentSet.name
	currentRooms  = currentSet.views
	for v,view in ipairs(currentRooms) do
		scene.setView(view)
	end
	local v,i = lume.match(currentRooms,function(x) return x.key==from end)
	nextRoom=nextRoom or (from and v or (currentRooms[1] or nil))
	controls:reset()
	if editingLevel then 
		delayedCall(function()
			cacheSetPreview()
		end,0.1)
	end
	cameras = {}
end

function game.enterRoom(room,cam,link,linkID,nostack)
	print("enterroom")
	if room then 
		if not cam then 
			waitFor(function()
				editor.enteringRoom=true
				if currentRoom.key == room.key then
					cacheRoomPreview()
					editor.enteringRoom=false
					return true
				end
			end)
		end
		if editingLevel and pathsPanel then 
			editor.closePathsPanel()
		end
		if nostack then 
			roomsView = false
			roomZoom=0
		else
			if #roomStack==0 then 
				cacheStackStart()
			end
			local _,i 	= lume.match(currentRooms,function(x) return x==currentRoom end)
			if #roomStack>1 and roomStack[#roomStack-1][3] == link then 
				roomStack 	= lume.slice(roomStack,1,#roomStack-1)
			else
				table.insert(roomStack,{currentSet.key,currentRoom,link})
			end
		end
		nextRoom  	= {key=room.key,cameraID = 1,camera=lume.merge(room.cameras[1] or camera.setup(),nostack and {} or (cam or {})),room=room,viewportID=1}
	end

	if roomsView then 
		cacheSetPreview()
	end

	roomsView = false

end

function game.exitRoom()
	lmouse.setRelativeMode(false)
	game.enterStackRoom(#roomStack-1)
	editor.closePathsPanel()
end

function game.exitAllRooms()
	for i=1,#roomStack+1,1 do 
		game.exitRoom()
	end
end

function game.enterStackRoom(stackPos)
	if #roomStack>0 then 
		--nextSet 	= roomStack[math.max(1,stackPos+1)][1]
		nextRoom    = roomStack[math.max(1,stackPos+1)][2]
		roomStack 	= lume.slice(roomStack,1,math.max(1,stackPos))
	end
	if stackPos<1 then  
		roomStack={}
		editor.stackStart = nil
	end
	if stackPos<0 then 
		editor.setPreview = nil
		roomsView 		  = true
	end
end

function game.switchSet()
	if nextSet~=nil then
		if editingLevel then 
			editor.loadSet(nextSet)
		else
			game.loadSet(nextSet)
		end
		nextSet = nil
	end
end

function game.switchRoom()
	nextRoom = type(nextRoom)=="number" and currentRooms[nextRoom] or nextRoom
	if nextRoom~=nil then
		local sameRoom = currentRoom.room.key==nextRoom.key
		local sameView = currentRoom.viewportID==nextRoom.viewportID
		if (not sameRoom) or (sameRoom and not sameView) then 
			local reopen = false
			
			reopen = (pathsPanel.entity and not pathsPanel.entity.isEntity) and pathsPanel.mode or nil
			editor.closePathsPanel()

			level.rooms[nextRoom.key].dateModified = os.time()

			if editingLevel then 
				local room, view = scene.setView(nextRoom)
				currentRoom = view
			end

			selectedEntities 		= lume.clear(selectedEntities)

			if reopen then 
			--	editor.openPathsPanel(room,nil,reopen)
			end

		end
		nextRoom=nil
	end
end

function game.interact()
	if controls.aButtonPressed then
    	game.talk()
	end
	if controls.bButtonPressed then
		game.touch()
	end
end

function game.touch()
	--Fire off whatever function is queued next
end

function game.talk()
	--Fire off whatever text is queued next
end

function game.flashRoom()
	--Temporarily show a different room , either over the top of the current ones or instead of
	--Can either be for a set amount of time or until something ends the flash 
end

-----------------------------------------------------------
-- LOOPS
-----------------------------------------------------------
function game.runGame(dt)
	
	controls:update	()
	pause:checkPause	()
	pause:updatePause	()

	if not game.paused then 
		game.switchSet 	()
		game.switchRoom ()
		game.interact 	()
		scene.update 	()
	end
end

function game.runEditor(dt)
	bench.push("game.run")
	if editingLevel then 
		editor.update ()
		scene.update 	()
	else 
		controls:update ()

		game.switchSet 	()
		game.switchRoom ()

		if not game.paused then 
			lmouse.setCursor(defaultCursor) 
			scene.update 		()
		end

		if keypressed("escape") then 
			editor.resumeEdit () 
		end
	end
	bench.pop("game.run")
end

