-- see copyright notice in wxLdb.lua

local net = require( "grldc.net" )
local socket = require( "grldc.socket" )

local print = print
local assert = assert
local pairs = pairs
local ipairs = ipairs
local type = type
local debug = debug
local xpcall = xpcall
local setmetatable = setmetatable
local error = error
local table = table
local tonumber = tonumber

module( "grld.engine" )

local clients
local activeClient
local nextClient = 1
local listeners = {}
local nextListener = 1
local events =
{
	onNewClient = {},
	onClientBreak = {},
	onClientLost = {},
}

local clientMeta = { __index = {} }

local function addClient( listenerIdx, connection, name )
	print( "New client connected on listener "..listenerIdx.." : assigned client ID "..nextClient )
	local client = { clientId = nextClient, listenerIdx = listenerIdx, connection = connection, status_ = "running", activeThread_ = "current", name_ = name, ip_ = connection:getpeername(), breakPoints_ = {} }
	setmetatable( client, clientMeta )
	clients[nextClient] = client
	if activeClient == nil or clients[activeClient] == nil then
		setactiveclient( nextClient )
	end
	runEvents_( "onNewClient", nextClient )
	nextClient = nextClient + 1
end

function init()
	print( "Initializing grld.engine..." )
	assert( clients == nil, "Trying to initialize grld.engine twice" )
	clients = {}
end

function initialized()
	return clients ~= nil
end

function listen( address, port )
	print( "Listening for new clients on "..address..":"..port..", listener ID "..nextListener )
	local listener = net.bind( address, port )
	listeners[nextListener] = listener
	nextListener = nextListener + 1
end

function registerEvent( eventName, callback )
	assert( events[eventName] ~= nil, "Unknown event name "..eventName )
	table.insert( events[eventName], callback )
end

function runEvents_( eventName, ... )
	for _, callback in pairs( events[eventName] ) do
		callback( ... )
	end
end

function update( timeout )
	local active = false
	
	for idx, listener in pairs( listeners ) do
		local connection = listener:accept()
		if connection ~= nil then
			local name = connection:receive()
			addClient( idx, connection, name )
			active = true
		end
	end
	
	local sockets = {}
	for idx, client in pairs( clients ) do
		table.insert( sockets, client.connection )
	end
	socket.select( sockets, timeout )
	
	for idx, client in pairs( clients ) do
		local ok, msg = xpcall( function() active = active or client:update() end, client.errorHandler )
		if not ok then
			active = true
			if msg == "closed" then
				print( "Connection with client "..idx.." closed" )
				runEvents_( "onClientLost", idx )
				clients[idx] = nil
			else
				error( msg )
			end
		end
	end
	
	return active
end

function clientMeta.__index:checkConnection()
	self.connection:send( "", "ka" )
end

function clientMeta.__index:update()
	local command = self.connection:tryReceive()
	if command ~= nil then
		self["cmd_"..command]( self )
		return true
	end
	return false
end

function clientMeta.__index:setactivethread( thread )
	self.activeThread_ = thread
end

function clientMeta.__index:getactivethread()
	return self.activeThread_
end

function clientMeta.__index:getcurrentthread()
	assert( self.status_ == "break", "Bad status: "..self.status_ )
	self.connection:send( "currentthread" )
	return self.connection:receive()
end

function clientMeta.__index:name()
	return self.name_
end

function clientMeta.__index:ip()
	return self.ip_
end

function clientMeta.__index:status()
	return self.status_
end

function clientMeta.__index:source()
	assert( self.status_ == "break", "Bad status: "..self.status_ )
	return self.source_
end

function clientMeta.__index:line()
	assert( self.status_ == "break", "Bad status: "..self.status_ )
	return self.line_
end

function clientMeta.__index:setbreakpoint( source, line, value )
	local setValue = value or nil
	local sourceBp = self.breakPoints_[source]
	if sourceBp == nil then
		sourceBp = {}
		self.breakPoints_[source] = sourceBp
	end
	sourceBp[line] = setValue
	self.connection:send( "setbreakpoint", "running" )
	self.connection:send( { source = source, line = line, value = value }, "running" )
end

function clientMeta.__index:callstack()
	assert( self.status_ == "break", "Bad status: "..self.status_ )
	self.connection:send( "callstack" )
	self.connection:send( self.activeThread_ )
	return self.connection:receive()
end

function clientMeta.__index:breakpoints()
	--if self.status_ == "break" then
	--	self.connection:send( "breakpoints" )
	--	self.breakPoints_ = self.connection:receive()
	--end
	return self.breakPoints_
end

function clientMeta.__index:locals( level )
	assert( self.status_ == "break", "Bad status: "..self.status_ )
	self.connection:send( "locals" )
	self.connection:send( self.activeThread_ )
	self.connection:send( level )
	return self.connection:receive()
end

function clientMeta.__index:upvalues( level )
	assert( self.status_ == "break", "Bad status: "..self.status_ )
	self.connection:send( "upvalues" )
	self.connection:send( self.activeThread_ )
	self.connection:send( level )
	return self.connection:receive()
end

function clientMeta.__index:evaluate( expr, level )
	assert( self.status_ == "break", "Bad status: "..self.status_ )
	self.connection:send( "evaluate" )
	self.connection:send( expr )
	self.connection:send( self.activeThread_ )
	self.connection:send( level )
	return self.connection:receive()
end

function clientMeta.__index:releaseValue( id )
	self.connection:send( "releaseValue", "running" )
	self.connection:send( id, "running" )
end

function clientMeta.__index:getValue( id )
	assert( self.status_ == "break", "Bad status: "..self.status_ )
	self.connection:send( "getValue" )
	self.connection:send( id )
	return self.connection:receive()
end

function clientMeta.__index:coroutines()
	assert( self.status_ == "break", "Bad status: "..self.status_ )
	self.connection:send( "coroutines" )
	return self.connection:receive()
end

for _, command in ipairs( { "run", "stepover", "stepin", "stepout" } ) do
	clientMeta.__index[command] = function( self )
		assert( self.status_ == "break", "Bad status: "..self.status_ )
		self.connection:send( command )
		--assert( self.connection:receive() == "ack_"..command )
		self.status_ = "running"
	end
end

function clientMeta.__index:breaknow()
	assert( self.status_ == "running", "Bad status: "..self.status_ )
	self.connection:send( "break", "running" )
end

function clientMeta.__index:cmd_synchronize()
	self.connection:send( 0 )
	self.connection:send( true )
end

function clientMeta.__index:cmd_break()
	assert( self.status_ == "running" )
	--self.connection:send( "ack_break" )
	self.source_ = self.connection:receive()
	self.line_ = self.connection:receive()
	self.status_ = "break"
	assert( type( self.source_ ) == "string" )
	assert( type( self.line_ ) == "number" )
	print( "Client "..self.clientId.." break at "..self.source_.."("..self.line_..")" )
	runEvents_( "onClientBreak", self.clientId )
end

function clientMeta.__index.errorHandler( msg )
	if msg == "closed" then return msg end
	return debug.traceback( msg )
end

function listclients()
	local res = {}
	for idx, client in pairs( clients ) do
		table.insert( res, { clientId = idx } )
	end
	return res
end

function getactiveclient()
	return activeClient or 0
end

function setactiveclient( idx )
	idx = assert( tonumber( idx ) )
	print( "Setting active client: "..idx )
	activeClient = idx
	if clients[idx] == nil then
		print( "Warning: no such client" )
	end
end

function getClient( clientId )
	return clients[clientId]
end

for _, func in ipairs( { "name", "ip", "status", "source", "line", "callstack", "locals", "coroutines", "run", "breaknow", "stepover", "stepin", "stepout", "setactivethread", "getactivethread", "getcurrentthread" } ) do
	_M[func] = function( ... )
		local idx = getactiveclient()
		local client = clients[idx]
		if client == nil then return "no such client" end
		return client[func]( client, ... )
	end
end

function shutdown()
	print( "Shuting down grld.engine..." )
	clients = nil
	listeners = {}
end
