---
-- Client library for Interaction Router (IR). 
-- 
-- @name LuaIrLink

local require = require
local json = require "json"
local Class = require "luanode.class"
local EventEmitter = require "luanode.event_emitter"
local console = console

module(..., package.seeall)

local core = require "LuaIrLink.core"
Class.MakeInheritFrom(core.IRLink, EventEmitter)

--
-- Constructs the luaCppBridge object, and the build the EventEmitter using that
function core.IRLink:__init (...)
	local obj = Class.construct(core.IRLink, core.IRLink:new(...))
	
	obj._handle_callbacks = function(data, callback)
		obj.m_logger.Debug("[LuaIrLink] llego un callback: %s", data, callback)
		data = json.decode(data)
		local handler = type(data.command) == "string" and data.command
		if handler and type(obj._callbacks[handler]) == "function" then
			obj._callbacks[handler](obj, data, function(response)
				local json_response = json.encode(response)
				callback(json_response)
			end)
		else
			local response = {
				Type = "Exception",
				Description = "Command " .. tostring(data.command) .. " not implemented"
			}
			return callback(json.encode(response))
		end
		return "response_pending"
	end
	
	obj._callbacks = {}
	
	return obj
end

--
-- When the response from the IR arrives it is handled by this function
-- @param self is an instance on an IRLink
-- @param ok indicates whether the "send" succeeded
-- @param command_id the ID of the command whose response we received
-- @param json_response either a json encoded response on success or an error message on failure
local function ResponseHandler(self, ok, command_id, json_response)
	self.m_logger.Profile("[LuaIrLink] ResponseHandler", ok, json_response)
	if ok then
		local response = json.decode(json_response)
		local message = self.m_messages[command_id]
		self.m_messages[command_id] = nil
		message.callback(true, response)
	else
		self.m_logger.Error("[LuaIrLink] error on IR response: %s", json_response)
		local message = self.m_messages[command_id]
		self.m_messages[command_id] = nil
		message.callback(false, json_response)
	end
end

--- 
-- Sends a command to the IR. All commands expect a response.
-- @name SendCommand
-- @param command a table with the data to send in the message. It must have a field named "command" with the name of 
-- the command being sent.
-- @param callback an optional function that will be called when the response arrives.
-- @usage irlink:SendCommand({ command = "Test", foo = "bar"}, function() ... end)
function core.IRLink:SendCommand (command, callback)
	assert(type(command) == "table")
	assert(command.command, "Must have a field named 'command'")
	
	command.proxyIP = self.ip
	command.proxyPort = self.port
	command.proxyType = self.proxyType
	
	local command_id = self:NextId()
	self.m_messages[command_id] = { message = command, callback = callback }
	self:RawSendCommand(json.encode(command), self.ResponseHandler, command_id)
end

--- 
-- Adds a callback. A callback is a function that can be called from the IR.
-- @name Callback
-- @param name the name of the callback.
-- @callback a function
-- @usage irlink:Callback("foo", function() end)
function core.IRLink:Callback (name, callback)
	assert(type(name) == "string")
	assert(type(callback) == "function")
	
	self._callbacks[name] = callback
end

---
-- Connects with a given set of endpoints.
-- @name AddNewLinks
-- @param ir_links an array with endpoints. Each endpoint is a string in the form "ip_address:port"
-- @usage irlink:AddNewLinks({ "192.168.20.20:6789", "192.168.20.21:7871" })
function core.IRLink:AddNewLinks (ir_links)
	ir_links = ir_links or {}
	assert(type(ir_links) == "table")

	for _, address in ipairs(ir_links) do
		local ir_address, ir_port = address:match("(.*):(.*)")
		if ir_address and ir_port then
			self:Connect(ir_address, ir_port, function()
				self.m_logger.info("Connected with IR %s:%s", ir_address, ir_port)
			end)
		else
			self.m_logger.warn("Address for IR %s is malformed", address)
		end
	end
end

---
-- Constructs an IRLink object.
-- @param ip the IP address on which we are listening.
-- @param port the port we are listening on.
-- @param proxyType the kind of proxy we are (i.e. IMPROXY).
-- @param logger is a table with the functions to be used for logging (usually, a lualiblogger instance)
-- @return an IRLink object.
-- @usage local link = LuaIrLink.IRLink("192.168.23.217", 10123, "IMPROXY", "inconcertCluster")
IRLink = function(ip, port, proxyType, cluster, logger)
	assert(type(ip) == "string", "Missing ip address")
	assert(tonumber(port), "Missing port")
	assert(type(proxyType) == "string", "Missing proxy type")
	assert(type(cluster) == "string", "Missing cluster ID")
	
	local newLink = core.IRLink(ip, port, proxyType, cluster)
	
	newLink.ip = ip
	newLink.port = tonumber(port)
	newLink.proxyType = proxyType
	newLink.cluster = cluster
	
	newLink.m_logger = logger or {
		Debug = console.debug,
		Info = console.info,
		Warn = console.warn,
		Error = console.error,
		Fatal = console.fatal,
		Profile = console.info
	}
	
	newLink.m_messages = {}	-- table of messages indexed by command
	
	newLink.ResponseHandler = function(...)
		return ResponseHandler(newLink, ...)
	end
	
	newLink.Capabilities = {
		
		Set = function(self, capability, capacity)
			newLink:SetCapability(capability, capacity)
		end,
		
		Rename = function(self, old_name, new_name)
			newLink:RenameCapability(old_name, new_name)
		end,
		
		Reserve = function(self, capability)
			local ticket = newLink:ReserveCapability(capability)
			return ticket
		end,
		
		Return = function(self, capability, ticket)
			newLink:ReturnCapability(capability, ticket)
		end,
	}
	
	return newLink
end

---
-- Connects to a given IR server.
-- @class function
-- @param address the IP address of the IR to connect to.
-- @param port the port the IR is listening on.
-- @param callback a function to be called when the connection is established.
-- @name Connect
-- @usage link:Connect("192.168.23.217", 12345, function() end)
