/*
 * chatdlg.cpp - dialog for handling chats
 * 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"chatdlg.h"

#include<qlabel.h>
#include<qcursor.h>
#include<qdragobject.h>
#include<qlineedit.h>
#include<qtoolbutton.h>
#include<qlayout.h>
#include<qsplitter.h>
#include<qtooltip.h>
#include<qtimer.h>
#include<qdatetime.h>
#include<qlcdnumber.h>
#include"profiles.h"
#include"psiaccount.h"
#include"common.h"
#include"userlist.h"
#include"iconwidget.h"
#include"fancylabel.h"
#include"msgmle.h"
#include"iconselect.h"
#include"psicon.h"
#include"psitoolbar.h"
#include"iconaction.h"
#include"avatars.h"
#include"tabdlg.h"

#ifdef Q_WS_WIN
#include<windows.h>
#endif

//----------------------------------------------------------------------------
// ChatDlg
//----------------------------------------------------------------------------
class ChatDlg::Private : public QObject
{
	Q_OBJECT
public:
	Private(ChatDlg *d) {
		dlg = d;
	}

	ChatDlg *dlg;
	Jid jid;
	PsiAccount *pa;
	QString dispNick;
	int status;
	QString statusString;

	ChatView *log;
	ChatEdit *mle;

#ifdef AVATARS
	QLabel *avatar;
#endif 

	QLabel *lb_ident;
	IconLabel *lb_status;
	QLineEdit *le_jid;
	QLCDNumber *lcd_count;
	QPopupMenu *pm_settings;

	QLabel *lb_composing;

	PsiToolBar *toolbar;
	IconAction *act_send, *act_clear, *act_history, *act_info, *act_pgp, *act_icon, *act_file, *act_compact;

	int pending;
	bool keepOpen, warnSend;

	QTimer *selfDestruct;

	QString key;
	int transid;
	Message m;
	bool lastWasEncrypted;
	bool smallChat;

	// Message Events
	QTimer *composingTimer;
	bool isComposing;
	bool contactIsComposing;
	bool sendComposingEvents;
	QString eventId;

signals:
	// Signals if user (re)started/stopped composing
	void composing(bool);

public slots:
	void addEmoticon(const Icon *icon) {
		if ( !dlg->isActiveWindow() )
		     return;
		
		QString text;

		QDict<QString> itext = icon->text();
		QDictIterator<QString> it ( itext );
		for ( ; it.current(); ++it) {
			if ( it.current() && !it.current()->isEmpty() ) {
				text = *(it.current()) + " ";
				break;
			}
		}

		if ( !text.isEmpty() )
			mle->insert( text );
	}

	void addEmoticon(QString text) {
		if ( !pa->psi()->isChatActiveWindow(dlg) )
		     return;

		mle->insert( text + " " );
	}

	void updateCounter() {
		lcd_count->display((int)mle->text().length());
	}

	// Records that the user is composing
	void setComposing() {
		if (!composingTimer) {
			/* User (re)starts composing */
			composingTimer = new QTimer(this);
			connect(composingTimer, SIGNAL(timeout()), SLOT(checkComposing()));
			composingTimer->start(2000); // FIXME: magic number
			emit composing(true);
		}
		isComposing = true;
	}

	// Checks if the user is still composing
	void checkComposing() {
		if (!isComposing) {
			// User stopped composing
			delete composingTimer;
			composingTimer = 0;
			emit composing(false);
		}
		isComposing = false; // Reset composing
	}
};

ChatDlg::ChatDlg(const Jid &jid, PsiAccount *pa)
{
	d = new Private(this);
	d->jid = jid;
	d->pa = pa;

	d->pending = 0;
	d->keepOpen = false;
	d->warnSend = false;
	d->selfDestruct = 0;
	d->transid = -1;
	d->key = "";
	d->lastWasEncrypted = false;

	setAcceptDrops(TRUE);

	QVBoxLayout *vb1 = new QVBoxLayout(this, 4);

	QWidget *sp;
	if ( !option.chatLineEdit ) {
		sp = new QSplitter(Vertical, this);
		vb1->addWidget(sp);
	}
	else
		sp = this;

	QWidget *sp_top = new QWidget(sp);
	sp_top->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
	if ( option.chatLineEdit )
		vb1->addWidget( sp_top );
	// first row
	QVBoxLayout *vb2 = new QVBoxLayout(sp_top, 0, 4);
	d->lb_ident = d->pa->accountLabel(sp_top, true);
	d->lb_ident->setSizePolicy(QSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum ));

	// second row
	QHBoxLayout *hb2 = new QHBoxLayout(vb2);
	d->lb_status = new IconLabel(sp_top);
	d->lb_status->setIcon(IconsetFactory::iconPtr("status/noauth"));
	hb2->addWidget(d->lb_status);
	d->le_jid = new QLineEdit(sp_top);
	d->le_jid->setReadOnly(true);
	d->lcd_count = new QLCDNumber(sp_top);
	d->le_jid->setFocusPolicy(QWidget::NoFocus);
	QToolTip::add(d->lcd_count, tr("Message length"));
	d->lcd_count->setFixedWidth(50);
	hb2->addWidget(d->le_jid);
	hb2->addWidget(d->lcd_count);
	hb2->addWidget(d->lb_ident);

	// mid area
	d->log = new ChatView(sp_top);
	vb2->addWidget(d->log);
#ifdef Q_WS_MAC
	connect(d->log,SIGNAL(selectionChanged()),SLOT(logSelectionChanged()));
#endif

	QWidget *sp_bottom = new QWidget(sp);
	sp_bottom->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Maximum );
	// tool area
	QVBoxLayout *vb3 = new QVBoxLayout(sp_bottom);
	QHBoxLayout *hb3 = new QHBoxLayout(vb3);

	d->toolbar = new PsiToolBar(tr("Chat toolbar"), 0, sp_bottom);
	d->toolbar->setCustomizeable( false ); // it isn't ready now, and we don't want segfaults
	d->toolbar->setFrameShape( QFrame::NoFrame );
	hb3->addWidget( d->toolbar );

	d->act_clear = new IconAction (tr("Clear chat window"), "psi/clearChat", tr("Clear chat window"), 0, this);
	connect( d->act_clear, SIGNAL( activated() ), SLOT( doClearButton() ) );

	connect(pa->psi()->iconSelectPopup(), SIGNAL(textSelected(QString)), d, SLOT(addEmoticon(QString)));
	d->act_icon = new IconAction( tr( "Select icon" ), "psi/smile", tr( "Select icon" ), 0, this );
	d->act_icon->setPopup( pa->psi()->iconSelectPopup() );

	d->act_file = new IconAction( tr( "Send file" ), "psi/upload", tr( "Send file" ), 0, this );
	connect( d->act_file, SIGNAL( activated() ), SLOT( doFile() ) );

	d->act_pgp = new IconAction( tr( "Toggle encryption" ), "psi/cryptoNo", tr( "Toggle encryption" ), 0, this, 0, true );

	d->act_info = new IconAction( tr( "User info" ), "psi/vCard", tr( "User info" ), CTRL+Key_I, this );
	connect( d->act_info, SIGNAL( activated() ), SLOT( doInfo() ) );

	d->act_history = new IconAction( tr( "Message history" ), "psi/history", tr( "Message history" ), CTRL+Key_H, this );
	connect( d->act_history, SIGNAL( activated() ), SLOT( doHistory() ) );
	
	d->act_compact = new IconAction( tr( "Toggle Compact/Full size" ), "psi/compact", tr( "Toggle Compact/Full size" ), 0, this );
	connect( d->act_compact, SIGNAL( activated() ), SLOT( toggleSmallChat() ) );

	d->act_clear->addTo( d->toolbar );
	d->toolbar->setStretchableWidget(new StretchWidget(d->toolbar));

	d->act_icon->addTo( d->toolbar );
	d->act_file->addTo( d->toolbar );
	d->act_pgp->addTo( d->toolbar );
	d->act_info->addTo( d->toolbar );
	d->act_history->addTo( d->toolbar );

	// Bottom but one row (composing notification)
	QHBoxLayout *hb_comp = new QHBoxLayout(vb3);
	d->lb_composing = new QLabel(sp_bottom,"composing");
	d->lb_composing->setPaletteBackgroundColor(Qt::yellow.light(190));
	hb_comp->addWidget(d->lb_composing);
	d->lb_composing->hide();

	// Bottom row
	QHBoxLayout *hb4 = new QHBoxLayout(vb3);

	// Text input
	if ( !option.chatLineEdit ) {
		d->mle = new ChatEdit(sp_bottom);
		hb4->addWidget(d->mle);
	}
	else {
		vb1->addWidget( sp_bottom );
		QHBoxLayout *hb5 = new QHBoxLayout( vb1 );
		d->mle = new LineEdit(sp);
#ifdef Q_WS_MAC
		hb5->addSpacing( 16 );
#endif
		hb5->addWidget( d->mle );
#ifdef Q_WS_MAC
		hb5->addSpacing( 16 );
#endif
	}

	connect(d->mle, SIGNAL(textChanged()), d, SLOT(updateCounter()));

	// Avatars
#ifdef AVATARS
	d->avatar = new QLabel(sp_bottom);
	hb4->addWidget(d->avatar);
	updateAvatar();
	// FIXME: Maybe the psiaccount should do this, to avoid a signal to each
	// open window when an avatar changes.
	connect(d->pa->avatarFactory(),SIGNAL(avatarChanged(const Jid&)), this, SLOT(updateAvatar(const Jid&)));
	connect(d->pa->psi(),SIGNAL(emitOptionsUpdate()), this, SLOT(updateAvatar()));
#endif

	d->pm_settings = new QPopupMenu(this);
	connect(d->pm_settings, SIGNAL(aboutToShow()), SLOT(buildMenu()));

	QValueList<int> list;
	list << 324;
	list << 96;
	if ( !option.chatLineEdit )
		((QSplitter *)sp)->setSizes(list);

	d->status = -1;
	d->pa->dialogRegister(this, d->jid);

	// Message events
	d->contactIsComposing = false;
	d->sendComposingEvents = false;
	d->isComposing = false;
	d->composingTimer = 0;
	connect(d->mle, SIGNAL(textChanged()), d, SLOT(setComposing()));
	connect(d, SIGNAL(composing(bool)), SLOT(updateIsComposing(bool)));

	updateContact(d->jid, true);

	d->smallChat = option.smallChats;
	X11WM_CLASS("chat");
	setLooks();

	updatePGP();
	connect(d->pa, SIGNAL(pgpKeyChanged()), SLOT(updatePGP()));
	connect(d->pa, SIGNAL(encryptedMessageSent(int, bool)), SLOT(encryptedMessageSent(int, bool)));
#ifdef Q_WS_X11
	d->le_jid->setFocusPolicy(QWidget::NoFocus);
	d->log->setFocusPolicy(QWidget::NoFocus);
#elif defined(Q_WS_MAC)
	d->log->setFocusPolicy(QWidget::NoFocus);
#endif
	d->mle->setFocus();
	resize(option.sizeChatDlg);

	UserListItem *u = d->pa->findFirstRelavent(d->jid);
	if(u && u->isSecure(d->jid.resource()))
		d->act_pgp->setOn(true);
}

ChatDlg::~ChatDlg()
{
	d->pa->dialogUnregister(this);

	delete d;
}

void ChatDlg::contextMenuEvent(QContextMenuEvent *)
{
	d->pm_settings->exec(QCursor::pos());
}

void ChatDlg::keyPressEvent(QKeyEvent *e)
{
	if(e->key() == Key_Escape && !option.useTabs)
		close();
//#ifdef Q_WS_MAC //this is a standard on other platforms too
	else if(e->key() == Key_W && e->state() & ControlButton && !option.useTabs)
		close();
//#endif
	else if(e->key() == Key_Return || e->key() == Key_Enter || (e->key() == Key_S && (e->state() & AltButton)))
		doSend();
	else if(e->key() == Key_PageUp && (e->state() & ShiftButton))
		d->log->setContentsPos(d->log->contentsX(), d->log->contentsY() - d->log->visibleHeight()/2);
	else if(e->key() == Key_PageDown && (e->state() & ShiftButton))
		d->log->setContentsPos(d->log->contentsX(), d->log->contentsY() + d->log->visibleHeight()/2);
	else
		e->ignore();
}

void ChatDlg::resizeEvent(QResizeEvent *e)
{
	if(option.keepSizes)
		option.sizeChatDlg = e->size();
}

void ChatDlg::closeEvent(QCloseEvent *e)
{
	// really lame way of checking if we are encrypting
	if(!d->mle->isEnabled())
		return;

	if(d->keepOpen) {
		int n = QMessageBox::information(this, tr("Warning"), tr("A new chat message was just received.\nDo you still want to close the window?"), tr("&Yes"), tr("&No"));
		if(n != 0)
			return;
	}

	// destroy the dialog if delChats is dcClose
	if(option.delChats == dcClose)
		setWFlags(getWFlags() | WDestructiveClose);
	else {
		if(option.delChats == dcHour)
			setSelfDestruct(60);
		else if(option.delChats == dcDay)
			setSelfDestruct(60 * 24);
	}

	// Reset 'contact is composing' & cancel own composing event
	updateContactIsComposing(false);
	if (d->composingTimer) {
		delete d->composingTimer;
		d->composingTimer = 0;
		d->isComposing = false;
		updateIsComposing(false);
	}


	if(d->pending > 0) {
		d->pending = 0;
		messagesRead(d->jid);
		updateCaption();
	}
	doFlash(false);

	d->mle->setFocus();
	e->accept();
}

void ChatDlg::showEvent(QShowEvent *)
{
	setSelfDestruct(0);
}

void ChatDlg::windowActivationChange(bool oldstate)
{
	QWidget::windowActivationChange(oldstate);

	// if we're bringing it to the front, get rid of the '*' if necessary
	if( isActiveWindow() && !isHidden() ) { //this is a tab hack
		activated();
	}
}

void ChatDlg::logSelectionChanged()
{
#ifdef Q_WS_MAC
	// A hack to only give the message log focus when text is selected
	if (d->log->hasSelectedText()) 
		d->log->setFocus();
	else 
		d->mle->setFocus();
#endif
}

void ChatDlg::activated()
{
	if(d->pending > 0) {
		d->pending = 0;
		messagesRead(d->jid);
		updateCaption();
	}
	doFlash(false);

	if(option.showCounter && !d->smallChat)
		d->lcd_count->show();
	else
		d->lcd_count->hide();

	d->mle->setFocus();
}


void ChatDlg::dropEvent(QDropEvent* event)
{
	QStringList l;
	if (d->pa->loggedIn() && QUriDrag::decodeLocalFiles(event,l) && !l.isEmpty())
		d->pa->actionSendFiles(d->jid,l);
}

void ChatDlg::dragEnterEvent(QDragEnterEvent* event)
{
	QStringList l;
	event->accept(d->pa->loggedIn() && QUriDrag::canDecode(event) && QUriDrag::decodeLocalFiles(event,l) && !l.isEmpty());
}


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

void ChatDlg::setJid(const Jid &jid)
{
	if(!jid.compare(d->jid)) {
		d->pa->dialogUnregister(this);
		d->jid = jid;
		d->pa->dialogRegister(this, d->jid);
		updateContact(d->jid, false);
	}
}

const QString& ChatDlg::getDisplayNick()
{
	return d->dispNick;
}

QSize ChatDlg::defaultSize()
{
	return QSize(320, 280);
}

void ChatDlg::updateContact(const Jid &jid, bool fromPresence)
{
	// if groupchat, only update if the resource matches
	if(d->pa->findGCContact(jid) && !d->jid.compare(jid))
		return;

	if(d->jid.compare(jid, false)) {
		QString rname = d->jid.resource();
		QPtrList<UserListItem> ul = d->pa->findRelavent(jid);
		UserListItem *u = 0;
		int status = -1;
		QString statusString, key;
		if(!ul.isEmpty()) {
			u = ul.first();
			if(rname.isEmpty()) {
				// use priority
				if(!u->isAvailable())
					status = STATUS_OFFLINE;
				else {
					const UserResource &r = *u->userResourceList().priority();
					status = makeSTATUS(r.status());
					statusString = r.status().status();
					key = r.publicKeyID();
				}
			}
			else {
				// use specific
				UserResourceList::ConstIterator rit = u->userResourceList().find(rname);
				if(rit != u->userResourceList().end()) {
					status = makeSTATUS((*rit).status());
					statusString = (*rit).status().status();
					key = (*rit).publicKeyID();
				}
				else {
					status = STATUS_OFFLINE;
					statusString = u->lastUnavailableStatus().status();
					key = "";
				}
			}
		}

		bool statusChanged = false;
		if(d->status != status || d->statusString != statusString) {
			statusChanged = true;
			d->status = status;
			d->statusString = statusString;
		}

		if(statusChanged) {
			if(status == -1 || !u)
				d->lb_status->setIcon(IconsetFactory::iconPtr("status/noauth"));
			else
				d->lb_status->setIcon(is->statusPtr(jid, status));
		}

		if(u)
			QToolTip::add(d->lb_status, u->makeTip(true, false));
		else
			QToolTip::remove(d->lb_status);

		if(u) {
			QString name;
			QString j;
			if(rname.isEmpty())
				j = u->jid().full();
			else
				j = u->jid().userHost() + '/' + rname;

			if(!u->name().isEmpty())
				name = u->name() + QString(" <%1>").arg(j);
			else
				name = j;

			d->le_jid->setText(name);
			d->le_jid->setCursorPosition(0);
			QToolTip::add(d->le_jid, name);

			d->dispNick = jidnick(u->jid().full(), u->name());
			updateCaption();

			d->key = key;
			updatePGP();

			if(fromPresence && statusChanged) {
				QString msg = tr("%1 is %2").arg(expandEntities(d->dispNick)).arg(status2txt(d->status));
				if(!statusString.isEmpty()) {
					QString ss = linkify(plain2rich(statusString));
					if(option.useEmoticons)
						ss = emoticonify(ss);

					msg += QString(" [%1]").arg(ss);
				}
				appendSysMsg(msg);
			}
		}

		// Reset 'is composing' event if the status changed
		if (statusChanged) {
			updateContactIsComposing(false);
		}
	}
}


void ChatDlg::updateAvatar(const Jid& j)
{
	if (j.compare(d->jid,false))
		updateAvatar();
}

void ChatDlg::updateAvatar()
{
#ifdef AVATARS
	QString res;
	QString client;

	if (option.chatLineEdit || !option.avatarsEnabled || !option.avatarsChatdlgEnabled) {
		d->avatar->hide();
		return;
	}

	UserListItem *ul = d->pa->findFirstRelavent(d->jid);
	if (ul && !ul->userResourceList().isEmpty()) {
		UserResourceList::Iterator it = ul->userResourceList().find(d->jid.resource());
		if(it == ul->userResourceList().end())
			it = ul->userResourceList().priority();

		res = (*it).name();
		client = (*it).clientName();
	}
	QPixmap p = d->pa->avatarFactory()->getAvatar(d->jid.withResource(res),client);
	if (p.isNull()) {
		d->avatar->hide();
	}
	else {
		d->avatar->setPixmap(p);
		d->avatar->show();
	}
#endif
}


void ChatDlg::setLooks()
{
	// update the font
	QFont f;
	f.fromString(option.font[fChat]);
	d->log->setFont(f);
	d->mle->setFont(f);

	if (d->smallChat) {
		d->lb_ident->hide();
		d->lb_status->hide();
		d->le_jid->hide();
		d->toolbar->hide();
	}
	else {
		d->lb_ident->show();
		d->lb_status->show();
		d->le_jid->show();
		d->toolbar->show();
	}

	if ( option.showCounter && !d->smallChat )
		d->lcd_count->show();
	else
		d->lcd_count->hide();

	// update contact info
	d->status = -2; // sick way of making it redraw the status
	updateContact(d->jid, false);

	// toolbuttons
	QIconSet i;
	i.setPixmap(IconsetFactory::icon("psi/cryptoNo"),  QIconSet::Automatic, QIconSet::Normal, QIconSet::Off);
	i.setPixmap(IconsetFactory::icon("psi/cryptoYes"), QIconSet::Automatic, QIconSet::Normal, QIconSet::On);
	d->act_pgp->setIcon( 0 );
	d->act_pgp->setIconSet( i );

	// update the widget icon
#ifndef Q_WS_MAC
	setIcon(IconsetFactory::icon("psi/start-chat"));
#endif

	/*QBrush brush;
	brush.setPixmap( QPixmap( option.chatBgImage ) );
	d->log->setPaper(brush);
	d->log->setStaticBackground(true);*/

#if QT_VERSION >= 0x030300
	setWindowOpacity(double(option.chatOpacity)/100);
#endif
}

void ChatDlg::optionsUpdate()
{
	if (option.oldSmallChats!=option.smallChats)
	{
		d->smallChat=option.smallChats;
	}

	setLooks();

	if(isHidden()) {
		if(option.delChats == dcClose) {
			deleteLater();
			return;
		}
		else {
			if(option.delChats == dcHour)
				setSelfDestruct(60);
			else if(option.delChats == dcDay)
				setSelfDestruct(60 * 24);
			else
				setSelfDestruct(0);
		}
	}
}

void ChatDlg::updatePGP()
{
	if(!d->pa->pgpKey().isEmpty()) {
		d->act_pgp->setEnabled(true);
	}
	else {
		d->act_pgp->setOn(false);
		d->act_pgp->setEnabled(false);
	}
}

void ChatDlg::doInfo()
{
	aInfo(d->jid);
}

void ChatDlg::doHistory()
{
	aHistory(d->jid);
}

void ChatDlg::doFile()
{
	aFile(d->jid);
}

void ChatDlg::doClearButton()
{
	int n = QMessageBox::information(this, tr("Warning"), tr("Are you sure you want to clear the chat window?\n(note: does not affect saved history)"), tr("&Yes"), tr("&No"));
	if(n == 0)
		doClear();
}

void ChatDlg::doClear()
{
	d->log->setText("");
}

void ChatDlg::setKeepOpenFalse()
{
	d->keepOpen = false;
}

void ChatDlg::setWarnSendFalse()
{
	d->warnSend = false;
}

void ChatDlg::setSelfDestruct(int minutes)
{
	if(minutes <= 0) {
		if(d->selfDestruct) {
			delete d->selfDestruct;
			d->selfDestruct = 0;
		}
		return;
	}

	if(!d->selfDestruct) {
		d->selfDestruct = new QTimer(this);
		connect(d->selfDestruct, SIGNAL(timeout()), SLOT(deleteLater()));
	}

	d->selfDestruct->start(minutes * 60000);
}

void ChatDlg::updateCaption()
{
	QString cap = "";

	if(d->pending > 0) {
		cap += "* ";
		if(d->pending > 1)
			cap += QString("[%1] ").arg(d->pending);
	}
	cap += d->dispNick;

	if (d->contactIsComposing)
		cap = tr("%1 (Composing ...)").arg(cap);

	setCaption(cap);

	emit captionChanged(this);
	emit unreadMessageUpdate(this, d->pending);
}

void ChatDlg::doSend()
{
	if(!d->mle->isEnabled())
		return;

	if(d->mle->text().isEmpty())
		return;

	if(d->mle->text() == "/clear") {
		d->mle->setText("");
		doClear();
		return;
	}

	if(!d->pa->loggedIn())
		return;

	if(d->warnSend) {
		d->warnSend = false;
		int n = QMessageBox::information(this, tr("Warning"), tr(
			"<p>Encryption was recently disabled by the remote contact.  "
			"Are you sure you want to send this message without encryption?</p>"
			), tr("&Yes"), tr("&No"));
		if(n != 0)
			return;
	}

	Message m(d->jid);
	m.setType("chat");
	m.setBody(d->mle->text());
	m.setTimeStamp(QDateTime::currentDateTime());
	if(d->act_pgp->isOn())
		m.setWasEncrypted(true);
	d->m = m;

 	// Request events
 	if (option.messageEvents) {
 		//m.addEvent(OfflineEvent);
 		//m.addEvent(DeliveredEvent);
 		//m.addEvent(DisplayedEvent);
 		m.addEvent(ComposingEvent);
 	}

	if(d->act_pgp->isOn()) {
		d->mle->setEnabled(false);
		d->transid = d->pa->sendMessageEncrypted(m);
		if(d->transid == -1) {
			d->mle->setEnabled(true);
			d->mle->setFocus();
			return;
		}
	}
	else {
		aSend(m);
		doneSend();
	}
}

void ChatDlg::doneSend()
{
	appendMessage(d->m, true);
	disconnect(d->mle, SIGNAL(textChanged()), d, SLOT(setComposing()));
	d->mle->setText("");

	// Reset composing timer
	connect(d->mle, SIGNAL(textChanged()), d, SLOT(setComposing()));
	if (d->composingTimer) {
		delete d->composingTimer;
		d->composingTimer = 0;
		d->isComposing = false;
	}
}

void ChatDlg::encryptedMessageSent(int x, bool b)
{
	if(d->transid == -1)
		return;
	if(d->transid != x)
		return;
	d->transid = -1;
	if(b)
		doneSend();
	else
		QMessageBox::information(this, tr("Error"), tr("There was an error trying to send the message encrypted.\nCheck your OpenPGP application/settings."));
	d->mle->setEnabled(true);
	d->mle->setFocus();
}

void ChatDlg::incomingMessage(const Message &m)
{
	if (m.body().isEmpty()) {
		/* Event message */
		if (m.containsEvent(CancelEvent))
			updateContactIsComposing(false);
		if (m.containsEvent(ComposingEvent))
			updateContactIsComposing(true);
	}
	else {
		// Normal message
		// Check if user requests event messages
		d->sendComposingEvents = m.containsEvent(ComposingEvent);
		if (!m.eventId().isEmpty())
			d->eventId = m.eventId();
		updateContactIsComposing(false);
		appendMessage(m);
	}
}

void ChatDlg::appendSysMsg(const QString &str)
{
	QString timestr;
	QDateTime time = QDateTime::currentDateTime();
	//timestr.sprintf("%02d:%02d:%02d", time.time().hour(), time.time().minute(), time.time().second());
	timestr = time.time().toString(LocalDate);

	int y = d->log->contentsHeight() - d->log->visibleHeight();
	if(y < 0)
		y = 0;
	bool atBottom = (d->log->contentsY() < y - 32) ? false: true;

	d->log->append(QString("<font color=\"#00A000\">[%1]").arg(timestr) + QString(" *** %1</font>").arg(str));
	if(atBottom)
		deferredScroll();
}

void ChatDlg::appendMessage(const Message &m, bool local)
{
	QString who, color;

	if(local) {
		who = d->pa->nick();
		color = "#FF0000";
	}
	else {
		who = d->dispNick;
		color = "#0000FF";
	}
	if(m.spooled())
		color = "#008000";

	// figure out the encryption state
	bool encChanged = false;
	bool encEnabled = false;
	if(d->lastWasEncrypted != m.wasEncrypted())
		encChanged = true;
	d->lastWasEncrypted = m.wasEncrypted();
	encEnabled = d->lastWasEncrypted;

	if(encChanged) {
		if(encEnabled) {
			appendSysMsg(QString("<icon name=\"psi/cryptoYes\"> ") + tr("Encryption Enabled"));
			if(!local)
				d->act_pgp->setOn(true);
		}
		else {
			appendSysMsg(QString("<icon name=\"psi/cryptoNo\"> ") + tr("Encryption Disabled"));
			if(!local) {
				d->act_pgp->setOn(false);

				// enable warning
				d->warnSend = true;
				QTimer::singleShot(3000, this, SLOT(setWarnSendFalse()));
			}
		}
	}

	QString timestr;
	QDateTime time = m.timeStamp();
	timestr = time.time().toString(LocalDate);

	int y = d->log->contentsHeight() - d->log->visibleHeight();
	if(y < 0)
		y = 0;
	bool atBottom = (d->log->contentsY() < y - 32) ? false: true;

	bool emote = false;
	if(m.body().left(4) == "/me ")
		emote = true;

	QString txt;
	if(emote)
		txt = plain2rich(m.body().mid(4));
	else
		txt = plain2rich(m.body());

	txt = linkify(txt);

	if(option.useEmoticons)
		txt = emoticonify(txt);

	who = expandEntities(who);

	if(emote) {
		d->log->append(QString("<font color=\"%1\">").arg(color) + QString("[%1]").arg(timestr) + QString(" *%1 ").arg(who) + txt + "</font>");
	}
	else {
		if(option.chatSays)
			d->log->append(QString("<font color=\"%1\">").arg(color) + QString("[%1] ").arg(timestr) + tr("%1 says:").arg(who) + "</font><br>" + txt);
		else
			d->log->append(QString("<font color=\"%1\">").arg(color) + QString("[%1] &lt;").arg(timestr) + who + QString("&gt;</font> ") + txt);
	}
	if(!m.subject().isEmpty()) {
		d->log->append(QString("<b>") + tr("Subject:") + "</b> " + QString("%1").arg(expandEntities(m.subject())));
	}
	if(!m.urlList().isEmpty()) {
		UrlList urls = m.urlList();
		d->log->append(QString("<i>") + tr("-- Attached URL(s) --") + "</i>");
		for(QValueList<Url>::ConstIterator it = urls.begin(); it != urls.end(); ++it) {
			const Url &u = *it;
			d->log->append(QString("<b>") + tr("URL:") + "</b> " + QString("%1").arg( linkify(expandEntities(u.url())) ));
			d->log->append(QString("<b>") + tr("Desc:") + "</b> " + QString("%1").arg(u.desc()));
		}
	}

	if(local || atBottom)
		deferredScroll();

	// if we're not active, notify the user by changing the title
	if(!isActiveWindow() || isHidden()) { //isHidden==tab hack
		++d->pending;
		updateCaption();
		doFlash(true);
		if(option.raiseChatWindow)
			bringToFront(this, false);
	}
	//else {
	//	messagesRead(d->jid);
	//}

	if(!local) {
		d->keepOpen = true;
		QTimer::singleShot(1000, this, SLOT(setKeepOpenFalse()));
	}
}

void ChatDlg::deferredScroll()
{
	QTimer::singleShot(250, this, SLOT(slotScroll()));
}

void ChatDlg::slotScroll()
{
	d->log->scrollToBottom();
}

void ChatDlg::updateIsComposing(bool c)
{
	if (option.messageEvents && d->sendComposingEvents) {
		// Don't send to offline resource
		QPtrList<UserListItem> ul = d->pa->findRelavent(d->jid);
		if(ul.isEmpty()) {
			d->sendComposingEvents = false;
			return;
		}

		QString rname = d->jid.resource();
		UserListItem *u = ul.first();
		if(rname.isEmpty() && !u->isAvailable() || u->userResourceList().find(rname) == u->userResourceList().end()) {
			d->sendComposingEvents = false;
			return;
		}

		// Send event message
		Message m(d->jid);
		m.setEventId(d->eventId);
		if (c) {
			m.addEvent(ComposingEvent);
		}
		else {
			m.addEvent(CancelEvent);
		}
		d->pa->dj_sendMessage(m, false);
	}
}

void ChatDlg::updateContactIsComposing(bool c)
{
	d->contactIsComposing = c;
	emit contactIsComposing(this, c);
	updateCaption();
	/*if (c)
	{
		d->lb_composing->setText(QString(d->dispNick+" "+tr("is composing a reply.")));
		d->lb_composing->show();
	}
	else
	{
		d->lb_composing->hide();
	}*/
}

void ChatDlg::toggleSmallChat()
{
	d->smallChat = !d->smallChat;
	setLooks();
}

void ChatDlg::toggleEncryption()
{
	d->act_pgp->setOn( !d->act_pgp->isOn() );
}

void ChatDlg::buildMenu()
{
	// Dialog menu
	d->pm_settings->clear();
	d->act_compact->addTo( d->pm_settings );
	d->act_clear->addTo( d->pm_settings );
	d->pm_settings->insertSeparator();

	d->act_icon->addTo( d->pm_settings );
	d->act_file->addTo( d->pm_settings );
	d->act_pgp->addTo( d->pm_settings );
	d->pm_settings->insertSeparator();

	d->act_info->addTo( d->pm_settings );
	d->act_history->addTo( d->pm_settings );
}

#include "chatdlg.moc"
