function recursivePrint(t,visited,indent)
  local visited = visited or {0}
  local indent = indent or 1
  for k,v in pairs(t) do
    if type(v)=="table" then 
      if visited[v] then 
        print(string.format("%s table:%i",string.rep("  ",indent),visited[v]))
      else 
        visited[1] = visited[1]+1
        visited[v] = visited[1]
        print(string.format("%s table %i:",string.rep(" ",indent-1),visited[v]))
        recursivePrint(v,visited,indent+1)
      end
    else
      print(string.format("%s%s: %s",string.rep(" ",indent),tostring(k),tostring(v)))
    end
  end
end

function startTimeFrame()
	engine.startTime = engine_getTime()
	engine.frameGarbageStart = collectgarbage("count")
end
function endTimeFrame()
	if not engine.frameGarbageStart then
		return
	end
	engine.frameTimeIter = (engine.frameTimeIter and engine.frameTimeIter < engine.frameTimePeriod) and engine.frameTimeIter + 1
		or 0

	clearTail(engine.frameTimes, engine.frameTimePeriod + 1)
	clearTail(engine.frameGarbages, engine.frameTimePeriod + 1)
  engine.trueTimes[engine.frameTimeIter] = engine.halfTime+engine_getTime()-engine.lastTime 
	engine.frameTimes[engine.frameTimeIter] = engine.window.frame_time--engine_getTime()-engine.startTime 
	engine.frameGarbages[engine.frameTimeIter] = collectgarbage("count") - engine.frameGarbageStart
end

local timers_enabled = true
function _enable_timers()
	timers_enabled = true
end
function _disable_timers()
	timers_enabled = false
end
function _time(tag, t)
	if not timers_enabled then
		return
	end
	LAST_TIME = LAST_TIME or engine_getTime()
	if tag then
		local thing = t and trace or print
		thing(
			string.format(
				ansicolors("time: %{bright red}%5.3f%{reset} frames (%{red} %s %{reset})"),
				60 * (engine_getTime() - LAST_TIME),
				tag
			)
		)
	end
	LAST_TIME = engine_getTime()
end

local timers = {}
function _start_timer(tag)
	if not timers_enabled then
		return
	end
	timers[tag] = engine_getTime()
	print(string.format(" [[[ \n  START TIMER: (%s)...  ", tag))
end

function _end_timer(tag)
	if not timers_enabled then
		return
	end
	if tag and timers[tag] then
		print(
			string.format(
				ansicolors("    END TIMER: (%s) %{redbg} %5.3f %{reset} frames \n ]]]"),
				tag,
				60 * (engine_getTime() - timers[tag])
			)
		)
	end
end

local clocks = {}
function _reset_clock(tag)
	clocks[tag] = 0
end
function _tick(tag)
	clocks[tag] = clocks[tag] or 0
	timers[tag] = engine_getTime()
end
function _tock(tag)
	clocks[tag] = clocks[tag] + (engine_getTime() - timers[tag])
end
function _tell_time(tag)
	if clocks[tag] then
		print(
			string.format(
				ansicolors("clock: %{bright red}%5.3f%{reset} frames %{red}(%s)%{reset}"),
				clocks[tag] * 60,
				tag
			)
		)
	end
	clocks[tag] = 0
end

local toLog = {}
local logShift = 1
local logHeight = 500
local logMaxheight = 500
local logNum = 0
local logx = 0
local rect = {}
local strt = {}
local trueSec = 0
local trueTick = 0
function drawTrace(update)
	engine.trace.x = easeTo(engine.trace.x, not engine.trace.enabled and -engine.trace.width - 20 or -8, 2)

  if not update then 
    gfx.setColor(0, 0, 0, 1)
    gfx.print(engine.trueTime*1000, screenW - 20, screenH - 11)
    gfx.setColor(1, 1, 1, 1)
    gfx.print(engine.trueTime*1000, screenW - 21, screenH - 12)
  else 
    trueTick = trueTick+1
    if trueTick>60 then 
      trueSec = trueSec+1
      trueTick=1
    end
  end

	if engine.trace.x < -engine.trace.width + 50 then
		-- gfx.setColor(0, 0, 0, 1)
		-- gfx.print(mf(1 / engine.frameTime), screenW - 41, screenH - 11)
		-- gfx.setColor(1, 1, 1, 1)
		-- gfx.print(mf(1 / engine.frameTime), screenW - 42, screenH - 12)
		-- gfx.print(inspect(scene))
		return
  end

  if not update then 
  	gfx.setColor(0.1, 0.1, 0.1, 1)
  	gfx.rectangle("fill", engine.trace.x, -8, engine.trace.width ,engine.trace.height + 4, 2)
    gfx.setColor(1,1,1, 1)
    gfx.setFont(fonts.inconsolata, 10)
  end
	engine.trace.height = 8

	local limit = screenH - (animatePanel and animatePanel.height or 0)
  local strt = {}
	for i, tp in ipairs(engine.trace.strings) do

		engine.trace.height = engine.trace.height + 11
		if engine.trace.height > limit then
			engine.trace.height = limit - 1
			break
		end

    if update then 
      local x,y,w,h =engine.trace.x,  (i - 1) * 11, engine.trace.width, 11
  		if mouse_in(x,y,w,h) then
        rect.x,rect.y,rect.w,rect.h = x,y,w,h
  			engine.trace.hovered = i
        if tp:sub(1,5)=="bench" then 
          mouse_set(function()
          bench.on=not bench.on
          if bench.on then 
            bench.enabled = true
          end
          end)
        end
  			if engine.trace.funcs[i] then
          -- print("tfn",engine.trace.funcs[i][1])
  				mouse_set(engine.trace.funcs[i])
  				if type(engine.trace.funcs[i][1]) == "table" then
  					mouse_setR(engine.trace.funcs[i])
  				end
  			end
  		end
    else 
      if engine.trace.hovered == i and rect.x then
        gfx.setColor(0, 0, 0, 0.5)
        gfx.rectangle("fill", rect.x, rect.y + 0.5, rect.w, rect.h + 2)
        gfx.setColor(1, 1, 1, 1)
      end
      gfx.print(tp, engine.trace.x + 16, (i - 1) * 11)
    end
	end

	-- gfx.setColor(0, 0, 0, 1)
  -- local str = table.concat(engine.trace.strings,"\n")
  -- gfx.print(str, engine.trace.x + 16,0)
end

function drawDebug()

	bench.push("tracing")

	if patterns.atlas and editor and editor.debug.show_patterns then
		gfx.draw(patterns.atlas, 0, 0, 0, 0.2, 0.2)
	end

	local p = animatePanel
	local packed = p and p.f and p.debugView and p.debugCompaction and p.f.packedCanvas
	if packed and packed.unpacker and packed.packer then
		local s = 1
		local umw, cmw = packed.unpacker:getWidth(), packed.packer:getWidth()
		gfx.setColor(1, 1, 1, 1)
		gfx.draw(packed.unpacker, 0, 0, 0, s, s)
		gfx.draw(packed.packer, umw * s, 0, 0, s, s)
		gfx.draw(packed.canvas, umw * s + cmw * s)
		if p.f.canvas then
			-- gfx.setColor(0,0,0,1)
			-- gfx.draw(animatePanel.f.canvas,0,packed.canvas:getHeight())
			if rasterChange then
				gfx.draw(rasterChange.unpacker, 0, packed.canvas:getHeight())
				gfx.draw(rasterChange.canvasBefore, rasterChange.unpacker:getWidth(), packed.canvas:getHeight())
				gfx.draw(
					rasterChange.canvas,
					rasterChange.unpacker:getWidth() + rasterChange.canvasBefore:getWidth(),
					packed.canvas:getHeight()
				)
			else
				gfx.draw(p.f.canvas, 0, packed.canvas:getHeight())
			end
			-- gfx.setTexture(RectMesh,unpackedCanvas)
			-- gfx.draw(RectMesh,0,packed.canvas:getHeight(),0,unpackedCanvas:getWidth(),unpackedCanvas:getHeight())
			-- gfx.setColor(1,1,1,0.2)
			-- gfx.setTexture(RectMesh,unpackerMap)
			-- gfx.draw(RectMesh,0,packed.canvas:getHeight(),0,unpackedCanvas:getWidth(),unpackedCanvas:getHeight())
		end
	end

	bench.pop("tracing")
end

local function toggleTrace()
	if shiftDown and altDown and cmdDown and input_keyPressed("w") then
		game.profile = 2
		editor_tempTip("Profiling")
	end

	if not lfs.isFused() and input_keyPressed(";") then
		engine.trace.enabled = not engine.trace.enabled
	end

	-- if keypressed("/") then
	-- logEnabled = not logEnabled
	-- end

	if input_keyPressed("h") then
		editor.helpEnabled = not editor.helpEnabled
	end
 
  if editor then 
    if lfs.isFused() or editor.helpEnabled then 
    	engine.trace.enabled = editor.helpEnabled
    end

  	if editor.show_help then
  		engine.trace.enabled = true
  		editor.helpEnabled = true
  	end
  end
end

function trace(val, ...)
	if not engine.trace.enabled or #engine.trace.strings * 11.5 > screenH - (animatePanel and animatePanel.height or 0) then
		return
	end
	local args = { ... }
	local newline = type(val) == "table" and args[1] or false
	local sub = type(val) == "table" and args[2] or false
	local nameString = ""

	if type(val) == "table" then
		if type(val[1]) == "function" or (type(val[1]) == "table" and type(val[1][1]) == "function") then
			table.insert(engine.trace.strings, args[1])
			engine.trace.funcs[#engine.trace.strings] = val
		else
			for k, v in pairs(val) do
				trace(k, v)
			end
		end
	else
		local printString = ""
		for i, a in ipairs(lume.concat({ val }, args)) do
			if type(a) == "number" then
				a = round(a, 3)
			end
			if type(a) == "string" and WINDOWS then
				a = a:gsub("cmd", "ctl")
			end
			printString = printString .. (i > 1 and ", " or "") .. tostring(a)
		end
		table.insert(engine.trace.strings, printString)
	end
end

function log(val, ...)
	print(inspect(val))
end

local tracehelp = ""
function traceReset()
	local time = " "
	if engine.frameTimeIter == 0 then
		engine.frameTime = mean(engine.frameTimes)
    engine.trueTime = mean(engine.trueTimes)
		engine.frameGarbage = posmean(engine.frameGarbages)
    -- print(engine.trueTime*1000)
	end
  toggleTrace()
  if not ctx or not engine.trace.enabled then return end
	lume.clear(engine.trace.strings)
	time = engine.frameGarbage
			and string.format(
				"vsync:%s %i %.2fms %03dkb %04dmb",
				lwindow.getVSync() and "ON" or "OFF",
				1 / engine.frameTime,
        -- engine.trueTime*1000,
				engine.frameTime * 1000,
				engine.frameGarbage,
				collectgarbage("count") / 1024
			)
		or ""
	trace(time .. tracehelp .. "    h - help "
    .. (shiftDown and "S" or "s")
    .. (altDown and "A" or "a")
    .. (cmdDown and "C" or "c")
  )
	trace(
		string.format(
			"tex: %imb, mesh: %imb, cal:%0.1fmb, val:%0.1fmb",
			mu(gfx.textureMemoryUsage / (1024 ^ 2)),
			mu(gfx.meshMemoryUsage / (1024 ^ 2)),
			_CALLOCED / (1024 ^ 2),
			_VALLOCED / (1024 ^ 2)
		)
	)
	trace(
		string.format(
			"canvases: %i, images:%i, meshes:%i, switches:%i",
			gfx.renderTextureCount,
			gfx.textureCount,
			gfx.meshCount,
			gfx.piplineSwitches
		)
	)
	local viewIndex = lume.find(ctx.views, editor.viewHovered)
	trace(
		string.format(
			"pages: %s(%i%%), view: %s, entity: %s",
			ctx.pageView,
      ctx.roomZoom*100,
			ctx.pageView and viewIndex or "N/A",
			editor.hoveredEntity and editor.hoveredEntity.key or ""
		)
	)
	if editor.selectedEntities and not animatePanel.open then
		trace(
			string.format(
				"Entities: %i/%i, State: %s, SymLoaded (%i):",
				#editor.selectedEntities,
				#ctx.viewport.room.entities,
				editor.state,
				#game.symbolsLoaded
			)
		)
		if not lfs.isFused() then
			for i, img in ipairs(game.symbolsLoaded) do
				local sym = load_getSymbol(img)
				if sym then
					trace("   " .. sym.name)
				end
			end
		end
	end
	trace("")
	
end

function numlines(s)
	return #s:gsub("[^\n]", "")
end

function bench.run(print_bench)
  bench.print = print_bench
  
  bench.step = bench.step < bench.period and bench.step + 1 or 1
  if not bench.on then
    bench.enabled = false
    bench.push = nothing
    bench.pop = nothing
    trace("bench")
    return
  end
  if not bench.enabled then 
    bench.push = nothing
    bench.pop = nothing
    return 
  else 
    bench.push = bench.pushFunc
    bench.pop = bench.popFunc
  end

  trace("bench                             ct    kb    ms")

  local folded = false
  for i, k in ipairs(bench.keys) do
    if folded then
      if k:sub(1,#folded) ~= folded then
        folded = false
      end
    end
    if not folded then
      if bench.step == 1 then
        bench.meanTimes[k] = bench.times[k] / bench.period
        bench.meanTrash[k] = bench.trash[k] / bench.period
        bench.meanCount[k] = bench.count[k] / bench.period
        bench.times[k] = 0
        bench.trash[k] = 0
        bench.count[k] = 0
      end
      local i, str, indent, name = 1, k:sub(1, #k - 1), ""
      while not name do
        local found = str:find(":", i + 1)
        if found then
          i = found
          indent = indent .. " "
        else
          name = indent .. str:sub(i)
        end
      end
      name = name .. (bench.hasChildren[k] and bench.folded[k] and " +" or "")
      local space = string.sub("                               ", #name)
      if bench.meanTimes[k] then
        -- if editor.debug and editor.debug.bench then
        local fold = bench.folded[k] and " >" or " <"
        local kb = tostring(mu(bench.meanTrash[k]))
        local ct = bench.meanCount[k]
        trace({
          { bench.fold, bench.click },
          k,
        }, string.format("%s %s %-3d   %-3d   %4.2f",name, space, ct, kb, bench.meanTimes[k]))
        -- end
      end
    end
    if not folded and bench.folded[k] then
      folded = k
    end
    bench.hasChildren[k] = false
  end

  bench.keys = {}
  trace("")
end

function bench.fold(name, line, source)
  print("FOLD")
  if bench.hasChildren[name] then
    print("from", name, "line", line, "source", source)
    bench.folded[name] = not bench.folded[name]
  end
end

function bench.click(name)
  bench.clicked = name
  engine_waitFor(bench.getFile)
end

function bench.getFile()
  if bench.lines[bench.clicked] then
    bench.open(bench.clicked, bench.lines[bench.clicked], bench.source[bench.clicked])
    return true
  end
end

function bench.open(name, line, source)
  print("from", name, "line", line, "source", source)
  local command = string.format("subl " .. ENGINE_DIR .. source .. ":%i", line)
  print(command)
  os.execute(command)
end

function bench.enable()
  if not bench.on then return end
  bench.enabled = true
end
function bench.disable()
  bench.enabled = false
end

function bench.name(name, pop)
  local out = ""
  if name and not pop then
    table.insert(bench.names, name)
  end
  for w, word in ipairs(bench.names) do
    out = out .. word .. ":"
  end
  if pop then
    if out:sub(#out - #name, #out - 1) ~= name then
      error("pop expected for " .. bench.names[#bench.names])
    end
  end
  return out
end

function bench.pushFunc(name)

  if bench.print then
    print("bench.push", name)
  end
  if not bench.enabled then
    return
  end
  bench.hasChildren[bench.name()] = true
  local name = bench.name(name)

  bench.times[name] = bench.times[name] or 0
  bench.trash[name] = bench.trash[name] or 0
  bench.count[name] = bench.count[name] or 0
  if bench.clicked == name then
    local info = debug.getinfo(2)
    bench.lines[name] = info.currentline
    bench.source[name] = info.short_src
    bench.clicked = false
  end
  table.add(bench.keys, name)
  local count = collectgarbage("count")
  bench.startGarbage[name] = count
  bench.startTimes[name] = engine_getTime()
end

function bench.popFunc(name)
  if bench.print then
    print("bench.pop", name)
  end
  if not bench.enabled then
    return
  end
  local newtime = engine_getTime()
  local count = collectgarbage("count")
  local name = bench.name(name, true)
  local garbage = (count - bench.startGarbage[name])
  bench.currentLevel = name
  bench.times[name] = bench.times[name] + (newtime - bench.startTimes[name]) * 1000
  bench.trash[name] = bench.trash[name] + (garbage-1 > 0 and garbage-1 or 0)
  bench.count[name] = bench.count[name] +1 
  table.remove(bench.names, #bench.names)
end


