local EventEmitter = require "luanode.event_emitter"
local Class = require "luanode.class"
local Session = Class.InheritsFrom(EventEmitter)
local _NAME = ...
local _ENV = Session
package.loaded[_NAME] = _ENV

local Protocol = require "LnChatClient.Protocol"
local helpers = require "LnChatClient.helpers"

local assert, type, error = assert, type, error
local pairs, ipairs, table = pairs, ipairs, table

-- no more external access after this point
setfenv(1, _ENV)

Session.__tostring = function(session)
	return ("Session: '%s'"):format(session.id or "")
end

Session.__type = "LnChatClient.Session"

-- session fields
-- id: string
-- vcc: string
-- participants: { users }
-- properties: { }


--
-- Checks if 'key' is not one of the following values:
function IsExcludedKey (key)
	key = key:lower()
	if key == "session_change_reason" or key:match("^joined_user_") or key:match("^left_user_") or key == "msgtime" or 
		key == "user_name" or key == "user_id" or key == "message"
	then
		return true
	end
	return false
end

--
-- @param client is the ChatServer connection
-- @param id is the id of the session
-- @param user is the user who creates the session
-- @param properties is an optional table with initial properties for the session
function Session:__init (client, id, user, properties)
	assert(self == Session)
	assert(client and id)
	assert(user)
	assert(user.vcc)
	
	local s = Class.construct(Session)
	
	s.client = client
	s.id = id
	s.participants = {}
	s.properties = helpers.Clone(properties or {})
	s.vcc = user.vcc
	
	return s
end

do
--
-- Adds a user to a session
-- @param user can be a username or a user object
-- @param role is a string with the role of the user in the session
--   (possible values: normal, monitor, ninja)
local function Add (session, user, role, callback)

	local user_id
	if type(user) == "table" then
		if user.vcc:lower() ~= session.vcc:lower() then
			error( ("user '%s' and session '%s' must belong to the same vcc"):format(user.user_id, session.id) )
		end
		user_id = user.user_id
	elseif type(user) == "string" then
		user_id = user .. "@" .. session.vcc
	else
		error("parameter must be a user or a username")
	end
	
	session.client.m_protocol:ConnectUserToSession(session.client, user_id, session.id, role, callback)
end

--
-- Adds a user as a coach to a session
-- @param user can be a username or a user object
-- @param coachedUsername is the username of the user to be coached
local function AddCoach (session, user, coachedUsername, callback)
	assert(type(coachedUsername) == "string", "you must supply a user to be coached")

	local user_id
	if type(user) == "table" then
		if user.vcc:lower() ~= session.vcc:lower() then
			error( ("user '%s' and session '%s' must belong to the same vcc"):format(user.user_id, session.id) )
		end
		user_id = user.user_id
	elseif type(user) == "string" then
		user_id = user .. "@" .. session.vcc
	else
		error("parameter must be a user or a username")
	end

	local coachedUserId = coachedUsername .. "@" .. session.vcc
	
	session.client.m_protocol:ConnectCoachToSession(session.client, user_id, session.id, coachedUserId, callback)
end

---
-- Adds a user to a session.
-- @param user can be a username or a user object
-- @param role is a string stating the role of the user in the session
--   the role can be any of { normal, monitor, ninja, coach }
--
function Session:Add (user, role, ...)
	helpers.AssertValidRole(role)

	if role == "coach" then
		return AddCoach(self, user, ...)
	else
		return Add(self, user, role, ...)
	end
end

end

---
-- Sends a message to a session. Only users that were logged by this library can send messages.
-- @param user_name can be a username or a user object
-- @param message the message to be sent
-- @param callback a function to be called asynchronously when the message has been sent
function Session:Send (user_name, message, callback)
	local user
	if type(user_name) == "table" then
		if user_name.vcc:lower() ~= self.vcc:lower() then
			error( ("user '%s' and session '%s' must belong to the same vcc"):format(user_name.user_id, self.id) )
		end
		user = user_name
	elseif type(user_name) == "string" then
		local user_id = user_name .. "@" .. self.vcc
		user = self.client:_FindUser(user_id)
		if not user then
			error("user '%s' not found", user_id)
		end
	else
		error("parameter must be a user or a username")
	end
	
	user:SendTo(self.id, message, callback)
end

---
-- Removes a user from a session. It can remove users that aren't logged locally.
-- @param user can be a username or a user object
-- @param options is an optional table with extra options to be sent
-- @param callback a function to be called asynchronously when the user has been removed from the session
--
function Session:Remove (user, options, callback)
	assert(type(user) == "string" or type(user) == "table", "'user' must be a User instance or a string")
	
	if type(options) == "function" then
		callback = options
		options = nil
	end

	local user_id
	if type(user) == "string" then
		user_id = user .. "@" .. self.vcc
	else
		user_id = user.user_id
	end
	
	self.client.m_protocol:DisconnectUserFromSession(self.client, user_id, self.id, options, callback)
end

---
--
function Session:Update (callback)
	self.client.m_protocol:SetSessionProperties(self.client, self.id, self.properties, callback)
end

---
-- Closes a session
function Session:Close (callback)
	self.client.m_protocol:DeleteSession(self.client, self.id, callback)
end

---
--
function Session:GetParticipants (callback)
	self.client.m_protocol:GetUsersInSession(self.client, self.id, callback)
end

---
--
function Session:GetMessages (callback)
	self.client.m_protocol:GetTextFromSession(self.client, self.id, callback)
end

---
-- 'Replays' a session. Retrieves a sequence of messages that can be used when we need
-- to reconnect a user. Used mostly in webchat, when the user navigates on a site with embedded
-- chat. Each time she navigates to a new page, the chat session is replayed so it appears that
-- the chat messages were always there.
--
function Session:Replay (callback)
	
	-- Fake messages for the session. Mbuahaha.
	self:GetMessages(function(text_messages)
		self:GetParticipants(function(participants)
			local messages = {}
			local onconnect = { message = "OnConnect", participants = participants }
			for k,v in pairs(self.properties) do
				onconnect[k] = v
			end
			table.insert(messages, onconnect)

			for k, v in pairs(onconnect.participants) do
				if v.user_type == "agent" then
					table.insert(messages, {
						replayed = true,
						message = "OnUserJoined",
						session_change_reason = "user_join",
						record_session = onconnect.record_session,
						icr_display_name = onconnect.icr_display_name,
						msgtime = nil,
						number_of_users_in_session = onconnect.number_of_users_in_session,
						user_name = v.user_name,
						user_id = v.user_id,
						joined_user_type = v.user_type,
						joined_user_id = v.user_id,
						joined_user_role = nil,
						joined_user_name = v.user_name,
						joined_user_display_name = v.display_name,
						session_id = onconnect.session_id,
						CanCloseSession = onconnect.CanCloseSession,
						announce_entry = onconnect.announce_entry
					})
				end
			end

			for _, message in ipairs(text_messages) do
				message.message = message.msgtype == "geo" and "OnCoordinatesMessage" or "OnNewMessage"
 				message.replayed = true
				table.insert(messages, message)
			end
			callback(messages)
		end)
	end)
end

return _ENV
