/*
 * psiaccount.cpp - handles a Psi account
 * Copyright (C) 2001-2005  Justin Karneges
 *
 * 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 option) any later version.
 *
 * You can also redistribute and/or modify this program under the
 * terms of the Psi License, specified in the accompanied COPYING
 * file, as published by the Psi Project; either dated January 1st,
 * 2005, or (at your option) 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 library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include"psiaccount.h"

#include<qinputdialog.h>
#include<qptrlist.h>
#include<qcstring.h>
#include<qtimer.h>
#include<qmessagebox.h>
#include<qguardedptr.h>
#include<qapplication.h>
#include<qpushbutton.h>
#include<qlayout.h>
#include<qobjectlist.h>
#include<qurl.h>
#include<qmap.h>
#include<qca.h>
#include<qfileinfo.h>

#include"psicon.h"
#include"profiles.h"
#include"im.h"
//#include"xmpp_client.h"
//#include"xmpp_stream.h"
//#include"xmpp_message.h"
#include"xmpp_tasks.h"
#include"xmpp_jidlink.h"
#include"s5b.h"
#include"filetransfer.h"
#include"accountdlg.h"
#include"changepwdlg.h"
#include"xmlconsole.h"
#include"userlist.h"
#include"eventdlg.h"
#include"chatdlg.h"
#include"contactview.h"
#include"groupchatdlg.h"
#include"statusdlg.h"
#include"infodlg.h"
#include"adduserdlg.h"
#include"historydlg.h"
#include"servicesdlg.h"
#include"discodlg.h"
#include"eventdb.h"
#include"jltest.h"
#include"passphrasedlg.h"
#include"vcardfactory.h"
//#include"qssl.h"
#include"sslcertdlg.h"
#include"qwextend.h"
#include"psipopup.h"
#include"fancylabel.h"
#include"iconwidget.h"
#include"base64.h"
#include"filetransdlg.h"
#include"avatars.h"
#include"tabdlg.h"

#if defined(Q_WS_MAC) && defined(HAVE_GROWL)
#include "psigrowlnotifier.h"
#endif

#include"bsocket.h"
/*#ifdef Q_WS_WIN
#include<windows.h>
typedef int socklen_t;
#else
#include<sys/socket.h>
#include<netinet/in.h>
#endif*/

//----------------------------------------------------------------------------
// AccountLabel
//----------------------------------------------------------------------------
AccountLabel::AccountLabel(PsiAccount *_pa, QWidget *par, bool smode)
:QLabel(par)
{
	pa = _pa;
	simpleMode = smode;
	setFrameStyle( QFrame::Panel | QFrame::Sunken );

	updateName();
	connect(pa, SIGNAL(updatedAccount()), this, SLOT(updateName()));
	connect(pa, SIGNAL(destroyed()), this, SLOT(deleteMe()));
}

AccountLabel::~AccountLabel()
{
}

void AccountLabel::updateName()
{
	setText(simpleMode ? pa->name() : pa->nameWithJid());
}

void AccountLabel::deleteMe()
{
	delete this;
}


//----------------------------------------------------------------------------
// PGPTransaction
//----------------------------------------------------------------------------
static int transid = 0;
class PGPTransaction::Private
{
public:
	Private() {}

	int id;
	Message m;
	QDomElement e;
	Jid j;
};

PGPTransaction::PGPTransaction(OpenPGP::Engine *pgp)
:OpenPGP::Request(pgp)
{
	d = new Private;
	d->id = transid++;
}

PGPTransaction::~PGPTransaction()
{
	delete d;
}

int PGPTransaction::id() const
{
	return d->id;
}

void PGPTransaction::setMessage(const Message &m)
{
	d->m = m;
}

const Message & PGPTransaction::message() const
{
	return d->m;
}

const QDomElement & PGPTransaction::xml() const
{
	return d->e;
}

void PGPTransaction::setXml(const QDomElement &e)
{
	d->e = e;
}

Jid PGPTransaction::jid() const
{
	return d->j;
}

void PGPTransaction::setJid(const Jid &j)
{
	d->j = j;
}

//----------------------------------------------------------------------------
// BlockTransportPopup -- blocks popups on transport status changes
//----------------------------------------------------------------------------

class BlockTransportPopupList;

class BlockTransportPopup : public QObject
{
	Q_OBJECT
public:
	BlockTransportPopup(QObject *, const Jid &);

	Jid jid() const;

private slots:
	void timeout();

private:
	Jid j;
	int userCounter;
	friend class BlockTransportPopupList;
};

BlockTransportPopup::BlockTransportPopup(QObject *parent, const Jid &_j)
: QObject(parent)
{
	j = _j;
	userCounter = 0;

	// Hack for ICQ SMS
	if ( j.host().left(3) == "icq" ) {
		new BlockTransportPopup(parent, "sms." + j.host()); // sms.icq.host.com
		new BlockTransportPopup(parent, "sms"  + j.host().right(j.host().length() - 3)); // sms.host.com
	}

	QTimer::singleShot(15000, this, SLOT(timeout()));
}

void BlockTransportPopup::timeout()
{
	if ( userCounter > 1 ) {
		QTimer::singleShot(15000, this, SLOT(timeout()));
		userCounter = 0;
	}
	else
		deleteLater();
}

Jid BlockTransportPopup::jid() const
{
	return j;
}

//----------------------------------------------------------------------------
// BlockTransportPopupList
//----------------------------------------------------------------------------

class BlockTransportPopupList : public QObject
{
	Q_OBJECT
public:
	BlockTransportPopupList();

	bool find(const Jid &, bool online = false);
};

BlockTransportPopupList::BlockTransportPopupList()
: QObject()
{
}

bool BlockTransportPopupList::find(const Jid &j, bool online)
{
	if ( j.user().isEmpty() ) // always show popups for transports
		return false;

	QObjectList *list = queryList("BlockTransportPopup");
	QObjectListIt it( *list );

	BlockTransportPopup *btp;
	for ( ; it.current(); ++it) {
		btp = (BlockTransportPopup *)it.current();
		if ( j.host() == btp->jid().host() ) {
			if ( online )
				btp->userCounter++;
			return true;
		}
	}
	delete list;

	return false;
}

//----------------------------------------------------------------------------
// PsiAccount
//----------------------------------------------------------------------------
struct item_dialog2
{
	QWidget *widget;
	QString className;
	Jid jid;
};

QMap<QString,QString> pgp_passphrases;

class PsiAccount::Private : public QObject
{
	Q_OBJECT
public:
	Private(PsiAccount *parent) {
		account = parent;
	}

	PsiCon *psi;
	PsiAccount *account;
	Client *client;
	ContactProfile *cp;
	UserAccount acc, accnext;
	Jid jid, nextJid;
	Status loginStatus;
	QPtrList<item_dialog2> dialogList;
	EventQueue *eventQueue;
	XmlConsole *xmlConsole;
	UserList userList;
	UserListItem self;
	int lastIdle;
	Status lastStatus, origStatus;
	bool nickFromVCard;
	QString cur_pgpSecretKeyID;
	QPtrList<Message> messageQueue;
	BlockTransportPopupList *blockTransportPopupList;
	int userCounter;

	// Avatars
	AvatarFactory* avatarFactory;

	// pgp related
	PassphraseDlg *ppdlg;
	QGuardedPtr<OpenPGP::Request> ppreq;

	QPtrList<GCContact> gcbank;
	QStringList groupchats;

	AdvancedConnector *conn;
	ClientStream *stream;
	QCA::TLS *tls;
	QCATLSHandler *tlsHandler;
	QPtrList<QCA::Cert> certList;
	bool usingSSL;
	bool doPopups;

	QHostAddress localAddress;

	QString pathToProfileEvents()
	{
		return pathToProfile(activeProfile) + "/events-" + acc.name + ".xml";
	}

public slots:
	void queueChanged()
	{
		eventQueue->toFile(pathToProfileEvents());
	}

	void loadQueue()
	{
		bool soundEnabled = useSound;
		useSound = FALSE; // disable the sound and popups
		doPopups = FALSE;

		QFileInfo fi( pathToProfileEvents() );
		if ( fi.exists() )
			eventQueue->fromFile(pathToProfileEvents());

		useSound = soundEnabled;
		doPopups = TRUE;
	}

	void setEnabled( bool e )
	{
		acc.opt_enabled = e;
		psi->enableAccount(account, e);
		cp->setEnabled(e);
		account->cpUpdate(self);

		// signals
		account->enabledChanged();
		account->updatedAccount();
	}
};

PsiAccount::PsiAccount(const UserAccount &acc, PsiCon *parent)
:QObject(0)
{
	d = new Private( this );
	d->psi = parent;
	d->client = 0;
	d->cp = 0;
	d->userCounter = 0;

#ifdef AVATARS
	d->avatarFactory = new AvatarFactory(this);
#endif

	d->blockTransportPopupList = new BlockTransportPopupList();

	d->doPopups = true;
	v_isActive = false;
	isDisconnecting = false;
	notifyOnlineOk = false;
	doReconnect = false;
	usingAutoStatus = false;
	presenceSent = false;

	d->loginStatus = Status("", "");
	d->lastIdle = 0;
	d->lastStatus = Status("", "", 0, false);

	d->dialogList.setAutoDelete(true);
	d->eventQueue = new EventQueue(this);
	connect(d->eventQueue, SIGNAL(queueChanged()), SIGNAL(queueChanged()));
	connect(d->eventQueue, SIGNAL(queueChanged()), d, SLOT(queueChanged()));
	connect(d->eventQueue, SIGNAL(handleEvent(PsiEvent *)), SLOT(handleEvent(PsiEvent *)));
	d->userList.setAutoDelete(true);
	d->self = UserListItem(true);
	d->self.setSubscription(Subscription::Both);
	d->nickFromVCard = false;

#ifdef AVATARS
	d->self.setAvatarFactory(d->avatarFactory);
#endif

	d->messageQueue.setAutoDelete(true);
	d->ppdlg = 0;
	d->ppreq = 0;

	d->gcbank.setAutoDelete(true);

	// we need to copy groupState, because later initialization will depend on that
	d->acc.groupState = acc.groupState;

	// stream
	d->conn = 0;
	d->tls = 0;
	d->tlsHandler = 0;
	d->stream = 0;
	d->certList.setAutoDelete(true);
	d->usingSSL = false;

	// create Jabber::Client
	d->client = new Client;
	d->client->setOSName(getOSName());
	d->client->setTimeZone(getTZString(), getTZOffset());
	d->client->setClientName(PROG_NAME);
	d->client->setClientVersion(PROG_VERSION);

	d->client->setFileTransferEnabled(true);

	//connect(d->client, SIGNAL(connected()), SLOT(client_connected()));
	//connect(d->client, SIGNAL(handshaken()), SLOT(client_handshaken()));
	//connect(d->client, SIGNAL(error(const StreamError &)), SLOT(client_error(const StreamError &)));
	//connect(d->client, SIGNAL(sslCertReady(const QSSLCert &)), SLOT(client_sslCertReady(const QSSLCert &)));
	//connect(d->client, SIGNAL(closeFinished()), SLOT(client_closeFinished()));
	//connect(d->client, SIGNAL(authFinished(bool, int, const QString &)), SLOT(client_authFinished(bool, int, const QString &)));
	connect(d->client, SIGNAL(rosterRequestFinished(bool, int, const QString &)), SLOT(client_rosterRequestFinished(bool, int, const QString &)));
	connect(d->client, SIGNAL(rosterItemAdded(const RosterItem &)), SLOT(client_rosterItemAdded(const RosterItem &)));
	connect(d->client, SIGNAL(rosterItemAdded(const RosterItem &)), SLOT(client_rosterItemUpdated(const RosterItem &)));
	connect(d->client, SIGNAL(rosterItemUpdated(const RosterItem &)), SLOT(client_rosterItemUpdated(const RosterItem &)));
	connect(d->client, SIGNAL(rosterItemRemoved(const RosterItem &)), SLOT(client_rosterItemRemoved(const RosterItem &)));
	connect(d->client, SIGNAL(resourceAvailable(const Jid &, const Resource &)), SLOT(client_resourceAvailable(const Jid &, const Resource &)));
	connect(d->client, SIGNAL(resourceUnavailable(const Jid &, const Resource &)), SLOT(client_resourceUnavailable(const Jid &, const Resource &)));
	connect(d->client, SIGNAL(presenceError(const Jid &, int, const QString &)), SLOT(client_presenceError(const Jid &, int, const QString &)));
	connect(d->client, SIGNAL(messageReceived(const Message &)), SLOT(client_messageReceived(const Message &)));
	connect(d->client, SIGNAL(subscription(const Jid &, const QString &)), SLOT(client_subscription(const Jid &, const QString &)));
	connect(d->client, SIGNAL(debugText(const QString &)), SLOT(client_debugText(const QString &)));
	connect(d->client, SIGNAL(groupChatJoined(const Jid &)), SLOT(client_groupChatJoined(const Jid &)));
	connect(d->client, SIGNAL(groupChatLeft(const Jid &)), SLOT(client_groupChatLeft(const Jid &)));
	connect(d->client, SIGNAL(groupChatPresence(const Jid &, const Status &)), SLOT(client_groupChatPresence(const Jid &, const Status &)));
	connect(d->client, SIGNAL(groupChatError(const Jid &, int, const QString &)), SLOT(client_groupChatError(const Jid &, int, const QString &)));
	connect(d->client, SIGNAL(incomingJidLink()), SLOT(client_incomingJidLink()));
	connect(d->client->fileTransferManager(), SIGNAL(incomingReady()), SLOT(client_incomingFileTransfer()));

	// contactprofile context
	d->cp = new ContactProfile(this, acc.name, d->psi->contactView());
	connect(d->cp, SIGNAL(actionDefault(const Jid &)),SLOT(actionDefault(const Jid &)));
	connect(d->cp, SIGNAL(actionRecvEvent(const Jid &)),SLOT(actionRecvEvent(const Jid &)));
	connect(d->cp, SIGNAL(actionSendMessage(const Jid &)),SLOT(actionSendMessage(const Jid &)));
	connect(d->cp, SIGNAL(actionSendMessage(const JidList &)),SLOT(actionSendMessage(const JidList &)));
	connect(d->cp, SIGNAL(actionSendUrl(const Jid &)),SLOT(actionSendUrl(const Jid &)));
	connect(d->cp, SIGNAL(actionRemove(const Jid &)),SLOT(actionRemove(const Jid &)));
	connect(d->cp, SIGNAL(actionRename(const Jid &, const QString &)),SLOT(actionRename(const Jid &, const QString &)));
	connect(d->cp, SIGNAL(actionGroupRename(const QString &, const QString &)),SLOT(actionGroupRename(const QString &, const QString &)));
	connect(d->cp, SIGNAL(actionHistory(const Jid &)),SLOT(actionHistory(const Jid &)));
	connect(d->cp, SIGNAL(actionOpenChat(const Jid &)),SLOT(actionOpenChat(const Jid &)));
	connect(d->cp, SIGNAL(actionOpenChatSpecific(const Jid &)),SLOT(actionOpenChatSpecific(const Jid &)));
	connect(d->cp, SIGNAL(actionAgentSetStatus(const Jid &, Status &)),SLOT(actionAgentSetStatus(const Jid &, Status &)));
	connect(d->cp, SIGNAL(actionInfo(const Jid &)),SLOT(actionInfo(const Jid &)));
	connect(d->cp, SIGNAL(actionAuth(const Jid &)),SLOT(actionAuth(const Jid &)));
	connect(d->cp, SIGNAL(actionAuthRequest(const Jid &)),SLOT(actionAuthRequest(const Jid &)));
	connect(d->cp, SIGNAL(actionAuthRemove(const Jid &)),SLOT(actionAuthRemove(const Jid &)));
	connect(d->cp, SIGNAL(actionAdd(const Jid &)),SLOT(actionAdd(const Jid &)));
	connect(d->cp, SIGNAL(actionGroupAdd(const Jid &, const QString &)),SLOT(actionGroupAdd(const Jid &, const QString &)));
	connect(d->cp, SIGNAL(actionGroupRemove(const Jid &, const QString &)),SLOT(actionGroupRemove(const Jid &, const QString &)));
	connect(d->cp, SIGNAL(actionTest(const Jid &)),SLOT(actionTest(const Jid &)));
	connect(d->cp, SIGNAL(actionSendFile(const Jid &)),SLOT(actionSendFile(const Jid &)));
	connect(d->cp, SIGNAL(actionSendFiles(const Jid &, const QStringList&)),SLOT(actionSendFiles(const Jid &, const QStringList&)));
	connect(d->cp, SIGNAL(actionDisco(const Jid &, const QString &)),SLOT(actionDisco(const Jid &, const QString &)));
	connect(d->cp, SIGNAL(actionInvite(const Jid &, const QString &)),SLOT(actionInvite(const Jid &, const QString &)));
	connect(d->cp, SIGNAL(actionAssignKey(const Jid &)),SLOT(actionAssignKey(const Jid &)));
	connect(d->cp, SIGNAL(actionUnassignKey(const Jid &)),SLOT(actionUnassignKey(const Jid &)));

	// restore cached roster
	for(Roster::ConstIterator it = acc.roster.begin(); it != acc.roster.end(); ++it)
		client_rosterItemUpdated(*it);

	// restore pgp key bindings
	for(VarList::ConstIterator kit = acc.keybind.begin(); kit != acc.keybind.end(); ++kit) {
		const VarListItem &i = *kit;
		UserListItem *u = find(Jid(i.key()));
		if(u) {
			u->setPublicKeyID(i.data());
			cpUpdate(*u);
		}
	}

	setUserAccount(acc);

	d->psi->link(this);
	connect(d->psi, SIGNAL(emitOptionsUpdate()), SLOT(optionsUpdate()));
	connect(d->psi, SIGNAL(pgpToggled(bool)), SLOT(pgpToggled(bool)));
	connect(d->psi, SIGNAL(pgpKeysUpdated()), SLOT(pgpKeysUpdated()));

	d->psi->setToggles(d->acc.tog_offline, d->acc.tog_away, d->acc.tog_agents, d->acc.tog_hidden,d->acc.tog_self);

	d->setEnabled(d->acc.opt_enabled);

	// auto-login ?
	if(d->acc.opt_auto && d->acc.opt_enabled)
		setStatus(Status("", "", d->acc.priority));

	//printf("PsiAccount: [%s] loaded\n", name().latin1());
	d->xmlConsole = new XmlConsole(this);
	if(option.xmlConsoleOnLogin && d->acc.opt_enabled) {
		this->showXmlConsole();
		d->xmlConsole->enable();
	}

	// load event queue from disk
	QTimer::singleShot(0, d, SLOT(loadQueue()));
}

PsiAccount::~PsiAccount()
{
	logout(true);
	QString str = name();

	d->messageQueue.clear();

	// nuke all related dialogs
	deleteAllDialogs();

	d->psi->ftdlg()->killTransfers(this);

	delete d->cp;
	delete d->client;
	cleanupStream();
	delete d->eventQueue;

	delete d->blockTransportPopupList;

	d->psi->unlink(this);
	delete d;

	//printf("PsiAccount: [%s] unloaded\n", str.latin1());
}

void PsiAccount::cleanupStream()
{
	delete d->stream;
	d->stream = 0;

	delete d->tls;
	d->tls = 0;
	d->tlsHandler = 0;

	delete d->conn;
	d->conn = 0;

	d->certList.clear();
	d->usingSSL = false;

	d->localAddress = QHostAddress();
}

bool PsiAccount::enabled() const
{
	return d->acc.opt_enabled;
}

void PsiAccount::setEnabled(bool e)
{
	if ( d->acc.opt_enabled == e )
		return;
		
	if (!e) {
		if (eventQueue()->count()) {
			QMessageBox::information(0, tr("Error"), tr("Unable to disable the account, as it has pending events."));
			return;
		}
		if (isActive()) {
			if (QMessageBox::information(0, tr("Disable Account"), tr("The account is currently active.\nDo you want to log out ?"),QMessageBox::Yes,QMessageBox::No | QMessageBox::Default | QMessageBox::Escape, QMessageBox::NoButton) == QMessageBox::Yes) {
				logout();
			}
			else {
				return;
			}
		}
	}

	d->setEnabled( e );
}

bool PsiAccount::isActive() const
{
	return v_isActive;
}

bool PsiAccount::isConnected() const
{
	return (d->stream && d->stream->isAuthenticated());
}

const QString & PsiAccount::name() const
{
	return d->acc.name;
}

const UserAccount & PsiAccount::userAccount() const
{
	d->psi->getToggles(&d->acc.tog_offline, &d->acc.tog_away, &d->acc.tog_agents, &d->acc.tog_hidden,&d->acc.tog_self);

	// save the roster and pgp key bindings
	d->acc.roster.clear();
	d->acc.keybind.clear();
	UserListIt it(d->userList);
	for(UserListItem *u; (u = it.current()); ++it) {
		if(u->inList())
			d->acc.roster += *u;

		if(!u->publicKeyID().isEmpty())
			d->acc.keybind.set(u->jid().full(), u->publicKeyID());
	}

	return d->acc;
}

UserList *PsiAccount::userList() const
{
	return &d->userList;
}

Client *PsiAccount::client() const
{
	return d->client;
}

ContactProfile *PsiAccount::contactProfile() const
{
	return d->cp;
}

EventQueue *PsiAccount::eventQueue() const
{
	return d->eventQueue;
}

EDB *PsiAccount::edb() const
{
	return d->psi->edb();
}

PsiCon *PsiAccount::psi() const
{
	return d->psi;
}

AvatarFactory *PsiAccount::avatarFactory() const
{
	return d->avatarFactory;
}

QString PsiAccount::pgpKey() const
{
	return d->cur_pgpSecretKeyID;
}

QHostAddress *PsiAccount::localAddress() const
{
	QString s = d->localAddress.toString();
	if(s == "0.0.0.0")
		return 0;
	return &d->localAddress;
}

void PsiAccount::setUserAccount(const UserAccount &acc)
{
	bool renamed = false;
	QString oldfname;
	if(d->acc.name != acc.name) {
		renamed = true;
		oldfname = d->pathToProfileEvents();
	}

	d->acc = acc;

	// rename queue file?
	if(renamed) {
		QFileInfo oldfi(oldfname);
		QFileInfo newfi(d->pathToProfileEvents());
		if(oldfi.exists()) {
			QDir dir = oldfi.dir();
			dir.rename(oldfi.fileName(), newfi.fileName());
		}
	}

	if(d->stream) {
		if(d->acc.opt_keepAlive)
			d->stream->setNoopTime(55000);  // prevent NAT timeouts every minute
		else
			d->stream->setNoopTime(0);
	}

	d->cp->setName(d->acc.name);

	Jid j = acc.jid;
	d->nextJid = j;
	if(!isActive()) {
		d->jid = j;
		d->self.setJid(j);
		d->cp->updateEntry(d->self);
	}
	if(!d->nickFromVCard)
		setNick(j.user());

	d->self.setPublicKeyID(d->acc.pgpSecretKeyID);
	if(d->psi->pgp()) {
		if(d->acc.pgpSecretKeyID != d->cur_pgpSecretKeyID && loggedIn()) {
			d->cur_pgpSecretKeyID = d->acc.pgpSecretKeyID;
			d->loginStatus.setXSigned("");
			setStatusDirect(d->loginStatus);
			pgpKeyChanged();
		}
	}

	cpUpdate(d->self);
	updatedAccount();
}

void PsiAccount::deleteQueueFile()
{
	QFileInfo fi(d->pathToProfileEvents());
	if(fi.exists()) {
		QDir dir = fi.dir();
		dir.remove(fi.fileName());
	}
}

const Jid & PsiAccount::jid() const
{
	return d->jid;
}

QString PsiAccount::nameWithJid() const
{
	return (name() + " (" + jid().full() + ')');
}

static QCA::Cert readCertXml(const QDomElement &e)
{
	QCA::Cert cert;
	// there should be one child data tag
	QDomElement data = e.elementsByTagName("data").item(0).toElement();
	if(!data.isNull())
		cert.fromDER(Base64::stringToArray(data.text()));
	return cert;
}

static QPtrList<QCA::Cert> getRootCerts(const QStringList &stores)
{
	QPtrList<QCA::Cert> list;

	for(QStringList::ConstIterator dit = stores.begin(); dit != stores.end(); ++dit) {
		QDir dir(*dit);
		if(!dir.exists())
			continue;
		dir.setNameFilter("*.xml");
		QStringList entries = dir.entryList();
		for(QStringList::ConstIterator it = entries.begin(); it != entries.end(); ++it) {
			QFile f(dir.filePath(*it));
			if(!f.open(IO_ReadOnly))
				continue;
			QDomDocument doc;
			bool ok = doc.setContent(&f);
			f.close();
			if(!ok)
				continue;

			QDomElement base = doc.documentElement();
			if(base.tagName() != "store")
				continue;
			QDomNodeList cl = base.elementsByTagName("certificate");

			int num = 0;
			for(int n = 0; n < (int)cl.count(); ++n) {
				QCA::Cert *cert = new QCA::Cert(readCertXml(cl.item(n).toElement()));
				if(cert->isNull()) {
					delete cert;
					continue;
				}

				++num;
				list.append(cert);
			}
		}
	}

	return list;
}

// logs on with the active account settings
void PsiAccount::login()
{
	if(isActive() && !doReconnect)
		return;

	if(d->acc.opt_ssl && !QCA::isSupported(QCA::CAP_TLS)) {
		QMessageBox::information(0, tr("%1: SSL Error").arg(name()), tr("Cannot login: SSL is enabled but no SSL/TLS (plugin) support is available."));
		return;
	}

	d->jid = d->nextJid;
	if(d->psi->pgp()) {
		d->cur_pgpSecretKeyID = d->acc.pgpSecretKeyID;
		pgpKeyChanged();
	}

	v_isActive = true;
	isDisconnecting = false;
	notifyOnlineOk = false;
	rosterDone = false;
	presenceSent = false;

	stateChanged();

	QString host;
	int port;
	if(d->acc.opt_host) {
		host = d->acc.host;
		port = d->acc.port;
	}
	else {
		host = d->jid.host();
		if(d->acc.opt_ssl)
			port = 5223;
		else
			port = 5222;
	}

	AdvancedConnector::Proxy p;
	if(d->acc.proxy_index > 0) {
		const ProxyItem &pi = d->psi->proxy()->getItem(d->acc.proxy_index-1);
		if(pi.type == "http") // HTTP Connect
			p.setHttpConnect(pi.settings.host, pi.settings.port);
		else if(pi.type == "socks") // SOCKS
			p.setSocks(pi.settings.host, pi.settings.port);
		else if(pi.type == "poll") { // HTTP Poll
			QUrl u = pi.settings.url;
			if(u.query().isEmpty()) {
				QString v = host + ':' + QString::number(port);
				QUrl::encode(v);
				u.setQuery(QString("server=") + v);
			}
			p.setHttpPoll(pi.settings.host, pi.settings.port, u.toString());
			p.setPollInterval(2);
		}

		if(pi.settings.useAuth)
			p.setUserPass(pi.settings.user, pi.settings.pass);
	}

	// stream
	d->conn = new AdvancedConnector;
	if(d->acc.opt_ssl) {
		QStringList certDirs;
		certDirs += g.pathHome + "/certs";
		certDirs += g.pathBase + "/certs";
		d->certList = getRootCerts(certDirs);

		d->tls = new QCA::TLS;
		d->tls->setCertificateStore(d->certList);
		d->tlsHandler = new QCATLSHandler(d->tls);
		connect(d->tlsHandler, SIGNAL(tlsHandshaken()), SLOT(tls_handshaken()));
	}
	d->conn->setProxy(p);
	d->conn->setOptHostPort(host, port);
	d->conn->setOptSSL(d->acc.opt_ssl);

	d->stream = new ClientStream(d->conn, d->tlsHandler);
	d->stream->setOldOnly(true);
	d->stream->setAllowPlain(d->acc.opt_plain);
	if(d->acc.opt_keepAlive)
		d->stream->setNoopTime(55000);  // prevent NAT timeouts every minute
	else
		d->stream->setNoopTime(0);
	connect(d->stream, SIGNAL(connected()), SLOT(cs_connected()));
	connect(d->stream, SIGNAL(securityLayerActivated(int)), SLOT(cs_securityLayerActivated()));
	connect(d->stream, SIGNAL(needAuthParams(bool, bool, bool)), SLOT(cs_needAuthParams(bool, bool, bool)));
	connect(d->stream, SIGNAL(authenticated()), SLOT(cs_authenticated()));
	connect(d->stream, SIGNAL(connectionClosed()), SLOT(cs_connectionClosed()));
	connect(d->stream, SIGNAL(delayedCloseFinished()), SLOT(cs_delayedCloseFinished()));
	connect(d->stream, SIGNAL(warning(int)), SLOT(cs_warning(int)));
	connect(d->stream, SIGNAL(error(int)), SLOT(cs_error(int)));

	Jid j = d->jid.withResource(d->acc.resource);
	d->client->connectToServer(d->stream, j);
}

// disconnect or stop reconnecting
void PsiAccount::logout(bool fast, const Status &s)
{
	if(!isActive())
		return;

	// cancel reconnect
	doReconnect = false;

	if(loggedIn()) {
		// send logout status
		d->client->setPresence(s);
	}

	isDisconnecting = true;

	if(!fast)
		simulateRosterOffline();
	v_isActive = false;
	stateChanged();

	QTimer::singleShot(0, this, SLOT(disconnect()));
}

// skz note: I had to split logout() because server seem to need some time to store status
// before stream is closed (weird, I know)
void PsiAccount::disconnect()
{
	// disconnect
	d->client->close();
	cleanupStream();

	disconnected();
}

bool PsiAccount::loggedIn() const
{
	return (v_isActive && presenceSent);
}

void PsiAccount::tls_handshaken()
{
	QCA::Cert cert = d->tls->peerCertificate();
	int r = d->tls->certificateValidityResult();
	if(r != QCA::TLS::Valid && !d->acc.opt_ignoreSSLWarnings) {
		QString str = resultToString(r);
		while(1) {
			int n = QMessageBox::warning(0,
				tr("%1: Server Authentication").arg(name()),
				tr("The %1 certificate failed the authenticity test.").arg(d->jid.host()) + '\n' + tr("Reason: %1").arg(str),
				tr("&Details..."),
				tr("Co&ntinue"),
				tr("&Cancel"), 0, 2);
			if(n == 0) {
				SSLCertDlg::showCert(cert, r);
			}
			else if(n == 1) {
				d->tlsHandler->continueAfterHandshake();
				break;
			}
			else if(n == 2) {
				logout();
				break;
			}
		}
	}
	else
		d->tlsHandler->continueAfterHandshake();
}

void PsiAccount::cs_connected()
{
	// get IP address
	ByteStream *bs = d->conn->stream();
	if(bs->inherits("BSocket") || bs->inherits("XMPP::BSocket")) {
//#if QT_VERSION >= 0x030300
		d->localAddress = ((BSocket *)bs)->address();
/*#else
		int s = ((BSocket *)bs)->socket();
		struct sockaddr addr;
		socklen_t size = sizeof(struct sockaddr);
		if(!getsockname(s, &addr, &size)) {
			Q_UINT32 ipv4addr;
			struct sockaddr_in *in = (struct sockaddr_in *)&addr;
			memcpy(&ipv4addr, &in->sin_addr.s_addr, 4);
			ipv4addr = ntohl(ipv4addr);
			d->localAddress = QHostAddress(ipv4addr);
		}
#endif*/
	}
}

void PsiAccount::cs_securityLayerActivated()
{
	d->usingSSL = true;
	stateChanged();
}

void PsiAccount::cs_needAuthParams(bool, bool pass, bool)
{
	if(pass)
		d->stream->setPassword(d->acc.pass);
	d->stream->continueAfterParams();
}

void PsiAccount::cs_authenticated()
{
	d->conn->changePollInterval(10); // for http poll, slow down after login

	d->client->start(d->jid.host(), d->jid.user(), d->acc.pass, d->acc.resource);

	//printf("PsiAccount: [%s] authenticated\n", name().latin1());

	// flag roster for delete
	UserListIt it(d->userList);
	for(UserListItem *u; (u = it.current()); ++it) {
		if(u->inList())
			u->setFlagForDelete(true);
	}

	// ask for roster
	d->client->rosterRequest();
}

void PsiAccount::cs_connectionClosed()
{
	cs_error(-1);
}

void PsiAccount::cs_delayedCloseFinished()
{
	//printf("PsiAccount: [%s] connection closed\n", name().latin1());
}

void PsiAccount::cs_warning(int)
{
	d->stream->continueAfterWarning();
}

void PsiAccount::getErrorInfo(int err, AdvancedConnector *conn, Stream *stream, QCATLSHandler *tlsHandler, QString *_str, bool *_reconn)
{
	QString str;
	bool reconn = false;

	if(err == -1) {
		str = tr("Disconnected");
		reconn = true;
	}
	else if(err == XMPP::ClientStream::ErrParse) {
		str = tr("XML Parsing Error");
		reconn = true;
	}
	else if(err == XMPP::ClientStream::ErrProtocol) {
		str = tr("XMPP Protocol Error");
		reconn = true;
	}
	else if(err == XMPP::ClientStream::ErrStream) {
		int x = stream->errorCondition();
		QString s;
		reconn = true;
		if(x == XMPP::Stream::GenericStreamError)
			s = tr("Generic stream error");
		else if(x == XMPP::ClientStream::Conflict) {
			s = tr("Conflict (remote login replacing this one)");
			reconn = false;
		}
		else if(x == XMPP::ClientStream::ConnectionTimeout)
			s = tr("Timed out from inactivity");
		else if(x == XMPP::ClientStream::InternalServerError)
			s = tr("Internal server error");
		else if(x == XMPP::ClientStream::InvalidXml)
			s = tr("Invalid XML");
		else if(x == XMPP::ClientStream::PolicyViolation) {
			s = tr("Policy violation");
			reconn = false;
		}
		else if(x == XMPP::ClientStream::ResourceConstraint) {
			s = tr("Server out of resources");
			reconn = false;
		}
		else if(x == XMPP::ClientStream::SystemShutdown)
			s = tr("Server is shutting down");
		str = tr("XMPP Stream Error: %1").arg(s);
	}
	else if(err == XMPP::ClientStream::ErrConnection) {
		int x = conn->errorCode();
		QString s;
		reconn = true;
		if(x == XMPP::AdvancedConnector::ErrConnectionRefused)
			s = tr("Unable to connect to server");
		else if(x == XMPP::AdvancedConnector::ErrHostNotFound)
			s = tr("Host not found");
		else if(x == XMPP::AdvancedConnector::ErrProxyConnect)
			s = tr("Error connecting to proxy");
		else if(x == XMPP::AdvancedConnector::ErrProxyNeg)
			s = tr("Error during proxy negotiation");
		else if(x == XMPP::AdvancedConnector::ErrProxyAuth) {
			s = tr("Proxy authentication failed");
			reconn = false;
		}
		else if(x == XMPP::AdvancedConnector::ErrStream)
			s = tr("Socket/stream error");
		str = tr("Connection Error: %1").arg(s);
	}
	else if(err == XMPP::ClientStream::ErrNeg) {
		int x = stream->errorCondition();
		QString s;
		if(x == XMPP::ClientStream::HostGone)
			s = tr("Host no longer hosted");
		else if(x == XMPP::ClientStream::HostUnknown)
			s = tr("Host unknown");
		else if(x == XMPP::ClientStream::RemoteConnectionFailed) {
			s = tr("A required remote connection failed");
			reconn = true;
		}
		else if(x == XMPP::ClientStream::SeeOtherHost)
			s = tr("See other host: %1").arg(stream->errorText());
		else if(x == XMPP::ClientStream::UnsupportedVersion)
			s = tr("Server does not support proper XMPP version");
		str = tr("Stream Negotiation Error: %1").arg(s);
	}
	else if(err == XMPP::ClientStream::ErrTLS) {
		int x = stream->errorCondition();
		QString s;
		if(x == XMPP::ClientStream::TLSStart)
			s = tr("Server rejected STARTTLS");
		else if(x == XMPP::ClientStream::TLSFail) {
			int t = tlsHandler->tlsError();
			if(t == QCA::TLS::ErrHandshake)
				s = tr("TLS handshake error");
			else
				s = tr("Broken security layer (TLS)");
		}
		str = s;
	}
	else if(err == XMPP::ClientStream::ErrAuth) {
		int x = stream->errorCondition();
		QString s;
		if(x == XMPP::ClientStream::GenericAuthError)
			s = tr("Unable to login");
		else if(x == XMPP::ClientStream::NoMech)
			s = tr("No appropriate mechanism available for given security settings");
		else if(x == XMPP::ClientStream::BadProto)
			s = tr("Bad server response");
		else if(x == XMPP::ClientStream::BadServ)
			s = tr("Server failed mutual authentication");
		else if(x == XMPP::ClientStream::EncryptionRequired)
			s = tr("Encryption required for chosen SASL mechanism");
		else if(x == XMPP::ClientStream::InvalidAuthzid)
			s = tr("Invalid account information");
		else if(x == XMPP::ClientStream::InvalidMech)
			s = tr("Invalid SASL mechanism");
		else if(x == XMPP::ClientStream::InvalidRealm)
			s = tr("Invalid realm");
		else if(x == XMPP::ClientStream::MechTooWeak)
			s = tr("SASL mechanism too weak for this account");
		else if(x == XMPP::ClientStream::NotAuthorized)
			s = tr("Not authorized");
		else if(x == XMPP::ClientStream::TemporaryAuthFailure)
			s = tr("Temporary auth failure");
		str = tr("Authentication error: %1").arg(s);
	}
	else if(err == XMPP::ClientStream::ErrSecurityLayer)
		str = tr("Broken security layer (SASL)");
	else
		str = tr("None");
	//printf("str[%s], reconn=%d\n", str.latin1(), reconn);
	*_str = str;
	*_reconn = reconn;
}

void PsiAccount::cs_error(int err)
{
	QString str;
	bool reconn;

	getErrorInfo(err, d->conn, d->stream, d->tlsHandler, &str, &reconn);

	d->client->close();
	cleanupStream();

	//printf("Error: [%s]\n", str.latin1());

	isDisconnecting = true;

	if ( loggedIn() ) { // FIXME: is this condition okay?
		simulateRosterOffline();
	}

	presenceSent = false; // this stops the idle detector?? (FIXME)

	// Auto-Reconnect?
	if(d->acc.opt_reconn && reconn) {
		// reconnect in 5 seconds
		doReconnect = true;
		stateChanged();
		QTimer::singleShot(5000, this, SLOT(reconnect()));
		return;
	}

	v_isActive = false;
	stateChanged();
	disconnected();

	QMessageBox::critical(0, tr("%1: Server Error").arg(name()), tr("There was an error communicating with the Jabber server.\nDetails: %1").arg(str));
}

void PsiAccount::client_rosterRequestFinished(bool success, int, const QString &)
{
	if(success) {
		//printf("PsiAccount: [%s] roster retrieved ok.  %d entries.\n", name().latin1(), d->client->roster().count());

		// delete flagged items
		UserListIt it(d->userList);
		for(UserListItem *u; (u = it.current());) {
			if(u->flagForDelete()) {
				//QMessageBox::information(0, "blah", QString("deleting: [%1]").arg(u->jid().full()));

				d->eventQueue->clear(u->jid());
				updateReadNext(u->jid());

				d->cp->removeEntry(u->jid());
				d->userList.removeRef(u);
			}
			else
				++it;
		}
	}
	else {
		//printf("PsiAccount: [%s] error retrieving roster: [%d, %s]\n", name().latin1(), code, str.latin1());
	}

	rosterDone = true;
	setStatusDirect(d->loginStatus);
}

void PsiAccount::resolveContactName()
{
	JT_VCard *j = (JT_VCard *)sender();
	if ( j->success() ) {
		QString nick = j->vcard().nickName();
		if ( !nick.isEmpty() ) {
			actionRename( j->jid(), nick );
		}
	}
}

void PsiAccount::client_rosterItemAdded(const RosterItem &r)
{
	if ( r.isPush() && r.name().isEmpty() && option.autoResolveNicksOnAdd ) {
		// automatically resolve nickname from vCard, if newly added item doesn't have any
		VCardFactory::getVCard(r.jid(), d->client->rootTask(), this, SLOT(resolveContactName()));
	}
}

void PsiAccount::client_rosterItemUpdated(const RosterItem &r)
{
	// see if the item added is already in our local list
	UserListItem *u = d->userList.find(r.jid());
	if(u) {
		u->setFlagForDelete(false);
		u->setRosterItem(r);
	}
	else {
		// we don't have it at all, so add it
		u = new UserListItem;
		u->setRosterItem(r);
#ifdef AVATARS
		u->setAvatarFactory(d->avatarFactory);
#endif
		d->userList.append(u);
	}
	u->setInList(true);

	d->cp->updateEntry(*u);
}

void PsiAccount::client_rosterItemRemoved(const RosterItem &r)
{
	UserListItem *u = d->userList.find(r.jid());
	if(!u)
		return;

	simulateContactOffline(u);

	// if the item has messages queued, then move them to 'not in list'
	if(d->eventQueue->count(r.jid()) > 0) {
		u->setInList(false);
		d->cp->updateEntry(*u);
	}
	// else remove them for good!
	else {
		d->cp->removeEntry(u->jid());
		d->userList.removeRef(u);
	}
}

void PsiAccount::tryVerify(UserListItem *u, UserResource *ur)
{
	if(d->psi->pgp())
		verifyStatus(u->jid().withResource(ur->name()), ur->status());
}

void PsiAccount::client_resourceAvailable(const Jid &j, const Resource &r)
{
	enum PopupType {
		PopupOnline = 0,
		PopupStatusChange = 1
	};
	PopupType popupType = PopupOnline;

	if ( j.user().isEmpty() )
		new BlockTransportPopup(d->blockTransportPopupList, j);

	bool doSound = false;
	bool doPopup = false;
	QPtrList<UserListItem> list = findRelavent(j);
	QPtrListIterator<UserListItem> it(list);
	for(UserListItem *u; (u = it.current()); ++it) {
		bool doAnim = false;
		bool local = false;
		if(u->isSelf() && r.name() == d->client->resource())
			local = true;

		// add/update the resource
		QString oldStatus, oldKey;
		UserResource *rp;
		UserResourceList::Iterator rit = u->userResourceList().find(j.resource());
		bool found = (rit == u->userResourceList().end()) ? false: true;
		if(!found) {
			popupType = PopupOnline;

			UserResource ur(r);
			//ur.setSecurityEnabled(true);
			if(local)
				ur.setClient(PROG_NAME,PROG_VERSION,getOSName());
			rp = &(*u->userResourceList().append(ur));

			if(notifyOnlineOk && !local) {
				doAnim = true;
				if (!u->isHidden()) {
					doSound = true;
					doPopup = true;
				}
			}

			if(!local && option.autoVersion && !status().isInvisible()) {
				// do a client-version request (too easy!)
				JT_ClientVersion *jcv = new JT_ClientVersion(d->client->rootTask());
				connect(jcv, SIGNAL(finished()), SLOT(slotClientVersionFinished()));
				jcv->get(j);
				jcv->go(true);
			}
		}
		else {
			if ( !doPopup )
				popupType = PopupStatusChange;

			oldStatus = (*rit).status().status();
			oldKey = (*rit).status().keyID();
			rp = &(*rit);

			(*rit).setResource(r);

			if (!local && !u->isHidden())
				doPopup = true;
		}

		rp->setPGPVerifyStatus(-1);
		if(!rp->status().xsigned().isEmpty())
			tryVerify(u, rp);

		u->setPresenceError("");
		cpUpdate(*u, r.name(), true);

		if(doAnim && option.rosterAnim)
			d->cp->animateNick(u->jid());
	}
	
	if(doSound)
		playSound(option.onevent[eOnline]);

#if !defined(Q_WS_MAC) || !defined(HAVE_GROWL)
	// Do the popup test earlier (to avoid needless JID lookups)
	if ((popupType == PopupOnline && option.ppOnline) || (popupType == PopupStatusChange && option.ppStatus)) 
#endif
	if(notifyOnlineOk && doPopup && d->doPopups && !d->blockTransportPopupList->find(j, popupType == PopupOnline) && makeSTATUS(status()) != STATUS_DND ) {
		QString name;
		UserListItem *u = findFirstRelavent(j);

		PsiPopup::PopupType pt = PsiPopup::AlertNone;
		if ( popupType == PopupOnline )
			pt = PsiPopup::AlertOnline;
		else if ( popupType == PopupStatusChange )
			pt = PsiPopup::AlertStatusChange;

		if ((popupType == PopupOnline && option.ppOnline) || (popupType == PopupStatusChange && option.ppStatus)) {
			PsiPopup *popup = new PsiPopup(pt, this);
			popup->setData(j, r, u);
		}
#if defined(Q_WS_MAC) && defined(HAVE_GROWL)
		PsiGrowlNotifier::instance()->popup(this, pt, j, r, u);
#endif
	}
	else if ( !notifyOnlineOk )
		d->userCounter++;
}

void PsiAccount::client_resourceUnavailable(const Jid &j, const Resource &r)
{
	bool doSound = false;
	bool doPopup = false;

	if ( j.user().isEmpty() )
		new BlockTransportPopup(d->blockTransportPopupList, j);

	QPtrList<UserListItem> list = findRelavent(j);
	QPtrListIterator<UserListItem> it(list);
	for(UserListItem *u; (u = it.current()); ++it) {
		bool local = false;
		if(u->isSelf() && r.name() == d->client->resource())
			local = true;

		// remove resource
		UserResourceList::Iterator rit = u->userResourceList().find(j.resource());
		bool found = (rit == u->userResourceList().end()) ? false: true;
		if(found) {
			u->setLastUnavailableStatus(r.status());
			u->userResourceList().remove(rit);

			if(!u->isAvailable())
				u->setLastAvailable(QDateTime::currentDateTime());

			if(!u->isAvailable() || u->isSelf()) {
				// don't sound for our own resource
				if(!isDisconnecting && !local && !u->isHidden()) {
					doSound = true;
					doPopup = true;
				}
			}
		}

		u->setPresenceError("");
		cpUpdate(*u, r.name(), true);
	}
	if(doSound)
		playSound(option.onevent[eOffline]);

#if !defined(Q_WS_MAC) || !defined(HAVE_GROWL)
	// Do the popup test earlier (to avoid needless JID lookups)
	if (option.ppOffline) 
#endif
	if(doPopup && d->doPopups && !d->blockTransportPopupList->find(j) && makeSTATUS(status()) != STATUS_DND ) {
		QString name;
		UserListItem *u = findFirstRelavent(j);

		if (option.ppOffline) {
			PsiPopup *popup = new PsiPopup(PsiPopup::AlertOffline, this);
			popup->setData(j, r, u);
		}
#if defined(Q_WS_MAC) && defined(HAVE_GROWL)
		PsiGrowlNotifier::instance()->popup(this, PsiPopup::AlertOffline, j, r, u);
#endif
	}
}

void PsiAccount::client_presenceError(const Jid &j, int, const QString &str)
{
	QPtrList<UserListItem> list = findRelavent(j);
	QPtrListIterator<UserListItem> it(list);
	for(UserListItem *u; (u = it.current()); ++it) {
		simulateContactOffline(u);
		u->setPresenceError(str);
		cpUpdate(*u, j.resource(), false);
	}
}

void PsiAccount::client_messageReceived(const Message &m)
{
	//check if it's a server message without a from, and set the from appropriately
	Message _m(m);
	if (_m.from().isEmpty())
	{
		_m.setFrom(jid().domain());
	}

	// if the sender is already in the queue, then queue this message also
	QPtrListIterator<Message> it(d->messageQueue);
	for(Message *mi; (mi = it.current()); ++it) {
		if(mi->from().compare(_m.from())) {
			Message *m = new Message(_m);
			d->messageQueue.append(m);
			return;
		}
	}

	// encrypted message?
	if(!pgpKey().isEmpty() && !_m.xencrypted().isEmpty()) {
		Message *m = new Message(_m);
		d->messageQueue.append(m);
		processMessageQueue();
		return;
	}

	processIncomingMessage(_m);
}

void PsiAccount::processIncomingMessage(const Message &_m)
{
	// skip empty messages
	if(_m.body().isEmpty() && _m.urlList().isEmpty() && _m.invite().isEmpty() && !_m.containsEvents())
		return;

	// skip headlines?
	if(_m.type() == "headline" && option.ignoreHeadline)
		return;

	//skip if on ignore list? KEVIN, must not forget

	if(_m.type() == "groupchat") {
		GCMainDlg *w = (GCMainDlg *)dialogFind("GCMainDlg", Jid(_m.from().userHost()));
		if(w)
			w->message(_m);
		return;
	}

	// only toggle if not an invite or body is not empty
	if(_m.invite().isEmpty() && !_m.body().isEmpty())
		toggleSecurity(_m.from(), _m.wasEncrypted());

	UserListItem *u = findFirstRelavent(_m.from());
	if(u) {
		if(_m.type() == "chat") u->setLastMessageType(1);
		else u->setLastMessageType(0);
	}

	Message m = _m;

	// smartchat: try to match up the incoming event to an existing chat
	// (prior to 0.9, m.from() always contained a resource)
	Jid j;
	ChatDlg *c;
	QPtrList<UserListItem> ul = findRelavent(m.from());

	// ignore events from non-roster JIDs?
	if (ul.isEmpty() && option.ignoreNonRoster)
	{
		if (option.excludeGroupChatsFromIgnore)
		{
			GCMainDlg *w = (GCMainDlg *)dialogFind("GCMainDlg", Jid(_m.from().userHost()));
			if(!w)
			{
				return;
			}

		}
		else
		{
			return;
		}
	}

	if(ul.isEmpty())
		j = m.from().userHost();
	else
		j = ul.first()->jid();

	c = (ChatDlg *)dialogFind("ChatDlg", j);
	if(!c)
		c = (ChatDlg *)dialogFind("ChatDlg", m.from().full());

	if(m.type() == "error")
		m.setBody(m.error().text + "\n------\n" + m.body());

	// change the type?
	if(m.type() != "headline" && m.invite().isEmpty()) {
		if(option.incomingAs == 1)
			m.setType("");
		else if(option.incomingAs == 2)
			m.setType("chat");
		else if(option.incomingAs == 3) {
			if(c != NULL && !c->isHidden())
				m.setType("chat");
			else
				m.setType("");
			}
	}

	// urls or subject on a chat message?  convert back to regular message
	//if(m.type() == "chat" && (!m.urlList().isEmpty() || !m.subject().isEmpty()))
	//	m.setType("");

	MessageEvent *me = new MessageEvent(m, this);
	me->setOriginLocal(false);
	handleEvent(me);
}

void PsiAccount::client_subscription(const Jid &j, const QString &str)
{
	// if they remove our subscription, then we lost presence
	if(str == "unsubscribed") {
		UserListItem *u = d->userList.find(j);
		if(u)
			simulateContactOffline(u);
	}

	AuthEvent *ae = new AuthEvent(j, str, this);
	ae->setTimeStamp(QDateTime::currentDateTime());
	handleEvent(ae);
}

void PsiAccount::client_debugText(const QString &)
{
	//printf("%s", str.latin1());
	//fflush(stdout);
}

void PsiAccount::client_incomingJidLink()
{
	JidLink *jl = d->client->jidLinkManager()->takeIncoming();
	if(!jl)
		return;
	//if(link_test) {
	//	printf("[%s] -- Incoming JidLink request from [%s]\n", name().latin1(), jl->peer().full().latin1());
	//	JLTestDlg *w = new JLTestDlg(jl->peer(), jl, this);
	//	w->show();
	//}
	//else
		jl->deleteLater();
}

void PsiAccount::client_incomingFileTransfer()
{
	FileTransfer *ft = d->client->fileTransferManager()->takeIncoming();
	if(!ft)
		return;

	/*printf("psiaccount: incoming file transfer:\n");
	printf("  From: [%s]\n", ft->peer().full().latin1());
	printf("  Name: [%s]\n", ft->fileName().latin1());
	printf("  Size: %d bytes\n", ft->fileSize());*/

	FileEvent *fe = new FileEvent(ft->peer().full(), ft, this);
	fe->setTimeStamp(QDateTime::currentDateTime());
	handleEvent(fe);
}

void PsiAccount::reconnect()
{
	if(doReconnect) {
		//printf("PsiAccount: [%s] reconnecting...\n", name().latin1());
		v_isActive = false;
		doReconnect = false;
		login();
	}
}

Status PsiAccount::status() const
{
	return d->loginStatus;
}

void PsiAccount::setStatus(const Status &_s)
{
	// Block all transports' contacts' status change popups from popping
	{
		Roster::ConstIterator rit = d->acc.roster.begin();
		for ( ; rit != d->acc.roster.end(); ++rit) {
			const RosterItem &i = *rit;
			if ( i.jid().user().isEmpty() /*&& i.jid().resource() == "registered"*/ ) // it is very likely then, that it's transport
				new BlockTransportPopup(d->blockTransportPopupList, i.jid());
		}
	}

	// cancel auto-status and reconnect
	usingAutoStatus = false;
	doReconnect = false;

	Status s = _s;
	s.setPriority(d->acc.priority);

	d->loginStatus = s;

	if(s.isAvailable()) {
		// if client is not active then attempt to login
		if(!isActive()) {
			Jid j = d->jid;
			if(d->acc.resource.isEmpty() || !j.isValid()) {
				QMessageBox::information(0, CAP(tr("Error")), tr("Unable to login.  Ensure your account information is filled out."));
				modify();
				return;
			}
			if(!d->acc.opt_pass) {
				bool ok = false;
				QString text = QInputDialog::getText(
					tr("Need Password"),
					tr("Please enter the password for %1:").arg(j.full()),
					QLineEdit::Password, QString::null, &ok, 0);
				if(ok && !text.isEmpty())
					d->acc.pass = text;
				else
					return;
			}

			login();
		}
		// change status
		else {
			if(rosterDone)
				setStatusDirect(s);

			if(s.isInvisible()) {//&&Pass invis to transports KEVIN
				//this is a nasty hack to let the transports know we're invisible, since they get an offline packet when we go invisible
				QPtrListIterator<UserListItem> it(d->userList);
				for(UserListItem *u; (u = it.current()); ++it) {
					if(u->isTransport()) {
						JT_Presence *j = new JT_Presence(d->client->rootTask());
						j->pres(u->jid(), s);
						j->go(true);
					}
				}
			}
		}
	}
	else {
		if(isActive())
			logout(false, s);
	}
}

void PsiAccount::setStatusDirect(const Status &_s)
{
	Status s = _s;
	s.setPriority(d->acc.priority);

	//printf("setting status to [%s]\n", s.status().latin1());

	// using pgp?
	if(d->psi->pgp() && !d->cur_pgpSecretKeyID.isEmpty()) {
		d->loginStatus = s;

		// sign presence
		trySignPresence();
	}
	else {
		/*if(d->psi->pgp() && !d->cur_pgpSecretKeyID.isEmpty())
			s.setKeyID(d->cur_pgpSecretKeyID);
		else
			s.setKeyID("");*/

		// send presence normally
		setStatusActual(s);
	}
}

void PsiAccount::setStatusActual(const Status &s)
{
	d->loginStatus = s;

	d->client->setPresence(s);
	if(presenceSent) {
		stateChanged();
	}
	else {
		presenceSent = true;
		stateChanged();
		QTimer::singleShot(15000, this, SLOT(enableNotifyOnline()));

		const VCard *vcard = VCardFactory::vcard(d->jid);
		if ( option.autoVCardOnLogin || !vcard || vcard->isEmpty() || vcard->nickName().isEmpty() )
			VCardFactory::getVCard(d->jid, d->client->rootTask(), this, SLOT(slotCheckVCard()));
		else {
			d->nickFromVCard = true;
			setNick( vcard->nickName() );
		}
	}
}

void PsiAccount::secondsIdle(int x)
{
	if(!loggedIn())
		return;

	int lastIdle = d->lastIdle;
	Status lastStatus = d->lastStatus;

	Resource r = *(d->client->resourceList().find(d->client->resource()));
	Status ls = r.status();

	//must avoid status change when invisible KEVIN
	if(ls.isInvisible())
		return;

	if(ls.isAvailable()) {
		// no longer idle?
		if(lastIdle > x) {
			if(ls.isAway() && usingAutoStatus) {
				lastStatus = d->origStatus;
				setStatusDirect(lastStatus);
				usingAutoStatus = false;
			}
		}
		else if( !(ls.isAway() && !usingAutoStatus) ) {
			int minutes = x / 60;

			if(option.use_asOffline && option.asOffline > 0 && minutes >= option.asOffline) {
				lastStatus = Status("", "", d->acc.priority, false);
				usingAutoStatus = false;
				logout();
			}
			else if(option.use_asXa && option.asXa > 0 && minutes >= option.asXa) {
				if(ls.show() != "xa" && lastStatus.show() != "xa") {
					lastStatus = Status("xa", option.asMessage, d->acc.priority);
					if(!usingAutoStatus)
						d->origStatus = d->loginStatus;
					setStatusDirect(lastStatus);
					usingAutoStatus = true;
				}
			}
			else if(option.use_asAway && option.asAway > 0 && minutes >= option.asAway) {
				if(ls.show() != "away" && lastStatus.show() != "away") {
					lastStatus = Status("away", option.asMessage, d->acc.priority);
					if(!usingAutoStatus)
						d->origStatus = d->loginStatus;
					setStatusDirect(lastStatus);
					usingAutoStatus = true;
				}
			}
		}
	}

	d->lastIdle = x;
	d->lastStatus = lastStatus;
}

void PsiAccount::playSound(const QString &str)
{
	if(str.isEmpty())
		return;

	int s = STATUS_OFFLINE;
	if(loggedIn())
		s = makeSTATUS(status());

	if(s == STATUS_DND)
		return;

	// no away sounds?
	if(option.noAwaySound && (s == STATUS_AWAY || s == STATUS_XA))
		return;

	d->psi->playSound(str);
}

QWidget *PsiAccount::dialogFind(const char *className, const Jid &j)
{
	QPtrListIterator<item_dialog2> it(d->dialogList);
	for(item_dialog2 *i; (i = it.current()); ++it) {
		// does the classname and jid match?
		if(i->className == className && i->jid.compare(j)) {
			return i->widget;
		}
	}
	return 0;
}

void PsiAccount::dialogRegister(QWidget *w, const Jid &j)
{
	item_dialog2 *i = new item_dialog2;
	i->widget = w;
	i->className = w->className();
	i->jid = j;
	d->dialogList.append(i);
}

void PsiAccount::dialogUnregister(QWidget *w)
{
	QPtrListIterator<item_dialog2> it(d->dialogList);
	for(item_dialog2 *i; (i = it.current()); ++it) {
		if(i->widget == w) {
			d->dialogList.removeRef(i);
			return;
		}
	}
}

void PsiAccount::deleteAllDialogs()
{
	QPtrListIterator<item_dialog2> it(d->dialogList);
	for(item_dialog2 *i; (i = it.current());)
		delete i->widget;
	d->dialogList.clear();
}

bool PsiAccount::checkConnected(QWidget *par)
{
	if(!loggedIn()) {
		QMessageBox::information(par, CAP(tr("Error")), tr("You must be connected to the server in order to do this."));
		return false;
	}

	return true;
}

void PsiAccount::modify()
{
	AccountModifyDlg *w = (AccountModifyDlg *)dialogFind("AccountModifyDlg");
	if(w)
		bringToFront(w);
	else {
		w = new AccountModifyDlg(this, 0);
		w->show();
	}
}

void PsiAccount::changeVCard()
{
	actionInfo(d->jid);
}

void PsiAccount::changePW()
{
	if(!checkConnected())
		return;

	ChangePasswordDlg *w = (ChangePasswordDlg *)dialogFind("ChangePasswordDlg");
	if(w)
		bringToFront(w);
	else {
		w = new ChangePasswordDlg(this);
		w->show();
	}
}

void PsiAccount::showXmlConsole()
{
	bringToFront(d->xmlConsole);
}

void PsiAccount::openAddUserDlg()
{
	if(!checkConnected())
		return;

	AddUserDlg *w = (AddUserDlg *)dialogFind("AddUserDlg");
	if(w)
		bringToFront(w);
	else {
		QStringList gl, services, names;
		UserListIt it(d->userList);
		for(UserListItem *u; (u = it.current()); ++it) {
			if(u->isTransport()) {
				services += u->jid().full();
				names += jidnick(u->jid().full(), u->name());
			}
			const QStringList &groups = u->groups();
			if(groups.isEmpty())
				continue;
			for(QStringList::ConstIterator git = groups.begin(); git != groups.end(); ++git) {
				if(qstringlistmatch(gl, *git) == -1)
					gl.append(*git);
			}
		}

		w = new AddUserDlg(services, names, gl, this);
		connect(w, SIGNAL(add(const Jid &, const QString &, const QStringList &, bool)), SLOT(dj_add(const Jid &, const QString &, const QStringList &, bool)));
		w->show();
	}
}

void PsiAccount::doDisco()
{
	actionDisco(d->jid.host(), "");
}

void PsiAccount::actionDisco(const Jid &j, const QString &node)
{
	DiscoDlg *w = new DiscoDlg(this, j, node);
	connect(w, SIGNAL(featureActivated(QString, Jid, QString)), SLOT(featureActivated(QString, Jid, QString)));
	w->show();
}

void PsiAccount::featureActivated(QString feature, Jid jid, QString node)
{
	Features f(feature);

	if ( f.canRegister() )
		actionRegister(jid);
	else if ( f.canSearch() )
		actionSearch(jid);
	else if ( f.canGroupchat() )
		actionJoin(jid);
	else if ( f.canDisco() )
		actionDisco(jid, node);
	else if ( f.isGateway() )
		; // TODO
	else if ( f.haveVCard() )
		actionInfo(jid);
	else if ( f.id() == Features::FID_Add ) {
		QStringList sl;
		dj_add(jid, QString::null, sl, true);
	}
}

void PsiAccount::actionJoin(const Jid &j)
{
	GCJoinDlg *w = new GCJoinDlg(psi(), this);

	w->le_host->setText ( j.host() );
	w->le_room->setText ( j.user() );

	w->show();
}

void PsiAccount::stateChanged()
{
	if(loggedIn())
		d->cp->setState(makeSTATUS(status()));
	else {
		if(isActive()) {
			d->cp->setState(-1);
			if(d->usingSSL)
				d->cp->setUsingSSL(true);
			else
				d->cp->setUsingSSL(false);
		}
		else {
			d->cp->setState(STATUS_OFFLINE);
			d->cp->setUsingSSL(false);
		}
	}

	updatedActivity();
}

void PsiAccount::simulateContactOffline(UserListItem *u)
{
	UserResourceList rl = u->userResourceList();
	u->setPresenceError("");
	if(!rl.isEmpty()) {
		for(UserResourceList::ConstIterator rit = rl.begin(); rit != rl.end(); ++rit) {
			const UserResource &r = *rit;
			Jid j = u->jid();
			if(u->jid().resource().isEmpty())
				j.setResource(r.name());
			client_resourceUnavailable(j, r);
		}
	}
	else
		cpUpdate(*u);
}

void PsiAccount::simulateRosterOffline()
{
	UserListIt it(d->userList);
	for(UserListItem *u; (u = it.current()); ++it)
		simulateContactOffline(u);

	// self
	{
		UserListItem *u = &d->self;
		UserResourceList rl = u->userResourceList();
		for(UserResourceList::ConstIterator rit = rl.begin(); rit != rl.end(); ++rit) {
			Jid j = u->jid();
			if(u->jid().resource().isEmpty())
				j.setResource((*rit).name());
			u->setPresenceError("");
			client_resourceUnavailable(j, *rit);
		}
	}

	d->gcbank.clear();
}

void PsiAccount::enableNotifyOnline()
{
	if ( d->userCounter > 1 ) {
		QTimer::singleShot(15000, this, SLOT(enableNotifyOnline()));
		d->userCounter = 0;
	}
	else
		notifyOnlineOk = true;
}

void PsiAccount::slotClientVersionFinished()
{
	JT_ClientVersion *j = (JT_ClientVersion *)sender();
	if(j->success()) {
		QPtrList<UserListItem> list = findRelavent(j->jid());
		QPtrListIterator<UserListItem> it(list);
		for(UserListItem *u; (u = it.current()); ++it) {
			UserResourceList::Iterator rit = u->userResourceList().find(j->jid().resource());
			bool found = (rit == u->userResourceList().end()) ? false: true;
			if(!found)
				continue;

			(*rit).setClient(j->name(),j->version(),j->os());

			cpUpdate(*u);
			/*if(u->isSelf()) {
				if(d->cp->self())
					d->cp->updateSelf(*u);
			}
			else
				d->cp->updateEntry(*u);*/
		}
	}
}

QPtrList<UserListItem> PsiAccount::findRelavent(const Jid &j) const
{
	QPtrList<UserListItem> list;

	// self?
	if(j.compare(d->self.jid(), false))
		list.append(&d->self);
	else {
		QPtrListIterator<UserListItem> it(d->userList);
		for(UserListItem *u; (u = it.current()); ++it) {
			if(!u->jid().compare(j, false))
				continue;

			if(!u->jid().resource().isEmpty()) {
				if(u->jid().resource() != j.resource())
					continue;
			}
			list.append(u);
		}
	}

	return list;
}

UserListItem *PsiAccount::findFirstRelavent(const Jid &j) const
{
	QPtrList<UserListItem> list = findRelavent(j);
	if(list.isEmpty())
		return 0;
	else
		return list.first();
}

UserListItem *PsiAccount::find(const Jid &j) const
{
	UserListItem *u;
	if(j.compare(d->self.jid()))
		u = &d->self;
	else
		u = d->userList.find(j);

	return u;
}

void PsiAccount::cpUpdate(const UserListItem &u, const QString &rname, bool fromPresence)
{
	PsiEvent *e = d->eventQueue->peek(u.jid());

	d->cp->updateEntry(u);

	if(e) {
		d->cp->setAlert(u.jid(), is->event2icon(e));
	}
	else
		d->cp->clearAlert(u.jid());

	updateContact(u);
	Jid j = u.jid();
	if(!rname.isEmpty())
		j.setResource(rname);
	updateContact(j);
	updateContact(j, fromPresence);
	d->psi->updateContactGlobal(this, j);
}

QLabel *PsiAccount::accountLabel(QWidget *par, bool simpleMode)
{
	AccountLabel *al = new AccountLabel(this, par, simpleMode);
	return al;
}

EventDlg *PsiAccount::ensureEventDlg(const Jid &j)
{
	EventDlg *w = (EventDlg *)dialogFind("EventDlg", j);
	if(!w) {
		w = new EventDlg(j, this, true);
		connect(w, SIGNAL(aReadNext(const Jid &)), SLOT(processReadNext(const Jid &)));
		connect(w, SIGNAL(aChat(const Jid &)), SLOT(actionOpenChat(const Jid&)));
		connect(w, SIGNAL(aReply(const Jid &, const QString &, const QString &, const QString &)), SLOT(dj_composeMessage(const Jid &, const QString &, const QString &, const QString &)));
		connect(w, SIGNAL(aAuth(const Jid &)), SLOT(dj_addAuth(const Jid &)));
		connect(w, SIGNAL(aDeny(const Jid &)), SLOT(dj_deny(const Jid &)));
		connect(d->psi, SIGNAL(emitOptionsUpdate()), w, SLOT(optionsUpdate()));
		connect(this, SIGNAL(updateContact(const Jid &)), w, SLOT(updateContact(const Jid &)));
	}

	return w;
}

ChatDlg *PsiAccount::ensureChatDlg(const Jid &j)
{
	ChatDlg *c = (ChatDlg *)dialogFind("ChatDlg", j);
	if(!c) {
		// create the chatbox
		c = new ChatDlg(j, this);
		if (option.useTabs)
		{
			//get a tab from the mainwin
			d->psi->getTabs()->addChat(c);
		}
		connect(c, SIGNAL(aSend(const Message &)), SLOT(dj_sendMessage(const Message &)));
		connect(c, SIGNAL(messagesRead(const Jid &)), SLOT(chatMessagesRead(const Jid &)));
		connect(c, SIGNAL(aInfo(const Jid &)), SLOT(actionInfo(const Jid &)));
		connect(c, SIGNAL(aHistory(const Jid &)), SLOT(actionHistory(const Jid &)));
		connect(c, SIGNAL(aFile(const Jid &)), SLOT(actionSendFile(const Jid &)));
		connect(d->psi, SIGNAL(emitOptionsUpdate()), c, SLOT(optionsUpdate()));
		connect(this, SIGNAL(updateContact(const Jid &, bool)), c, SLOT(updateContact(const Jid &, bool)));
	}
	else {
		// on X11, do a special reparent to open on the right desktop
#ifdef Q_WS_X11
		/* KIS added an exception for tabs here. We do *not* want chats flying 
		 * randomlyi, it pulls them out of tabsets. So instead, we move the 
		 * tabset instead. It's just as filthy, unfortunately, but it's the 
		 * only way */
		//TODO: This doesn't work as expected atm, it doesn't seem to reparent the tabset
		QWidget *window=c;
		if ( option.useTabs )
			window = d->psi->getManagingTabs(c);
		if(window && window->isHidden()) {
			const QPixmap *pp = c->icon();
			QPixmap p;
			if(pp)
				p = *pp;
			reparent_good(window, 0, false);
			if(!p.isNull())
				c->setIcon(p);
		}
#endif
	}

	return c;
}

void PsiAccount::changeStatus(int x)
{
	if(x == STATUS_OFFLINE && !option.askOffline) {
		setStatus(Status("","Logged out",0,false));
	}
	else {
		if(x == STATUS_ONLINE && !option.askOnline) {
			setStatus(Status());
		}
		else if(x == STATUS_INVISIBLE){
			Status s("","",0,true);
			s.setIsInvisible(true);
			setStatus(s);
		}
		else {
			StatusSetDlg *w = new StatusSetDlg(this, makeStatus(x, ""));
			connect(w, SIGNAL(set(const Status &)), SLOT(setStatus(const Status &)));
			w->show();
		}
	}
}

void PsiAccount::actionTest(const Jid &j)
{
	UserListItem *u = find(j);
	if(!u)
		return;
	Jid j2 = j;
	if(j.resource().isEmpty()) {
		if(u->isAvailable())
			j2.setResource((*u->userResourceList().priority()).name());
	}

	//S5BConnection *c = new S5BConnection(d->client->s5bManager());
	//c->connectToJid(j2, d->client->s5bManager()->genUniqueSID(j2));
	JLTestDlg *w = new JLTestDlg(j2, this);
	w->show();
}

void PsiAccount::actionSendFile(const Jid &j)
{
	QStringList l;
	actionSendFiles(j, l);
}

void PsiAccount::actionSendFiles(const Jid &j, const QStringList& l)
{
	Jid j2 = j;
	if(j.resource().isEmpty()) {
		UserListItem *u = find(j);
		if(u && u->isAvailable())
			j2.setResource((*u->userResourceList().priority()).name());
	}

	// Create a dialog for each file in the list. Once the xfer dialog itself
	// supports multiple files, only the 'else' branch needs to stay.
	if (!l.isEmpty()) {
		for (QStringList::ConstIterator f = l.begin(); f != l.end(); ++f ) {
			QStringList fl(*f);
			FileRequestDlg *w = new FileRequestDlg(j2, d->psi, this, fl);
			w->show();
		}
	}
	else {
		FileRequestDlg *w = new FileRequestDlg(j2, d->psi, this, l);
		w->show();
	}
}

void PsiAccount::actionDefault(const Jid &j)
{
	UserListItem *u = find(j);
	if(!u)
		return;

	if(d->eventQueue->count(u->jid()) > 0)
		openNextEvent(*u);
	else {
		if(option.defaultAction == 0)
			actionSendMessage(u->jid());
		else
			actionOpenChat(u->jid());
	}
}

void PsiAccount::actionRecvEvent(const Jid &j)
{
	UserListItem *u = find(j);
	if(!u)
		return;

	openNextEvent(*u);
}

void PsiAccount::actionSendMessage(const Jid &j)
{
	EventDlg *w = d->psi->createEventDlg(j.full(), this);
	w->show();
}

void PsiAccount::actionSendMessage(const JidList &j)
{
	QString str;
	bool first = true;
	for(QValueList<Jid>::ConstIterator it = j.begin(); it != j.end(); ++it) {
		if(!first)
			str += ", ";
		first = false;

		str += (*it).full();
	}

	EventDlg *w = d->psi->createEventDlg(str, this);
	w->show();
}

void PsiAccount::actionSendUrl(const Jid &j)
{
	EventDlg *w = d->psi->createEventDlg(j.full(), this);
	w->setUrlOnShow();
	w->show();
}

void PsiAccount::actionRemove(const Jid &j)
{
#ifdef AVATARS
	avatarFactory()->removeManualAvatar(j);
#endif
	dj_remove(j);
}

void PsiAccount::actionRename(const Jid &j, const QString &name)
{
	dj_rename(j, name);
}

void PsiAccount::actionGroupRename(const QString &oldname, const QString &newname)
{
	UserListIt it(d->userList);
	QPtrList<UserListItem> nu;
	for(UserListItem *u; (u = it.current()); ++it) {
		if(u->inGroup(oldname)) {
			u->removeGroup(oldname);
			u->addGroup(newname);
			cpUpdate(*u);
			if(u->inList())
				nu.append(u);
		}
	}

	if(!nu.isEmpty()) {
		JT_Roster *r = new JT_Roster(d->client->rootTask());

		QPtrListIterator<UserListItem> it(nu);
		for(UserListItem *u; (u = it.current()); ++it)
			r->set(u->jid(), u->name(), u->groups());

		r->go(true);
	}
}

void PsiAccount::actionHistory(const Jid &j)
{
	HistoryDlg *w = (HistoryDlg *)dialogFind("HistoryDlg", j);
	if(w)
		bringToFront(w);
	else {
		w = new HistoryDlg(j, this);
		connect(w, SIGNAL(openEvent(PsiEvent *)), SLOT(actionHistoryBox(PsiEvent *)));
		w->show();
	}
}

void PsiAccount::actionHistoryBox(PsiEvent *e)
{
	EventDlg *w = new EventDlg(e->from(), this, false);
	connect(w, SIGNAL(aChat(const Jid &)), SLOT(actionOpenChat(const Jid&)));
	connect(w, SIGNAL(aReply(const Jid &, const QString &, const QString &, const QString &)), SLOT(dj_composeMessage(const Jid &, const QString &, const QString &, const QString &)));
	connect(w, SIGNAL(aAuth(const Jid &)), SLOT(dj_addAuth(const Jid &)));
	connect(w, SIGNAL(aDeny(const Jid &)), SLOT(dj_deny(const Jid &)));
	connect(d->psi, SIGNAL(emitOptionsUpdate()), w, SLOT(optionsUpdate()));
	connect(this, SIGNAL(updateContact(const Jid &)), w, SLOT(updateContact(const Jid &)));
	w->updateEvent(e);
	w->show();
}

void PsiAccount::actionOpenChat(const Jid &j)
{
	UserListItem *u = find(j);
	if(!u)
		return;

	// if 'j' is bare, we might want to switch to a specific resource
	QString res;
	if(j.resource().isEmpty()) {
		// first, are there any queued chats?
		/*PsiEvent *e = d->eventQueue->peekFirstChat(j, false);
		if(e) {
			res = e->from().resource();
			// if we have a bare chat, change to 'res'
			ChatDlg *c = (ChatDlg *)dialogFind("ChatDlg", j);
			if(c)
				c->setJid(j.withResource(res));
		}
		// else, is there a priority chat window available?
		else*/ if(u->isAvailable()) {
			QString pr = (*u->userResourceList().priority()).name();
			if(!pr.isEmpty() && dialogFind("ChatDlg", j.withResource(pr)))
				res = pr;
		}
		else {
			QStringList list = hiddenChats(j);
			if(!list.isEmpty())
				res = list.first();
		}
	}

	if(!res.isEmpty())
		openChat(j.withResource(res));
	else
		openChat(j);
}

void PsiAccount::actionOpenChatSpecific(const Jid &j)
{
	openChat(j);
}

void PsiAccount::actionAgentSetStatus(const Jid &j, Status &s)
{
	if ( j.user().isEmpty() ) // add all transport popups to block list
		new BlockTransportPopup(d->blockTransportPopupList, j);

	JT_Presence *p = new JT_Presence(d->client->rootTask());
	p->pres(j, s);
	p->go(true);
}

void PsiAccount::actionInfo(const Jid &_j)
{
	bool useCache = true;
	Jid j;
	if(findGCContact(_j)) {
		useCache = false;
		j = _j;
	}
	else {
		j = _j.userHost();
	}

	InfoDlg *w = (InfoDlg *)dialogFind("InfoDlg", j);
	if(w) {
		w->updateStatus();
		bringToFront(w);
	}
	else {
		const VCard *vcard = VCardFactory::vcard(j);

		VCard tmp;
		if ( vcard )
			tmp = *vcard;
		w = new InfoDlg(j.compare(d->jid) ? InfoDlg::Self : InfoDlg::Contact, j, tmp, this, 0, 0, useCache);
		w->show();

		// automatically retrieve info if it doesn't exist
		if(!vcard && loggedIn())
			w->doRefresh();
	}
}

void PsiAccount::actionAuth(const Jid &j)
{
	dj_auth(j);
}

void PsiAccount::actionAuthRequest(const Jid &j)
{
	dj_authReq(j);
}

void PsiAccount::actionAuthRemove(const Jid &j)
{
	dj_deny(j);
}

void PsiAccount::actionAdd(const Jid &j)
{
	dj_addAuth(j);
}

void PsiAccount::actionGroupAdd(const Jid &j, const QString &g)
{
	UserListItem *u = d->userList.find(j);
	if(!u)
		return;

	if(!u->addGroup(g))
		return;
	cpUpdate(*u);

	JT_Roster *r = new JT_Roster(d->client->rootTask());
	r->set(u->jid(), u->name(), u->groups());
	r->go(true);
}

void PsiAccount::actionGroupRemove(const Jid &j, const QString &g)
{
	UserListItem *u = d->userList.find(j);
	if(!u)
		return;

	if(!u->removeGroup(g))
		return;
	cpUpdate(*u);

	JT_Roster *r = new JT_Roster(d->client->rootTask());
	r->set(u->jid(), u->name(), u->groups());
	r->go(true);
}

void PsiAccount::actionRegister(const Jid &j)
{
	if(!checkConnected())
		return;

	RegistrationDlg *w = (RegistrationDlg *)dialogFind("RegistrationDlg", j);
	if(w)
		bringToFront(w);
	else {
		w = new RegistrationDlg(j, this);
		w->show();
	}
}

void PsiAccount::actionSearch(const Jid &j)
{
	if(!checkConnected())
		return;

	SearchDlg *w = (SearchDlg *)dialogFind("SearchDlg", j);
	if(w)
		bringToFront(w);
	else {
		w = new SearchDlg(j, this);
		connect(w, SIGNAL(add(const Jid &, const QString &, const QStringList &, bool)), SLOT(dj_add(const Jid &, const QString &, const QStringList &, bool)));
		connect(w, SIGNAL(aInfo(const Jid &)), SLOT(actionInfo(const Jid &)));
		w->show();
	}
}

void PsiAccount::actionInvite(const Jid &j, const QString &gc)
{
	Message m;
	m.setTo(j);
	m.setInvite(gc);
	m.setBody(tr("You have been invited to %1").arg(gc));
	m.setTimeStamp(QDateTime::currentDateTime());
	dj_sendMessage(m);
}

void PsiAccount::actionAssignKey(const Jid &j)
{
	if(ensureKey(j)) {
		UserListItem *u = findFirstRelavent(j);
		if(u)
			cpUpdate(*u);
	}
}

void PsiAccount::actionUnassignKey(const Jid &j)
{
	UserListItem *u = findFirstRelavent(j);
	if(u) {
		u->setPublicKeyID("");
		cpUpdate(*u);
	}
}

void PsiAccount::dj_sendMessage(const Message &m, bool log)
{
	UserListItem *u = findFirstRelavent(m.to());
	Message nm = m;

        if(option.incomingAs == 3) {
		if(u) {
			switch(u->lastMessageType()) {
				case 0: nm.setType(""); break;
				case 1: nm.setType("chat"); break;
			}
		}
	}

	d->client->sendMessage(nm);

	// only toggle if not an invite or body is not empty
	if(m.invite().isEmpty() && !m.body().isEmpty())
		toggleSecurity(m.to(), m.wasEncrypted());

	// don't log groupchat, private messages, or encrypted messages
	if(d->acc.opt_log && log) {
		if(m.type() != "groupchat" && m.xencrypted().isEmpty() && !findGCContact(m.to())) {
			MessageEvent *me = new MessageEvent(m, this);
			me->setOriginLocal(true);
			me->setTimeStamp(QDateTime::currentDateTime());
			logEvent(m.to(), me);
			delete me;
		}
	}

	// don't sound when sending groupchat messages or message events
	if(m.type() != "groupchat" && !m.body().isEmpty())
		playSound(option.onevent[eSend]);

	// auto close an open messagebox (if non-chat)
	if(m.type() != "chat" && !m.body().isEmpty()) {
		UserListItem *u = findFirstRelavent(m.to());
		if(u) {
			EventDlg *e = (EventDlg *)dialogFind("EventDlg", u->jid());
			if(e)
				e->closeAfterReply();
		}
	}
}

void PsiAccount::dj_composeMessage(const Jid &jid, const QString &body, const QString &subject, const QString &thread)
{
	EventDlg *w = d->psi->createEventDlg(jid.full(), this);
	if(!body.isEmpty())
		w->setText(qstrquote(body));

	if(!subject.isEmpty() && subject.left(3) != "Re:")
		w->setSubject("Re: " + subject);
	else if (subject.left(3) == "Re:")
		w->setSubject(subject);

	if(!thread.isEmpty())
		w->setThread(thread);

	w->show();
}

void PsiAccount::dj_composeMessage(const Jid &j, const QString &body)
{
	dj_composeMessage(j, body, QString::null, QString::null);
}

void PsiAccount::dj_addAuth(const Jid &j)
{
	QString name;
	QStringList groups;
	UserListItem *u = d->userList.find(j);
	if(u) {
		name = u->name();
		groups = u->groups();
	}

	dj_add(j, name, groups, true);
	dj_auth(j);
}

void PsiAccount::dj_add(const Jid &j, const QString &name, const QStringList &groups, bool authReq)
{
	JT_Roster *r = new JT_Roster(d->client->rootTask());
	r->set(j, name, groups);
	r->go(true);

	if(authReq)
		dj_authReq(j);
}

void PsiAccount::dj_authReq(const Jid &j)
{
	d->client->sendSubscription(j, "subscribe");
}

void PsiAccount::dj_auth(const Jid &j)
{
	d->client->sendSubscription(j, "subscribed");
}

void PsiAccount::dj_deny(const Jid &j)
{
	d->client->sendSubscription(j, "unsubscribed");
}

void PsiAccount::dj_rename(const Jid &j, const QString &name)
{
	UserListItem *u = d->userList.find(j);
	if(!u)
		return;

	QString str;
	if(name == u->jid().full())
		str = "";
	else
		str = name;

	// strange workaround to avoid a null string ??
	QString uname;
	if(u->name().isEmpty())
		uname = "";
	else
		uname = u->name();

	if(uname == str)
		return;
	u->setName(str);

	cpUpdate(*u);

	if(u->inList()) {
		JT_Roster *r = new JT_Roster(d->client->rootTask());
		r->set(u->jid(), u->name(), u->groups());
		r->go(true);
	}
}

void PsiAccount::dj_remove(const Jid &j)
{
	UserListItem *u = d->userList.find(j);
	if(!u)
		return;

	// remove all events from the queue
	d->eventQueue->clear(j);
	updateReadNext(j);

	// TODO: delete the item immediately (to simulate local change)
	if(!u->inList()) {
		//simulateContactOffline(u);
		d->userList.removeRef(u);
	}
	else {
		JT_Roster *r = new JT_Roster(d->client->rootTask());
		r->remove(j);
		r->go(true);

		// if it looks like a transport, unregister (but not if it is the server!!)
		if(u->isTransport() && !Jid(d->client->host()).compare(u->jid())) {
			JT_UnRegister *ju = new JT_UnRegister(d->client->rootTask());
			ju->unreg(j);
			ju->go(true);
		}
	}
}

// handle an incoming event
void PsiAccount::handleEvent(PsiEvent *e)
{
	if ( e )
		setEnabled();

	bool doPopup    = false;
	bool putToQueue = true;
	PsiPopup::PopupType popupType = PsiPopup::AlertNone;

	// find someone to accept the event
	Jid j;
	QPtrList<UserListItem> ul = findRelavent(e->from());
	if(ul.isEmpty()) {
		// if groupchat, then we want the full JID
		if(findGCContact(e->from())) {
			j = e->from();
		}
		else {
			Jid bare = e->from().userHost();
			Jid reg = bare.withResource("registered");

			// see if we have a "registered" variant of the jid
			if(findFirstRelavent(reg)) {
				j = reg;
				e->setFrom(reg); // HACK!!
			}
			// retain full jid if sent to "registered"
			else if(e->from().resource() == "registered")
				j = e->from();
			// otherwise don't use the resource for new entries
			else
				j = bare;
		}
	}
	else
		j = ul.first()->jid();
	e->setJid(j);

	if(d->acc.opt_log) {
		if(e->type() == PsiEvent::Message || e->type() == PsiEvent::Auth) {
			// don't log private messages
			if(!findGCContact(e->from()) && !(e->type() == PsiEvent::Message && ((MessageEvent *)e)->message().body().isEmpty()))
				logEvent(e->from(), e);
		}
	}

	if(e->type() == PsiEvent::Message) {
		MessageEvent *me = (MessageEvent *)e;
		const Message &m = me->message();

		// Pass message events to chat window
		if (m.containsEvents() && m.body().isEmpty()) {
			if (option.messageEvents) {
				ChatDlg *c = (ChatDlg *)dialogFind("ChatDlg", e->from());
				if(!c)
					c = (ChatDlg *)dialogFind("ChatDlg", e->jid());
				if (c)
					c->incomingMessage(m);
			}
			return;
		}

		// pass chat messages directly to a chat window if possible (and deal with sound)
		if(m.type() == "chat") {
			ChatDlg *c = (ChatDlg *)dialogFind("ChatDlg", e->from());
			if(!c)
				c = (ChatDlg *)dialogFind("ChatDlg", e->jid());

			if(c)
				c->setJid(e->from());

			if ( !c || !c->isActiveWindow() || option.alertOpenChats ) {
				doPopup = true;
				popupType = PsiPopup::AlertChat;
			}

			//if the chat exists, and is either open in a tab,
			//or in a window
			if( c && ( d->psi->isChatTabbed(c) || !c->isHidden() ) ) {
				c->incomingMessage(m);
				playSound(option.onevent[eChat2]);
				if(option.alertOpenChats && !d->psi->isChatActiveWindow(c)) {
					// to alert the chat also, we put it in the queue
					me->setSentToChatWindow(true);
				}
				else
					putToQueue = false;
			}
			else {
				bool firstChat = !d->eventQueue->hasChats(e->from());
				playSound(option.onevent[firstChat ? eChat1: eChat2]);
			}
		} // /chat
		else if (m.type() == "headline") {
			playSound(option.onevent[eHeadline]);
			doPopup = true;
			popupType = PsiPopup::AlertHeadline;
		} // /headline
		else if (m.type() == "") {
			playSound(option.onevent[eMessage]);
			if (m.type() == "") {
				doPopup = true;
				popupType = PsiPopup::AlertMessage;
			}
		} // /""
		else
			playSound(option.onevent[eSystem]);

		if(m.type() == "error") {
			// FIXME: handle message errors
			//msg.text = QString(tr("<big>[Error Message]</big><br>%1").arg(plain2rich(msg.text)));
		}
	}
	else if(e->type() == PsiEvent::File) {
		playSound(option.onevent[eIncomingFT]);
		doPopup = true;
		popupType = PsiPopup::AlertFile;
	}
	else {
		playSound(option.onevent[eSystem]);

		AuthEvent *ae = (AuthEvent *)e;
		if(ae->authType() == "subscribe") {
			if(option.autoAuth) {
				// Check if we want to request auth as well
				UserListItem *u = d->userList.find(ae->from());
				if (!u || (u->subscription().type() != Subscription::Both && u->subscription().type() != Subscription::To)) {
					dj_addAuth(ae->from());
				}
				else {
					dj_auth(ae->from());
				}
				putToQueue = false;
			}
		} 
		else if(ae->authType() == "subscribed") {
			if(!option.notifyAuth) 
				putToQueue = false;
		}
		else if(ae->authType() == "unsubscribe") {
			putToQueue = false;
		}
	}

#if !defined(Q_WS_MAC) || !defined(HAVE_GROWL)
	// Do the popup test earlier (to avoid needless JID lookups)
	if ((popupType == PsiPopup::AlertChat && option.ppChat) || (popupType == PsiPopup::AlertMessage && option.ppMessage) || (popupType == PsiPopup::AlertHeadline && option.ppHeadline) || (popupType == PsiPopup::AlertFile && option.ppFile))
#endif
	if ( doPopup && d->doPopups && makeSTATUS(status()) != STATUS_DND ) {
		Resource r;
		UserListItem *u = findFirstRelavent(j);
		if ( u )
			r = *(u->priority());

		if (((popupType == PsiPopup::AlertChat && option.ppChat) || (popupType == PsiPopup::AlertMessage && option.ppMessage) || (popupType == PsiPopup::AlertHeadline && option.ppHeadline) || (popupType == PsiPopup::AlertFile && option.ppFile)) && makeSTATUS(status()) != STATUS_DND) {
			PsiPopup *popup = new PsiPopup(popupType, this);
			popup->setData(j, r, u, e);
		}
#if defined(Q_WS_MAC) && defined(HAVE_GROWL)
		PsiGrowlNotifier::instance()->popup(this, popupType, j, r, u, e);
#endif
	}

	if ( putToQueue )
		queueEvent(e);
	else
		delete e;
}

// put an event into the event queue, and update the related alerts
void PsiAccount::queueEvent(PsiEvent *e)
{
	// do we have roster item for this?
	UserListItem *u = find(e->jid());
	if(!u) {
		// create item
		u = new UserListItem;
		u->setJid(e->jid());
		u->setInList(false);
#ifdef AVATARS
		u->setAvatarFactory(d->avatarFactory);
#endif

		// is it a private groupchat?
		Jid j = u->jid();
		GCContact *c = findGCContact(j);
		if(c) {
			u->setName(j.resource());
			u->setPrivate(true);

			// make a resource so the contact appears online
			UserResource ur;
			ur.setName(j.resource());
			ur.setStatus(c->status);
			u->userResourceList().append(ur);
		}

		// treat it like a push  [pushinfo]
		//VCard info;
		//if(readUserInfo(item->jid, &info) && !info.field[vNickname].isEmpty())
		//	item->nick = info.field[vNickname];
		//else {
		//	if(localStatus != STATUS_OFFLINE)
		//		serv->getVCard(item->jid);
		//}

		d->userList.append(u);
	}

	//printf("queuing message from [%s] for [%s].\n", e->from().full().latin1(), e->jid().full().latin1());
	d->eventQueue->enqueue(e);

	updateReadNext(e->jid());
	if(option.raise)
		d->psi->raiseMainwin();

	// udpate the roster
	cpUpdate(*u);

	bool noPopup = false;
	if(d->loginStatus.isAvailable()) {
		QString show = d->loginStatus.show();
		if(show == "dnd")
			noPopup = true;
		else if((show == "away" || show == "xa") && option.noAwayPopup)
			noPopup = true;
	}

	 if (!noPopup) {
		bool doPopup = false;

		// Check to see if we need to popup
		 if(e->type() == PsiEvent::Message) {
			MessageEvent *me = (MessageEvent *)e;
			const Message &m = me->message();
			 if (m.type() == "chat")
				doPopup = option.popupChats;
			else if (m.type() == "headline")
				doPopup = option.popupHeadlines;
			else
				doPopup = option.popupMsgs;
		} else if (e->type() == PsiEvent::File) {
			doPopup = option.popupFiles;
		}
		else
			doPopup = option.popupMsgs;

		// Popup
		if (doPopup) {
			UserListItem *u = find(e->jid());
			if (u && (!option.noUnlistedPopup || u->inList()))
				openNextEvent(*u);
		}

	}
}

// take the next event from the queue and display it
void PsiAccount::openNextEvent(const UserListItem &u)
{
	PsiEvent *e = d->eventQueue->peek(u.jid());
	if(!e)
		return;

	psi()->processEvent(e);
}

void PsiAccount::openNextEvent()
{
	PsiEvent *e = d->eventQueue->peekNext();
	if(!e)
		return;

	if(e->type() == PsiEvent::PGP) {
		psi()->processEvent(e);
		return;
	}

	UserListItem *u = find(e->jid());
	if(!u)
		return;
	openNextEvent(*u);
}

void PsiAccount::updateReadNext(const Jid &j)
{
	// update eventdlg's read-next
	EventDlg *w = (EventDlg *)dialogFind("EventDlg", j);
	if(w) {
		Icon *nextAnim = 0;
		int nextAmount = d->eventQueue->count(j);
		if(nextAmount > 0)
			nextAnim = is->event2icon(d->eventQueue->peek(j));
		w->updateReadNext(nextAnim, nextAmount);
	}

	queueChanged();
}

void PsiAccount::processReadNext(const Jid &j)
{
	UserListItem *u = find(j);
	if(u)
		processReadNext(*u);
}

void PsiAccount::processReadNext(const UserListItem &u)
{
	EventDlg *w = (EventDlg *)dialogFind("EventDlg", u.jid());
	if(!w) {
		// this should NEVER happen
		return;
	}

	// peek the event
	PsiEvent *e = d->eventQueue->peek(u.jid());
	if(!e)
		return;

	bool isChat = false;
	if(e->type() == PsiEvent::Message) {
		MessageEvent *me = (MessageEvent *)e;
		const Message &m = me->message();
		if(m.type() == "chat")
			isChat = true;
	}

	// if it's a chat message, just open the chat window.  there is no need to do
	// further processing.  the chat window will remove it from the queue, update
	// the cvlist, etc etc.
	if(isChat) {
		openChat(e->from());
		return;
	}

	// remove from queue
	e = d->eventQueue->dequeue(u.jid());

	// update the eventdlg
	w->updateEvent(e);
	delete e;

	// update the contact
	cpUpdate(u);

	updateReadNext(u.jid());
}

void PsiAccount::processChats(const Jid &j)
{
	//printf("processing chats for [%s]\n", j.full().latin1());
	ChatDlg *c = (ChatDlg *)dialogFind("ChatDlg", j);
	if(!c)
		return;

	// extract the chats
	QPtrList<PsiEvent> chatList;
	d->eventQueue->extractChats(&chatList, j);
	chatList.setAutoDelete(true);

	if(!chatList.isEmpty()) {
		// dump the chats into the chat window, and remove the related cvlist alerts
		QPtrListIterator<PsiEvent> it(chatList);
		for(PsiEvent *e; (e = it.current()); ++it) {
			MessageEvent *me = (MessageEvent *)e;
			const Message &m = me->message();

			// process the message
			if(!me->sentToChatWindow())
				c->incomingMessage(m);
		}

		QPtrList<UserListItem> ul = findRelavent(j);
		if(!ul.isEmpty()) {
			UserListItem *u = ul.first();
			cpUpdate(*u);
			updateReadNext(u->jid());
		}
	}
}

void PsiAccount::openChat(const Jid &j)
{
	ChatDlg *c = ensureChatDlg(j);
	processChats(j);
	if ( option.useTabs )
	{
		if ( !d->psi->isChatTabbed(c) )
		{
			//get a tab from the psicon
			d->psi->getTabs()->addChat(c);
		}
		TabDlg* tabSet = d->psi->getManagingTabs(c);
		tabSet->selectTab(c);
	}
	bringToFront(c);
}

void PsiAccount::chatMessagesRead(const Jid &j)
{
	if(option.alertOpenChats)
		processChats(j);
}

void PsiAccount::logEvent(const Jid &j, PsiEvent *e)
{
	EDBHandle *h = new EDBHandle(d->psi->edb());
	connect(h, SIGNAL(finished()), SLOT(edb_finished()));
	h->append(j, e);
}

void PsiAccount::edb_finished()
{
	EDBHandle *h = (EDBHandle *)sender();
	delete h;
}

void PsiAccount::openGroupChat(const Jid &j)
{
	QString str = j.userHost();
	bool found = false;
	for(QStringList::ConstIterator it = d->groupchats.begin(); it != d->groupchats.end(); ++it) {
		if((*it) == str) {
			found = true;
			break;
		}
	}
	if(!found)
		d->groupchats += str;

	GCMainDlg *w = new GCMainDlg(this, j);
	connect(w, SIGNAL(aSend(const Message &)), SLOT(dj_sendMessage(const Message &)));
	connect(d->psi, SIGNAL(emitOptionsUpdate()), w, SLOT(optionsUpdate()));
	w->show();
}

bool PsiAccount::groupChatJoin(const QString &host, const QString &room, const QString &nick)
{
	return d->client->groupChatJoin(host, room, nick);
}

void PsiAccount::groupChatChangeNick(const QString &host, const QString &room, const QString& nick, const Status &s)
{
	d->client->groupChatChangeNick(host, room, nick, s);
}

void PsiAccount::groupChatSetStatus(const QString &host, const QString &room, const Status &s)
{
	d->client->groupChatSetStatus(host, room, s);
}

void PsiAccount::groupChatLeave(const QString &host, const QString &room)
{
	d->groupchats.remove(room + '@' + host);
	d->client->groupChatLeave(host, room);
}

GCContact *PsiAccount::findGCContact(const Jid &j)
{
	QPtrListIterator<GCContact> it(d->gcbank);
	for(GCContact *c; (c = it.current()); ++it) {
		if(c->jid.compare(j))
			return c;
	}
	return 0;
}

QStringList PsiAccount::groupchats() const
{
	return d->groupchats;
}

void PsiAccount::client_groupChatJoined(const Jid &j)
{
	d->client->groupChatSetStatus(j.host(), j.user(), d->loginStatus);

	GCMainDlg *m = (GCMainDlg *)dialogFind("GCMainDlg", Jid(j.userHost()));
	if(m) {
		m->joined();
		return;
	}
	GCJoinDlg *w = (GCJoinDlg *)dialogFind("GCJoinDlg", j);
	if(!w)
		return;
	w->joined();

	openGroupChat(j);
}

void PsiAccount::client_groupChatLeft(const Jid &j)
{
	// remove all associated groupchat contacts from the bank
	QPtrListIterator<GCContact> it(d->gcbank);
	for(GCContact *c; (c = it.current());) {
		// contact from this room?
		if(!c->jid.compare(j, false)) {
			++it;
			continue;
		}
		UserListItem *u = find(c->jid);
		if(!u) {
			++it;
			continue;
		}

		simulateContactOffline(u);
		d->gcbank.removeRef(c);
	}
}

void PsiAccount::client_groupChatPresence(const Jid &j, const Status &s)
{
	GCMainDlg *w = (GCMainDlg *)dialogFind("GCMainDlg", Jid(j.userHost()));
	if(!w)
		return;

	GCContact *c = findGCContact(j);
	if(!c) {
		c = new GCContact;
		c->jid = j;
		c->status = s;
		d->gcbank.append(c);
	}

	w->presence(j.resource(), s);

	// pass through the core presence handling also
	Resource r;
	r.setName(j.resource());
	r.setStatus(s);
	if(s.isAvailable())
		client_resourceAvailable(j, r);
	else
		client_resourceUnavailable(j, j.resource());
}

void PsiAccount::client_groupChatError(const Jid &j, int code, const QString &str)
{
	GCMainDlg *w = (GCMainDlg *)dialogFind("GCMainDlg", Jid(j.userHost()));
	if(w) {
		w->error(code, str);
	}
	else {
		GCJoinDlg *w = (GCJoinDlg *)dialogFind("GCJoinDlg", j);
		if(w) {
			w->error(code, str);
		}
	}
}

QStringList PsiAccount::hiddenChats(const Jid &j) const
{
	QStringList list;

	QPtrListIterator<item_dialog2> it(d->dialogList);
	for(item_dialog2 *i; (i = it.current()); ++it) {
		if(i->className == "ChatDlg" && i->jid.compare(j, false))
			list += i->jid.resource();
	}

	return list;
}

void PsiAccount::slotCheckVCard()
{
	JT_VCard *j = (JT_VCard *)sender();
	if(!j->success() && j->statusCode() == Task::ErrDisc) {
		setNick(d->jid.user());
		return;
	}

	if(j->vcard().isEmpty()) {
		changeVCard();
		return;
	}

	if(!j->vcard().nickName().isEmpty()) {
		d->nickFromVCard = true;
		setNick(j->vcard().nickName());
	}
	else
		setNick(d->jid.user());
}

void PsiAccount::setNick(const QString &nick)
{
	d->self.setName(nick);
	cpUpdate(d->self);
	nickChanged();
}

QString PsiAccount::nick() const
{
	return d->self.name();
}

void PsiAccount::pgpToggled(bool b)
{
	QString oldkey = d->cur_pgpSecretKeyID;

	// gaining pgp?
	if(b)
		d->cur_pgpSecretKeyID = d->acc.pgpSecretKeyID;
	// losing it?
	else {
		d->cur_pgpSecretKeyID = "";
	}

	if(oldkey != d->cur_pgpSecretKeyID) {
		pgpKeyChanged();
		// resend status if online
		if(loggedIn())
			setStatusDirect(d->loginStatus);
	}
}

void PsiAccount::pgpKeysUpdated()
{
	OpenPGP::KeyList list = d->psi->pgp()->publicKeys();

	// are there any sigs that need verifying?
	QPtrListIterator<UserListItem> it(d->userList);
	for(UserListItem *u; (u = it.current()); ++it) {
		UserResourceList &rl = u->userResourceList();
		for(UserResourceList::Iterator rit = rl.begin(); rit != rl.end(); ++rit) {
			UserResource &r = *rit;
			if(!r.status().xsigned().isEmpty() && r.pgpVerifyStatus() == OpenPGP::VerifyNoKey) {
				bool haveKey = false;
				QString key = r.publicKeyID();
				for(OpenPGP::KeyList::ConstIterator kit = list.begin(); kit != list.end(); ++kit) {
					if((*kit).keyID() == key) {
						haveKey = true;
						break;
					}
				}
				if(haveKey)
					tryVerify(u, &r);
			}
		}
	}
}

void PsiAccount::verifyStatus(const Jid &j, const Status &s)
{
	PGPTransaction *t = new PGPTransaction(d->psi->pgp());
	t->setJid(j);
	connect(t, SIGNAL(finished(bool)), SLOT(pgp_verifyFinished(bool)));
	QCString cs = s.status().utf8();
	QByteArray buf(cs.length());
	memcpy(buf.data(), cs.data(), buf.size());
	t->verify(buf, OpenPGP::addHeaderFooter(s.xsigned(), 1));

	//printf("%s: verifying\n", j.full().latin1());
}

void PsiAccount::trySignPresence()
{
	OpenPGP::Request *r = new OpenPGP::Request(d->psi->pgp());
	connect(r, SIGNAL(finished(bool)), SLOT(pgp_signFinished(bool)));
	connect(r, SIGNAL(needPassphrase()), SLOT(pgp_needPassphrase()));
	QCString cs = d->loginStatus.status().utf8();
	QByteArray buf(cs.length());
	memcpy(buf.data(), cs.data(), buf.size());
	r->sign(buf, d->cur_pgpSecretKeyID);
}

void PsiAccount::pgp_needPassphrase()
{
	OpenPGP::Request *r = (OpenPGP::Request *)sender();
	d->ppreq = r;

	if(pgp_passphrases.contains(d->cur_pgpSecretKeyID))
		r->submitPassphrase(pgp_passphrases[d->cur_pgpSecretKeyID]);
	else
		promptPassphrase();
}

void PsiAccount::promptPassphrase()
{
	if(d->ppdlg)
		d->ppdlg->unblock();
	else {
		PassphraseDlg *w = new PassphraseDlg(0);
		connect(w, SIGNAL(submitPassphrase(const QString &)), SLOT(submitPassphrase(const QString &)));
		connect(w, SIGNAL(rejectPassphrase()), SLOT(rejectPassphrase()));
		w->setCaption(tr("%1: OpenPGP Passphrase").arg(name()));
		w->show();

		d->ppdlg = w;
	}
}

void PsiAccount::submitPassphrase(const QString &pp)
{
	d->ppreq->submitPassphrase(pp);
}

void PsiAccount::rejectPassphrase()
{
	d->ppdlg = 0;

	// cancel request
	if(d->ppreq)
		d->ppreq->deleteLater();

	logout();
}

void PsiAccount::pgp_signFinished(bool ok)
{
	OpenPGP::Request *r = (OpenPGP::Request *)sender();
	bool badPassphrase = r->badPassphrase();
	QString sig;
	if(ok)
		sig = r->signature();
	if(d->ppdlg) {
		pgp_passphrases[d->cur_pgpSecretKeyID] = d->ppdlg->passphrase();
	}
	r->deleteLater();

	if(ok) {
		Status s = d->loginStatus;
		s.setXSigned(OpenPGP::stripHeaderFooter(sig));
		setStatusActual(s);
	}
	else {
		if(badPassphrase) {
			pgp_passphrases.erase(d->cur_pgpSecretKeyID);
			QMessageBox::information(d->ppdlg ? d->ppdlg : 0, CAP(tr("Error")), tr("You entered a bad passphrase.  Please try again."));
			QTimer::singleShot(0, this, SLOT(trySignPresence()));
			return;
		}
		else {
			QMessageBox::information(d->ppdlg ? d->ppdlg : 0, CAP(tr("Error")), tr("There was an error during OpenPGP processing.  Check your settings and try again."));
			logout();
		}
	}

	// remove the dialog if it is there
	if(d->ppdlg) {
		d->ppdlg->deleteLater();
		d->ppdlg = 0;
	}
}

void PsiAccount::pgp_verifyFinished(bool b)
{
	PGPTransaction *t = (PGPTransaction *)sender();

	Jid j = t->jid();
	//printf("%s: verify complete\n", j.full().latin1());
	QPtrList<UserListItem> list = findRelavent(j);
	QPtrListIterator<UserListItem> it(list);
	for(UserListItem *u; (u = it.current()); ++it) {
		UserResourceList::Iterator rit = u->userResourceList().find(j.resource());
		bool found = (rit == u->userResourceList().end()) ? false: true;
		if(!found)
			continue;
		UserResource &ur = *rit;

		if(b) {
			//printf("vergood\n");
			ur.setPublicKeyID(t->keyID());
			ur.setPGPVerifyStatus(t->verifyResult());
			ur.setSigTimestamp(t->timestamp());

			// if the key doesn't match the assigned key, unassign it
			if(t->keyID() != u->publicKeyID())
				u->setPublicKeyID("");
		}
		else {
			//QMessageBox::information(0, "sig verify error", QString("error verifying [%1]").arg(u->jid().full()));
			ur.setPGPVerifyStatus(OpenPGP::VerifyError);
		}

		//printf("updating [%s]\n", u->jid().full().latin1());
		cpUpdate(*u);
	}

	t->deleteLater();
}

int PsiAccount::sendMessageEncrypted(const Message &_m)
{
	if(!ensureKey(_m.to()))
		return -1;
	QString key = findFirstRelavent(_m.to())->publicKeyID();

	PGPTransaction *pt = new PGPTransaction(d->psi->pgp());
	Message m = _m;
	pt->setMessage(m); // keep a copy
	//QByteArray a = m.generateEncryptablePayload(d->client->doc());
	QCString cs = m.body().utf8();
	QByteArray a(cs.length());
	memcpy(a.data(), cs.data(), a.size());

	// encrypt
	QStringList rcpt;
	rcpt += key;
	connect(pt, SIGNAL(finished(bool)), SLOT(pgp_finished(bool)));
	pt->encrypt(a, rcpt);

	return pt->id();
}

void PsiAccount::pgp_finished(bool b)
{
	PGPTransaction *pt = (PGPTransaction *)sender();

	int x = pt->id();
	if(pt->type() == OpenPGP::Encrypt) {
		if(b) {
			Message m = pt->message();
			// log the message here, before we encrypt it
			if(d->acc.opt_log) {
				MessageEvent *me = new MessageEvent(m, this);
				me->setOriginLocal(true);
				me->setTimeStamp(QDateTime::currentDateTime());
				logEvent(m.to(), me);
				delete me;
			}

			Message mwrap;
			mwrap.setTo(m.to());
			mwrap.setType(m.type());
			QString enc = OpenPGP::stripHeaderFooter(pt->encrypted());
			mwrap.setBody(tr("[ERROR: This message is encrypted, and you are unable to decrypt it.]"));
			mwrap.setXEncrypted(enc);
			mwrap.setWasEncrypted(true);
			// FIXME: Should be done cleaner, with an extra method in Iris
			if (m.containsEvent(OfflineEvent)) mwrap.addEvent(OfflineEvent);
			if (m.containsEvent(DeliveredEvent)) mwrap.addEvent(DeliveredEvent);
			if (m.containsEvent(DisplayedEvent)) mwrap.addEvent(DisplayedEvent);
			if (m.containsEvent(ComposingEvent)) mwrap.addEvent(ComposingEvent);
			if (m.containsEvent(CancelEvent)) mwrap.addEvent(CancelEvent);
			dj_sendMessage(mwrap);
		}
		encryptedMessageSent(x, b);
	}

	pt->deleteLater();
}

void PsiAccount::pgp_decryptFinished(bool b)
{
	PGPTransaction *pt = (PGPTransaction *)sender();

	bool tryAgain = false;
	if(b) {
		Message m = pt->message();
		//if(m.applyDecryptedPayload(pt->decrypted(), d->client->doc()))
		QByteArray buf = pt->decrypted();
		QCString cs(buf.size()+1);
		memcpy(cs.data(), buf.data(), buf.size());
		QString str = QString::fromUtf8(cs);
		m.setBody(str);
		m.setXEncrypted("");
		m.setWasEncrypted(true);
		processIncomingMessage(m);

		//else
		//	QMessageBox::information(0, CAP(tr("Error")), tr("A successful decryption operation resulted in an invalid message, so it has been ignored."));
	}
	else {
		if(loggedIn()) {
			Message m;
			m.setTo(pt->message().from());
			m.setType("error");
			m.setBody(pt->message().body());
			Stanza::Error err;
			err.condition = 500;
			err.text = "Unable to decrypt";
			m.setError(err);
			d->client->sendMessage(m);
		}
	}

	pt->deleteLater();

	if(tryAgain) {
		processEncryptedMessageNext();
	}
	else {
		processEncryptedMessageDone();
	}
}

void PsiAccount::processEncryptedMessage(const Message &m)
{
	// decrypt
	PGPTransaction *t = new PGPTransaction(d->psi->pgp());
	t->setMessage(m); // keep a copy
	connect(t, SIGNAL(needPassphrase()), SLOT(pgp_needPassphrase()));
	connect(t, SIGNAL(finished(bool)), SLOT(pgp_decryptFinished(bool)));
	QString str = OpenPGP::addHeaderFooter(m.xencrypted(), 0);
	t->decrypt(str);
}

void PsiAccount::processMessageQueue()
{
	while(!d->messageQueue.isEmpty()) {
		Message *mp = d->messageQueue.getFirst();

		// encrypted?
		if(d->psi->pgp() && !mp->xencrypted().isEmpty()) {
			processEncryptedMessageNext();
			break;
		}

		processIncomingMessage(*mp);
		d->messageQueue.removeRef(mp);
	}
}

void PsiAccount::processEncryptedMessageNext()
{
	// 'peek' and try to process it
	Message *mp = d->messageQueue.getFirst();
	processEncryptedMessage(*mp);
}

void PsiAccount::processEncryptedMessageDone()
{
	// 'pop' the message
	Message *mp = d->messageQueue.getFirst();
	d->messageQueue.removeRef(mp);

	// do the rest of the queue
	processMessageQueue();
}

void PsiAccount::optionsUpdate()
{
	d->cp->updateEntry(d->self);
}

QString PsiAccount::resultToString(int result)
{
	QString s;
	switch(result) {
		case QCA::TLS::NoCert:
			s = tr("The server did not present a certificate.");
			break;
		case QCA::TLS::Valid:
			s = tr("Certificate is valid.");
			break;
		case QCA::TLS::HostMismatch:
			s = tr("The hostname does not match the one the certificate was issued to.");
			break;
		case QCA::TLS::Rejected:
			s = tr("Root CA is marked to reject the specified purpose.");
			break;
		case QCA::TLS::Untrusted:
			s = tr("Certificate not trusted for the required purpose.");
			break;
		case QCA::TLS::SignatureFailed:
			s = tr("Invalid signature.");
			break;
		case QCA::TLS::InvalidCA:
			s = tr("Invalid CA certificate.");
			break;
		case QCA::TLS::InvalidPurpose:
			s = tr("Invalid certificate purpose.");
			break;
		case QCA::TLS::SelfSigned:
			s = tr("Certificate is self-signed.");
			break;
		case QCA::TLS::Revoked:
			s = tr("Certificate has been revoked.");
			break;
		case QCA::TLS::PathLengthExceeded:
			s = tr("Maximum certificate chain length exceeded.");
			break;
		case QCA::TLS::Expired:
			s = tr("Certificate has expired.");
			break;
		case QCA::TLS::Unknown:
		default:
			s = tr("General certificate validation error.");
			break;
	}
	return s;
}

void PsiAccount::invokeGCMessage(const Jid &j)
{
	GCContact *c = findGCContact(j);
	if(!c)
		return;

	// create dummy item, open chat, then destroy item.  HORRIBLE HACK!
	UserListItem *u = new UserListItem;
	u->setJid(j);
	u->setInList(false);
	u->setName(j.resource());
	u->setPrivate(true);
#ifdef AVATARS
	u->setAvatarFactory(d->avatarFactory);
#endif

	// make a resource so the contact appears online
	UserResource ur;
	ur.setName(j.resource());
	ur.setStatus(c->status);
	u->userResourceList().append(ur);

	d->userList.append(u);
	actionSendMessage(j);
	d->userList.remove(u);
}

void PsiAccount::invokeGCChat(const Jid &j)
{
	GCContact *c = findGCContact(j);
	if(!c)
		return;

	// create dummy item, open chat, then destroy item.  HORRIBLE HACK!
	UserListItem *u = new UserListItem;
	u->setJid(j);
	u->setInList(false);
	u->setName(j.resource());
	u->setPrivate(true);
#ifdef AVATARS
	u->setAvatarFactory(d->avatarFactory);
#endif

	// make a resource so the contact appears online
	UserResource ur;
	ur.setName(j.resource());
	ur.setStatus(c->status);
	u->userResourceList().append(ur);

	d->userList.append(u);
	actionOpenChat(j);
	d->userList.remove(u);
}

void PsiAccount::invokeGCInfo(const Jid &j)
{
	actionInfo(j);
}

void PsiAccount::invokeGCFile(const Jid &j)
{
	actionSendFile(j);
}

void PsiAccount::toggleSecurity(const Jid &j, bool b)
{
	UserListItem *u = findFirstRelavent(j);
	if(!u)
		return;

	bool isBare = j.resource().isEmpty();
	bool isPriority = false;

	// sick sick sick sick sick sick
	UserResource *r1, *r2;
	r1 = r2 = 0;
	UserResourceList::Iterator it1 = u->userResourceList().find(j.resource());
	UserResourceList::Iterator it2 = u->userResourceList().priority();
	r1 = (it1 != u->userResourceList().end() ? &(*it1) : 0);
	r2 = (it2 != u->userResourceList().end() ? &(*it2) : 0);
	if(r1 && (r1 == r2))
		isPriority = true;

	bool needUpdate = false;
	bool sec = u->isSecure(j.resource());
	if(sec != b) {
		u->setSecure(j.resource(), b);
		needUpdate = true;
	}
	if(isBare && r2) {
		sec = u->isSecure(r2->name());
		if(sec != b) {
			u->setSecure(r2->name(), b);
			needUpdate = true;
		}
	}
	if(isPriority) {
		sec = u->isSecure("");
		if(sec != b) {
			u->setSecure("", b);
			needUpdate = true;
		}
	}

	if(needUpdate)
		cpUpdate(*u);
}

bool PsiAccount::ensureKey(const Jid &j)
{
	if(!d->psi->pgp())
		return false;

	UserListItem *u = findFirstRelavent(j);
	if(!u)
		return false;

	// no key?
	if(u->publicKeyID().isEmpty()) {
		// does the user have any presence signed with a key?
		QString akey;
		const UserResourceList &rl = u->userResourceList();
		for(UserResourceList::ConstIterator it = rl.begin(); it != rl.end(); ++it) {
			const UserResource &r = *it;
			if(!r.publicKeyID().isEmpty()) {
				akey = r.publicKeyID();
				break;
			}
		}

		bool inList = false;
		OpenPGP::KeyList list = d->psi->pgp()->publicKeys();
		for(OpenPGP::KeyList::ConstIterator lit = list.begin(); lit != list.end(); ++lit) {
			const OpenPGP::Key &k = *lit;
			if(k.keyID() == akey) {
				inList = true;
				break;
			}
		}

		if(akey.isEmpty() || !inList) {
			int n = QMessageBox::information(0, CAP(tr("No key")), tr(
				"<p>Psi was unable to locate the OpenPGP key to use for <b>%1</b>.<br>"
				"<br>"
				"This can happen if you do not have the key that the contact is advertising "
				"via signed presence, or if the contact is not advertising any key at all.</p>"
				).arg(u->jid().full()), tr("&Choose key manually"), tr("Do &nothing"));
			if(n != 0)
				return false;
		}

		PGPKeyDlg *w = new PGPKeyDlg(list, akey, 0);
		w->setCaption(tr("Public Key: %1").arg(j.full()));
		int r = w->exec();
		QString key;
		if(r == QDialog::Accepted)
			key = w->keyID();
		delete w;
		if(key.isEmpty())
			return false;
		u->setPublicKeyID(key);
		cpUpdate(*u);
	}

	return true;
}


//----------------------------------------------------------------------------
// PGPKeyDlg
//----------------------------------------------------------------------------
class KeyViewItem : public QListViewItem
{
public:
	KeyViewItem(const QString &_keyID, QListView *par)
	:QListViewItem(par)
	{
		keyID = _keyID;
	}

	QString keyID;
};

class PGPKeyDlg::Private
{
public:
	Private() {}

	QString keyID;
	QString userID;
};

PGPKeyDlg::PGPKeyDlg(const OpenPGP::KeyList &list, const QString &choose, QWidget *parent, const char *name)
:PGPKeyUI(parent, name, true)
{
	d = new Private;

	connect(lv_keys, SIGNAL(doubleClicked(QListViewItem *)), SLOT(qlv_doubleClicked(QListViewItem *)));
	connect(pb_ok, SIGNAL(clicked()), SLOT(do_accept()));
	connect(pb_cancel, SIGNAL(clicked()), SLOT(reject()));

	QListViewItem *isel = 0;
	for(OpenPGP::KeyList::ConstIterator it = list.begin(); it != list.end(); ++it) {
		const OpenPGP::Key &k = *it;
		KeyViewItem *i = new KeyViewItem(k.keyID(), lv_keys);
		//i->setPixmap(0, IconsetFactory::icon("psi/gpg-yes"));
		i->setText(0, k.keyID().right(8));
		i->setText(1, k.userID());

		if(!choose.isEmpty() && k.keyID() == choose) {
			lv_keys->setSelected(i, true);
			isel = i;
		}
	}
	if(lv_keys->childCount() > 0 && !isel)
		lv_keys->setSelected(lv_keys->firstChild(), true);
	else if(isel)
		lv_keys->ensureItemVisible(isel);
}

PGPKeyDlg::~PGPKeyDlg()
{
	delete d;
}

QString PGPKeyDlg::keyID() const
{
	return d->keyID;
}

QString PGPKeyDlg::userID() const
{
	return d->userID;
}

void PGPKeyDlg::qlv_doubleClicked(QListViewItem *i)
{
	lv_keys->setSelected(i, true);
	do_accept();
}

void PGPKeyDlg::do_accept()
{
	KeyViewItem *i = (KeyViewItem *)lv_keys->selectedItem();
	if(!i) {
		QMessageBox::information(this, tr("Error"), tr("Please select a key."));
		return;
	}
	d->keyID = i->keyID;
	d->userID = i->text(1);
	accept();
}

#include "psiaccount.moc"
