/*
 * psicon.cpp - core of Psi
 * Copyright (C) 2001, 2002  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.
 *
 * 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"psicon.h"

#include<qptrlist.h>
#include<qapplication.h>
#include<qdesktopwidget.h>
#include<qguardedptr.h>
#include<qiconset.h>
#include<qcolor.h>
#include<qimage.h>
#include<qstylesheet.h>
#include<qpixmapcache.h>

#include"s5b.h"
#include"psiaccount.h"
#include"contactview.h"
#include"common.h"
#include"mainwin.h"
#include"idle.h"
#include"accountdlg.h"
#include"statusdlg.h"
#include"options/optionsdlg.h"
#include"options/toolbardlg.h"
#include"groupchatdlg.h"
#include"userlist.h"
#include"eventdlg.h"
#include"eventdb.h"
#include"openpgp.h"
#include"gnupg.h"
#include"proxy.h"
#include"psipng.h"
#ifdef PSIMNG
#include"psimng.h"
#endif
#include"psitextview.h"
#include"alerticon.h"
#include"iconselect.h"
#include"psitoolbar.h"
#include"filetransfer.h"
#include"filetransdlg.h"
#include"psiactionlist.h"
#include"systemwatch.h"
#include"globalaccelman.h"
#include "tabdlg.h"
#include "chatdlg.h"

#ifdef Q_WS_MAC
#include "mac_dock.h"
#endif

//----------------------------------------------------------------------------
// PsiIconFactory
//----------------------------------------------------------------------------
class PsiIconFactory : public QIconFactory
{
public:
	PsiIconFactory();

	QPixmap *createPixmap(const QIconSet &, QIconSet::Size, QIconSet::Mode, QIconSet::State);
};

PsiIconFactory::PsiIconFactory()
{
	setAutoDelete(true);
}

QPixmap *PsiIconFactory::createPixmap(const QIconSet &iconSet, QIconSet::Size size, QIconSet::Mode mode, QIconSet::State state)
{
	Q_UNUSED(size);
	Q_UNUSED(mode);
	Q_UNUSED(state);

	QPixmap pix = iconSet.pixmap(size, QIconSet::Normal, state);
	if ( mode == QIconSet::Disabled && !pix.isNull() ) {
		QImage img = pix.convertToImage();
		if ( img.depth() != 32 )
			img = img.convertDepth(32);
		img.setAlphaBuffer(true);

		for (int x = 0; x < img.width(); x++) {
			for (int y = 0; y < img.height(); y++) {
				QRgb *rgb = (QRgb *)img.scanLine(y) + x;
				QColor col(*rgb);

				// bigger divisor will result in darker shades of gray
				int gray = (col.red() + col.green() + col.blue()) / 3;
				if ( gray > 0xFF )
					gray = 0xFF;
				int alpha = qAlpha(*rgb) /*+ 150*/;
				if ( alpha > 0xFF )
					alpha = 0xFF;
				*rgb = qRgba(gray, gray, gray, alpha);
			}
		}

		QPixmap *pix = new QPixmap(img);
		return pix;
	}

	return 0;
}

//----------------------------------------------------------------------------
// PsiConObject
//----------------------------------------------------------------------------
class PsiConObject : public QObject
{
	Q_OBJECT
public:
	PsiConObject(QObject *parent)
	: QObject(parent, "PsiConObject")
	{
		QImage::inputFormats(); // make sure, that the Qt built-in formats are initialized before our custom ones

		initPsiPngIO(); // our custom animation format
#ifdef PSIMNG
		initPsiMngIO();
#endif
		QIconFactory::installDefaultFactory(new PsiIconFactory);

		QDir p(g.pathHome);
		QDir v(g.pathHome + "/tmp-sounds");
		if(!v.exists())
			p.mkdir("tmp-sounds");
		Iconset::setSoundPrefs(v.absPath(), this, SLOT(playSound(QString)));
		URLLabel::connectOpenURL(this, SLOT(openURL(QString)));
		QStyleSheet::setDefaultSheet( PsiTextView::styleSheet() );
	}

	~PsiConObject()
	{
		// removing temp dirs
		QDir p(g.pathHome);
		QDir v(g.pathHome + "/tmp-sounds");
		folderRemove(v);
	}

public slots:
	void playSound(QString file)
	{
		if ( file.isEmpty() || !useSound )
			return;

		soundPlay(file);
	}

	void openURL(QString url)
	{
		::openURL(url);
	}

private:
	// ripped from profiles.cpp
	bool folderRemove(const QDir &_d)
	{
		QDir d = _d;

		QStringList entries = d.entryList();
		for(QStringList::Iterator it = entries.begin(); it != entries.end(); ++it) {
			if(*it == "." || *it == "..")
				continue;
			QFileInfo info(d, *it);
			if(info.isDir()) {
				if(!folderRemove(QDir(info.filePath())))
					return FALSE;
			}
			else {
				//printf("deleting [%s]\n", info.filePath().latin1());
				d.remove(info.fileName());
			}
		}
		QString name = d.dirName();
		if(!d.cdUp())
			return FALSE;
		//printf("removing folder [%s]\n", d.filePath(name).latin1());
		d.rmdir(name);

		return TRUE;
	}
};

//----------------------------------------------------------------------------
// AccountsComboBox
//----------------------------------------------------------------------------
AccountsComboBox::AccountsComboBox(PsiCon *_psi, QWidget *parent, bool online_only)
:QComboBox(parent), onlineOnly(online_only)
{
	psi = _psi;
	// TODO: Status changes of accounts should be notified when onlineOnly is true
	connect(psi, SIGNAL(accountCountChanged()), this, SLOT(updateAccounts()));
	connect(psi, SIGNAL(destroyed()), SLOT(deleteMe()));
	connect(this, SIGNAL(activated(int)), this, SLOT(changeAccount()));
	if (online_only)
		connect(psi, SIGNAL(accountActivityChanged()), this, SLOT(updateAccounts()));

	pa = 0;
	setSizePolicy(QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ));

	if(!psi->accountList(TRUE).isEmpty())
		setAccount(psi->accountList(TRUE).getFirst());
}

AccountsComboBox::~AccountsComboBox()
{
}

void AccountsComboBox::setAccount(PsiAccount *_pa)
{
	pa = _pa;
	updateAccounts();
}

void AccountsComboBox::changeAccount()
{
	int i = currentItem();

	PsiAccountListIt it(psi->accountList(TRUE));
	int n = 0;
	bool found = false;
	for(PsiAccount *p; (p = it.current()); ++it) {
		if (!onlineOnly || p->loggedIn()) {
			if(i == n) {
				pa = p;
				found = true;
				break;
			}
			++n;
		}
	}
	if(!found)
		pa = 0;

	activated(pa);
}

void AccountsComboBox::updateAccounts()
{
	clear();
	PsiAccountListIt it(psi->accountList(TRUE));
	int n = 0;
	bool found = false;
	PsiAccount *firstAccount = 0;
	for(PsiAccount *p; (p = it.current()); ++it) {
		if (!onlineOnly || p->loggedIn()) {
			insertItem(p->nameWithJid());
			if(p == pa) {
				setCurrentItem(n);
				found = true;
			}
			if (!firstAccount)
				firstAccount = p;
			++n;
		}
	}
	if(!found) {
		// choose a different account
		pa = firstAccount;
		activated(pa);
	}
}

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


//----------------------------------------------------------------------------
// PsiCon::Private
//----------------------------------------------------------------------------

struct item_dialog
{
	QWidget *widget;
	QString className;
};

class PsiCon::Private
{
public:
	Private() {
		iconSelect = 0;
		//the list 'owns' the tabs
		tabs.setAutoDelete( true );
		tabControlledChats.setAutoDelete( false );
	}
	~Private() {
		if ( iconSelect )
			delete iconSelect;
	}

	void saveProfile(UserAccountList acc = UserAccountList())
	{
		pro.recentGCList = recentGCList;
		pro.recentBrowseList = recentBrowseList;
		pro.lastStatusString = lastStatusString;
		pro.useSound = useSound;
		pro.prefs = option;
		if ( proxy )
			pro.proxyList = proxy->itemList();

		if ( acc.count() )
			pro.acc = acc;

		pro.toFile(pathToProfileConfig(activeProfile));
	}

	void updateIconSelect()
	{
		Iconset iss;
		QPtrListIterator<Iconset> iconsets(is->emoticons);
		Iconset *iconset;
		while ( (iconset = iconsets.current()) != 0 ) {
			iss += *iconset;

			++iconsets;
		}

		iconSelect->setIconset(iss);
	}

	PsiAccount *queueLowestEventId(bool includeDND)
	{
		PsiAccount *low = 0;
		int low_id = 0;
		int low_prior = option.EventPriorityDontCare;

		PsiAccountListIt it(listEnabled);
		for ( PsiAccount *pa; (pa = it.current()); ++it ) {
			int n = pa->eventQueue()->nextId();
			if ( n == -1 )
				continue;

			if ( !includeDND && pa->status().show() == "dnd" )
				continue;

			int p = pa->eventQueue()->peekNext()->priority();
			if ( !low || (n < low_id && p == low_prior) || p > low_prior ) {
				low = pa;
				low_id = n;
				low_prior = p;
			}
		}

		return low;
	}


	PsiAccountList list;
	PsiAccountList listEnabled;
	UserProfile pro;
	QString lastStatusString;
	MainWin *mainwin;
	Idle idle;
	QPtrList<item_dialog> dialogList;
	QPtrList<TabDlg> tabs;
	QPtrList<ChatDlg> tabControlledChats;
	int eventId;
	QStringList recentGCList, recentBrowseList, recentNodeList;
	EDB *edb;
	OpenPGP::Engine *pgp;
	bool pgpReady;
	S5BServer *s5bServer;
	ProxyManager *proxy;
	IconSelectPopup *iconSelect;
	QRect mwgeom;
	FileTransDlg *ftwin;
	PsiActionList *actionList;
	GlobalAccelManager *globalAccelManager;
};

//----------------------------------------------------------------------------
// PsiCon
//----------------------------------------------------------------------------

PsiCon::PsiCon()
:QObject(0)
{
	//pdb(DEBUG_JABCON, QString("%1 v%2\n By Justin Karneges\n    infiniti@affinix.com\n\n").arg(PROG_NAME).arg(PROG_VERSION));

	d = new Private;

	d->lastStatusString = "";
	useSound = true;
	d->mainwin = 0;
	d->ftwin = 0;

	d->dialogList.setAutoDelete(true);

	d->eventId = 0;
	d->edb = new EDBFlatFile;

	d->pgp = 0;
	d->pgpReady = false;
	d->s5bServer = 0;
	d->proxy = 0;

	d->actionList = 0;
}

PsiCon::~PsiCon()
{
	deinit();

	delete d->actionList;
	delete d->pgp;
	delete d->edb;
	delete d;
}

bool PsiCon::init()
{
	// load the profile
	d->pro.reset();
	d->pro.fromFile(pathToProfileConfig(activeProfile));

	QDir profileDir( pathToProfile( activeProfile ) );
	profileDir.rmdir( "info" ); // remove unused dir

	d->recentGCList = d->pro.recentGCList;
	d->recentBrowseList = d->pro.recentBrowseList;
	d->lastStatusString = d->pro.lastStatusString;
	useSound = d->pro.useSound;

	option = d->pro.prefs;

	new PsiConObject(this);

	// first thing, try to load the iconset
	is = new PsiIconset();
	if( !is->loadAll() ) {
		//option.iconset = "stellar";
		//if(!is.load(option.iconset)) {
			QMessageBox::critical(0, tr("Error"), tr("Unable to load iconset!  Please make sure Psi is properly installed."));
			return false;
		//}
	}

	if ( !d->actionList )
		d->actionList = new PsiActionList( this );

	d->iconSelect = new IconSelectPopup(0);
	d->updateIconSelect();

	// setup the main window
	d->mainwin = new MainWin(option.alwaysOnTop, (option.useDock && option.dockToolMW), this, "psimain");
	d->mainwin->setUseDock(option.useDock);

	connect(d->mainwin, SIGNAL(closeProgram()), SLOT(closeProgram()));
	connect(d->mainwin, SIGNAL(changeProfile()), SLOT(changeProfile()));
	connect(d->mainwin, SIGNAL(doManageAccounts()), SLOT(doManageAccounts()));
	connect(d->mainwin, SIGNAL(doGroupChat()), SLOT(doGroupChat()));
	connect(d->mainwin, SIGNAL(blankMessage()), SLOT(doNewBlankMessage()));
	connect(d->mainwin, SIGNAL(statusChanged(int)), SLOT(statusMenuChanged(int)));
	connect(d->mainwin, SIGNAL(doOptions()), SLOT(doOptions()));
	connect(d->mainwin, SIGNAL(doToolbars()), SLOT(doToolbars()));
	connect(d->mainwin, SIGNAL(doFileTransDlg()), SLOT(doFileTransDlg()));
	connect(d->mainwin, SIGNAL(recvNextEvent()), SLOT(recvNextEvent()));
	connect(d->mainwin, SIGNAL(geomChanged(int, int, int, int)), SLOT(mainWinGeomChanged(int, int, int, int)));
	connect(this, SIGNAL(emitOptionsUpdate()), d->mainwin, SLOT(optionsUpdate()));

	connect(this, SIGNAL(emitOptionsUpdate()), d->mainwin->cvlist, SLOT(optionsUpdate()));

	// if the coordinates are out of the desktop bounds, reset to the top left
	QRect mwgeom = d->pro.mwgeom;
	QDesktopWidget *pdesktop = QApplication::desktop();
	int nscreen = pdesktop->screenNumber(mwgeom.topLeft());
	QRect r = pdesktop->screenGeometry(nscreen);

	int pad = 10;
	if((mwgeom.width() + pad * 2) > r.width())
		mwgeom.setWidth(r.width() - pad * 2);
	if((mwgeom.height() + pad * 2) > r.height())
		mwgeom.setHeight(r.height() - pad * 2);
	if(mwgeom.left() < r.left())
		mwgeom.moveLeft(r.left());
	if(mwgeom.right() >= r.right())
		mwgeom.moveRight(r.right() - 1);
	if(mwgeom.top() < r.top())
		mwgeom.moveTop(r.top());
	if(mwgeom.bottom() >= r.bottom())
		mwgeom.moveBottom(r.bottom() - 1);

	d->mwgeom = mwgeom;
	d->mainwin->move(mwgeom.x(), mwgeom.y());
	d->mainwin->resize(mwgeom.width(), mwgeom.height());

	d->ftwin = new FileTransDlg(this);

	if(!(option.useDock && option.dockHideMW))
		d->mainwin->show();

	d->idle.start();

	// S5B
	d->s5bServer = new S5BServer;
	s5b_init();

	// pgp
	if(use_gpg) {
		option.pgp = "gpg";
		pgp_init(option.pgp);
	}

	// proxy
	d->proxy = new ProxyManager(this);
	d->proxy->setItemList(d->pro.proxyList);
	connect(d->proxy, SIGNAL(settingsChanged()), SLOT(proxy_settingsChanged()));

	// load accounts
	loadAccounts(d->pro.acc);

	// Connect to the system monitor
	SystemWatch* sw = SystemWatch::instance();
	connect(sw, SIGNAL(sleep()), this, SLOT(doSleep()));
	connect(sw, SIGNAL(idleSleep()), this, SLOT(doSleep()));
	connect(sw, SIGNAL(wakeup()), this, SLOT(doWakeup()));

	// global accelerator manager
	d->globalAccelManager = new GlobalAccelManager;
	d->globalAccelManager->setAccel(option.globalAccels[0]);
	d->globalAccelManager->setAccel(option.globalAccels[1]);
	connect(d->globalAccelManager, SIGNAL(activated(int)), SLOT(accel_activated(int)));

	return true;
}

void PsiCon::deinit()
{
	// this deletes all dialogs except for mainwin
	deleteAllDialogs();

	d->idle.stop();

	// shut down all accounts
	UserAccountList acc = unloadAccounts();

	// delete s5b server
	delete d->s5bServer;

	delete d->ftwin;

	if(d->mainwin) {
		// shut down mainwin
		QRect mwgeom;
		if ( !d->mainwin->isHidden() && !d->mainwin->isMinimized() ) {
			mwgeom.setX(d->mainwin->x());
			mwgeom.setY(d->mainwin->y());
			mwgeom.setWidth(d->mainwin->width());
			mwgeom.setHeight(d->mainwin->height());
		}
		else
			mwgeom = d->mwgeom;

		delete d->mainwin;
		d->mainwin = 0;

		d->pro.mwgeom = mwgeom;
	}

	// unload iconset
	delete is;

	// save profile
	d->saveProfile(acc);
}

PsiAccount *PsiCon::loadAccount(const UserAccount &acc)
{
	PsiAccount *pa = new PsiAccount(acc, this);
	connect(&d->idle, SIGNAL(secondsIdle(int)), pa, SLOT(secondsIdle(int)));
	connect(pa, SIGNAL(updatedActivity()), SLOT(pa_updatedActivity()));
	connect(pa, SIGNAL(updatedAccount()), SLOT(pa_updatedAccount()));
	connect(pa, SIGNAL(queueChanged()), SLOT(queueChanged()));
	connect(pa, SIGNAL(enabledChanged()), SIGNAL(accountCountChanged()));
	accountAdded(pa);
	if(d->s5bServer)
		pa->client()->s5bManager()->setServer(d->s5bServer);
	return pa;
}

void PsiCon::loadAccounts(const UserAccountList &list)
{
	if(list.count() > 0) {
		for(UserAccountList::ConstIterator it = list.begin(); it != list.end(); ++it)
			loadAccount(*it);
	}
	else {
		// if there are no accounts, then prompt the user to add one
		AccountAddDlg *w = new AccountAddDlg(this, 0);
		w->show();
	}
}

UserAccountList PsiCon::unloadAccounts()
{
	UserAccountList acc;

	QPtrListIterator<PsiAccount> it(d->list);
	for(PsiAccount *pa; (pa = it.current());) {
		acc += pa->userAccount();
		delete pa;
	}

	return acc;
}

const PsiAccountList & PsiCon::accountList(bool enabledOnly) const
{
	if(enabledOnly)
		return d->listEnabled;
	else
		return d->list;
}

ContactView *PsiCon::contactView() const
{
	if(d->mainwin)
		return d->mainwin->cvlist;
	else
		return 0;
}

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

OpenPGP::Engine *PsiCon::pgp() const
{
	if(d->pgp && d->pgpReady)
		return d->pgp;
	else
		return 0;
}

ProxyManager *PsiCon::proxy() const
{
	return d->proxy;
}

FileTransDlg *PsiCon::ftdlg() const
{
	return d->ftwin;
}

void PsiCon::link(PsiAccount *pa)
{
	d->list.append(pa);
	if(pa->enabled() && !d->listEnabled.containsRef(pa))
		d->listEnabled.append(pa);
	connect(pa, SIGNAL(updatedActivity()), this, SIGNAL(accountActivityChanged()));
	accountCountChanged();
}

void PsiCon::unlink(PsiAccount *pa)
{
	disconnect(pa, SIGNAL(updatedActivity()), this, SIGNAL(accountActivityChanged()));
	d->list.remove(pa);
	d->listEnabled.remove(pa);
	accountCountChanged();
}

void PsiCon::closeProgram()
{
	quit(QuitProgram);
}

void PsiCon::changeProfile()
{
	bool ok = true;
	PsiAccountListIt it(d->listEnabled);
	for(PsiAccount *pa; (pa = it.current()); ++it) {
		if(pa->isActive()) {
			ok = false;
			break;
		}
	}

	if(!ok) {
		QMessageBox::information(0, CAP(tr("Error")), tr("Please disconnect before changing the profile."));
		return;
	}

	quit(QuitProfile);
}

void PsiCon::doManageAccounts()
{
	AccountManageDlg *w = (AccountManageDlg *)dialogFind("AccountManageDlg");
	if(w)
		bringToFront(w);
	else {
		w = new AccountManageDlg(this);
		w->show();
	}
}

void PsiCon::doGroupChat()
{
	PsiAccount *pa = d->listEnabled.getFirst();
	if(!pa)
		return;

	GCJoinDlg *w = new GCJoinDlg(this, pa);
	w->show();
}

void PsiCon::doNewBlankMessage()
{
	PsiAccount *pa = d->listEnabled.getFirst();
	if(!pa)
		return;

	EventDlg *w = createEventDlg("", pa);
	w->show();
}

EventDlg *PsiCon::createEventDlg(const QString &to, PsiAccount *pa)
{
	EventDlg *w = new EventDlg(to, this, pa);
	connect(this, SIGNAL(emitOptionsUpdate()), w, SLOT(optionsUpdate()));
	return w;
}

void PsiCon::updateContactGlobal(PsiAccount *pa, const Jid &j)
{
	QPtrListIterator<item_dialog> it(d->dialogList);
	for(item_dialog *i; (i = it.current()); ++it) {
		if(i->className == "EventDlg") {
			EventDlg *e = (EventDlg *)i->widget;
			if(e->psiAccount() == pa)
				e->updateContact(j);
		}
	}
}

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

TabDlg* PsiCon::newTabs()
{
	TabDlg *tab;
	tab=new TabDlg(this);
	d->tabs.append(tab);
	connect (tab, SIGNAL ( isDying(TabDlg*) ), SLOT ( tabDying(TabDlg*) ) );
	return tab;
}

TabDlg* PsiCon::getTabs()
{
	if (!d->tabs.isEmpty())
	{
		return d->tabs.getFirst();
	}
	else
	{
		return newTabs();
	}
}

void PsiCon::tabDying(TabDlg* tab)
{
	d->tabs.remove(tab);
}

bool PsiCon::isChatTabbed(ChatDlg* chat)
{
	for (uint i = 0; i < d->tabs.count(); ++i)
	{
		if ( d->tabs.at(i)->managesChat(chat) )
				return true;
	}
	return false;
}

ChatDlg* PsiCon::getChatInTabs(QString jid){
	for (uint i = 0; i < d->tabs.count(); ++i)
	{
		if ( d->tabs.at(i)->getChatPointer(jid) )
				return d->tabs.at(i)->getChatPointer(jid);
	}
	return NULL;

}

TabDlg* PsiCon::getManagingTabs(ChatDlg* chat)
{
	for (uint i = 0; i < d->tabs.count(); ++i)
	{
		if ( d->tabs.at(i)->managesChat(chat) )
				return d->tabs.at(i);
	}
	return NULL;

}

QPtrList<TabDlg>* PsiCon::getTabSets()
{
	return &d->tabs;
}

bool PsiCon::isChatActiveWindow(ChatDlg* chat)
{
	//returns true if chat is on top of a tab pile
	if ( chat->isHidden() )
	{
		return false;
	}
	if (!option.useTabs)
	{
		return chat->isActiveWindow();
	}
	for (uint i = 0; i < d->tabs.count(); ++i)
	{
		if ( d->tabs.at(i)->isActiveWindow() )
		{
			if ( d->tabs.at(i)->chatOnTop( chat ) )
			{
				return true;
			}
		}
	}
	return false;
}

void PsiCon::dialogRegister(QWidget *w)
{
	item_dialog *i = new item_dialog;
	i->widget = w;
	i->className = w->className();
	d->dialogList.append(i);
}

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

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

AccountsComboBox *PsiCon::accountsComboBox(QWidget *parent, bool online_only)
{
	AccountsComboBox *acb = new AccountsComboBox(this, parent, online_only);
	return acb;
}

bool PsiCon::isValid(PsiAccount *pa)
{
	if(d->list.findRef(pa) == -1)
		return false;
	else
		return true;
}

void PsiCon::createAccount(const QString &name, const Jid &j, const QString &pass, bool opt_host, const QString &host, int port, bool ssl, int proxy)
{
	UserAccount acc;
	acc.name = name;

	acc.jid = j.full();
	if(!pass.isEmpty()) { 
		acc.opt_pass = true;
		acc.pass = pass;
	}

	acc.opt_host = opt_host;
	acc.host = host;
	acc.port = port;

	acc.opt_ssl = ssl;
	acc.proxy_index = proxy;

	PsiAccount *pa = loadAccount(acc);
	saveAccounts();

	// pop up the modify dialog so the user can customize the new account
	pa->modify();
}

void PsiCon::modifyAccount(PsiAccount *pa)
{
	pa->modify();
}

void PsiCon::removeAccount(PsiAccount *pa)
{
	accountRemoved(pa);
	pa->deleteQueueFile();
	delete pa;
	saveAccounts();
}

void PsiCon::enableAccount(PsiAccount *pa, bool e)
{
	if(e){
		if(!d->listEnabled.containsRef(pa))
			d->listEnabled.append(pa);
	}else
		d->listEnabled.remove(pa);
}

void PsiCon::statusMenuChanged(int x)
{
	if(x == STATUS_OFFLINE && !option.askOffline) {
		setGlobalStatus(Status("","Logged out",0,false));
		if(option.useDock == true)
			d->mainwin->setTrayToolTip(Status("","",0,false));
	}
	else {
		if(x == STATUS_ONLINE && !option.askOnline) {
			setGlobalStatus(Status());
			if(option.useDock == true)
				d->mainwin->setTrayToolTip(Status());
		}
		else if(x == STATUS_INVISIBLE){
			Status s("","",0,true);
			s.setIsInvisible(true);
			setGlobalStatus(s);
			if(option.useDock == true)
				d->mainwin->setTrayToolTip(s);
		}
		else {
			// Create a dialog with the last status message
			StatusSetDlg *w = new StatusSetDlg(this, makeStatus(x, d->lastStatusString));
			connect(w, SIGNAL(set(const Status &)), SLOT(setStatusFromDialog(const Status &)));
			connect(w, SIGNAL(cancelled()), SLOT(updateMainwinStatus()));
			if(option.useDock == true)
				connect(w, SIGNAL(set(const Status &)), d->mainwin, SLOT(setTrayToolTip(const Status &)));
			w->show();
		}
	}
}

void PsiCon::setStatusFromDialog(const Status &s)
{
	d->lastStatusString = s.status();
	setGlobalStatus(s);
}

void PsiCon::setGlobalStatus(const Status &s)
{
	// Check whether all accounts are logged off
	bool allOffline = true;
	PsiAccountListIt i(d->listEnabled);
	PsiAccount *pa;
	for( ; (pa = i.current()); ++i) {
		if ( pa->isActive() ) {
			allOffline = false;
			break;
		}
	}

	// globally set each account which is logged in
	PsiAccountListIt it(d->listEnabled);
	for( ; (pa = it.current()); ++it)
		if (allOffline || pa->isActive())
			pa->setStatus(s);
}

void PsiCon::pa_updatedActivity()
{
	PsiAccount *pa = (PsiAccount *)sender();
	accountUpdated(pa);

	// update s5b server
	updateS5BServerAddresses();

	updateMainwinStatus();
}

void PsiCon::pa_updatedAccount()
{
	PsiAccount *pa = (PsiAccount *)sender();
	accountUpdated(pa);

	saveAccounts();
}

void PsiCon::saveAccounts()
{
	UserAccountList acc;
	PsiAccountListIt it(d->list);
	for(PsiAccount *pa; (pa = it.current()); ++it)
		acc += pa->userAccount();

	d->pro.proxyList = d->proxy->itemList();
	//d->pro.acc = acc;
	//d->pro.toFile(pathToProfileConfig(activeProfile));
	d->saveProfile(acc);
}

void PsiCon::updateMainwinStatus()
{
	bool active = false;
	bool loggedIn = false;
	int state = STATUS_ONLINE;
	PsiAccountListIt it(d->listEnabled);
	for(PsiAccount *pa; (pa = it.current()); ++it) {
		if(pa->isActive())
			active = true;
		if(pa->loggedIn()) {
			loggedIn = true;
			state = makeSTATUS(pa->status());
		}
	}
	if(loggedIn)
		d->mainwin->decorateButton(state);
	else {
		if(active)
			d->mainwin->decorateButton(-1);
		else
			d->mainwin->decorateButton(STATUS_OFFLINE);
	}
}

void PsiCon::setToggles(bool tog_offline, bool tog_away, bool tog_agents, bool tog_hidden, bool tog_self)
{
	if(d->listEnabled.count() > 1)
		return;

	d->mainwin->cvlist->setShowOffline(tog_offline);
	d->mainwin->cvlist->setShowAway(tog_away);
	d->mainwin->cvlist->setShowAgents(tog_agents);
	d->mainwin->cvlist->setShowHidden(tog_hidden);
	d->mainwin->cvlist->setShowSelf(tog_self);
}

void PsiCon::getToggles(bool *tog_offline, bool *tog_away, bool *tog_agents, bool *tog_hidden, bool *tog_self)
{
	*tog_offline = d->mainwin->cvlist->isShowOffline();
	*tog_away = d->mainwin->cvlist->isShowAway();
	*tog_agents = d->mainwin->cvlist->isShowAgents();
	*tog_hidden = d->mainwin->cvlist->isShowHidden();
	*tog_self = d->mainwin->cvlist->isShowSelf();
}

void PsiCon::doOptions()
{
	OptionsDlg *w = (OptionsDlg *)dialogFind("OptionsDlg");
	if(w)
		bringToFront(w);
	else {
		w = new OptionsDlg(this, option);
		connect(w, SIGNAL(applyOptions(const Options &)), SLOT(slotApplyOptions(const Options &)));
		w->show();
	}
}

void PsiCon::doFileTransDlg()
{
	bringToFront(d->ftwin);
}

QWidget *PsiCon::doToolbars()
{
	ToolbarDlg *w = (ToolbarDlg *)dialogFind("ToolbarDlg");
	if (w)
		bringToFront(w);
	else {
		w = new ToolbarDlg(this);
		w->show();
	}

	return w;
}

void PsiCon::slotApplyOptions(const Options &opt)
{
	QString oldpgp = option.pgp;
	Options oldOpt = option;
	bool notifyRestart = true;

	option = opt;

	if ( option.useTabs != oldOpt.useTabs || opt.chatLineEdit != oldOpt.chatLineEdit ) {
		QMessageBox::information(0, tr("Information"), tr("Some of the options you changed will only have full effect upon restart."));
		notifyRestart = false;
	}

	// change icon set
	if ( option.systemIconset		!= oldOpt.systemIconset		||
	     option.emoticons			!= oldOpt.emoticons		||
	     option.defaultRosterIconset	!= oldOpt.defaultRosterIconset	||
	     option.serviceRosterIconset	!= oldOpt.serviceRosterIconset	||
	     option.customRosterIconset		!= oldOpt.customRosterIconset )
	{
		if ( notifyRestart && is->optionsChanged(&oldOpt) )
			QMessageBox::information(0, tr("Information"), tr("The complete iconset update will happen on next Psi start."));

		// update icon selector
		d->updateIconSelect();

		// flush the QPixmapCache
		QPixmapCache::clear();
	}

	if ( oldOpt.alertStyle != option.alertStyle )
		alertIconUpdateAlertStyle();

	mainWin()->buildToolbars();

	/*// change pgp engine
	if(option.pgp != oldpgp) {
		if(d->pgp) {
			delete d->pgp;
			d->pgp = 0;
			pgpToggled(false);
		}
		pgp_init(option.pgp);
	}*/

	// update s5b
	if(oldOpt.dtPort != option.dtPort)
		s5b_init();
	updateS5BServerAddresses();

	// mainwin stuff
	d->mainwin->setWindowOpts(option.alwaysOnTop, (option.useDock && option.dockToolMW));
	d->mainwin->setUseDock(option.useDock);

	// notify about options change
	emitOptionsUpdate();

	// save just the options
	//d->pro.prefs = option;
	//d->pro.toFile(pathToProfileConfig(activeProfile));
	d->saveProfile();
}

int PsiCon::getId()
{
	return d->eventId++;
}

int PsiCon::queueCount()
{
	int total = 0;
	PsiAccountListIt it(d->listEnabled);
	for(PsiAccount *pa; (pa = it.current()); ++it)
		total += pa->eventQueue()->count();
	return total;
}

PsiAccount *PsiCon::queueLowestEventId()
{
	PsiAccount *low = 0;

	low = d->queueLowestEventId( false ); // first try to get event from non-dnd account

	// if failed, then get the event from dnd account instead
	if ( !low )
		low = d->queueLowestEventId( true );

	return low;
}

void PsiCon::queueChanged()
{
	Icon *nextAnim = 0;
	int nextAmount = queueCount();
	PsiAccount *pa = queueLowestEventId();
	if(pa)
		nextAnim = is->event2icon(pa->eventQueue()->peekNext());

#ifdef Q_WS_MAC
	{
		// The Mac Dock icon

		// Update the event count
		if (nextAmount) 
			MacDock::overlay(QString::number(nextAmount));
		else 
			MacDock::overlay(QString::null);

		// Check if bouncing is necessary
		bool doBounce = false;
		PsiAccountListIt it(d->listEnabled);
		for(PsiAccount *pa; (pa = it.current()); ++it) {
			if ( pa->eventQueue()->count() && pa->status().show() != "dnd" ) {
				doBounce = true;
				break;
			}
		}

		// Bouncy bouncy
		if (doBounce) {
			if (option.bounceDock != Options::NoBounce) {
				MacDock::startBounce();
				if (option.bounceDock == Options::BounceOnce) 
					MacDock::stopBounce();
			}
		}
		else 
			MacDock::stopBounce();
	}
#endif

	d->mainwin->updateReadNext(nextAnim, nextAmount);
}

void PsiCon::recvNextEvent()
{
	/*printf("--- Queue Content: ---\n");
	PsiAccountListIt it(d->list);
	for(PsiAccount *pa; (pa = it.current()); ++it) {
		printf(" Account: [%s]\n", pa->name().latin1());
		pa->eventQueue()->printContent();
	}*/
	PsiAccount *pa = queueLowestEventId();
	if(pa)
		pa->openNextEvent();
}

void PsiCon::playSound(const QString &str)
{
	if(str.isEmpty() || !useSound)
		return;

	soundPlay(str);
}

void PsiCon::raiseMainwin()
{
	d->mainwin->showNoFocus();
}

const QStringList & PsiCon::recentGCList() const
{
	return d->recentGCList;
}

void PsiCon::recentGCAdd(const QString &str)
{
	// remove it if we have it
	for(QStringList::Iterator it = d->recentGCList.begin(); it != d->recentGCList.end(); ++it) {
		if(*it == str) {
			d->recentGCList.remove(it);
			break;
		}
	}

	// put it in the front
	d->recentGCList.prepend(str);

	// trim the list if bigger than 10
	while(d->recentGCList.count() > 10)
		d->recentGCList.remove(d->recentGCList.fromLast());
}

const QStringList & PsiCon::recentBrowseList() const
{
	return d->recentBrowseList;
}

void PsiCon::recentBrowseAdd(const QString &str)
{
	// remove it if we have it
	for(QStringList::Iterator it = d->recentBrowseList.begin(); it != d->recentBrowseList.end(); ++it) {
		if(*it == str) {
			d->recentBrowseList.remove(it);
			break;
		}
	}

	// put it in the front
	d->recentBrowseList.prepend(str);

	// trim the list if bigger than 10
	while(d->recentBrowseList.count() > 10)
		d->recentBrowseList.remove(d->recentBrowseList.fromLast());
}

const QStringList & PsiCon::recentNodeList() const
{
	return d->recentNodeList;
}

void PsiCon::recentNodeAdd(const QString &str)
{
	// remove it if we have it
	for(QStringList::Iterator it = d->recentNodeList.begin(); it != d->recentNodeList.end(); ++it) {
		if(*it == str) {
			d->recentNodeList.remove(it);
			break;
		}
	}

	// put it in the front
	d->recentNodeList.prepend(str);

	// trim the list if bigger than 10
	while(d->recentNodeList.count() > 10)
		d->recentNodeList.remove(d->recentNodeList.fromLast());
}

void PsiCon::pgp_init(const QString &id)
{
	if(id.isEmpty())
		return;

	d->pgp = OpenPGP::createEngine(id);
	if(d->pgp) {
		connect(d->pgp, SIGNAL(initFinished(bool, const QString &)), SLOT(pgp_initFinished(bool, const QString &)));
		connect(d->pgp, SIGNAL(keysUpdated()), SLOT(pgp_keysUpdated()));
		d->pgpReady = false;
		d->pgp->init();
	}
}

void PsiCon::pgp_initFinished(bool b, const QString &)
{
	if(b) {
		d->pgpReady = true;
		if(no_gpg_agent) {
			GnuPG *gpg = (GnuPG *)d->pgp;
			gpg->setTryAgent(false);
		}
		pgpToggled(true);
	}
}

void PsiCon::pgp_keysUpdated()
{
	pgpKeysUpdated();
	//QMessageBox::information(0, CAP(tr("OpenPGP")), tr("Psi has detected that you have modified your keyring.  That is all."));
}

void PsiCon::proxy_settingsChanged()
{
	// properly index accounts
	PsiAccountListIt it(d->list);
	for(PsiAccount *pa; (pa = it.current()); ++it) {
		UserAccount acc = pa->userAccount();
		if(acc.proxy_index > 0) {
			int x = d->proxy->findOldIndex(acc.proxy_index-1);
			if(x == -1)
				acc.proxy_index = 0;
			else
				acc.proxy_index = x+1;
			pa->setUserAccount(acc);
		}
	}

	saveAccounts();
}

void PsiCon::accel_activated(int x)
{
	switch (x) {
	case 1:
		recvNextEvent();
		break;
	case 2:
		d->mainwin->toggleVisible();
		break;
	}
}

MainWin *PsiCon::mainWin() const
{
	return d->mainwin;
}

IconSelectPopup *PsiCon::iconSelectPopup() const
{
	return d->iconSelect;
}

void PsiCon::processEvent(PsiEvent *e)
{
	if ( e->type() == PsiEvent::PGP ) {
		e->account()->eventQueue()->dequeue(e);
		e->account()->queueChanged();
		e->account()->promptPassphrase();
		return;
	}

	if ( !e->account() )
		return;

	UserListItem *u = e->account()->find(e->jid());
	if ( !u ) {
		qWarning("SYSTEM MESSAGE: Bug #1. Contact the developers and tell them what you did to make this message appear. Thank you.");
		e->account()->eventQueue()->dequeue(e);
		e->account()->queueChanged();
		return;
	}

	if( e->type() == PsiEvent::File ) {
		FileEvent *fe = (FileEvent *)e;
		FileTransfer *ft = fe->takeFileTransfer();
		e->account()->eventQueue()->dequeue(e);
		e->account()->queueChanged();
		e->account()->cpUpdate(*u);
		if(ft) {
			FileRequestDlg *w = new FileRequestDlg(fe->timeStamp(), ft, e->account());
			bringToFront(w);
		}
		return;
	}

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

	if ( isChat ) {
		if ( option.alertOpenChats && sentToChatWindow ) {
			// Message already displayed, need only to pop up chat dialog, so that
			// it will be read (or marked as read)
			ChatDlg *c = (ChatDlg *)e->account()->dialogFind("ChatDlg", e->from());
			if(!c)
				c = (ChatDlg *)e->account()->dialogFind("ChatDlg", e->jid());
			if(!c)
				return; // should never happen

			e->account()->processChats(e->from()); // this will delete all events, corresponding to that chat dialog
			//KIS: I changed the following line with the one following that to attempt to get tabs to behave correctly. Lots of this stuff isn't great.
			bringToFront((QWidget *)c);
			if ( option.useTabs)
			{
				TabDlg* tabSet = getManagingTabs(c);
				tabSet->selectTab(c);
			}
		}
		else
			e->account()->openChat(e->from());
	}
	else {
		// search for an already opened eventdlg
		EventDlg *w = (EventDlg *)e->account()->dialogFind("EventDlg", u->jid());

		if ( !w ) {
			// create the eventdlg
			w = e->account()->ensureEventDlg(u->jid());

			// load next message
			e->account()->processReadNext(*u);
		}

		bringToFront(w);
	}
}

void PsiCon::mainWinGeomChanged(int x, int y, int w, int h)
{
	d->mwgeom.setX(x);
	d->mwgeom.setY(y);
	d->mwgeom.setWidth(w);
	d->mwgeom.setHeight(h);
}

void PsiCon::updateS5BServerAddresses()
{
	if(!d->s5bServer)
		return;

	QValueList<QHostAddress> list;

	// grab all IP addresses
	QPtrListIterator<PsiAccount> it(d->list);
	for(PsiAccount *pa; (pa = it.current()); ++it) {
		QHostAddress *a = pa->localAddress();
		if(!a)
			continue;

		// don't take dups
		bool found = false;
		for(QValueList<QHostAddress>::ConstIterator hit = list.begin(); hit != list.end(); ++hit) {
			const QHostAddress &ha = *hit;
			if(ha == (*a)) {
				found = true;
				break;
			}
		}
		if(!found)
			list += (*a);
	}

	// convert to stringlist
	QStringList slist;
	for(QValueList<QHostAddress>::ConstIterator hit = list.begin(); hit != list.end(); ++hit)
		slist += (*hit).toString();

	// add external
	if(!option.dtExternal.isEmpty()) {
		bool found = false;
		for(QStringList::ConstIterator sit = slist.begin(); sit != slist.end(); ++sit) {
			const QString &s = *sit;
			if(s == option.dtExternal) {
				found = true;
				break;
			}
		}
		if(!found)
			slist += option.dtExternal;
	}

	// set up the server
	d->s5bServer->setHostList(slist);
}

void PsiCon::s5b_init()
{
	if(d->s5bServer->isActive())
		d->s5bServer->stop();

	if (option.dtPort) {
		if(!d->s5bServer->start(option.dtPort)) {
			QMessageBox::information(0, tr("Error"), tr("Unable to bind to port %1 for Data Transfer").arg(option.dtPort));
		}
	}
}

void PsiCon::doSleep()
{
	setGlobalStatus(Status("",tr("Computer went to sleep"),0,false));
}

void PsiCon::doWakeup()
{
	// TODO: Restore the status from before the log out. Make it an (hidden) option for people with a bad wireless connection.
	//setGlobalStatus(Status());
}


QPtrList<PsiToolBar> *PsiCon::toolbarList() const
{
	QPtrList<PsiToolBar> *list = new QPtrList<PsiToolBar>();

	QPtrListIterator<PsiToolBar> it ( mainWin()->toolbars );
	for ( ; it.current(); ++it )
		list->append( it.current() );

	return list;
}

PsiToolBar *PsiCon::findToolBar(QString group, int index)
{
	PsiToolBar *toolBar = 0;

	if ( group == "mainWin" )
		toolBar = mainWin()->toolbars.at(index);

	return toolBar;
}

PsiActionList *PsiCon::actionList() const
{
	return d->actionList;
}

#include "psicon.moc"
