//====================================================================================
//
//   File : kvi_sp_literal.cpp
//   Creation date : Thu Aug 3 2000 01:29:12 by Szymon Stefanek
//
//   This file is part of the KVirc irc client distribution
//   Copyright (C) 1999-2004 Szymon Stefanek (pragma at kvirc dot net)
//
//   This program is FREE software. You can redistribute it and/or
//   modify it under the terms of the GNU General Public License
//   as published by the Free Software Foundation; either version 2
//   of the License, or (at your opinion) any later version.
//
//   This program is distributed in the HOPE that it will be USEFUL,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//   See the GNU General Public License for more details.
//
//   You should have received a copy of the GNU General Public License
//   along with this program. If not, write to the Free Software Foundation,
//   Inc. ,59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
//====================================================================================

#define __KVIRC__

#include "kvi_sparser.h"
#include "kvi_window.h"
#include "kvi_console.h"
#include "kvi_out.h"
#include "kvi_locale.h"
#include "kvi_ircsocket.h"
#include "kvi_options.h"
#include "kvi_ircmask.h"
#include "kvi_channel.h"
#include "kvi_topicw.h"
#include "kvi_frame.h"
#include "kvi_mirccntrl.h"
#include "kvi_query.h"
#include "kvi_userlistview.h"
#include "kvi_antispam.h"
#include "kvi_nickserv.h"
#include "kvi_parameterlist.h"
#include "kvi_ircuserdb.h"
#include "kvi_app.h"
#include "kvi_regusersdb.h"
#include "kvi_debug.h"
#include "kvi_time.h"
#include "kvi_useraction.h"
#include "kvi_ircconnection.h"
#include "kvi_ircconnectionuserinfo.h"
#include "kvi_ircconnectiontarget.h"
#include "kvi_ircconnectionserverinfo.h"
#include "kvi_ircconnectionstatedata.h"
#include "kvi_ircconnectionnetsplitdetectordata.h"
#include "kvi_iconmanager.h"
#include "kvi_lagmeter.h"
#include "kvi_ircserver.h"
#include "kvi_kvs_eventtriggers.h"
#include "kvi_qcstring.h"

#include "kvi_settings.h"

#ifdef COMPILE_CRYPT_SUPPORT
	#include "kvi_crypt.h"
	#include "kvi_cryptcontroller.h"
#endif

#include "kvi_kvs_script.h"

//#include "kvi_regusersdb.h"
//#include "kvi_iconmanager.h"
#include <qdatetime.h>

extern KviNickServRuleSet * g_pNickServRuleSet;

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// PING
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void KviServerParser::parseLiteralPing(KviIrcMessage *msg)
{
	// PING
	// <optional_prefix> PING :<argument>
	msg->connection()->sendFmtData("PONG %s",msg->console()->connection()->encodeText(msg->allParams()).data());

	QString szPrefix = msg->connection()->decodeText(msg->safePrefix());
	QString szAllParams = msg->connection()->decodeText(msg->allParams());

	if(KVS_TRIGGER_EVENT_2_HALTED(KviEvent_OnPing,msg->console(),szPrefix,szAllParams))
		msg->setHaltOutput();

	if((!msg->haltOutput()) && KVI_OPTION_BOOL(KviOption_boolShowPingPong))
	{
		msg->console()->output(KVI_OUT_SERVERPING,
			__tr2qs("Received ping from \r!s\r%Q\r (PING %Q), replied pong"),
			&szPrefix,&szAllParams);
	}
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// PONG
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void KviServerParser::parseLiteralPong(KviIrcMessage *msg)
{
	QString szPrefix = msg->connection()->decodeText(msg->safePrefix());
	QString szAllParams = msg->connection()->decodeText(msg->allParams());

	if(KVS_TRIGGER_EVENT_2_HALTED(KviEvent_OnPong,msg->console(),szPrefix,szAllParams))
		msg->setHaltOutput();

	if(msg->console()->connection()->lagMeter())
	{
		if(msg->console()->connection()->lagMeter()->lagCheckComplete("@ping@"))
			msg->setHaltOutput(); // was internally generated!
	}

	if((!msg->haltOutput()) && KVI_OPTION_BOOL(KviOption_boolShowPingPong))
	{
		msg->console()->output(KVI_OUT_SERVERPING,
			__tr2qs("Received pong from \r!s\r%s\r (PONG %s)"),msg->safePrefix(),msg->allParams());
	}
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// ERROR
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void KviServerParser::parseLiteralError(KviIrcMessage *msg)
{
	// ERROR
	// <optional_prefix> ERROR :<argument>
	// ERROR :Closing Link: phoenix.pragmaware.net (Ping timeout)

	QString szPrefix = msg->connection()->decodeText(msg->safePrefix());
	QString szParams = msg->connection()->decodeText(msg->allParams());

	if(KVS_TRIGGER_EVENT_2_HALTED(KviEvent_OnError,msg->console(),szPrefix,szParams))
		msg->setHaltOutput();

	if(!msg->haltOutput())
	{
		msg->console()->output(KVI_OUT_SERVERERROR,
			__tr2qs("Server ERROR: %Q"),&szParams);
	}
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// JOIN
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void KviServerParser::parseLiteralJoin(KviIrcMessage *msg)
{
	// JOIN
	// :<joiner_mask> JOIN :<channel>
	QString szNick,szUser,szHost;
	msg->decodeAndSplitPrefix(szNick,szUser,szHost);

	const char * encodedChan = msg->safeTrailing();
	QString channel = msg->connection()->decodeText(encodedChan);

	if(channel.isEmpty())
	{
		// This is broken....
		UNRECOGNIZED_MESSAGE(msg,__tr2qs("Missing channel parameter in join message"));
		return;
	}

	// check for extended join syntax.
	// it is used in splits only (AFAIK)
	// nick!user@host JOIN :#channel\x07[o|v]
	const QChar * pExt = KviQString::nullTerminatedArray(channel);
	char  chExtMode = 0;
	while(pExt->unicode() && (pExt->unicode() != 0x07))pExt++;
	if(pExt->unicode())
	{
		++pExt;
		if(pExt->unicode())
		{
			chExtMode = (char)pExt->unicode();
			channel.remove(channel.length() - 2,2); // assuming that we're at the end (we should be)
		} // else { senseless 0x07 in channel name ?
	}

	// Now lookup the channel
	KviConsole * console = msg->console();
	KviChannel * chan = msg->connection()->findChannel(channel);

	bool bIsMe = IS_ME(msg,szNick);

	if(!chan)
	{
		// This must be me...(or desync)
		if(bIsMe)
		{
			msg->connection()->userInfoReceived(szUser,szHost);
			chan = msg->connection()->createChannel(channel); // New channel (will resurrect an eventual dead one too!)
		} else {
			// Someone is joining an inexsisting channel!!!
			UNRECOGNIZED_MESSAGE(msg,__tr("Received a join message for an unknown channel, possible desync"));
			return;
		}

		int iFlags = 0;
		iFlags = msg->connection()->serverInfo()->modeFlagFromModeChar(chExtMode);

		KviUserListEntry * it = chan->join(szNick,szUser,szHost,iFlags);
		if(iFlags)chan->updateCaption();

		// FIXME: #warning "Trigger also OnMeVoice and OnMeOp here ?"
		if(!(it->globalData()->avatar()))
		{
			KviAvatar * av = console->defaultAvatarFromOptions();
			if(av)
			{
				it->globalData()->setAvatar(av);
				console->avatarChanged(av,szNick,szUser,szHost,QString::null);
			}
		}

		if(KVS_TRIGGER_EVENT_0_HALTED(KviEvent_OnMeJoin,chan))
			msg->setHaltOutput();

		// the channel requests must be sent AFTER we have created and accessed the chan
		// since it MAY happen that a sendFmtData() call fails by detecting a disconnect
		// and thus destroys the channel window!
		
		// If this problem persists in other parts of the KVIrc core then
		// we should disable disconnection detection during the parsing of a single
		// message in KviIrcSocket. See the comment in KviIrcSocket::processData() for more info.

		// FIXME: #warning "IF VERBOSE SAY THAT WE'RE REQUESTING MODES & BAN LIST" (Synching channel)

		if(!msg->connection()->sendFmtData("MODE %s",encodedChan))return; // disconnected

		if(msg->connection()->serverInfo()->supportsModesIe())
		{
			if(!KVI_OPTION_BOOL(KviOption_boolDisableBanExceptionListRequestOnJoin))
			{
				if(!msg->connection()->sendFmtData("MODE %s e",encodedChan))return; // disconnected
				chan->setSentBanExceptionListRequest();
			}
			if(!KVI_OPTION_BOOL(KviOption_boolDisableInviteListRequestOnJoin))
			{
				if(!msg->connection()->sendFmtData("MODE %s I",encodedChan))return; // disconnected
				chan->setSentInviteListRequest();
			}
		}

		// MODE %s b MUST BE THE LAST AUTOMATIC CHANNEL QUERY
		// so we get RPL_ENDOFBANLIST as the last reply
		// and we know that the channel is in sync

		if(!KVI_OPTION_BOOL(KviOption_boolDisableWhoRequestOnJoin))
		{
			msg->connection()->stateData()->setLastSentChannelWhoRequest(kvi_unixTime());
			if(msg->connection()->lagMeter())
			{
				KviStr tmp(KviStr::Format,"WHO %s",encodedChan);
				msg->connection()->lagMeter()->lagCheckRegister(tmp.ptr(),60);
			}
			if(!msg->connection()->sendFmtData("WHO %s",encodedChan))return; // disconnected
			chan->setSentWhoRequest();
		}
		if(!KVI_OPTION_BOOL(KviOption_boolDisableBanListRequestOnJoin))
		{
			if(!msg->connection()->sendFmtData("MODE %s b",encodedChan))return; // disconnected
			chan->setSentBanListRequest();
		}

	} else {
		// This must be someone else...(or desync)
		int iFlags = 0;
		iFlags = msg->connection()->serverInfo()->modeFlagFromModeChar(chExtMode);

		KviUserListEntry * it = chan->join(szNick,szUser,szHost,iFlags);

		// FIXME: #warning "Trigger also OnVoice and OnOp here ?"
		// Note: checkDefaultAvatar() makes a KviRegisteredUser lookup
		//       if later it is needed, make it return a pointer
		if(!(it->globalData()->avatar()))console->checkDefaultAvatar(it->globalData(),szNick,szUser,szHost);

		if(KVS_TRIGGER_EVENT_3_HALTED(KviEvent_OnJoin,chan,szNick,szUser,szHost))
			msg->setHaltOutput();
		// FIXME: #warning "WE COULD OPTIONALLY REQUEST A /WHO FOR THE USERS JOINING THAT WE DON'T KNOW THE HOST OF"
	}

	// Now say it to the world
	if(!msg->haltOutput())
	{
		// FIXME: #warning "CHECK IF MESSAGES GO TO CONSOLE OR NOT"

		if(chExtMode != 0)
		{
			chan->output(KVI_OUT_JOIN,
				__tr2qs("\r!n\r%Q\r [%Q@\r!h\r%Q\r] has joined \r!c\r%Q\r [implicit +%c umode change]"),
				&szNick,&szUser,&szHost,&channel,chExtMode);

		} else {
			chan->output(KVI_OUT_JOIN,
				__tr2qs("\r!n\r%Q\r [%Q@\r!h\r%Q\r] has joined \r!c\r%Q\r"),
				&szNick,&szUser,&szHost,&channel);
		}
	}

	//if(!bisMe) deleted because we can open query with our nick
	QString szChans;
	int iChans = msg->connection()->getCommonChannels(szNick,szChans);
	KviQuery * q = console->connection()->findQuery(szNick);
	if(q)
	{
		if(KVI_OPTION_BOOL(KviOption_boolEnableQueryTracing))
		{
			q->output(KVI_OUT_QUERYTRACE,
				__tr2qs("\r!n\r%Q\r [%Q@\r!h\r%Q\r] has just joined \r!c\r%Q\r"),&szNick,&szUser,
				&szHost,&channel);
			q->notifyCommonChannels(szNick,szUser,szHost,iChans,szChans);
		} else {
			q->updateLabelText();
		}
	}
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// PART
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void KviServerParser::parseLiteralPart(KviIrcMessage *msg)
{
	// PART
	// :<source_mask> PART <channel> :<part message>
	QString szNick,szUser,szHost;
	msg->decodeAndSplitPrefix(szNick,szUser,szHost);

	QString szChan = msg->connection()->decodeText(msg->safeParam(0));

	// Now lookup the channel
	KviConsole * console = msg->console();
	KviChannel * chan = msg->connection()->findChannel(szChan);

	if(!chan)
	{
		//chan = msg->context()->findDeadChannel(msg->safeParam(0));
		UNRECOGNIZED_MESSAGE(msg,__tr("Received a part message for an unknown channel, possible desync"));
		return;
	}

	// always decode with the textEncoding of the channel
	QString partMsg = msg->paramCount() > 1 ? chan->decodeText(msg->safeTrailing()) : QString::null;

	if(IS_ME(msg,szNick))
	{
		if(KVS_TRIGGER_EVENT_1_HALTED(KviEvent_OnMePart,chan,partMsg))
			msg->setHaltOutput();

		KviWindow * pOut = console;

		// It's me!
		if(chan->closeOnPart() && !KVI_OPTION_BOOL(KviOption_boolKeepChannelOpenOnPart))
		{
			chan->frame()->closeWindow(chan); // <-- deleted path
		} else {
			chan->part(szNick); // this will trigger the action too
			chan->setDeadChan();
			pOut = chan;
		}

		if(!msg->haltOutput())
		{
			if(KVI_OPTION_BOOL(KviOption_boolShowOwnParts))
			{
				if(partMsg.isEmpty())
					pOut->output(KVI_OUT_PART,__tr2qs("You have left channel \r!c\r%Q\r"),&szChan);
				else
					pOut->output(KVI_OUT_PART,__tr2qs("You have left channel \r!c\r%Q\r: %Q"),&szChan,&partMsg);
			}
		}

	} else {
		// Someone else

		if(KVS_TRIGGER_EVENT_4_HALTED(KviEvent_OnPart,chan,szNick,szUser,szHost,partMsg))
			msg->setHaltOutput();

		chan->part(szNick);

		if(!msg->haltOutput())
		{
			if(!partMsg.isEmpty())
				chan->output(KVI_OUT_PART,
					__tr2qs("\r!n\r%Q\r [%Q@\r!h\r%Q\r] has left \r!c\r%Q\r: %Q"),&szNick,&szUser,
					&szHost,&szChan,&partMsg);
			else
				chan->output(KVI_OUT_PART,
					__tr2qs("\r!n\r%Q\r [%Q@\r!h\r%Q\r] has left \r!c\r%Q\r"),&szNick,&szUser,
					&szHost,&szChan);

		}

		if(KVI_OPTION_BOOL(KviOption_boolEnableQueryTracing))
		{
			QString szChans;
			int iChans = console->connection()->getCommonChannels(szNick,szChans);
			KviQuery * q = console->connection()->findQuery(szNick);
			if(q)
			{
				if(!partMsg.isEmpty())
					q->output(KVI_OUT_QUERYTRACE,
						__tr2qs("\r!nc\r%Q\r [%Q@\r!h\r%Q\r] has just left \r!c\r%Q\r: %Q"),
						&szNick,&szUser,&szHost,&szChan,&partMsg);
				else
					q->output(KVI_OUT_QUERYTRACE,
						__tr2qs("\r!nc\r%Q\r [%Q@\r!h\r%Q\r] has just left \r!c\r%Q\r"),
						&szNick,&szUser,&szHost,&szChan);
				q->notifyCommonChannels(szNick,szUser,szHost,iChans,szChans);
			}
		}
	}
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// QUIT
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void KviServerParser::parseLiteralQuit(KviIrcMessage *msg)
{
	// QUIT
	// :<source_mask> QUIT :<quit message>
	QString szNick,szUser,szHost;
	msg->decodeAndSplitPrefix(szNick,szUser,szHost);

	KviConsole * console = msg->console();

	// NETSPLIT DETECTION STUFF
	// this doesn't need to be decoded for the moment
	const char * aux = msg->safeTrailing();
	bool bWasSplit = false;
	//determine if signoff string matches "%.% %.%", and only one space (from eggdrop code)
	char *p = (char *)strchr(aux, ' ');
	if (p && (p == (char *)strrchr(aux,' ')))
	{
		char *daSpace = p;
		// one space detected. go ahead
		char *z1, *z2;
		*p = 0;
		z1 = (char *)strchr(p + 1, '.');
		z2 = (char *)strchr(aux, '.');
		if (z1 && z2 && (*(z1 + 1) > 47) && (z1 - 1 != p) && (z2 + 1 != p) && (z2 != aux) && console->connection())
		{
			// server split, or else it looked like it anyway
			KviIrcConnectionNetsplitDetectorData * ndd = msg->connection()->netsplitDetectorData();
			*p=' ';
			bWasSplit = true;

			time_t curTime = kvi_unixTime();
			int diff = ((unsigned int)curTime) - ((unsigned int)ndd->lastNetsplitOnQuitTime());
			bool bDuplicate = false;
			
			QString szReason = aux;
			if(diff < 6)
			{
				if(KviQString::equalCI(ndd->lastNetsplitOnQuitReason(),szReason))
				{
					bDuplicate = true;
				}
			}

			ndd->setLastNetsplitOnQuitTime(curTime);
			ndd->setLastNetsplitOnQuitReason(szReason);

			if(!bDuplicate)
			{
				KviStr sz1(aux,daSpace - aux);
				KviStr sz2(daSpace + 1);
				
				QString szD1 = msg->connection()->decodeText(sz1.ptr());
				QString szD2 = msg->connection()->decodeText(sz2.ptr());
				if(!KVS_TRIGGER_EVENT_2_HALTED(KviEvent_OnNetsplit,console,szD1,szD2))
				{
					if(!msg->haltOutput())
						console->output(KVI_OUT_SPLIT,__tr2qs("Netsplit detected: %s"),aux);
				}
			}
		} else *p = ' ';
	}

	// FIXME: #warning "Add a netsplit parameter ?"

	if(KviKvsEventManager::instance()->hasAppHandlers(KviEvent_OnQuit))
	{
		// compute the channel list
		QString chanlist;
		QString szReason = msg->connection()->decodeText(msg->safeTrailing());
		
		for(KviChannel *daChan=console->channelList()->first();daChan;daChan=console->channelList()->next())
		{
			if(daChan->isOn(szNick))
			{
				if(chanlist.isEmpty())chanlist = daChan->windowName();
				else {
					chanlist.append(',');
					chanlist.append(daChan->windowName());
				}
			}
		}
		
		KviKvsVariantList vList;
		vList.append(szNick);
		vList.append(szUser);
		vList.append(szHost);
		vList.append(szReason);
		vList.append(chanlist);

		if(KviKvsEventManager::instance()->trigger(KviEvent_OnQuit,console,&vList))
			msg->setHaltOutput();
	}


	for(KviChannel *c=console->channelList()->first();c;c=console->channelList()->next())
	{
		if(c->part(szNick))
		{
			if(!msg->haltOutput())
			{
				QString quitMsg = c->decodeText(msg->safeTrailing());

				if(bWasSplit)
				{
					quitMsg.prepend("NETSPLIT ");
				}

				if(!msg->haltOutput())c->output(KVI_OUT_QUIT,
						__tr2qs("\r!n\r%Q\r [%Q@\r!h\r%Q\r] has quit IRC: %Q"),
						&szNick,&szUser,&szHost,&quitMsg);
			}
		}
	}

	if(!msg->haltOutput())
	{	
		KviQuery * q = msg->connection()->findQuery(szNick);
		if(q)
		{
			QString quitMsg = q->decodeText(msg->safeTrailing());
			if(bWasSplit)
			{
				quitMsg.prepend("NETSPLIT ");
			}
			q->output(KVI_OUT_QUIT,__tr2qs("\r!n\r%Q\r [%Q@\r!h\r%Q\r] has quit IRC: %Q"),
					&szNick,&szUser,&szHost,&quitMsg);
		}
	}
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// KICK
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void KviServerParser::parseLiteralKick(KviIrcMessage *msg)
{
	// KICK
	// :<source_mask> KICK <channel> <nick> :<kick message>
	QString szNick,szUser,szHost;
	msg->decodeAndSplitPrefix(szNick,szUser,szHost);

	QString szChan = msg->connection()->decodeText(msg->safeParam(0));
	QString victim = msg->connection()->decodeText(msg->safeParam(1));

	KviConsole * console = msg->console();
	KviChannel * chan = msg->connection()->findChannel(szChan);

	if(!chan){
		// Ooops , desync with the server.
		UNRECOGNIZED_MESSAGE(msg,__tr("Received a kick message for an unknown channel, possible desync"));
		return;
	}

	QString szKickMsg = chan->decodeText(msg->safeTrailing());

	if(IS_ME(msg,victim))
	{
		// ops...I have been kicked

		if(KVS_TRIGGER_EVENT_4_HALTED(KviEvent_OnMeKick,chan,
				szNick,szUser,szHost,szKickMsg))
			msg->setHaltOutput();
		if(!KVI_OPTION_STRING(KviOption_stringOnMeKickedSound).isEmpty()) KviKvsScript::run("snd.play $0",0,new KviKvsVariantList(new KviKvsVariant(KVI_OPTION_STRING(KviOption_stringOnMeKickedSound))));

		QString szPass = chan->channelKey();

		if(KVI_OPTION_BOOL(KviOption_boolKeepChannelOpenOnKick))
		{
			chan->userAction(szNick,szUser,szHost,KVI_USERACTION_KICK);
			chan->part(victim);
			chan->setDeadChan();

			if(!msg->haltOutput())
			{
				// FIXME: #warning "OPTION FOR THIS TO GO TO THE CONSOLE!"
				chan->output(KVI_OUT_MEKICK,
					__tr2qs("You have been kicked from \r!c\r%Q\r by \r!n\r%Q\r [%Q@\r!h\r%Q\r]: %Q"),
					&szChan,&szNick,&szUser,&szHost,&szKickMsg);
			}
		} else {
			chan->frame()->closeWindow(chan); // <-- deleted path

			if(!msg->haltOutput())
			{
				// FIXME: #warning "This could go also to the active window!"
				console->output(KVI_OUT_MEKICK,
					__tr2qs("You have been kicked from \r!c\r%Q\r by \r!n\r%Q\r [%Q@\r!h\r%Q\r]: %Q"),
					&szChan,&szNick,&szUser,&szHost,&szKickMsg);
			}
		}
		if(KVI_OPTION_BOOL(KviOption_boolRejoinChannelOnKick))
		{
			if(_OUTPUT_VERBOSE)
				console->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Attempting to rejoin \r!c\r%Q\r..."),&szChan);
			KviQCString szC = msg->connection()->encodeText(szChan);
			if(!szPass.isEmpty())
			{
				KviQCString szP = msg->connection()->encodeText(szChan);
				msg->connection()->sendFmtData("JOIN %s %s",szC.data(),szP.data());
			} else msg->connection()->sendFmtData("JOIN %s",szC.data());
		}
	} else {
		// ok...someone else has been kicked

		if(KVS_TRIGGER_EVENT_5_HALTED(KviEvent_OnKick,chan,
				szNick,szUser,szHost,victim,szKickMsg))
			msg->setHaltOutput();

		KviIrcUserEntry * e = msg->connection()->userDataBase()->find(victim);

		QString szVHost;
		QString szVUser;

		if(e)
		{
			szVHost = e->host();
			szVUser = e->user();
		} else {
			szVHost = "*";
			szVUser = "*";
		}

		chan->userAction(szNick,szUser,szHost,KVI_USERACTION_KICK);

		chan->part(victim);

		if(!msg->haltOutput())
		{
// FIXME: #warning "OPTION FOR THIS TO GO TO THE CONSOLE!"
			chan->output(KVI_OUT_KICK,
				__tr2qs("\r!n\r%Q\r [%Q@\r!h\r%Q\r] has been kicked from \r!c\r%Q\r by \r!n\r%Q\r [%Q@\r!h\r%Q\r]: %Q"),
				&victim,&szVUser,&szVHost,&szChan,&szNick,&szUser,&szHost,&szKickMsg);
		}

		if(KVI_OPTION_BOOL(KviOption_boolEnableQueryTracing))
		{
			KviQuery * q = console->connection()->findQuery(victim);
			if(q)
			{
				QString szChans;
				int iChans = console->connection()->getCommonChannels(victim,szChans);
				q->output(KVI_OUT_QUERYTRACE,
					__tr2qs("\r!n\r%Q\r [%Q@\r!h\r%Q\r] has just been kicked from \r!c\r%Q\r by \r!n\r%Q\r [%Q@\r!h\r%Q\r]: %Q"),
					&victim,&szVUser,&szVHost,&szChan,
					&szNick,&szUser,&szHost,&szKickMsg);
				q->notifyCommonChannels(victim,szVUser,szVHost,iChans,szChans);
			}
		}
	}
}

#ifdef COMPILE_CRYPT_SUPPORT
	#define DECRYPT_IF_NEEDED(_target,_txt,_type,_type2,_buffer,_retptr,_retmsgtype) \
		if(KviCryptSessionInfo * cinf = _target->cryptSessionInfo()){ \
			if(cinf->bDoDecrypt){ \
				switch(cinf->pEngine->decrypt(_txt,_buffer)) \
				{ \
					case KviCryptEngine::DecryptOkWasEncrypted: \
						_retptr = _buffer.ptr(); \
						_retmsgtype = _type2; \
					break; \
					case KviCryptEngine::DecryptOkWasPlainText: \
					case KviCryptEngine::DecryptOkWasEncoded: \
						_retptr = _buffer.ptr(); \
						_retmsgtype = _type; \
					break; \
					default: /* also case KviCryptEngine::DecryptError: */ \
					{ \
						QString szEngineError = cinf->pEngine->lastError(); \
						_target->output(KVI_OUT_SYSTEMERROR, \
							__tr2qs("The following message appears to be encrypted, but the crypto engine failed to decode it: %Q"), \
							&szEngineError); \
						_retptr = _txt + 1; _retmsgtype=_type; \
					} \
					break; \
				} \
			} else _retptr = _txt, _retmsgtype=_type; \
		} else _retptr = _txt, _retmsgtype=_type;
#else //!COMPILE_CRYPT_SUPPORT
	#define DECRYPT_IF_NEEDED(_target,_txt,_type,_type2,_buffer,_retptr,_retmsgtype) \
		_retptr = _txt; _retmsgtype = _type;
#endif //!COMPILE_CRYPT_SUPPORT



void KviServerParser::parseLiteralPrivmsg(KviIrcMessage *msg)
{
	// PRIVMSG
	// :source PRIVMSG <target> :<message>
	QString szNick,szUser,szHost;
	msg->decodeAndSplitPrefix(szNick,szUser,szHost);

	QString szTarget = msg->connection()->decodeText(msg->safeParam(0));
	QString szMsg = msg->connection()->decodeText(msg->safeTrailing());

	KviConsole * console = msg->console();
	KviRegisteredUser * u = msg->connection()->userDataBase()->registeredUser(szNick,szUser,szHost);
    //Highlight it?

	// FIXME: 	#warning "DEDICATED CTCP WINDOW ?"
	KviStr * pTrailing = msg->trailingString();
	if(pTrailing)
	{
		if(*(pTrailing->ptr()) == 0x01)
		{
			if(pTrailing->len() > 1)
			{
				if(pTrailing->lastCharIs(0x01))pTrailing->cutRight(1);
				pTrailing->cutLeft(1);
				KviCtcpMessage ctcp;
				ctcp.msg = msg;
				ctcp.pData = pTrailing->ptr();
				KviIrcMask talker(szNick,szUser,szHost); // FIXME!
				ctcp.pSource = &talker;
				ctcp.szTarget = msg->connection()->decodeText(msg->safeParam(0));
				ctcp.bIgnored = false;
				ctcp.bIsFlood = false;
				ctcp.bUnknown = false;
				parseCtcpRequest(&ctcp);
				return;
			}
		}
	}

	// Normal PRIVMSG
	if(IS_ME(msg,szTarget))
	{
		//Ignore it?
		if (u)
		{
			if (u->isIgnoreEnabledFor(KviRegisteredUser::Query))
			{
				if(KVS_TRIGGER_EVENT_5_HALTED(KviEvent_OnIgnoredMessage,msg->console(),szNick,szUser,szHost,szTarget,szMsg)) return;

				if (KVI_OPTION_BOOL(KviOption_boolVerboseIgnore))
				{
					console->output(KVI_OUT_IGNORE,__tr2qs("Ignoring query-PRIVMSG from \r!nc\r%Q\r [%Q@\r!h\r%Q\r]: %Q"),&szNick,&szUser,&szHost,&szMsg);
				}
				return;
			}
		}
		// FIXME: 	#warning "PROCESS MULTIMEDIA FILE REQUESTS"

		//		if(g_pOptions->m_bListenToMultimediaFileRequests)
		//		{
		//			if(*aux == '!')
		//			{
		//				const char *tmp = aux;
		//				tmp++;
		//				if(kvi_strEqualCI(tmp,m_pFrm->m_global.szCurrentNick.ptr()))
		//				{
		//					// A multimedia file request ?
		//					tmp += m_pFrm->m_global.szCurrentNick.len();
		//					if(((*tmp) == ' ') || ((*tmp) == '\t'))
		//					{
		//						while(((*tmp) == ' ') || ((*tmp) == '\t'))tmp++;
		//						if(*tmp)
		//						{
		//							KviStr file = tmp;
		//							KviStr filePath;
		//							m_pFrm->findMultimediaFileOffert(filePath,file);
		//							if(filePath.hasData())
		//							{
		//								m_pFrm->activeWindow()->output(KVI_OUT_INTERNAL,__tr("%s requests previously offered file %s: sending (%s)"),talker.nick(),file.ptr(),filePath.ptr());
		//								KviStr cmd(KviStr::Format,"DCC SEND %s %s",talker.nick(),filePath.ptr());
		//								m_pFrm->m_pUserParser->parseUserCommand(cmd,m_pConsole);
		//								return;
	
		//							} else {
		//								m_pFrm->activeWindow()->output(KVI_OUT_INTERNAL,__tr("%s requests file %s: no such file was offered , ignoring"),talker.nick(),file.ptr());
		//								return;
		//							}
		//						}
		//					}
		//				}
		//			}
		//		}
				
		// A query request
		// do we have a matching window ?
		KviQuery * query = msg->connection()->findQuery(szNick);
		
		if(!query)
		{
			// New query requested. Check if we really should create it or not

			// first of all the anti spam , if desired.
			// the antispam blocks anything else
			// Eventually we could trigger a special event to notify the user of the
			// spam message...
			if(KVI_OPTION_BOOL(KviOption_boolUseAntiSpamOnPrivmsg))
			{
				KviStr * theMsg = msg->trailingString();
				if(theMsg)
				{
					KviStr spamWord;
					if(kvi_mayBeSpam(theMsg,spamWord))
					{
						// FIXME: OnSpam ?
						if(!(msg->haltOutput() || KVI_OPTION_BOOL(KviOption_boolSilentAntiSpam)))
						{
							QString szMsg = msg->connection()->decodeText(msg->safeTrailing());
							console->output(KVI_OUT_SPAM,
								__tr2qs("Spam privmsg from \r!n\r%Q\r [%Q@\r!h\r%Q\r]: %Q (matching spamword \"%s\")"),
								&szNick,&szUser,&szHost,&szMsg,spamWord.ptr());
						}
						return;
					}
				}
			}

			// this is not a spam, or at least it hasn't been recognized as spam
			
			// user option ? (this should again override any script)
			// if the scripters want really to force the query creation they can do
			// it manually or they can set the option to true at KVIrc startup
			if(KVI_OPTION_BOOL(KviOption_boolCreateQueryOnPrivmsg))
			{
				QString szMsg = msg->connection()->decodeText(msg->safeTrailing());
				// We still want to create it
				// Give the scripter a chance to filter it out again
				if(KVS_TRIGGER_EVENT_4_HALTED(KviEvent_OnQueryWindowRequest,
						console,szNick,szUser,szHost,szMsg))
				{
					// check if the scripter hasn't created it
					query = msg->connection()->findQuery(szNick);
				} else {
					// no query yet, create it!
					// this will trigger OnQueryWindowCreated
					query = console->connection()->createQuery(szNick);
					// and this will trigger OnQueryTargetAdded
					query->setTarget(szNick,szUser,szHost);
				}
			}
		}

		// ok, now we either have a query or not
		if(query)
		{
			// ok, we have the query. Trigger the user action anyway
			query->userAction(szNick,szUser,szHost,KVI_USERACTION_PRIVMSG);
			// decrypt the message if needed
			KviStr szBuffer; const char * txtptr; int msgtype;
			DECRYPT_IF_NEEDED(query,msg->safeTrailing(),KVI_OUT_QUERYPRIVMSG,KVI_OUT_QUERYPRIVMSGCRYPTED,szBuffer,txtptr,msgtype)
			// trigger the script event and eventually kill the output
			QString szMsgText = query->decodeText(txtptr);
			if(KVS_TRIGGER_EVENT_4_HALTED(KviEvent_OnQueryMessage,query,szNick,szUser,szHost,szMsgText)) 
				msg->setHaltOutput();
			
			if(!KVI_OPTION_STRING(KviOption_stringOnQueryMessageSound).isEmpty() && query!=g_pActiveWindow)
			{
				// KviKvsScript does NOT take parameters ownership
				KviKvsVariantList soundParams(new KviKvsVariant(KVI_OPTION_STRING(KviOption_stringOnQueryMessageSound)));
			 	//KviKvsScript::run("snd.play $0",0,&soundParams); <-- we also should provide a window for the script: it's always a good idea
				KviKvsScript::run("snd.play $0",query,&soundParams);
			}
			
			// spit out the message text
			if(!msg->haltOutput())
			{
				int iFlags = 0;
				if(!query->hasAttention())
				{
					if(KVI_OPTION_BOOL(KviOption_boolFlashQueryWindowOnNewMessages))
					{
						// avoid double window flashing
						iFlags |= KviConsole::NoWindowFlashing;
						query->demandAttention();
					}
					if(KVI_OPTION_BOOL(KviOption_boolPopupNotifierOnNewQueryMessages))
					{
						// don't send the message to the notifier twice
						iFlags |= KviConsole::NoNotifier;
						QString szMsg = "";
						//QString szMsg = "<b>&lt;";
						//szMsg += szNick;
						//szMsg += "&gt;</b> ";
						QString szHtml = szMsgText;
						szHtml.replace("<","&lt;");
						szHtml.replace(">","&gt;");
						szMsg += szHtml;
						//debug("kvi_sp_literal.cpp:908 debug: %s",szHtml.data());
						g_pApp->notifierMessage(query,KVI_SMALLICON_QUERYPRIVMSG,szMsg,1800);
					}
				}
				console->outputPrivmsg(query,msgtype,szNick,szUser,szHost,szMsgText,iFlags);
			}
		} else {
			// no query creation: no decryption possible
			// trigger the query message event in the console
			QString szMsgText = msg->connection()->decodeText(msg->safeTrailing());
			if(KVS_TRIGGER_EVENT_4_HALTED(KviEvent_OnQueryMessage,console,szNick,szUser,szHost,szMsgText))
					msg->setHaltOutput();
			
			// we don't have a query here!
			//if(!KVI_OPTION_STRING(KviOption_stringOnQueryMessageSound).isEmpty() && query!=g_pActiveWindow)
			if(!KVI_OPTION_STRING(KviOption_stringOnQueryMessageSound).isEmpty() && console!=g_pActiveWindow)
			{
				// same as above
				KviKvsVariantList soundParams(new KviKvsVariant(KVI_OPTION_STRING(KviOption_stringOnQueryMessageSound)));
			 	KviKvsScript::run("snd.play $0",console,&soundParams);
			}
			// spit the message text out
			if(!msg->haltOutput())
			{
				KviWindow * pOut = KVI_OPTION_BOOL(KviOption_boolExternalMessagesToActiveWindow) ?
					console->activeWindow() : (KviWindow *)(console);

				if(KviIrcConnection * pConnection = console->connection())
				{
					KviWindow * aWin = console->activeWindow();
					if((aWin->type() == KVI_WINDOW_TYPE_CHANNEL) && ((KviChannel *)aWin)->isOn(szNick))
						pOut = aWin;
					else {
						for(KviChannel * c = pConnection->channelList()->first();c;c = pConnection->channelList()->next())
							if(c->isOn(szNick))
							{
								pOut = (KviWindow *) c;
								break;
							}
					}
				}

				pOut->output(KVI_OUT_QUERYPRIVMSG,"[PRIVMSG \r!nc\r%Q\r]: %Q",&szNick,&szMsgText);
			}
		}
	} else {
		// Channel PRIVMSG

		KviChannel * chan = msg->connection()->findChannel(szTarget);

		QString szOriginalTarget = szTarget;
		QString szPrefixes;

		//Ignore it?
		if(u) 
		{
			if(u->isIgnoreEnabledFor(KviRegisteredUser::Channel))
			{
				if(KVS_TRIGGER_EVENT_5_HALTED(KviEvent_OnIgnoredMessage,msg->console(),szNick,szUser,szHost,szTarget,szMsg))
					return;

				if (KVI_OPTION_BOOL(KviOption_boolVerboseIgnore))
				{
					console->output(KVI_OUT_IGNORE,__tr2qs("Ignoring channel-PRIVMSG from \r!nc\r%Q\r [%Q@\r!h\r%Q\r]: %Q"),&szNick,&szUser,&szHost,&szMsg);
				}
				return;
			}
		}

		if(!chan)
		{
			// check if the channel has some leading mode prefixes 
			while((szTarget.length() > 0) && console->connection()->serverInfo()->isSupportedModePrefix(szTarget[0].unicode()))
			{
				szPrefixes += szTarget[0];
				szTarget.remove(0,1);
			}
			chan = msg->connection()->findChannel(szTarget);
		}

		if(!chan)
		{
			if(!msg->haltOutput())
			{
				QString szMsgText = msg->connection()->decodeText(msg->safeTrailing());
				KviWindow * pOut = KVI_OPTION_BOOL(KviOption_boolOperatorMessagesToActiveWindow) ? 
					console->activeWindow() : (KviWindow *)(console);
				QString broad;
				KviQString::sprintf(broad,"[>> %Q] %Q",&szOriginalTarget,&szMsgText);
				console->outputPrivmsg(pOut,KVI_OUT_BROADCASTPRIVMSG,szNick,szUser,szHost,broad,0);
			}
		} else {
			chan->userAction(szNick,szUser,szHost,KVI_USERACTION_PRIVMSG);

			KviStr szBuffer; const char * txtptr; int msgtype;
			DECRYPT_IF_NEEDED(chan,msg->safeTrailing(),KVI_OUT_CHANPRIVMSG,KVI_OUT_CHANPRIVMSGCRYPTED,szBuffer,txtptr,msgtype)

			QString szMsgText = chan->decodeText(txtptr);

			if(KVS_TRIGGER_EVENT_5_HALTED(KviEvent_OnChannelMessage,chan,szNick,szUser,szHost,szMsgText,szPrefixes))
				msg->setHaltOutput();

			if(!msg->haltOutput())
			{
				if(szPrefixes.length() > 0)
				{
					QString szBroad;
					KviQString::sprintf(szBroad,"[>> %Q\r!c\r%Q\r] %Q",&szPrefixes,&szTarget,&szMsgText);
					console->outputPrivmsg(chan,msgtype,szNick,szUser,szHost,szBroad,0);
				} else {
					console->outputPrivmsg(chan,msgtype,szNick,szUser,szHost,szMsgText,0);
				}
			}
		}
	}
}


void KviServerParser::parseLiteralNotice(KviIrcMessage *msg)
{
	// NOTICE
	// :source NOTICE <target> :<message>
	QString szNick,szUser,szHost;
	msg->decodeAndSplitPrefix(szNick,szUser,szHost);

	KviConsole * console = msg->console();

	if(szHost == "*")
	{
		if(szUser == "*")
		{
			if(szNick.find('.') != -1)
			{
				// server notice
				// FIXME: "Dedicated window for server notices ?"
				QString szMsgText = msg->connection()->decodeText(msg->safeTrailing());
				if(KVS_TRIGGER_EVENT_2_HALTED(KviEvent_OnServerNotice,console,szNick,szMsgText))
					msg->setHaltOutput();
				if(!msg->haltOutput())
				{
					KviWindow * pOut = KVI_OPTION_BOOL(KviOption_boolServerNoticesToActiveWindow) ?
						console->activeWindow() : (KviWindow *)(console);
					pOut->output(KVI_OUT_SERVERNOTICE,"[\r!s\r%Q\r]: %Q",&szNick,&szMsgText);
				}
				return;
			}
		}
	}

	// FIXME: "DEDICATED CTCP WINDOW ?"

	KviStr * pTrailing = msg->trailingString();
	if(pTrailing)
	{
		if(*(pTrailing->ptr()) == 0x01){
			if(pTrailing->len() > 1)
			{
				if(pTrailing->lastCharIs(0x01))pTrailing->cutRight(1);
				pTrailing->cutLeft(1);
				KviCtcpMessage ctcp;
				ctcp.msg = msg;
				ctcp.pData = pTrailing->ptr();
				KviIrcMask talker(szNick,szUser,szHost); // FIXME
				ctcp.pSource = &talker;
				ctcp.szTarget = msg->connection()->decodeText(msg->safeParam(0));
				ctcp.bIgnored = false;
				ctcp.bIsFlood = false;
				ctcp.bUnknown = false;
				parseCtcpReply(&ctcp);
				return;
			}
		}
	}

	QString szTarget = msg->connection()->decodeText(msg->safeParam(0));

	KviRegisteredUser * u = msg->connection()->userDataBase()->registeredUser(szNick,szUser,szHost);
	//Ignore it?
	if(u)
	{
		if(u->isIgnoreEnabledFor(KviRegisteredUser::Notice))
		{
			if(KVI_OPTION_BOOL(KviOption_boolVerboseIgnore))
			{
				QString szMsg = msg->connection()->decodeText(msg->safeTrailing());
				console->output(KVI_OUT_IGNORE,__tr2qs("Ignoring Notice from \r!nc\r%Q\r [%Q@\r!h\r%Q\r]: %Q"),&szNick,&szUser,&szHost,&szMsg);
			}
			return;
		}
	}

	// Normal NOTICE
	if(IS_ME(msg,szTarget))
	{
		// FIXME: "The NickServ and ChanServ handling should be optional!"

		if(KviQString::equalCI(szNick,"NickServ"))
		{
			QString szMsgText = msg->connection()->decodeText(msg->safeTrailing());
			if(KVS_TRIGGER_EVENT_4_HALTED(KviEvent_OnNickServNotice,console,szNick,szUser,szHost,szMsgText))
				msg->setHaltOutput();

			// nickname service... does it ask for identification ?
			if(!msg->haltOutput())
			{
				KviWindow * pOut = KVI_OPTION_BOOL(KviOption_boolServicesNoticesToActiveWindow) ?
					console->activeWindow() : (KviWindow *)(console);
				pOut->output(KVI_OUT_NICKSERV,"\r!n\r%Q\r [%Q@\r!h\r%Q\r]: %Q",&szNick,&szUser,&szHost,&szMsgText);
			}

			bool bAuthDone = false;
			KviNickServRuleSet * r = msg->connection()->target()->network()->nickServRuleSet();
			if(r)
			{
				if(r->isEnabled() && !r->isEmpty())
				{
					KviIrcMask talker(szNick,szUser,szHost);
					KviNickServRule * rule = r->matchRule(msg->connection()->currentNickName(),&talker,szMsgText);
					if(rule)
					{
						bAuthDone = true;
						console->outputNoFmt(KVI_OUT_SYSTEMMESSAGE,__tr2qs("NickServ requests authentication, executing scheduled command"));
						if(!KviKvsScript::run(rule->identifyCommand(),console))
						{
							console->outputNoFmt(KVI_OUT_SYSTEMERROR,__tr2qs("The scheduled NickServ identification command appears to be broken, please change the setting"));
						}
					}
				}
			}

			if(!bAuthDone)
			{
				if(g_pNickServRuleSet->isEnabled() && !g_pNickServRuleSet->isEmpty())
				{
					KviIrcMask talker(szNick,szUser,szHost);
					KviNickServRule * rule = g_pNickServRuleSet->matchRule(msg->connection()->currentNickName(),&talker,szMsgText,msg->connection()->currentServerName());
					if(rule)
					{
						console->outputNoFmt(KVI_OUT_SYSTEMMESSAGE,__tr2qs("NickServ requests authentication, executing scheduled command"));
						if(!KviKvsScript::run(rule->identifyCommand(),console))
						{
							console->outputNoFmt(KVI_OUT_SYSTEMERROR,__tr2qs("The scheduled NickServ identification command appears to be broken, please change the setting"));
						}
					}
				}
			}

			return;
		}

		if(KviQString::equalCI(szNick,"ChanServ"))
		{
			QString szMsgText = msg->connection()->decodeText(msg->safeTrailing());
			if(KVS_TRIGGER_EVENT_4_HALTED(KviEvent_OnChanServNotice,console,szNick,szUser,szHost,szMsgText))
				msg->setHaltOutput();
			if(!msg->haltOutput())
			{
				KviWindow * pOut = KVI_OPTION_BOOL(KviOption_boolServicesNoticesToActiveWindow) ?
					console->activeWindow() : (KviWindow *)(console);
				pOut->output(KVI_OUT_CHANSERV,"\r!n\r%Q\r [%Q@\r!h\r%Q\r]: %Q",&szNick,&szUser,&szHost,&szMsgText);
			}
			return;
		}

		// FIXME: PROCESS MULTIMEDIA FILE REQUESTS

		// A query request
		// do we have a matching window ?
		KviQuery * query = msg->connection()->findQuery(szNick);
		
		if(!query)
		{
			// New query requested. Check if we really should create it or not

			// first of all the anti spam , if desired.
			// the antispam blocks anything else
			// Eventually we could trigger a special event to notify the user of the
			// spam message...
			if(KVI_OPTION_BOOL(KviOption_boolUseAntiSpamOnNotice))
			{
				KviStr * theMsg = msg->trailingString(); // FIXME
				if(theMsg)
				{
					KviStr spamWord;
					if(kvi_mayBeSpam(theMsg,spamWord))
					{
						// FIXME: OnSpam ?

						if(!(msg->haltOutput() || KVI_OPTION_BOOL(KviOption_boolSilentAntiSpam)))
						{
							QString szMsgText = msg->connection()->decodeText(msg->safeTrailing());
							QString szSpamWord = spamWord.ptr();
							console->output(KVI_OUT_SPAM,__tr2qs("Spam notice from \r!n\r%Q\r [%Q@\r!h\r%Q\r]: %Q (matching spamword \"%Q\")"),
								&szNick,&szUser,&szHost,&szMsgText,&szSpamWord);
						}
						return;
					}
				}
			}

			// this is not a spam, or at least it hasn't been recognized as spam
			
			// user option ? (this should again override any script)
			// if the scripters want really to force the query creation they can do
			// it manually or they can set the option to true at KVIrc startup
			if(KVI_OPTION_BOOL(KviOption_boolCreateQueryOnNotice))
			{
				QString szMsgText = msg->connection()->decodeText(msg->safeTrailing());
				// We still want to create it
				// Give the scripter a chance to filter it out again
				if(KVS_TRIGGER_EVENT_4_HALTED(KviEvent_OnQueryWindowRequest,console,szNick,szUser,szHost,szMsgText))
				{
					// check if the scripter hasn't created it
					query = msg->connection()->findQuery(szNick);
				} else {
					// no query yet, create it!
					// this will trigger OnQueryWindowCreated
					query = console->connection()->createQuery(szNick);
					// and this will trigger OnQueryTargetAdded
					query->setTarget(szNick,szUser,szHost);
				}
			}
		}

		// ok, now we either have a query or not
		if(query)
		{
			// ok, we have the query. Trigger the user action anyway
			query->userAction(szNick,szUser,szHost,KVI_USERACTION_NOTICE);
			// decrypt it if needed
			KviStr szBuffer; const char * txtptr; int msgtype;
			DECRYPT_IF_NEEDED(query,msg->safeTrailing(),KVI_OUT_QUERYNOTICE,KVI_OUT_QUERYNOTICECRYPTED,szBuffer,txtptr,msgtype)
			QString szMsgText = query->decodeText(txtptr);
			// trigger the script event and eventually kill the output
			if(KVS_TRIGGER_EVENT_4_HALTED(KviEvent_OnQueryNotice,query,szNick,szUser,szHost,szMsgText)) 
				msg->setHaltOutput();
			// spit out the message text
			if(!msg->haltOutput())
			{
				int iFlags = 0;

				if(!query->hasAttention())
				{
					if(KVI_OPTION_BOOL(KviOption_boolFlashQueryWindowOnNewMessages))
					{
						// avoid double window flashing
						iFlags |= KviConsole::NoWindowFlashing;
						query->demandAttention();
					}
					if(KVI_OPTION_BOOL(KviOption_boolPopupNotifierOnNewQueryMessages))
					{
						// don't send the message twice to the notifier
						iFlags |= KviConsole::NoNotifier;
						QString szMsg = "";
						//QString szMsg = "<b>&lt;";
						//szMsg += szNick;
						//szMsg += "&gt;</b> ";
						QString szHtml = szMsgText;
						szHtml.replace("<","&lt;");
						szHtml.replace(">","&gt;");
						szMsg += szHtml;
						//debug("kvi_sp_literal.cpp:1262 debug: %s",szHtml.data());
						g_pApp->notifierMessage(query,KVI_SMALLICON_QUERYNOTICE,szMsg,1800);
					}
				}

				console->outputPrivmsg(query,msgtype,szNick,szUser,szHost,szMsgText,iFlags);
			}
		} else {
			QString szMsgText = msg->connection()->decodeText(msg->safeTrailing());
			// no query creation: no decryption possible
			// trigger the query message event in the console
			if(KVS_TRIGGER_EVENT_4_HALTED(KviEvent_OnQueryNotice,console,szNick,szUser,szHost,szMsgText))
				msg->setHaltOutput();
			// spit the message text out
			if(!msg->haltOutput())
			{
				KviWindow * pOut = KVI_OPTION_BOOL(KviOption_boolExternalMessagesToActiveWindow) ?
					console->activeWindow() : (KviWindow *)(console);

				if(KviIrcConnection * pConnection = console->connection())
				{
					KviWindow * aWin = console->activeWindow();
					if((aWin->type() == KVI_WINDOW_TYPE_CHANNEL) && ((KviChannel *)aWin)->isOn(szNick))
						pOut = aWin;
					else {
						for(KviChannel * c = pConnection->channelList()->first();c;c = pConnection->channelList()->next())
							if(c->isOn(szNick))
							{
								pOut = (KviWindow *) c;
								break;
							}
					}
				}

				pOut->output(KVI_OUT_QUERYNOTICE,"*\r!n\r%Q\r* %Q",&szNick,&szMsgText);
			}
		}
		return;
	}

	// Channel NOTICE
	KviChannel * chan = msg->connection()->findChannel(szTarget);
	
	QString szOriginalTarget = szTarget;
	QString szPrefixes;

	if(!chan)
	{
		// check if the channel has some leading mode prefixes 
		while((szTarget.length() > 0) && console->connection()->serverInfo()->isSupportedModePrefix(szTarget[0].unicode()))
		{
			szPrefixes += szTarget[0];
			szTarget.remove(0,1);
		}
		chan = msg->connection()->findChannel(szTarget);
	}

	if(!chan)
	{
		if(!msg->haltOutput())
		{
			KviWindow * pOut = KVI_OPTION_BOOL(KviOption_boolOperatorMessagesToActiveWindow) ? 
				console->activeWindow() : (KviWindow *)(console);
			QString szBroad;
			QString szMsgText = msg->connection()->decodeText(msg->safeTrailing());
			KviQString::sprintf(szBroad,"[>> %Q] %Q",&szOriginalTarget,&szMsgText);
			console->outputPrivmsg(pOut,KVI_OUT_BROADCASTNOTICE,szNick,szUser,szHost,szBroad,0);
			return;
		}
	} else {
		chan->userAction(szNick,szUser,szHost,KVI_USERACTION_NOTICE);

		KviStr szBuffer; const char * txtptr; int msgtype;
		DECRYPT_IF_NEEDED(chan,msg->safeTrailing(),KVI_OUT_CHANNELNOTICE,KVI_OUT_CHANNELNOTICECRYPTED,szBuffer,txtptr,msgtype)
		QString szMsgText = chan->decodeText(txtptr);

		if(KVS_TRIGGER_EVENT_3_HALTED(KviEvent_OnChannelNotice,chan,szNick,szMsgText,szOriginalTarget))msg->setHaltOutput();

		if(!msg->haltOutput())
		{
			if(szPrefixes.length() > 0)
			{
				QString szBroad;
				KviQString::sprintf(szBroad,"[>> %Q\r!c\r%Q\r] %Q",&szPrefixes,&szTarget,&szMsgText);
				console->outputPrivmsg(chan,msgtype,szNick,szUser,szHost,szBroad,0);
			} else {
				console->outputPrivmsg(chan,msgtype,szNick,szUser,szHost,szMsgText,0);
			}
		}
	}
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// TOPIC
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void KviServerParser::parseLiteralTopic(KviIrcMessage *msg)
{
	// TOPIC
	// :<source_mask> TOPIC <channel> :<topic>
	QString szNick,szUser,szHost;
	msg->decodeAndSplitPrefix(szNick,szUser,szHost);
	QString szTarget = msg->connection()->decodeText(msg->safeParam(0));
	
	// Now lookup the channel
	KviChannel * chan = msg->connection()->findChannel(szTarget);

	if(!chan)
	{
		UNRECOGNIZED_MESSAGE(msg,__tr2qs("Received a topic message for an unknown channel, possible desync"));
		return;
	}

	QString szTopic = chan->decodeText(msg->safeTrailing());

	if(KVS_TRIGGER_EVENT_4_HALTED(KviEvent_OnTopic,chan,szNick,szUser,szHost,szTopic))
		msg->setHaltOutput();

	chan->topicWidget()->setTopic(szTopic);
	chan->topicWidget()->setTopicSetBy(szNick);
	QString tmp = QDateTime::currentDateTime().toString();
	chan->topicWidget()->setTopicSetAt(tmp);

	chan->userAction(szNick,szUser,szHost,KVI_USERACTION_TOPIC);

	if(!msg->haltOutput())
	{
		chan->output(KVI_OUT_TOPIC,
			__tr2qs("\r!n\r%Q\r [%Q@\r!h\r%Q\r] has changed topic to \"%Q%c\""),
			&szNick,&szUser,&szHost,&szTopic,KVI_TEXT_RESET);
	}
}

void KviServerParser::parseLiteralNick(KviIrcMessage *msg)
{
	// NICK
	// :source NICK <newnick>
	QString szNick,szUser,szHost;
	msg->decodeAndSplitPrefix(szNick,szUser,szHost);
	KviConsole * console = msg->console();
	QString szNewNick = msg->connection()->decodeText(msg->safeTrailing());

	bool bIsMe = IS_ME(msg,szNick);

	if(bIsMe)
	{
		// We have changed our nick
		msg->connection()->nickChange(szNewNick);

		if(KVS_TRIGGER_EVENT_2_HALTED(KviEvent_OnMeNickChange,console,szNick,szNewNick))
				msg->setHaltOutput();
	} else {
		if(KVS_TRIGGER_EVENT_4_HALTED(KviEvent_OnNickChange,console,szNick,szUser,szHost,szNewNick))
				msg->setHaltOutput();
	}

	for(KviChannel * c = console->channelList()->first();c;c = console->channelList()->next())
	{
		if(c->nickChange(szNick,szNewNick))
		{
			if(!msg->haltOutput())
				c->output(KVI_OUT_NICK,__tr2qs("\r!n\r%Q\r [%Q@\r!h\r%Q\r] is now known as \r!n\r%Q\r"),
					&szNick,&szUser,&szHost,&szNewNick);
			// FIXME if(bIsMe)output(YOU ARE now known as.. ?)
		}
		if(bIsMe)c->updateCaption();
	}


	// FIXME: #warning "NEW NICK MIGHT BE REGISTERED AND HAVE AN AVATAR!"

	if(bIsMe)
	{
		// just update all the captions : we have changed OUR nick
		for(KviQuery * q = console->queryList()->first();q;q = console->queryList()->next())
		{
			if(!msg->haltOutput())
				q->output(KVI_OUT_NICK,__tr2qs("You have changed your nickname to %Q"),&szNewNick);
			q->updateCaption();
		}
	}
	KviQuery * q = console->connection()->findQuery(szNick);
	// It CAN happen that szNewNick first queries us without being
	// on any channel then he QUITS , he reconnects , he joins
	// a channel with szNick , queries us and changes nick to szNewNick : gotcha!
	// should merge the queries!
	KviQuery * old = console->connection()->findQuery(szNewNick);
	if(old && (old != q))
	{
		if(KVI_OPTION_BOOL(KviOption_boolEnableQueryTracing) && (!_OUTPUT_QUIET))
		{
		old->output(KVI_OUT_QUERYTRACE,
			__tr2qs("The target of this query was lost and has been found when \r!n\r%Q\r [%Q@\r!h\r%Q\r] changed his nickname to \r!n\r%Q\r"),
			&szNick,&szUser,&szHost,&szNewNick);
		}
		if(q)
		{
			bool bQWasActive = (q == g_pActiveWindow);
			if(!_OUTPUT_MUTE)
				{
				old->output(KVI_OUT_SYSTEMWARNING,
						__tr2qs("The recent nickname change from \r!n\r%Q\r to \r!n\r%Q\r caused a query collision: merging output"),
						&szNick,&szNewNick);
			}
				old->mergeQuery(q);
			q->frame()->closeWindow(q); // deleted path
			if(!msg->haltOutput())
					old->output(KVI_OUT_NICK,__tr2qs("\r!n\r%Q\r [%Q@\r!h\r%Q\r] is now known as \r!n\r%Q\r"),
					&szNick,&szUser,&szHost,&szNewNick);
			if(!_OUTPUT_MUTE)
				old->output(KVI_OUT_SYSTEMWARNING,__tr2qs("End of merged output"));
				old->userAction(szNewNick,szUser,szHost,KVI_USERACTION_NICK);
			if(bQWasActive)old->delayedAutoRaise();
		}
		if(KVI_OPTION_BOOL(KviOption_boolEnableQueryTracing))
		{
				QString szChans;
			int iChans = console->connection()->getCommonChannels(szNewNick,szChans);
			old->notifyCommonChannels(szNewNick,szUser,szHost,iChans,szChans);
		}
	} else {
		if(q)
		{
			// the target SHOULD have changed his nick here
			if(!q->nickChange(szNick,szNewNick))
					debug("Internal error: query %s failed to change nick from %s to s",szNick.utf8().data(),szNick.utf8().data(),szNewNick.utf8().data());
			if(!msg->haltOutput())
				q->output(KVI_OUT_NICK,__tr2qs("\r!n\r%Q\r [%Q@\r!h\r%Q\r] is now known as \r!n\r%Q\r"),
					&szNick,&szUser,&szHost,&szNewNick);
			q->userAction(szNewNick,szUser,szHost,KVI_USERACTION_NICK);
		}
	}

// FIXME: #warning "UPDATE ALL THE OTHER CONNECTION RELATED WINDOW CAPTIONS WHEN bIsMe!!"
}

void KviServerParser::parseLiteralInvite(KviIrcMessage *msg)
{
	// INVITE
	// :source INVITE <target> <channel>
	QString szNick,szUser,szHost;
	msg->decodeAndSplitPrefix(szNick,szUser,szHost);

	QString szTarget = msg->connection()->decodeText(msg->safeParam(0));
	QString szChannel = msg->connection()->decodeText(msg->safeParam(1));

	KviConsole * console = msg->console();
	KviRegisteredUser * u = msg->connection()->userDataBase()->registeredUser(szNick,szUser,szHost);
	//Ignore it?
	if(u)
	{
		if(u->isIgnoreEnabledFor(KviRegisteredUser::Invite))
		{
			if(KVI_OPTION_BOOL(KviOption_boolVerboseIgnore))
			{
				console->output(KVI_OUT_IGNORE,__tr2qs("Ignoring invite from \r!nc\r%Q\r [%Q@\r!h\r%Q\r]"),&szNick,&szUser,&szHost);
			}
			return;
		}
	}

	if(IS_ME(msg,szTarget))
	{
		if(KVS_TRIGGER_EVENT_4_HALTED(KviEvent_OnInvite,msg->console(),szNick,szUser,szHost,szChannel))
			msg->setHaltOutput();
		
		if(!msg->haltOutput())
		{
			KviWindow * pOut = KVI_OPTION_BOOL(KviOption_boolInvitesToActiveWindow) ?
				msg->console()->activeWindow() : (KviWindow *)(msg->console());
			QString szAction = KVI_OPTION_BOOL(KviOption_boolAutoJoinOnInvite) ? __tr2qs("autojoining") : __tr2qs("double-click the channel name to join");
			pOut->output(KVI_OUT_INVITE,__tr2qs("\r!n\r%Q\r [%Q@\r!h\r%Q\r] invites you to channel \r!c\r%Q\r (%Q)"),
				&szNick,&szUser,&szHost,&szChannel,&szAction);
		}

		if(KVI_OPTION_BOOL(KviOption_boolAutoJoinOnInvite))
			msg->connection()->sendFmtData("JOIN %s",msg->safeParam(1));

	} else {
		UNRECOGNIZED_MESSAGE(msg,__tr("Received an invite message directed to another nick, possible desync"));
	}
}

void KviServerParser::parseLiteralWallops(KviIrcMessage *msg)
{
	// WALLOPS
	// :source WALLOPS :msg
	QString szNick,szUser,szHost;
	msg->decodeAndSplitPrefix(szNick,szUser,szHost);

	QString szMsg = msg->connection()->decodeText(msg->safeTrailing());

	if(KVS_TRIGGER_EVENT_4_HALTED(KviEvent_OnWallops,msg->console(),szNick,szUser,szHost,szMsg))
		msg->setHaltOutput();

	if(!msg->haltOutput())
	{
		KviWindow * pOut = KVI_OPTION_BOOL(KviOption_boolOperatorMessagesToActiveWindow) ?
			msg->console()->activeWindow() : (KviWindow *)(msg->console());
		pOut->output(KVI_OUT_WALLOPS,__tr2qs("WALLOPS from \r!n\r%Q\r [%Q@\r!h\r%Q\r]: %Q"),
			&szNick,&szUser,&szHost,&szMsg);
	}
}

void KviServerParser::parseUserMode(KviIrcMessage *msg,const char * modeflptr)
{
	// changed my user mode
	bool bSet = true;
	while(*modeflptr)
	{
		switch(*modeflptr)
		{
			case '+': bSet = true; break;
			case '-': bSet = false; break;
			default:
				if(msg->connection()->changeUserMode(*modeflptr,bSet))
				{
					if(msg->connection()->serverInfo()->registerModeChar()==*modeflptr)
					{
						KviKvsVariantList vList;
						KviKvsEventManager::instance()->trigger(KviEvent_OnNickServAuth,msg->console(),&vList);
					}
					// There was a mode change
					if(KviKvsEventManager::instance()->hasAppHandlers(KviEvent_OnUserMode))
					{
						QString szModeFlag(bSet ? QChar('+') : QChar('-'));
						szModeFlag += QChar(*modeflptr);
						KviKvsVariantList vList(new KviKvsVariant(szModeFlag));
						if(KviKvsEventManager::instance()->trigger(KviEvent_OnUserMode,msg->console(),&vList))
							msg->setHaltOutput();
					}
				}
			break;
		}
		++modeflptr;
	}
}



void KviServerParser::parseLiteralMode(KviIrcMessage *msg)
{
	// NICK
	// :source MODE target <params>
	//    :source MODE <me> +|-modeflag
	//    :source MODE <channel> +-modeflags [parameters]
	QString szNick,szUser,szHost;
	msg->decodeAndSplitPrefix(szNick,szUser,szHost);

	//	if(!source.hasHost())
	//	{
	//		// This is a server or a channel service
	//		KviStr snick = source.nick();
	//		if(snick.contains('.'))source.setHost(source.nick()); // this is a server
	//	}

	QString szTarget = msg->connection()->decodeText(msg->safeParam(0));
	KviStr modefl(msg->safeParam(1));

	if(IS_ME(msg,szTarget))
	{
		parseUserMode(msg,modefl.ptr());
		if(!msg->haltOutput())
			msg->console()->output(KVI_OUT_MODE,__tr2qs("You have set user mode %s"),modefl.ptr());
	} else {
		// a channel mode
		KviChannel * chan = msg->connection()->findChannel(szTarget);

		if(!chan){
			// Ooops , desync with the server.
			UNRECOGNIZED_MESSAGE(msg,__tr("Received a mode change for an unknown channel, possible desync"));
			return;
		}

		chan->userAction(szNick,szUser,szHost,KVI_USERACTION_CHANMODE);
		parseChannelMode(szNick,szUser,szHost,chan,modefl,msg,2);
	}
}

void KviServerParser::parseChannelMode(const QString &szNick,const QString &szUser,const QString &szHost,KviChannel * chan,KviStr &modefl,KviIrcMessage *msg,int curParam)
{
	// FIXME: freenode has two ugly incompatible extensions:
	// mode e: that is NOT viewable (???)
	// mode q that stands for "quiet-ban"
	//    mode #chan +q mask
	// adds mask to the banlist with the prefix %
	// and doesn't allow the users matching the mask to talk to the channel

	bool bSet = true;
	const char * aux = modefl.ptr();
	QString aParam;

	QString nickBuffer;
	QString hostBuffer;
	
	if(szHost != "*")
	{
		KviQString::sprintf(nickBuffer,"\r!n\r%Q\r",&szNick);
		KviQString::sprintf(hostBuffer,"\r!h\r%Q\r",&szHost);
	} else {
		if(nickBuffer.find('.') != -1)
		{
			// This looks a lot like a server!
			KviQString::sprintf(nickBuffer,"\r!s\r%Q\r",&szNick);
		} else {
			// Probably a service....whois should work
			KviQString::sprintf(nickBuffer,"\r!n\r%Q\r",&szNick);
		}
		hostBuffer = szHost;
	}

	KviIrcMask * auxMask;

	int curParamSave = curParam;
	bool bIsMe;

	//FIXME: Use PREFIX in 005 numeric instead of bServerSupportsModeIe - get rid of it altogether
	//bool bModeIe = console->connection()->serverInfo()->supportsModesIe();


	while(*aux)
	{
		switch(*aux)
		{
			case '+':
				bSet = true;
			break;
			case '-':
				bSet = false;
			break;
			case 'k':
				if(bSet)aParam = msg->safeParam(curParam++);
				else aParam = "";
				chan->setChannelKey(aParam);

				if(bSet) {
					if(KVS_TRIGGER_EVENT_4_HALTED(KviEvent_OnKeySet,chan,szNick,szUser,szHost,aParam))
						msg->setHaltOutput();
				} else {
					if(KVS_TRIGGER_EVENT_3_HALTED(KviEvent_OnKeyUnset,chan,szNick,szUser,szHost))
						msg->setHaltOutput();
				}
				
				if(!(msg->haltOutput() || KVI_OPTION_BOOL(KviOption_boolShowCompactModeChanges)))
				{
					if(bSet)chan->output(KVI_OUT_KEY,
						__tr2qs("%Q [%Q@%Q] has set channel key to \"\r!m-k\r%Q\r\""),
						&nickBuffer,&szUser,&hostBuffer,&aParam);
					else chan->output(KVI_OUT_KEY,
						__tr2qs("%Q [%Q@%Q] has unset the channel key"),
						&nickBuffer,&szUser,&hostBuffer);
				}
			break;
			case 'l':
				if(bSet)aParam = msg->safeParam(curParam++);
				else aParam = "";
				chan->setChannelLimit(aParam);
				
				if(bSet) {
					if(KVS_TRIGGER_EVENT_4_HALTED(KviEvent_OnLimitSet,chan,szNick,szUser,szHost,aParam))
						msg->setHaltOutput();
				} else {
					if(KVS_TRIGGER_EVENT_3_HALTED(KviEvent_OnLimitUnset,chan,szNick,szUser,szHost))
						msg->setHaltOutput();
				}
				
				if(!(msg->haltOutput() || KVI_OPTION_BOOL(KviOption_boolShowCompactModeChanges)))
				{
					if(bSet)chan->output(KVI_OUT_LIMIT,
						__tr2qs("%Q [%Q@%Q] has set channel \r!m-l\rlimit to %Q\r"),
						&nickBuffer,&szUser,&hostBuffer,&aParam);
					else chan->output(KVI_OUT_LIMIT,
						__tr2qs("%Q [%Q@%Q] has unset the channel limit"),
						&nickBuffer,&szUser,&hostBuffer);
				}
			break;

#define CHANUSER_MODE(__modechar,__chanfunc,__evmeset,__evmeunset,__evset,__evunset,__icomeset,__icomeunset,__icoset,__icounset) \
			case __modechar: \
				if(msg->connection()->serverInfo()->isSupportedModeFlag(__modechar)) { \
					aParam = msg->connection()->decodeText(msg->safeParam(curParam++)); \
					chan->__chanfunc(aParam,bSet); \
					bIsMe = IS_ME(msg,aParam); \
					if(bIsMe) \
					{ \
						if(KVS_TRIGGER_EVENT_3_HALTED(bSet ? __evmeset : __evmeunset,chan,szNick,szUser,szHost))msg->setHaltOutput(); \
						chan->updateCaption(); \
					} else { \
						if(KVS_TRIGGER_EVENT_4_HALTED(bSet ? __evset : __evunset,chan,szNick,szUser,szHost,aParam))msg->setHaltOutput(); \
					} \
					if(!(msg->haltOutput() || KVI_OPTION_BOOL(KviOption_boolShowCompactModeChanges))) \
					{ \
						chan->output(bSet ? (bIsMe ? __icomeset : __icoset) : (bIsMe ? __icomeunset : __icounset), \
							__tr2qs("%Q [%Q@%Q] has set mode %c%c \r!n\r%Q\r"), \
							&nickBuffer,&szUser,&hostBuffer,bSet ? '+' : '-',__modechar,&aParam); \
					} \
			} else {\
					chan->setChannelMode(__modechar,bSet);\
					if(!(msg->haltOutput() || KVI_OPTION_BOOL(KviOption_boolShowCompactModeChanges)))\
					{\
						chan->output(KVI_OUT_CHANMODE,\
							__tr2qs("%Q [%Q@%Q] has set channel \r!m%c%c\rmode %c%c\r"),\
							&nickBuffer,&szUser,&hostBuffer,\
							bSet ? '-' : '+',__modechar,bSet ? '+' : '-',__modechar);\
					}\
				}\
			break;
			CHANUSER_MODE('q',setChanOwner,KviEvent_OnMeChanOwner,KviEvent_OnMeDeChanOwner,KviEvent_OnChanOwner,KviEvent_OnDeChanOwner,KVI_OUT_MECHANOWNER,KVI_OUT_MEDECHANOWNER,KVI_OUT_CHANOWNER,KVI_OUT_DECHANOWNER)
			CHANUSER_MODE('a',setChanAdmin,KviEvent_OnMeChanAdmin,KviEvent_OnMeDeChanAdmin,KviEvent_OnChanAdmin,KviEvent_OnDeChanAdmin,KVI_OUT_MECHANADMIN,KVI_OUT_MEDECHANADMIN,KVI_OUT_CHANADMIN,KVI_OUT_DECHANADMIN)
			CHANUSER_MODE('o',op,KviEvent_OnMeOp,KviEvent_OnMeDeOp,KviEvent_OnOp,KviEvent_OnDeOp,KVI_OUT_MEOP,KVI_OUT_MEDEOP,KVI_OUT_OP,KVI_OUT_DEOP)
			CHANUSER_MODE('h',halfop,KviEvent_OnMeHalfOp,KviEvent_OnMeDeHalfOp,KviEvent_OnHalfOp,KviEvent_OnDeHalfOp,KVI_OUT_MEHALFOP,KVI_OUT_MEDEHALFOP,KVI_OUT_HALFOP,KVI_OUT_HALFDEOP)
			CHANUSER_MODE('v',voice,KviEvent_OnMeVoice,KviEvent_OnMeDeVoice,KviEvent_OnVoice,KviEvent_OnDeVoice,KVI_OUT_MEVOICE,KVI_OUT_MEDEVOICE,KVI_OUT_VOICE,KVI_OUT_DEVOICE)
			CHANUSER_MODE('u',userop,KviEvent_OnMeUserOp,KviEvent_OnMeDeUserOp,KviEvent_OnUserOp,KviEvent_OnDeUserOp,KVI_OUT_MEUSEROP,KVI_OUT_MEDEUSEROP,KVI_OUT_USEROP,KVI_OUT_USERDEOP)

#define CHANNEL_MODE(__modefl,__evmeset,__evmeunset,__evset,__evunset,__icomeset,__icomeunset,__icoset,__icounset) \
			case __modefl: \
				aParam = msg->connection()->decodeText(msg->safeParam(curParam++)); \
				chan->setMask(*aux,aParam,bSet,msg->connection()->decodeText(msg->safePrefix()),QDateTime::currentDateTime().toTime_t()); \
				auxMask = new KviIrcMask(aParam); \
				bIsMe = auxMask->matchesFixed( \
							msg->connection()->userInfo()->nickName(), \
							msg->connection()->userInfo()->userName(), \
							msg->connection()->userInfo()->hostName()); \
				delete auxMask; \
				if(bIsMe) \
				{ \
					if(KVS_TRIGGER_EVENT_4_HALTED(bSet ? __evmeset : __evmeunset,chan,szNick,szUser,szHost,aParam))msg->setHaltOutput(); \
				} else { \
					if(KVS_TRIGGER_EVENT_4_HALTED(bSet ? __evset : __evunset,chan,szNick,szUser,szHost,aParam))msg->setHaltOutput(); \
				} \
				if(!(msg->haltOutput() || KVI_OPTION_BOOL(KviOption_boolShowCompactModeChanges))) \
				{ \
					chan->output(bSet ? (bIsMe ? __icomeset : __icoset) : (bIsMe ? __icomeunset : __icounset), \
						__tr2qs("%Q [%Q@%Q] has set mode %c%c \r!m%c%c\r%Q\r"), \
						&nickBuffer,&szUser,&hostBuffer, \
						bSet ? '+' : '-',__modefl,bSet ? '-' : '+',__modefl,&aParam); \
				} \
			break;

			CHANNEL_MODE('b',KviEvent_OnMeBan,KviEvent_OnMeUnban,KviEvent_OnBan,KviEvent_OnUnban,KVI_OUT_MEBAN,KVI_OUT_MEUNBAN,KVI_OUT_BAN,KVI_OUT_UNBAN)
			CHANNEL_MODE('I',KviEvent_OnMeInviteException,KviEvent_OnMeInviteExceptionRemove,KviEvent_OnInviteException,KviEvent_OnInviteExceptionRemove,KVI_OUT_MEINVITEEXCEPT,KVI_OUT_MEINVITEUNEXCEPT,KVI_OUT_INVITEEXCEPT,KVI_OUT_INVITEUNEXCEPT)
			CHANNEL_MODE('e',KviEvent_OnMeBanException,KviEvent_OnMeBanExceptionRemove,KviEvent_OnBanException,KviEvent_OnBanExceptionRemove,KVI_OUT_MEBANEXCEPT,KVI_OUT_MEBANUNEXCEPT,KVI_OUT_BANEXCEPT,KVI_OUT_BANUNEXCEPT)

			default:
				chan->setChannelMode(*aux,bSet);
				if(!(msg->haltOutput() || KVI_OPTION_BOOL(KviOption_boolShowCompactModeChanges)))
				{
					chan->output(KVI_OUT_CHANMODE,
						__tr2qs("%Q [%Q@%Q] has set channel \r!m%c%c\rmode %c%c\r"),
						&nickBuffer,&szUser,&hostBuffer,
						bSet ? '-' : '+',*aux,bSet ? '+' : '-',*aux);
				}
			break;
		}
		++aux;
	}

	QString param;
	QString params;
	param = msg->connection()->decodeText(msg->safeParam(curParamSave++));
	while(!param.isEmpty())
	{
		if(!params.isEmpty())params.append(' ');
		params.append(param);
		param = msg->connection()->decodeText(msg->safeParam(curParamSave++));
	}

	if(KVS_TRIGGER_EVENT_5_HALTED(KviEvent_OnChannelModeChange,chan,szNick,szUser,szHost,modefl.ptr(),params))
		msg->setHaltOutput();

	if(KVI_OPTION_BOOL(KviOption_boolShowCompactModeChanges) && (!msg->haltOutput()) && (!kvi_strEqualCS(modefl.ptr(),"+")))
	{
		if(!params.isEmpty())
		{
			chan->output(KVI_OUT_CHANMODE,__tr2qs("%Q [%Q@%Q] has set mode %s %Q"),
				&nickBuffer,&szUser,&hostBuffer,modefl.ptr(),&params);
		} else {
			chan->output(KVI_OUT_CHANMODE,__tr2qs("%Q [%Q@%Q] has set channel mode %s"),
				&nickBuffer,&szUser,&hostBuffer,modefl.ptr());
		}
	}
}
