function LoqProtocolInterpreter_Connected()
{
	// close all channel windows
	for (var channel in this.loq_main.chat_status_mgr.GetChannelStatus()) {
		this.loq_main.chat_window_mgr.CloseChannelWindow(channel);
	}

	// close all query windows
	for (var nick in this.loq_main.chat_status_mgr.GetQueryStatus()) {
		this.loq_main.chat_window_mgr.CloseQueryWindow(nick);
	}

	this.loq_main.channel_list_data = new Object();
	this.loq_main.chat_status_mgr.Init();

	/*
	if (password)
		this.loq_main.network.Send('PASS', password);
	*/

	if (! this.loq_main.chat_status_mgr.myNick) {
		this.loq_main.chat_status_mgr.IChangeNick(randomNick()); // use a random nick
	}

	this.loq_main.network.Send('USER', this.loq_main.chat_status_mgr.myNick + ' b c ' + this.loq_main.chat_status_mgr.myNick);
	this.loq_main.network.Send('NICK', this.loq_main.chat_status_mgr.myNick);
}

function LoqProtocolInterpreter_ConnectionFailed(err_msg)
{
	this.loq_main.status_window.WriteLine('<font color="#000080">*** '.concat('Unable to connect (', err_msg, ')</font>'), 'CER');
}

function LoqProtocolInterpreter_Disconnected()
{
	var channel_windows = this.loq_main.chat_window_mgr.GetChannelWindow();
	if (channel_windows) {
		for (var channel in channel_windows) {
			var channel_window = channel_windows[channel];
			if (channel_window && channel_window.isOpen())
				channel_window.WriteLine('chat_f', '<font color="#000080">*** Disconnected</font>');
		}
	}

	var query_windows = this.loq_main.chat_window_mgr.GetQueryWindow();
	if (query_windows) {
		for (var user in query_windows) {
			var query_window = query_windows[user];
			if (query_window && query_window.isOpen())
				query_window.WriteLine('chat_f', '<font color="#000080">*** Disconnected</font>');
		}
	}

	this.loq_main.status_window.WriteLine('<font color="#000080">*** Disconnected</font>', 'DIS');
}

function LoqProtocol_001(loq_main, origin, params)
{/*
	001    RPL_WELCOME
  			"Welcome to the Internet Relay Network <nick>!<user>@<host>" */
	loq_main.status_window.WriteLine('<font color="#000000">'.concat(htmlSafeString(params.slice(1).join(' ')), '</font>'), 'RPL');
}

function LoqProtocol_002(loq_main, origin, params)
{/*
	002    RPL_YOURHOST
  			"Your host is <servername>, running version <ver>" */
	loq_main.status_window.WriteLine('<font color="#000000">'.concat(htmlSafeString(params.slice(1).join(' ')), '</font>'), 'RPL');
}

function LoqProtocol_003(loq_main, origin, params)
{/*
	003    RPL_CREATED
  			"This server was created <date>" */
	loq_main.status_window.WriteLine('<font color="#000000">'.concat(htmlSafeString(params.slice(1).join(' ')), '</font>'), 'RPL');
}

function LoqProtocol_004(loq_main, origin, params)
{/*
	004    RPL_MYINFO
		  "<servername> <version> <available user modes>
		   <available channel modes>"
	 - The server sends Replies 001 to 004 to a user upon
	   successful registration. */
	loq_main.status_window.WriteLine('<font color="#000000">'.concat(htmlSafeString(params.slice(1).join(' ')), '</font>'), 'RPL');
}

function LoqProtocol_005(loq_main, origin, params)
{/*
	005    RPL_BOUNCE
		  "Try server <server name>, port <port number>"

	 - Sent by the server to a user to suggest an alternative
	   server.  This is often used when the connection is
	   refused because the server is already full. */
	loq_main.status_window.WriteLine('<font color="#000000">'.concat(htmlSafeString(params.slice(1).join(' ')), '</font>'), 'RPL');
}

function LoqProtocol_251(loq_main, origin, params)
{/*
	251    RPL_LUSERCLIENT
			  ":There are <integer> users and <integer> */
	loq_main.status_window.WriteLine('<font color="#000000">'.concat(htmlSafeString(params.slice(1).join(' ')), '</font>'), 'CUR');
}

function LoqProtocol_252(loq_main, origin, params)
{/*
	252    RPL_LUSEROP
			  "<integer> :operator(s) online" */
	loq_main.status_window.WriteLine('<font color="#000000">'.concat(htmlSafeString(params.slice(1).join(' ')), '</font>'), 'CUR');
}

function LoqProtocol_253(loq_main, origin, params)
{/*
	253    RPL_LUSERUNKNOWN
			  "<integer> :unknown connection(s)" */
	loq_main.status_window.WriteLine('<font color="#000000">'.concat(htmlSafeString(params.slice(1).join(' ')), '</font>'), 'CUR');
}

function LoqProtocol_254(loq_main, origin, params)
{/*
	254    RPL_LUSERCHANNELS
		  "<integer> :channels formed" */
	loq_main.status_window.WriteLine('<font color="#000000">'.concat(htmlSafeString(params.slice(1).join(' ')), '</font>'), 'CUR');
}

function LoqProtocol_255(loq_main, origin, params)
{/*
	255    RPL_LUSERME
			  ":I have <integer> clients and <integer> servers" */
	loq_main.status_window.WriteLine('<font color="#000000">'.concat(htmlSafeString(params.slice(1).join(' ')), '</font>'), 'CUR');
}

function LoqProtocol_265(loq_main, origin, params)
{
	loq_main.status_window.WriteLine('<font color="#000000">'.concat(htmlSafeString(params.slice(1).join(' ')), '</font>'), 'CUR');
}

function LoqProtocol_266(loq_main, origin, params)
{
	loq_main.status_window.WriteLine('<font color="#000000">'.concat(htmlSafeString(params.slice(1).join(' ')), '</font>'), 'CUR');
}

function LoqProtocol_321(loq_main, origin, params)
{/*
	321     RPL_LISTSTART
		   "Channel :Users  Name" */
	loq_main.status_window.WriteLine('<font color="#000000">Listing channels</font>', 'LST');
}


function LoqProtocol_322(loq_main, origin, params)
{/*
	322    RPL_LIST
		  "<channel> <# visible> :<topic>" */

	loq_main.channel_list_count += 1;

	if (loq_main.channel_list_count <= loq_main.channel_list_limit) {
		var users = new Number(params[2]);
		if (users > loq_main.channel_list_min && users < loq_main.channel_list_max) {
			loq_main.channel_list_data[params[1].toLowerCase()] = {
				'channel':params[1],
				'users':params[2],
				'topic':(params[3] ? params[3] : '')
			};
			/*
			if (loq_main.channel_list_window.isOpen()) {
				loq_main.channel_list_window.Write('.');
			}
			*/
		}
	}
}

function LoqProtocol_323(loq_main, origin, params)
{/*
	323    RPL_LISTEND
		  ":End of LIST" */
	loq_main.status_window.WriteLine('<font color="#000000">'.concat(htmlSafeString(params[1]), '</font>'), 'LST');
	loq_main.status_window.WriteLine('', 'EOL');

	if (! loq_main.channel_list_window.isOpen()) {
		loq_main.channel_list_window.Open();
	}
	else {
		loq_main.channel_list_window.Focus();
	}
	loq_main.channel_list_window.Update(loq_main.channel_list_data);
}

function LoqProtocol_331(loq_main, origin, params)
{/*
	331    RPL_NOTOPIC
		  "<channel> :No topic is set" */
}

function LoqProtocol_332(loq_main, origin, params)
{/*
	332    RPL_TOPIC
		  "<channel> :<topic>"

		 - When sending a TOPIC message to determine the
		   channel topic, one of two replies is sent.  If
		   the topic is set, RPL_TOPIC is sent back else
		   RPL_NOTOPIC. */
	var channel = params[1];
	var topic = params[2];

	loq_main.chat_status_mgr.ChangeTopic(channel, topic);

	var channel_window = loq_main.chat_window_mgr.GetChannelWindow(channel);
	if (channel_window) {
		channel_window.UpdateTopic(topic);
		channel_window.WriteLine('chat_f', '<font color="#008000">*** Topic is \''.concat(Msg2Text(htmlSafeString(topic)), '\'</font>'));
	}
}

function LoqProtocol_333(loq_main, origin, params)
{/*
	333
		<channel> <nick> <time> */
	var channel_window = loq_main.chat_window_mgr.GetChannelWindow(params[1]);
	if (channel_window)
		channel_window.WriteLine('chat_f', '<font color="#008000">*** Set by '.concat(params[2], ' on ', (new Date(params[3].valueOf()*1000)).toString(), '</font>'));
}

function LoqProtocol_334(loq_main, origin, params)
{/*
	334
		<usage> */
	loq_main.status_window.WriteLine('<font color="#000000">'.concat(Msg2Html(htmlSafeString(params[1])), '</font>'), 'USG');
}

function LoqProtocol_353(loq_main, origin, params)
{/*
	353    RPL_NAMREPLY
		  "( "=" / "*" / "@" ) <channel>
		   :[ "@" / "+" ] <nick> *( " " [ "@" / "+" ] <nick> )
	 - "@" is used for secret channels, "*" for private
	   channels, and "=" for others (public channels). */
	var channel = params[2];

	loq_main.status_window.Write('<div style="text-indent: -4em; margin-left: 4em;"><font color="#000000">'.concat(htmlSafeString(channel.concat(' ', params[3])), '</font></div>'), 'NAM');

	var users = params[3].split(/ /);
	for (var i = 0; i < users.length; i++) {
		var userNick = users[i].match(/^[@+]?(.*)$/)[1];
		if (userNick.toLowerCase() != loq_main.chat_status_mgr.myNick.toLowerCase())
			loq_main.chat_status_mgr.UserJoinsChannel(userNick, channel);
	}

	/*
	var channel_window = loq_main.chat_window_mgr.GetChannelWindow(channel);
	if (channel_window) {
		var channel_status = loq_main.chat_status_mgr.GetChannelStatus(channel);
		if (channel_status)
			channel_window.UpdateUserList(channel_status.channelUsers);
	}
	*/
}

function LoqProtocol_366(loq_main, origin, params)
{/*
	366    RPL_ENDOFNAMES
		  "<channel> :End of NAMES list"

	 - To reply to a NAMES message, a reply pair consisting
	   of RPL_NAMREPLY and RPL_ENDOFNAMES is sent by the
	   server back to the client.  If there is no channel
	   found as in the query, then only RPL_ENDOFNAMES is
	   returned.  The exception to loq_main is when a NAMES
	   message is sent with no parameters and all visible
	   channels and contents are sent back in a series of
	   RPL_NAMEREPLY messages with a RPL_ENDOFNAMES to mark
	   the end. */
	var channel = params[1];

	loq_main.status_window.WriteLine(htmlSafeString(channel.concat(' ', params[2])), 'NAM');
	loq_main.status_window.WriteLine('', 'EON');

	var channel_window = loq_main.chat_window_mgr.GetChannelWindow(channel);
	if (channel_window) {
		var channel_status = loq_main.chat_status_mgr.GetChannelStatus(channel);
		if (channel_status)
			channel_window.UpdateUserList(channel_status.channelUsers);
	}
}

function LoqProtocol_375(loq_main, origin, params)
{/*
	375    RPL_MOTDSTART
		  ":- <server> Message of the day - " */
	loq_main.status_window.WriteLine('<font color="#000000">'.concat(Msg2Html(htmlSafeString(params[params.length-1])), '</font>'), 'RES');
}

function LoqProtocol_372(loq_main, origin, params)
{/*
	372    RPL_MOTD
		  ":- <text>" */
	loq_main.status_window.WriteLine('<font color="#000000">'.concat(Msg2Html(htmlSafeString(params[params.length-1])), '</font>'), 'RES');
}

function LoqProtocol_376(loq_main, origin, params)
{/*
	376    RPL_ENDOFMOTD
		  ":End of MOTD command" */
	loq_main.status_window.WriteLine('<font color="#000000">'.concat(Msg2Html(htmlSafeString(params[params.length-1])), '</font>'), 'RES');
}

function LoqProtocol_401(loq_main, origin, params)
{/*
	401    ERR_NOSUCHNICK
		  "<nickname> :No such nick/channel"

	  - Used to indicate the nickname parameter supplied to a
		command is currently unused.  */
	loq_main.status_window.WriteLine('<font color="#000000">'.concat(htmlSafeString(params.slice(1).join(' ')), '</font>'), 'RER');
}

function LoqProtocol_421(loq_main, origin, params)
{/*
	421    ERR_UNKNOWNCOMMAND
			"<command> :Unknown command"
		- Returned to a registered client to indicate that the command sent is unknown by the server. */
	loq_main.status_window.WriteLine('<font color="#000000">'.concat(htmlSafeString(params.slice(1).join(' ')), '</font>'), 'RER');
}

function LoqProtocol_433(loq_main, origin, params)
{/*
	433	ERR_NICKNAMEINUSE "<nick> :Nickname is already in use" */
	if (params[0] == '*') { // nick is not set
		loq_main.chat_status_mgr.IChangeNick(randomNick()); // use a random nick
		loq_main.network.Send('NICK', loq_main.chat_status_mgr.myNick);
	}
	loq_main.status_window.WriteLine('<font color="#000000">'.concat(htmlSafeString(params[params.length-1]), '</font>'), 'RER');
}

function LoqProtocol_439(loq_main, origin, params)
{/*
	439	<channel> :Target change too fast. Please wait <> seconds. */
	loq_main.chat_status_mgr.IPartChannel(params[1]);
	loq_main.status_window.WriteLine('<font color="#000000">'.concat(htmlSafeString(params[2]), '</font>'), 'RER');
}

var LoqProtocol_ReplyTable = {
'001':LoqProtocol_001,
'002':LoqProtocol_002,
'003':LoqProtocol_003,
'004':LoqProtocol_004,
'005':LoqProtocol_005,
'251':LoqProtocol_251,
'252':LoqProtocol_252,
'253':LoqProtocol_253,
'254':LoqProtocol_254,
'255':LoqProtocol_255,
'265':LoqProtocol_265,
'266':LoqProtocol_266,
'321':LoqProtocol_321,
'322':LoqProtocol_322,
'323':LoqProtocol_323,
'331':LoqProtocol_331,
'332':LoqProtocol_332,
'333':LoqProtocol_333,
'334':LoqProtocol_334,
'353':LoqProtocol_353,
'366':LoqProtocol_366,
'372':LoqProtocol_372,
'375':LoqProtocol_375,
'376':LoqProtocol_376,
'401':LoqProtocol_401,
'421':LoqProtocol_421,
'433':LoqProtocol_433,
'439':LoqProtocol_439
};

function LoqProtocol_Reply(loq_main, origin, command, params)
{
	switch (command.charAt(0)) {
	case '0': /* Numerics in the range from 001 to 099 are used for client-server
				connections only and should never travel between servers.  */
		loq_main.status_window.WriteLine('<font color="#000000">'.concat(htmlSafeString(params[params.length-1]), '</font>'), 'RPL');
		break;

	case '1':
		loq_main.status_window.WriteLine('<font color="#000000">'.concat(htmlSafeString(params[params.length-1]), '</font>'), 'RPL');
		break;

	case '2': /* Replies generated in the response to commands are found in the range from 200 to 399. */
		loq_main.status_window.WriteLine('<font color="#000000">'.concat(htmlSafeString(params[params.length-1]), '</font>'), 'RES');
		break;

	case '3': /* Replies generated in the response to commands are found in the range from 200 to 399. */
		loq_main.status_window.WriteLine('<font color="#000000">'.concat(htmlSafeString(params.join(' ')), '</font>'), 'RES');
		break;

	case '4':
	case '5': /* Error replies are found in the range from 400 to 599. */
		loq_main.status_window.WriteLine('<font color="#000000">'.concat(htmlSafeString(params.slice(1).join(' ')), '</font>'), 'RER');
		break;

	default:
		loq_main.status_window.WriteLine('<font color="#808080">'.concat(htmlSafeString(params.join(' ')), '</font>'), 'RPL');
	}
}

function LoqProtocol_NOTICE(loq_main, origin, params)
{
	//    Command:  NOTICE 
	// Parameters:  <nickname> <text> 
	if (origin) {
		var channel_window;
		var last_channel = loq_main.chat_status_mgr.GetLastJoinedChannel();
		if (last_channel) {
			if (loq_main.chat_status_mgr.Joins(origin, last_channel))
					channel_window = loq_main.chat_window_mgr.GetChannelWindow(last_channel);
		}
		var txt = '<div style="text-indent: -2em; margin-left: 2em;"><font color="#800000">-'.concat(origin, '- ', Msg2Html(htmlSafeString(params[1])), '</font></div>');
		if (channel_window) {
			channel_window.Write('chat_f', txt);
		}
		else {
			loq_main.status_window.Write(txt, 'NTC');
		}
	}
	else {
		loq_main.status_window.WriteLine('<font color="#800000">'.concat(Msg2Html(htmlSafeString(params[1])), '</font>'), 'NTC');
	}
}

function LoqProtocol_ERROR(loq_main, origin, params)
{
	//    Command: ERROR
	// Parameters: <error message>
	loq_main.status_window.WriteLine('<font color="#008000">'.concat(params[0], '</font>'), 'ERR');
}

function LoqProtocol_PING(loq_main, origin, params)
{
	//    Command:  PING 
	// Parameters:  <server1> [<server2>] 
	//    Command:  PONG 
	// Parameters:  <daemon> [<daemon2>] 
	switch (params.length) {
	case 1:
		loq_main.network.Send('PONG', ':'.concat(params[0]));
		loq_main.status_window.WriteLine('<font color="#008000">PING? PONG!</font>', 'PNG');
		break;
	case 2:
		loq_main.network.Send('PONG', params[0].concat(' ', ':', params[1]));
		loq_main.status_window.WriteLine('<font color="#008000">PING? PONG!</font>', 'PNG');
		break;
	default:
		top.DebugWindow.Error('params error', 'PING');
	}
	loq_main.status_window.WriteLine('', 'EOP');
}

function LoqProtocol_MODE(loq_main, origin, params)
{
	//    Command: MODE
	// Parameters: <channel> {[+|-]|o|p|s|i|t|n|b|v} [<limit>] [<user>] [<ban mask>]
	// Parameters: <nickname> {[+|-]|i|w|s|o}
	if (isChannelName(params[0])) {
		var channel = params[0];

		var modes = params[1].match(/[+\-]?[a-z]/g);
		for (var i = 0; i < modes.length; i++) {
			if (modes[i].charAt(modes[i].length-1) == 'o') {
				switch (modes[i].charAt(0)) {
				case '-':
					break;

				default: // +o, o
				}
			}
		}
		var channel_window = loq_main.chat_window_mgr.GetChannelWindow(channel);
		if (channel_window) {
			channel_window.WriteLine('chat_f',
				'<font color="#008000">*** '.concat(origin, ' sets mode: ', params.slice(1).join(' '), '</font>'));
			var channel_status = loq_main.chat_status_mgr.GetChannelStatus(channel);
			if (channel_status)
				channel_window.UpdateUserList(channel_status.channelUsers);
		}
	} else {
		loq_main.status_window.WriteLine(
			'<font color="#008000">*** '.concat(origin, ' sets mode: ', params.slice(1).join(' '), '</font>'), 'MOD');
	}
}

function LoqProtocol_NICK(loq_main, origin, params)
{
	//    Command: NICK
	// Parameters: <nickname> [ <hopcount> ]
	var new_nick = params[0];
	var user_status;

	if (origin.toLowerCase() == loq_main.chat_status_mgr.myNick.toLowerCase()) {
		user_status = loq_main.chat_status_mgr.IChangeNick(new_nick);
		loq_main.status_window.WriteLine('<font color="#008000">*** Your nick is now '.concat(loq_main.chat_status_mgr.myNick, '</font>'), 'NIC');
	}
	else {
		user_status = loq_main.chat_status_mgr.GetUserStatus(origin);
		if (user_status) {
			user_status = loq_main.chat_status_mgr.UserChangesNick(origin, new_nick);
		}
	}

	if (user_status) {
		for (var channel in user_status.userChannels) {
			var channel_window = loq_main.chat_window_mgr.GetChannelWindow(channel);
			if (channel_window) {
				channel_window.WriteLine('chat_f', '<font color="#008000">*** '.concat(origin, ' is now known as ', new_nick, '</font>'));
				var channel_status = loq_main.chat_status_mgr.GetChannelStatus(channel);
				if (channel_status)
					channel_window.UpdateUserList(channel_status.channelUsers);
			}
		}
	}
}

function LoqProtocol_PRIVMSG(loq_main, origin, params)
{
	//    Command: PRIVMSG
	// Parameters: <receiver>{,<receiver>} <text to be sent>
	if (params[0].toLowerCase() == loq_main.chat_status_mgr.myNick.toLowerCase()) { // query
		var query_status = loq_main.chat_status_mgr.GetQueryStatus(origin);
		if (! query_status)
			query_status = loq_main.chat_status_mgr.OpenQuery(origin);

		var query_window = loq_main.chat_window_mgr.GetQueryWindow(origin);
		if (! query_window || ! query_window.isOpen())
			query_window = loq_main.chat_window_mgr.OpenQueryWindow(origin);

		query_window.WriteUserMessage(origin, params[1]);
	}
	else { // channel
		var channel_window = loq_main.chat_window_mgr.GetChannelWindow(params[0]);
		if (! channel_window || ! channel_window.isOpen())
			channel_window = loq_main.chat_window_mgr.OpenChannelWindow(params[0]);

		channel_window.WriteUserMessage(origin, params[1]);
	}
}

function LoqProtocol_JOIN(loq_main, origin, params)
{
	//    Command:  JOIN 
	// Parameters:  <channel>{,<channel>} [<key>{,<key>}] 
	var channel = params[0];

	var channel_window = loq_main.chat_window_mgr.GetChannelWindow(channel);

	if (origin.toLowerCase() == loq_main.chat_status_mgr.myNick.toLowerCase()) {
		loq_main.chat_status_mgr.IJoinChannel(channel);
		if (! channel_window || ! channel_window.isOpen())
			channel_window = loq_main.chat_window_mgr.OpenChannelWindow(channel);

		channel_window.WriteLine('chat_f', '<font color="#008000">*** Now talking in '.concat(channel, '</font>'));
		loq_main.status_window.WriteLine('<font color="#008000">*** You have joined '.concat(channel, '</font>'), 'JOI');
	}
	else {
		loq_main.chat_status_mgr.UserJoinsChannel(origin, channel);
		if (channel_window) {
			var channel_status = loq_main.chat_status_mgr.GetChannelStatus(channel);
			if (channel_status)
				channel_window.UpdateUserList(channel_status.channelUsers);
			channel_window.WriteLine('chat_f', '<font color="#008000">*** '.concat(origin, ' has joined ', channel, '</font>'));
		}
	}
}

function LoqProtocol_PART(loq_main, origin, params)
{
	//    Command: PART
	// Parameters: <channel>{,<channel>}

	var channel = params[0];

	var channel_window = loq_main.chat_window_mgr.GetChannelWindow(channel);

	// i have left
	if (origin.toLowerCase() == loq_main.chat_status_mgr.myNick.toLowerCase()) {
		if (channel_window && channel_window.isOpen())
			loq_main.chat_window_mgr.CloseChannelWindow(channel);
		loq_main.chat_status_mgr.IPartChannel(channel);
		loq_main.status_window.WriteLine('<font color="#008000">*** '.concat('You have left ', channel, '</font>'), 'LFT');
	}
	else { // user has left
		loq_main.chat_status_mgr.UserPartsChannel(origin, channel);
		if (channel_window) {
			var channel_status = loq_main.chat_status_mgr.GetChannelStatus(channel);
			if (channel_status)
				channel_window.UpdateUserList(channel_status.channelUsers);
			channel_window.WriteLine('chat_f', '<font color="#008000">*** '.concat(origin, ' has left ', channel, '</font>'));
		}
	}
}

function LoqProtocol_KICK(loq_main, origin, params)
{
	//    Command:  KICK 
	// Parameters:  <channel> <user> [<comment>] 

	var channel = params[0];
	var user = params[1];
	var comment = params[2] ? Msg2Html(htmlSafeString(params[2])) : '';

	var channel_window = loq_main.chat_window_mgr.GetChannelWindow(channel);

	// i was kicked
	if (user.toLowerCase() == loq_main.chat_status_mgr.myNick.toLowerCase()) {
		if (channel_window && channel_window.isOpen())
			loq_main.chat_window_mgr.CloseChannelWindow(channel);
		loq_main.chat_status_mgr.IPartChannel(channel);
		loq_main.status_window.WriteLine('<font color="#008000">*** '.concat('You were kicked from ', channel, ' by ', origin, ' (', comment, ')</font>'), 'KCK');
	}
	else { // user was kicked
		loq_main.chat_status_mgr.UserPartsChannel(user, channel);
		if (channel_window) {
			var channel_status = loq_main.chat_status_mgr.GetChannelStatus(channel);
			if (channel_status)
				channel_window.UpdateUserList(channel_status.channelUsers);
			channel_window.WriteLine('chat_f', '<font color="#008000">*** '.concat(user, ' was kicked by ', origin, ' (', comment, ')</font>'));
		}
	}
}

function LoqProtocol_TOPIC(loq_main, origin, params)
{
	//    Command: TOPIC
	// Parameters: <channel> [ <topic> ]

	var channel = params[0];
	var topic = params[1];

	loq_main.chat_status_mgr.ChangeTopic(channel, topic);

	var channel_window = loq_main.chat_window_mgr.GetChannelWindow(channel);
	if (channel_window) {
		channel_window.UpdateTopic(topic);
		channel_window.WriteLine('chat_f', '<font color="#008000">*** '.concat(origin, ' changes topic to \'', Msg2Text(htmlSafeString(topic)), '\'</font>'));
	}
}

function LoqProtocol_QUIT(loq_main, origin, params)
{
	//    Command: QUIT
	// Parameters: [ <Quit Message> ]

	var message = params[0] ? Msg2Html(htmlSafeString(params[0])) : '';

	var user_status = loq_main.chat_status_mgr.GetUserStatus(origin);
	if (user_status) {
		var user_channels = user_status.userChannels;
		if (user_channels) {
			for (var channel in user_channels) {
				loq_main.chat_status_mgr.UserPartsChannel(origin, channel);

				var channel_window = loq_main.chat_window_mgr.GetChannelWindow(channel);
				if (channel_window) {
					channel_window.WriteLine('chat_f', '<font color="#000080">*** '.concat(origin, ' has quit IRC (', message, ')</font>'));
					var channel_status = loq_main.chat_status_mgr.GetChannelStatus(channel);
					if (channel_status)
						channel_window.UpdateUserList(channel_status.channelUsers);
				}
			}
		}
		else {
			top.DebugWindow.Error('no user_channels', 'QUIT');
		}
	}

	var query_status = loq_main.chat_status_mgr.GetQueryStatus(origin);
	if (query_status) {
		loq_main.chat_status_mgr.ShutdownQuery(origin);
	}
	var query_window = loq_main.chat_window_mgr.GetQueryWindow(origin);
	if (query_window)
		query_window.WriteLine('chat_f', '<font color="#000080">*** '.concat(origin, ' has quit IRC(', message, ')</font>'));

	//loq_main.chat_status_mgr.UserQuits(origin);
}

var LoqProtocol_CommandTable = {
'ERROR':LoqProtocol_ERROR,
'JOIN':LoqProtocol_JOIN,
'KICK':LoqProtocol_KICK,
'MODE':LoqProtocol_MODE,
'NICK':LoqProtocol_NICK,
'NOTICE':LoqProtocol_NOTICE,
'PART':LoqProtocol_PART,
'PING':LoqProtocol_PING,
'PRIVMSG':LoqProtocol_PRIVMSG,
'QUIT':LoqProtocol_QUIT,
'TOPIC':LoqProtocol_TOPIC
};

function LoqProtocolInterpreter_Execute(srv_msg)
{
//top.DebugWindow.Trace(srv_msg, '<');
/*
for (var i = 0; i < srv_msg.length; i++) {
	if (srv_msg.charCodeAt(i) < 32) {
		top.DebugWindow.Trace(srv_msg.charCodeAt(i).toString(), ':');
	}
}
*/

	if (srv_msg == null) {
		this.Disconnected();
		return;
	}

	// tokenize server message
	{
		var arr = srv_msg.match(/^(:[^! ]*)?(![^ ]*)? *(\w*) *([^:]*[^: ])? *( :.*)?$/);
		if (arr) {
			if (arr[1]) // origin
				origin = arr[1].substring(1);
			else
				origin = '';

			if (arr[3]) // command
				command = arr[3];
			else
				command = '';

			// parameters
			if (arr[4] && arr[5])
				params = arr[4].split(/ /).concat(arr[5].substring(2));
			else if (arr[4])
				params = arr[4].split(/ /);
			else if (arr[5])
				params = [ arr[5].substring(2) ];
			else
				params = [];
		}
	}

	if (! command) {
		top.DebugWindow.Error(srv_msg, 'srv_msg');
		top.DebugWindow.Error(command, 'command');
		return;
	}

	if (command.search(/^[0-9]/) == 0) { 
		var rep_func = LoqProtocol_ReplyTable[command];
		if (rep_func) {
			rep_func(this.loq_main, origin, params);
		}
		else {
			this.loq_main.status_window.WriteLine('<u>'+srv_msg+'</u>', command);
		}
	}
	else {
		// commands
		var cmd_func = LoqProtocol_CommandTable[command];
		if (cmd_func) {
			cmd_func(this.loq_main, origin, params);
		}
		else {
			this.loq_main.status_window.WriteLine('<u>'+srv_msg+'</u>', command);
		}
	}
}

function LoqProtocolInterpreter(loq_main)
{
	this.Connected = LoqProtocolInterpreter_Connected;
	this.ConnectionFailed = LoqProtocolInterpreter_ConnectionFailed;
	this.Disconnected = LoqProtocolInterpreter_Disconnected;
	this.Execute = LoqProtocolInterpreter_Execute;

	this.loq_main = loq_main;
}
