--[[----------------------------------------------------------------------------

KrDebugLoop.lua

--------------------------------------------------------------------------------

ADOBE CONFIDENTIAL
------------------
Copyright 2007 Adobe Systems Incorporated
All Rights Reserved.

NOTICE: All information contained herein is, and remain the property of
Adobe Systems Incorporated and its suppliers, if any. The intellectual and
technical concepts contained herein are proprietary to Adobe Systems
Incorporated and its suppliers and may be covered by U.S. and Foreign
Patents, patents in process, and are protected by trade secret or copyright
law.  Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

------------------------------------------------------------------------------]]


--[[

	Each lua universe gets its own AgDebugger.  This object does the work of inspecting
	that universe's lua states.  It gathers info like what's on the stack, and
	what is the traceback for the current function and so on.

	The debugger communicates w/ a central debug thread.  The first time a universe
	is set up it launches that thread and creates a queue to send messages to it.
	That queue is stored in native code, so the second and after universes fetch
	the queue and thus all universes have a way to send message to the debug thread.

	There is also a listener thread which listens for incoming http requests.  It uses
	the same debug queue to dispatch those messages to the debug thread.

	The debug thread sits in the middle and handles debug work.  When a universe
	halts, it sends a callback queue to the debug thread.  When an http request comes
	in it forwards the work request to the current universe via its callback queue,
	sending a third queue with which to get a response.  This response is then
	sent back to the http listener thread (via another callback queue).

	queues:

	debugQueue : Written to by all lua universe threads and http thread to send messages to "Debugger".
	responseQueues : Various temporary queues that are passed to the debugger
	with each message received on the debugQueue.  Used for various purposes, e.g. to signal the
	main thread that it should step or continue.

	AgDebugger has a unique way of getting invoked early in launch: All the requisites
	are passed in as parameters from native code.
	]]

local params = { ... }
params = params[ 1 ]

local kernel = params.kernel
local logger = kernel.logger "KrDebugLoop"
local cllogger = kernel.logger "KrDebugCodeLoading"

local function myAssert( p, x )
	local v = p[ x ]
	if not v then
		local message = string.format( "Missing required param %s", x )
		logger:fatal( message )
		error( message )
	end
	return v
end

local alwaysPrint = myAssert( kernel, 'alwaysPrint' )
local newTransitQueue = myAssert( kernel, 'newTransitQueue' )
local getDebugQueue = myAssert( kernel, 'getDebugQueue' )

local threadId = myAssert( params, 'threadId' )

local pumpDebugQueue = myAssert( params, 'pumpDebugQueue' )
local activationParam
local deactivateRemoteLuaDebugger = myAssert( params, 'deactivateRemoteLuaDebugger' )
local activateRemoteLuaDebugger = myAssert( params, 'activateRemoteLuaDebugger' )
local supportsBreakpoints = true
local AgDebugger_setBreakpoint = params.setBreakpoint
if not AgDebugger_setBreakpoint then
	supportsBreakpoints = false
	AgDebugger_setBreakpoint = function( ... )
		logger:warn( "breakpoint set request ignored: ", ... )
		return 0
	end
end
local loadChunk = myAssert( params, 'loadChunk' )
local luaState = myAssert( params, 'luaState' )
local AgDebugClearHook = myAssert( _G, 'AgDebugClearHook' )

--logger:debugf( "Starting debugger for thread %d", threadId )

--local AgLuaDebuggerServer = require "AgLuaDebuggerServer"
--local AgLuaDebuggerThread = require "AgLuaDebuggerThread"

local newAgDebugStackInspector

--============================================================================--

local function activateBreakpoints( breakpoints )
	logger:debugf( "Received initial breakpoints: %d", #breakpoints )
	local registry = debug.getregistry()
	registry.breakpoints = breakpoints

	for _, bp in ipairs( breakpoints ) do
		local numFound = AgDebugger_setBreakpoint( bp.chunkName, bp.lineNumber, true, bp.code )
		logger:debugf( "Activated breakpoint %s:%d : %d found", bp.chunkName, bp.lineNumber, numFound )
	end
end

local setOrClearBreakpointInThisUniverse

local AgDebuggerMt = {}

local function makeDebugger( threadsLaunched )

	local bp = {
	}
	local registry = debug.getregistry()
	registry.breakpoints = bp

	local self = setmetatable( {}, { __index = AgDebuggerMt } )

	logger:debugf( "AgDebugger init'd for thread %d", threadsLaunched )
	self.id = threadsLaunched

	if threadsLaunched > 0 then
		self:initByFetchingPreexistingDebugQueue()
	else
		self:initByLaunchingDebugThreads()
	end

	registry.debugQueue = self.debugQueue
	self.debuggeeQueue = newTransitQueue()
	self.codeQueue = newTransitQueue()
	self.loadQueue = newTransitQueue()
	registry.debuggeeQueue = self.debuggeeQueue
		-- used in AgLuaDebuggingInterface.c line 525

	-- logger:tracef( "Current debugger flags: AgBreakOnHalt %s, AgBreakOnThrow %s", AgGetBreakOnHalt(), AgGetBreakOnThrow() )

	self.debugQueue:enqueue {
		message = 'newThread',
		debuggeeQueue = self.debuggeeQueue,
		codeQueue = self.codeQueue,
		name = debug.getregistry().universeName or "Unnamed",
		id = self.id,
		luaState = luaState
	}

	pumpDebugQueue() -- the debug queue is usually only pumped by HTTP requests

	if supportsBreakpoints then
		local initialBreakpointMessage = self.debuggeeQueue:dequeue()

		assert( initialBreakpointMessage.message == "initialBreakpoints",
			'initialBreakpointMessage.message == "initialBreakpoints"' )

		activateBreakpoints( initialBreakpointMessage.breakpoints )
	end

	return self

end

----------------------------------------------------------------------------

function AgDebuggerMt:initByFetchingPreexistingDebugQueue( )
	self.debugQueue = getDebugQueue()
	self.inspector = newAgDebugStackInspector( )
end

	----------------------------------------------------------------------------

function AgDebuggerMt:initByLaunchingDebugThreads()

	logger:debug( "Initializing debugger." )

	self.debugQueue = getDebugQueue()
--	local startedQueue = newTransitQueue()

	self.inspector = newAgDebugStackInspector()
--
--	-- start the debugger
--	AgAlternateUniverseSupport.callWithPrivateUniverse( "debugger", nil, function( debugQueue, startedQueue )
--		import 'AgDebugger'._startDebugger( debugQueue, startedQueue )
--	end, self.debugQueue, startedQueue )
--
--	logger:debug( "Waiting for debugger threads to start up" )
--	-- wait for the http server to be started
--	logger:tracef( "Read start info %s", startedQueue:dequeue() )
--
--	-- wait for the debug thread to be started
--	logger:tracef( "Read start info %s", startedQueue:dequeue() )

end

----------------------------------------------------------------------------

function AgDebuggerMt:shutdown( )

	self.debugQueue:enqueue {
		message = 'threadTerminate',
		id = self.id,
	}

	logger:debugf( "Requesting termination: %d", self.id )

	pumpDebugQueue()

	local done
	while not done do
		local msg = self.debuggeeQueue:dequeue( false )

		if not msg then
			logger:warnf( "Termination accept message not received: %d", self.id )
			break
		end

		if msg.message == "threadTerminateAccept" then
			logger:debugf( "Termination accepted: %d", self.id )
			break
		end
	end

	logger:debugf( "Thread terminating: %d", self.id )

end

----------------------------------------------------------------------------

function AgDebuggerMt:halt( luathread, reasonWhyHalted )

	-- This code is an essential part of the debugger, therefore we cannot
	-- allow the debugger to try and halt within it, or deadlock would result
	AgDebugClearHook()

	if reasonWhyHalted == 'applyBreakpoints' then
		logger:debug "Applying breakpoints"

		local count = 0
		local stepIntent = "Unchanged"

		while true do
			local bp = self.debuggeeQueue:dequeue( false )
			if not bp then
				break
			end

			count = count + 1

			if bp.message == "setClearBreakpoint" then
				local what = setOrClearBreakpointInThisUniverse( bp.set, bp.chunkName, bp.lineNumber, bp.code or false )
				bp.queue:enqueue( what )
			elseif bp.message == "continue" then
				logger:debugf( "Received continue request." )
				stepIntent = "Run"
			elseif bp.message == "pause" then
				logger:debugf( "Received pause request." )
				stepIntent = "Stop"
			elseif bp.message == "activationParameter" then
				activationParam = bp.param
				logger:debugf( "Received new activationParameter: %s", tostring( activationParam ) )
			else
				logger:warnf( "Unexpected message type on debuggeeQueue: %s", tostring( bp.message ) )
				count = count - 1
			end
		end

		logger:debugf( "Processed %d messages.", count )
		return stepIntent, count
	end

	if luathread == nil or luathread == coroutine.running() then
		logger:error "Attempt to halt from within debugger.  Bailing"
		return "Run"
	end

	-- bypass any global read protection that might be installed by using rawget()
	if rawget( _G, "runningMainThreadAction" ) then
		logger:error( "Halting from inside a task started by Halide is not supported. You can work around this with an event loop post." )
		return "Run"
	end

	-- bypass any global write protection that might be installed by using rawset()
	rawset( _G, "DEBUGGING", true )

	local result

	logger:debug( "Halting." )
	local responseQueue = newTransitQueue()

	self.inspector.threadList = nil
	self.inspector.luathread = luathread
	logger:tracef( "Got luathread: %s running thread is: %s", self.inspector.luathread, coroutine.running() )

	local success, doHaltOrMessage = self.inspector:runBreakpointAction( )

	if success and not doHaltOrMessage then

		result = {
			stepIntent = 'Unchanged',
		}

	else
		if not success then
			logger:warnf( "Breakpoint action failed to run: %s", doHaltOrMessage )
			reasonWhyHalted = string.format( "%s breakpoint code error: %s", reasonWhyHalted, doHaltOrMessage )
		end

		self.debugQueue:enqueue {
			message = 'halt',
			responseQueue = responseQueue,
			id = self.id,
			reason = reasonWhyHalted,
		}

		local action = activateRemoteLuaDebugger( reasonWhyHalted, activationParam )

		if action == "continue" then
			result = {
				stepIntent = 'Run',
			}
		else
			while true do

				result = responseQueue:dequeue()

				if result.stepIntent then
					logger:debugf( "Exiting halt() with stepIntent %s", result.stepIntent )
					break
				end

				logger:tracef( "Processing message '%s'", tostring( result.message ) )

				if result.message == 'stack' then
					self.inspector:reset_marshal_values()
					local level = result.level
					logger:debugf( "Getting stack for level %d", level )
					local stack = self.inspector:getStack( level )

					result.resultQueue:enqueue {
						message = 'stack',
						value = stack
					}
					logger:trace( "Sent stack" )
				end

				if result.message == 'code' then
					local level = result.level
					logger:debugf( "Getting code for level %d", level )
					local code, line = self.inspector:getCode( level )

					result.resultQueue:enqueue {
						message = 'code',
						value = {
							code = code,
							line = line,
						}
					}
					logger:trace( "Sent code" )
				end

				if result.message == 'objectInfo' then
					logger:debugf( "Getting objectInfo for marshalled object %d", result.marshal_value )
					local objectInfo = self.inspector:getObjectInfo( result.marshal_value )

					result.resultQueue:enqueue {
						message = 'objectInfo',
						value = objectInfo
					}
					logger:trace( "Sent objectInfo" )
				end

				if result.message == 'runLua' then
					local code = result.code
					local level = result.level
					logger:debugf( "Running code %s on level %d", code, level )
					local str = self.inspector:runLua( code, level )

					result.resultQueue:enqueue {
						message = 'runLua',
						value = str
					}
					logger:trace( "Sent runLua result" )
				end

				if result.message == 'traceback' then
					logger:debugf( "Getting traceback" )
					local traceback = self.inspector:getTraceback( )
					traceback.reason = reasonWhyHalted
					traceback.thread = self.id

					result.resultQueue:enqueue {
						message = 'traceback',
						value = traceback,
					}
					logger:trace( "Sent traceback" )
				end

				if result.message == 'setClearBreakpoint' then
					logger:tracef( "Main thread processing set/clear (%s) breakpoint '%s' @ %d", result.set, result.chunkName, result.lineNumber )

					local what = setOrClearBreakpointInThisUniverse( result.set, result.chunkName, result.lineNumber, result.code or false )
					result.queue:enqueue( what )
				end
			end
		end

		deactivateRemoteLuaDebugger( activationParam )

	end

	self.inspector:reset()

	-- bypass any global write protection that might be installed by using rawset()
	rawset( _G, "DEBUGGING", nil)

	return result.stepIntent, result.args and unpack( result.args )

end

--============================================================================--

local AgDebugStackInspector = {
}

----------------------------------------------------------------------

function newAgDebugStackInspector()

	local self = setmetatable( {}, { __index = AgDebugStackInspector } )
	self.marshal_cache1 = {}
	self.marshal_cache2 = {}
	self.synthetics = {}
	self.marshal_count = 1

	return self

end

----------------------------------------------------------------------

function AgDebugStackInspector:reset( )

	logger:trace( "Reset." )
	self.luathread = nil
	self:reset_marshal_values()

end

----------------------------------------------------------------------

function AgDebugStackInspector:reset_marshal_values( )

	logger:trace( "Reset marshal values." )
	self.marshal_count = 1
	self.marshal_cache1 = {}
	self.marshal_cache2 = {}
	self.synthetics = {}

end

----------------------------------------------------------------------

function AgDebugStackInspector:marshalledValue( val )

	local t = type( val )
	if t == 'number' or t == 'boolean' or t == 'string' or t == 'nil' then
		return val
	elseif t == 'proto' then
		-- Lua 5.x bug workaround (see also CL 824886)
		return "(proto)"
	end

	local marshal_value
	if self.marshal_cache1[ val ] then
		marshal_value = self.marshal_cache1[ val ]
	else
		marshal_value = self.marshal_count
		self.marshal_count = marshal_value + 1
		self.marshal_cache1[ val ] = marshal_value
		self.marshal_cache2[ marshal_value ] = val
	end

	return {
		type = t,
		marshal_value = marshal_value,
	}

end

----------------------------------------------------------------------

function AgDebugStackInspector:wrapResult( name, value, kind )
	local strVal
	local extendedInfo = { }
	local typename = type( value )
	if type( value ) == "function" then
		local info = debug.getinfo( value, "S" )
		if info then
			extendedInfo.source = info.source
			extendedInfo.linedefined = info.linedefined
		end
	elseif kind == "stack_frame" then
		extendedInfo.source = value.source
		extendedInfo.linedefined = value.linedefined
		extendedInfo.currentline = value.currentline
		typename = "stack frame"
		strVal = ""
	elseif type( value ) == "proto" then
		-- The "proto" type is special and tostring will cause
		-- a bad memory access (bug reported to the Lua mailing
		-- list 2012/04/20 @ 12:54 PM), so keep the below code
		-- from running in this (rare) case. The situation where
		-- this occurs is if a breakpoint is set on a line right
		-- after a call to loadstring that receives both results.
		--
		-- However, the type() function doesn't correctly report
		-- these as such, so mark them accordingly w/o tostring.
		strVal = "!>(proto)<!"
	end

	if not strVal then
		local success, result = pcall( tostring, value )
		if success then
			strVal = result
		else
			strVal = string.format( "!>%s<!", tostring( result ) )
		end
	end

	local result = {
		name = name,
		kind = kind,
		type = typename,
		value = self:marshalledValue( value ),
		extendedInfo = extendedInfo,
		string_rep = strVal,
	}

	return result
end

----------------------------------------------------------------------

local function fenvName( source )
	if type( source ) == "userdata" then
		return '< userdata environment >'
	else
		return '< function environment >'
	end
end

--------------------------------------------------------------------------------

local fenv_store = function( self, func, index )
	local result
	if func and index == 1 then
		local fenv = debug.getfenv( func )
		if fenv then
			result = self:wrapResult( fenvName( func ), fenv, "fenv" )
		end
	end
	logger:tracef( "Got fenv %d %s", index, result and "yep" or "nada" )
	return result
end
----------------------------------------------------------------------

local upvalue_store = function( self, func, index )
	local result
	if func then
		local name, value = debug.getupvalue( func, index )
		if name then
			result = self:wrapResult( name, value, "upvalue" )
		end
	end
	logger:tracef( "Got upvalue %d %s", index, result and result.name or "nada" )
	return result
end

--------------------------------------------------------------------------------

local local_store = function ( self, stack_info, index )
	local result
	local level = stack_info.level
	local thread = stack_info.thread
	if thread and level and index then
		local name, value = debug.getlocal( thread, level, index )

		if name then
			result = self:wrapResult( name, value, "local" )
		end
	end
	logger:tracef( "Got local %d %s", index, result and result.name or "Nada" )
	return result
end

--------------------------------------------------------------------------------

local vararg_store = function() end

local getvarargs = debug.getvarargs

if getvarargs then
	vararg_store = function( self, stack_info )
		local level = stack_info.level
		local thread = stack_info.thread
		if thread and level then
			local args = getvarargs( thread, level )
			if args then
				return self:wrapResult( "...", args, "local" )
			end
		end
	end
end

--------------------------------------------------------------------------------

local function yieldLoop( kind )
	local counter = 1
	while coroutine.yield( kind, counter ) do
		counter = counter + 1
	end
end

----------------------------------------------------------------------

local function tostringSort( ts )
	-- store these up front, if by chance a tostring result changes
	-- during the sort it can result in an error being thrown by the
	-- table sort function that the comparator is invalid
	local tostrings = { }
	for _, v in ipairs( ts ) do
		tostrings[ v ] = tostring( v ) or "nil"
	end

	table.sort( ts, function( a, b )
		return tostrings[ a ] < tostrings[ b ]
	end )
end

----------------------------------------------------------------------

local stackNodes = {

	[ 'function' ] = {
		_iterator = function( func )
			return coroutine.wrap( function()
				coroutine.yield( 'fenv', 1 )
				yieldLoop( 'upvalue' )
			end )
		end,

		fenv = fenv_store,

		upvalue = upvalue_store,

	},

	[ 'stack_info' ] = {
		_iterator = function( info )
			return coroutine.wrap( function()
				coroutine.yield( 'fenv', 1 )
				yieldLoop( 'upvalue' )
				yieldLoop( 'local' )
				coroutine.yield( 'varargs', 1 )
			end )
		end,

		fenv = function( self, stack_info, index )
			return fenv_store( self, stack_info.func, index )
		end,

		upvalue = function( self, stack_info, index )
			return upvalue_store( self, stack_info.func, index )
		end,

		[ 'local' ] = local_store,

		varargs = vararg_store,
	},

	[ 'stack_frame' ] = {
		_iterator = function( info )
			return coroutine.wrap( function()
				coroutine.yield( 'func', 1 )
				yieldLoop( 'local' )
				coroutine.yield( 'varargs', 1 )
			end )
		end,

		func = function( self, stack_info, index )
			return self:wrapResult( "< function >", stack_info.func, "framefn" )
		end,

		[ 'local' ] = local_store,

		varargs = vararg_store,
	},

	[ 'table' ] = {
		_iterator = function( tbl )
			return coroutine.wrap( function()
				local sortedKeyTable = {}
				local keyTableLen = 0
				for k, v in pairs( tbl ) do
					sortedKeyTable[ keyTableLen + 1 ] = k
					keyTableLen = keyTableLen + 1
				end

				tostringSort( sortedKeyTable )

				local iter, t, i = ipairs( sortedKeyTable )
				while true do
					local k
					i, k = iter( t, i )
					if k == nil then
						break
					end
					if type( k ) == 'table' then
						coroutine.yield( 'table_key', k )
					end
					coroutine.yield( 'item', k )
				end
				local mt = debug.getmetatable( tbl )
				if mt then
					coroutine.yield( "mt", 1 )
				end
			end )
		end,

		item = function( self, tbl, key )
			return self:wrapResult( tostring( key ), tbl[ key ] )
		end,

		table_key = function( self, tbl, key )
			return self:wrapResult( string.format( "%s<-",tostring( key ) ), key )
		end,

		mt = function( self, tbl, key )
			return self:wrapResult( "< metatable >", debug.getmetatable( tbl ) )
		end,
	},

	[ 'thread' ] = {
		_iterator = function( td )
			return coroutine.wrap( function()
				yieldLoop( 'stack_frame' )
			end )
		end,
		stack_frame = function( self, td, counter )
			local info = debug.getinfo( td, counter )
			if info then
				info.thread = td
				info.level = counter
				self.synthetics[ info ] = 'stack_frame'
				return self:wrapResult( tostring( counter ), info, "stack_frame" )
			end
		end
	},

	[ 'userdata' ] = {
		_iterator = function( ud )
			return coroutine.wrap( function()
				local mt = debug.getmetatable( ud )
				if mt then
					coroutine.yield( "mt", 1 )
				end

				local fenv = debug.getfenv( ud )
				if fenv then
					coroutine.yield( "fenv", 1 )
				end
			end )
		end,

		fenv = fenv_store,

		mt = function( self, ud )
			return self:wrapResult( "< metatable >", debug.getmetatable( ud ) )
		end,
	},

}

----------------------------------------------------------------------

function AgDebugStackInspector:getStack_obj_type( obj, objType )

	local nodes = stackNodes[ objType ]

	if not nodes then
		return obj
	end

	local iterator = nodes._iterator
	iterator = iterator and iterator( obj )
	local result = {}

	local value
	local kMaxStringLength = 20000

	local function truncateString( str )
		local len = #str

		if len > kMaxStringLength then
			str = string.sub( str, 1, kMaxStringLength )
			str = str .. ' ... (truncated ' .. len - kMaxStringLength .. ' characters)'
		end

		return str
	end

	while iterator do

		local nodeType, index = iterator( value )

		if not nodeType then
			break
		end

		if not result[ nodeType ] then
			result[ nodeType ] = {}
		end

		local store = nodes[ nodeType ]
		value = store( self, obj, index )

		if value and value.value and type( value.value ) == 'string' and #value.value > kMaxStringLength then
			value.value = truncateString( value.value )
		end

		if value and value.string_rep and type( value.string_rep ) == 'string' and #value.string_rep > kMaxStringLength then
			value.string_rep = truncateString( value.string_rep )
		end

		table.insert( result[ nodeType ], value )

	end

	return result

end

----------------------------------------------------------------------

function AgDebugStackInspector:getStack_obj( obj )

	local objType = self.synthetics[ obj ] or type( obj )

	return self:getStack_obj_type( obj, objType )

end

--------------------------------------------------------------------------------

function AgDebugStackInspector:getThreadLevelForLevel( level )
	local thread = self.luathread
	local resultLevel = level - 1
	if self.threadList and self.threadList[ level ] then
		local threadInfo = self.threadList[ level ]
		resultLevel = threadInfo.level
		thread = threadInfo.thread
	end

	return thread, resultLevel
end

----------------------------------------------------------------------

function AgDebugStackInspector:getStack( inLevel )

	inLevel = inLevel or 1

	local thread, level = self:getThreadLevelForLevel( inLevel )

	logger:tracef( "Getting stack on thread: %s, %d", thread, level )
	local info = thread and debug.getinfo( thread, level )
	if not info then
		logger:errorf( "Can't get info for stack: %s : %d", thread, level )
		return
	end

	info.thread, info.level = self:getThreadLevelForLevel( inLevel )

	return self:getStack_obj_type( info, 'stack_info' )

end

----------------------------------------------------------------------

function AgDebugStackInspector:getCode( level )

	level = level or 1

	local thread
	thread, level = self:getThreadLevelForLevel( level )

	local info = thread and debug.getinfo( thread, level )
	if not info then
		logger:errorf( "Can't get info for code chunk: %s : %d", thread, level )
		return
	end

	local func = info.func

	if not func then
		logger:error( "Can't get func for code chunk" )
		return
	end

	local fenv = debug.getfenv( func )

	if not fenv then
		logger:error( "Can't get fenv for code chunk" )
		return
	end

	local success, codeChunk = pcall( function()
		return fenv == _G and "No source available" or fenv[ "< code chunk >" ]
	end )

	if not success then
		codeChunk = "UNKNOWN"
	end

	return codeChunk, info.currentline

end

----------------------------------------------------------------------

local function runCodeInHaltedState( luathread, code, level, overlay )

	if type( code ) ~= 'string' then
		return 'invalid code.  String required.'
	end

	local func, message = loadstring( code, "runLua" )
	if not func then
		return message
	end

	local info = luathread and debug.getinfo( luathread, level )
	local halted_func = info.func
	local upvalIndexes = {}
	local localIndexes = {}
	local i = 1

	while true do
		logger:tracef( "Looking for upvalue %d for func at level %d", i, level )
		local name = debug.getupvalue( halted_func, i )
		if not name then
			break
		end
		logger:debugf( "halted func has upvalue called %s", name )
		upvalIndexes[ name ] = i
		i = i + 1
	end

	i = 1
	while true do
		logger:tracef( "Looking for local %d for func at level %d", i, level )
		local name = debug.getlocal( luathread, level, i )
		if not name then
			break
		end
		logger:debugf( "halted func has local called %s", name )
		localIndexes[ name ] = i
		i = i + 1
	end

	logger:debug( "Setting up fenv" )

	local fnGlobals = getfenv( halted_func )

	local fenv_meta = {
		__index = function( fenv, key )
			if key == 'co' then

				return luathread
			elseif overlay and overlay[ key ] ~= nil then
					return overlay[ key ]
			elseif upvalIndexes[ key ] then
				logger:tracef( "Getting upval %s", tostring( key ) )
				local _, val = debug.getupvalue( halted_func, upvalIndexes[ key ] )
				return val
			end
			if localIndexes[ key ] then
				logger:tracef( "Getting local %s", tostring( key ) )
				local _, val = debug.getlocal( luathread, level, localIndexes[ key ] )
				return val
			end
			logger:tracef( "Getting global %s", tostring( key ) )
			return fnGlobals[ key ]
		end,
		__newindex = function( fenv, key, value )
			if upvalIndexes[ key ] then

				logger:tracef( "Setting upval %s to value %s", tostring( key ), tostring( value ) )
				debug.setupvalue( halted_func, upvalIndexes[ key ], value )

			elseif localIndexes[ key ] then

				logger:tracef( "Setting local %s to value %s", tostring( key ), tostring( value ) )
				debug.setlocal( luathread, level, localIndexes[ key ], value )

			else

				logger:tracef( "Setting global %s to value %s", tostring( key ), tostring( value ) )
				fnGlobals[ key ] = value

			end
		end,
	}

	local fenv = setmetatable( {}, fenv_meta )
	setfenv( func, fenv )

	return pcall( func )

end
--------------------------------------------------------------------------------

local breakpointOverlay = {
	print = alwaysPrint
}

function AgDebugStackInspector:runBreakpointAction()
	local info = self.luathread and debug.getinfo( self.luathread, 0 )
	if info then
		local chunk = info.source
		local currentline = info.currentline
		local bps = debug.getregistry().breakpoints
		for _, bp in ipairs( bps ) do

			if bp.chunkName == chunk and bp.lineNumber == currentline and bp.code then
				return runCodeInHaltedState( self.luathread, bp.code, 0, breakpointOverlay )
			end
		end
	end
	return true, true
end

----------------------------------------------------------------------

function AgDebugStackInspector:runLua( code, level )

	level = level or 1
	local thread
	thread, level = self:getThreadLevelForLevel( level )

	local _, a, b, c, d, e, f, g = runCodeInHaltedState( thread, code, level )

	local results = {}

	for j, v in ipairs { a, b, c, d, e, f, g } do
		results[ j ] = tostring( v )
	end

	return table.concat( results, "\t" )

end

----------------------------------------------------------------------

function AgDebugStackInspector:getObjectInfo( marshal_value )

	local obj = self.marshal_cache2[ marshal_value ]
	if obj ~= nil then
		logger:debugf( "Located marshalled object %d of type %s", marshal_value, type( obj ) )
		return self:getStack_obj( obj )
	else
		logger:warnf( "Cannot locate marshalled object for id %d", marshal_value )
		for k,v in pairs( self.marshal_cache2 ) do

			logger:tracef( "Cache does contain item: %d", k )

		end
	end

end

----------------------------------------------------------------------

function AgDebugStackInspector:getTraceback( )

	local result = {}
	local threadList = {}

	local threadIndex = 0
	local index = 0
	local curThread = self.luathread
	while true do

		local chained = false
		threadList[ curThread ] = true
		local next = curThread and debug.getinfo( curThread, threadIndex )
		if not next then
			local callers = debug.getregistry()
			callers = callers and callers.KrDebuggerCoroutineCallers
			local caller = callers and callers[ curThread ]
			if caller and not threadList[ caller ] then
				logger:debugf( "Chaining: %s to %s", curThread, caller )
				curThread = caller
				threadIndex = 0
				chained = true
			end
			if not chained then
				break
			end
		end
		if not chained then
			index = index + 1
			threadList[ index ] = {
				thread = curThread,
				level = threadIndex,
			}
			threadIndex = threadIndex + 1

			local name = next.name or string.format( "< %s >", next.namewhat or "?" )
			table.insert( result, {
				name = name,
				namewhat = next.namewhat,
				what = next.what,
				chunk = next.source,
				currentline = next.currentline,
			} )
		end

	end
	self.threadList = threadList

	return result

end

--------------------------------------------------------------------------------

function setOrClearBreakpointInThisUniverse( doSet, chunkName, linenumber, code )

	assert( type( doSet ) == 'boolean' )
	assert( type( chunkName ) == 'string' )
	assert( type( linenumber ) == 'number' )
	if code then assert( type( code ) == 'string' ) end

	logger:debugf( "%s breakpoint %s:%d", doSet and "Setting" or "Clearing", chunkName, linenumber )

	local registry = debug.getregistry()

	if doSet then
		local i = 1
		local found = false
		while i <= #registry.breakpoints do
			local b = registry.breakpoints[ i ]
			if b.chunkName == chunkName and b.lineNumber == linenumber then
				b.code = code
				found = true
			end
			i = i + 1
		end
		if not found then
			table.insert( registry.breakpoints, {
				chunkName = chunkName,
				lineNumber = linenumber,
				code = code,
			} )
		end
	else
		local i = 1
		while i <= #registry.breakpoints do
			local b = registry.breakpoints[ i ]
			if b.chunkName == chunkName and b.lineNumber == linenumber then
				table.remove( registry.breakpoints, i )
			end
			i = i + 1
		end
	end

	local numFound = AgDebugger_setBreakpoint( chunkName, linenumber, doSet, code )

	logger:debugf( "Done with scan '%s' @ %d . %d functions modified", chunkName, linenumber, numFound )

	return numFound

end

local debugger = makeDebugger( threadId )

--==============================================================================

local print, getPrintedText, getEnvPrintAndResults

do
	-- hide private statics
	local env
	local luaSays = {}
	local prefix

	function getEnvPrintAndResults( newPrefix )
		if not env then
			luaSays = {}
			env = setmetatable( {}, {
				__index = function( t, k )
					if k == 'print' then
						return print
					elseif k == 'env' then
						return env
					elseif k == '_S' then
						return env
					else
						return _G[ k ]
					end
				end,
			} )
		end
		prefix = newPrefix

		return env
	end

	print = function( ... )
		local t = { ... }
		local n = select( '#', ... )
		for i = 1, n do
			t[ i ] = tostring( t[ i ] )
		end
		table.insert( luaSays, prefix .. table.concat( t, "\t" ) )
	end

	getPrintedText = function()
		local text = table.concat( luaSays, "\n" )
		luaSays = {}
		return text
	end
end

--------------------------------------------------------------------------------

local runMainThreadLuaCode = function( code )
	logger:debugf( "Running code : %s", code )
	local prefix = "\n-- "
	local func, mess
	func, mess = loadstring( string.format( [[
		local select = select
		function __printresults__( ... )
			if 0 < select( '#', ... ) then
				print( ... )
			end
		end
		__printresults__( %s )
	]], code ), "interactive code" )
	if not func then
		prefix = "-- "
		logger:trace( "Not an expression" )
		func, mess = loadstring( code, "interactive code" )
	end
	if func then
		logger:trace( "Code compiles" )
		setfenv( func, getEnvPrintAndResults( prefix ) )
		local success

		success, mess = pcall( func )

		if success then
			logger:trace( "Code runs" )
			mess = getPrintedText()
		end
	end

	logger:tracef( "Returning result: %s", mess )
	return mess or "no result"
end

--------------------------------------------------------------------------------

debug.getregistry().drainCodeQueue = function()
	local queue = debugger.codeQueue

	local codeMessage = queue:dequeue( false )

	if codeMessage then
		local result = runMainThreadLuaCode( codeMessage.code )
		codeMessage.queue:enqueue( result )
	else
		logger:error( "Spurious drain request!" )
	end
end

debug.getregistry().registerCodeRunner = function( createTrigger )
	local registry = debug.getregistry()

	local object, method = createTrigger( registry.drainCodeQueue )
	local debugQueue = registry.debugQueue

	debugQueue:enqueue {
		message = 'mainThreadCodeAction',
		id = debugger.id,
		action = object,
		method = method
	}
end


debug.getregistry().loadCodeFromDebugger = function( chunkName )
	local debugQueue = debugger and debugger.debugQueue
	local loadQueue = debugger and debugger.loadQueue

	if debugQueue and loadQueue then
		debugQueue:enqueue {
			message = "loadChunk",
			chunkName = chunkName,
			responseQueue = loadQueue
		}

		pumpDebugQueue()

		-- the debug bottleneck is required to respond with one of:
		-- - the loaded code from its cache
		-- - an indication that code should not be side loaded
		-- - an argument to pass to the chunk loader
		local msg = loadQueue:dequeue( true )

		if msg and msg.message == "loadChunk" and msg.chunkName == chunkName then
			if msg.code then
				cllogger:debugf( "Loading code for: %s", msg.chunkName )
				return msg.code
			elseif msg.loadParameter then
				local result = loadChunk( msg.loadParameter )
				if result then
					cllogger:debugf( "Loaded: %s (%s)", msg.chunkName, msg.loadParameter )
					-- the debug loop is acting as a worker thread so the
					-- bottleneck does not have to block on what is likely
					-- an HTTP fetch, so send the resulted code back to
					-- the debug bottleneck so it can be used by other
					-- debug loops that require the same namespace
					debugQueue:enqueue {
						message = "loadedChunk",
						chunkName = chunkName,
						code = result
					}

					return result
				else
					cllogger:debugf( "Failed: %s (%s)", msg.chunkName, msg.loadParameter )
				end
			else
				cllogger:tracef( "Did not load: %s", msg.chunkName )
			end
		end
	end
end

--==============================================================================

local action, luathread, reasonWhyHalted = coroutine.yield()

logger:tracef( "Debugger activated for %s", luathread )

while true do

	logger:debugf( "Got %s %s", action, luathread )
	local a, b, c, d

	if action == 'halt' then

		logger:debugf( "Halting..." )
		a, b, c, d = debugger:halt( luathread, reasonWhyHalted )

	elseif action == 'shutdown' then

		logger:trace( "Shutting down debugger" )
		debugger:shutdown()
		debugger = nil
		break

	end

	action, luathread, reasonWhyHalted = coroutine.yield( a, b, c, d )

end
