#include <QtCore>
#include <QtGui>
#include <QSet>
#include <QMap>
#include <QButtonGroup>

#include "psiplugin.h"
#include "stanzafilter.h"
#include "stanzasender.h"
#include "stanzasendinghost.h"
#include "optionaccessor.h"
#include "optionaccessinghost.h"
#include "eventfilter.h"
#include "plugininfoprovider.h"


#define OPTION_TEMPLATE_MESSAGES "message"
#define OPTION_LAST_CHECK_TIME "ltime"
#define OPTION_LAST_TID "ltid"
#define OPTION_LAST_CHECK_TIME_KEYS "ltimekey"
#define OPTION_LAST_TID_KEYS "ltidkey"
#define OPTION_SHOW_ALL_MESSAGES "allunread"
#define OPTION_NOTIFICATION_ON "notifyon"
#define PLUGIN_VERSION "0.4.5"

class GmailNotifyPlugin : public QObject, public PsiPlugin, public EventFilter,
	public StanzaFilter, public StanzaSender, public OptionAccessor, public PluginInfoProvider
{
	Q_OBJECT
	Q_INTERFACES(PsiPlugin StanzaFilter StanzaSender EventFilter OptionAccessor PluginInfoProvider)
public:
	GmailNotifyPlugin();
	virtual QString name() const;
	virtual QString shortName() const;
	virtual QString version() const;
	virtual QWidget* options();
	virtual bool enable();
	virtual bool disable();
	// OptionAccessor
	virtual void setOptionAccessingHost(OptionAccessingHost* host);
	virtual void optionChanged(const QString& option);

	virtual void applyOptions();
	virtual void restoreOptions();

	virtual void setStanzaSendingHost(StanzaSendingHost *host);
	virtual bool incomingStanza(int account, const QDomElement& stanza);

	virtual bool processEvent(int account, QDomElement& e);
	virtual bool processMessage(int account, const QString& fromJid, const QString& body, const QString& subject);

	virtual QString pluginInfo();

private:
	bool enabled;
	StanzaSendingHost* stanzaSender;
	OptionAccessingHost* psiOptions;
	QHash<int,QString> accounts;
	QMap<QString,QString> lastCheck;
	QMap<QString,QString> lastTid;
	QString message;
	QTextEdit *editMessage;
	QRadioButton *notifyOnButton;
	QRadioButton *notifyOffButton;
	bool allUnread;
	QRadioButton *onlyNewMsg;
	QRadioButton *allUnreadMsg;
	bool notifyOn;

	void requestMail(int account, QString time = QString(), QString tid = QString());
	void updateNotifySettings(int account);
};

Q_EXPORT_PLUGIN(GmailNotifyPlugin);


GmailNotifyPlugin::GmailNotifyPlugin():enabled(false), allUnread(true) {
	message =	"From: %1 %2\n"\
				"Header: %3\n"\
				"Url: http://mail.google.com/mail/";
	editMessage = 0;
	onlyNewMsg = 0;
	allUnreadMsg = 0;
	notifyOn = true;
}

//PsiPlugin
QString GmailNotifyPlugin::name() const {
	return "GMail Notification Plugin";
}

QString GmailNotifyPlugin::shortName() const {
	return "gmailnotify";
}

QString GmailNotifyPlugin::version() const {
	return PLUGIN_VERSION;
}

QWidget* GmailNotifyPlugin::options() {
	if (!enabled) {
		return 0;
	}
	QWidget *optionsWid = new QWidget();

	editMessage= new QTextEdit(optionsWid);
	editMessage->setPlainText(message);
	QVBoxLayout *vbox= new QVBoxLayout(optionsWid);
	QHBoxLayout *hbox= new QHBoxLayout();
	QButtonGroup *notifyGroup = new QButtonGroup(optionsWid);
	QGroupBox *notifyBox = new QGroupBox(tr("Notification"));
	notifyOnButton = new QRadioButton(tr("On"),optionsWid);
	notifyOffButton = new QRadioButton(tr("Off"),optionsWid);
	if (notifyOn){
		notifyOnButton->setChecked(true);
	} else {
		notifyOffButton->setChecked(true);
	}
	notifyGroup->addButton(notifyOnButton);
	notifyGroup->addButton(notifyOffButton);
	QVBoxLayout *vNotifyBox= new QVBoxLayout();
	vNotifyBox->addWidget(notifyOnButton);
	vNotifyBox->addWidget(notifyOffButton);
	notifyBox->setLayout(vNotifyBox);
	hbox->addWidget(notifyBox);
	QButtonGroup *bgroup = new QButtonGroup(optionsWid);
	QGroupBox *msgBox = new QGroupBox(tr("Messages"));
	allUnreadMsg = new QRadioButton(tr("Show all unread messages"),optionsWid);
	onlyNewMsg = new QRadioButton(tr("Show new messages only"),optionsWid);
	if (allUnread){
		allUnreadMsg->setChecked(true);
	} else {
		onlyNewMsg->setChecked(true);
	}
	bgroup->addButton(allUnreadMsg);
	bgroup->addButton(onlyNewMsg);
	QVBoxLayout *vMsgBox = new QVBoxLayout();
	vMsgBox->addWidget(allUnreadMsg);
	vMsgBox->addWidget(onlyNewMsg);
	msgBox->setLayout(vMsgBox);
	hbox->addWidget(msgBox);
	QGridLayout *layout = new QGridLayout();
	layout->addWidget(new QLabel("Message:\n%1 - name\n%2 - e-mail\n%3 - message", optionsWid), 0, 0);
	layout->addWidget(editMessage, 0, 1);
	vbox->addLayout(hbox);
	vbox->addLayout(layout);
	QLabel *wikiLink = new QLabel(tr("<a href=\"http://code.google.com/p/psi-dev/"\
			"wiki/plugins#GMail_Notification_Plugin\">Wiki (Online)</a>"),optionsWid);
	wikiLink->setOpenExternalLinks(true);
	vbox->addWidget(wikiLink);
	return optionsWid;
}

bool GmailNotifyPlugin::enable() {
	enabled = true;
	//Read default values
	message = psiOptions->getPluginOption(OPTION_TEMPLATE_MESSAGES, QVariant(message)).toString();
	notifyOn = psiOptions->getPluginOption(OPTION_NOTIFICATION_ON, QVariant(notifyOn)).toBool();
	allUnread = psiOptions->getPluginOption(OPTION_SHOW_ALL_MESSAGES, QVariant(allUnread)).toBool();
	lastCheck.clear();
	lastTid.clear();
	if (!allUnread){
		QStringList ltimekey;
		ltimekey = psiOptions->getPluginOption(OPTION_LAST_CHECK_TIME_KEYS, QVariant(ltimekey)).toStringList();
		QStringList ltime;
		ltime = psiOptions->getPluginOption(OPTION_LAST_CHECK_TIME, QVariant(ltime)).toStringList();
		int key = 0;
		foreach(QString value,ltime){
			lastCheck.insert(ltimekey.at(key++),value);
		}
		QStringList ltid;
		ltid = psiOptions->getPluginOption(OPTION_LAST_TID, QVariant(ltid)).toStringList();
		QStringList ltidkey;
		ltidkey = psiOptions->getPluginOption(OPTION_LAST_TID_KEYS, QVariant(ltidkey)).toStringList();
		key = 0;
		foreach(QString value,ltid){
			lastTid.insert(ltidkey.at(key++),value);
		}
	}
	return true;
}

bool GmailNotifyPlugin::disable() {
	enabled = false;
	return true;
}

void GmailNotifyPlugin::setOptionAccessingHost(OptionAccessingHost* host) {
	psiOptions = host;
}

void GmailNotifyPlugin::optionChanged(const QString& option) {
	Q_UNUSED(option);
}

void GmailNotifyPlugin::applyOptions() {
	if (!editMessage && !onlyNewMsg && !allUnreadMsg) {
		return;
	}
	QVariant vMessage(editMessage->toPlainText());
	psiOptions->setPluginOption(OPTION_TEMPLATE_MESSAGES, vMessage);
	message = vMessage.toString();
	QVariant vRead(!onlyNewMsg->isChecked());
	psiOptions->setPluginOption(OPTION_SHOW_ALL_MESSAGES, vRead);
	allUnread = vRead.toBool();
	if (allUnread){
		lastCheck.clear();
		lastTid.clear();
	}
	QVariant vNotifyOn(notifyOnButton->isChecked());
	psiOptions->setPluginOption(OPTION_NOTIFICATION_ON, vNotifyOn);
	notifyOn = vNotifyOn.toBool();
	foreach(int account,accounts.keys()){
		updateNotifySettings(account);
		if (notifyOn == true){
			QString lastMailTime = lastCheck.value(QString::number(account),"");
			QString lastMailTid = lastTid.value(QString::number(account),"");
			requestMail(account, lastMailTime, lastMailTid);
		}
	}
}

void GmailNotifyPlugin::restoreOptions() {
	if (editMessage == 0 || onlyNewMsg == 0 || allUnreadMsg == 0) {
		return;
	}
	message = psiOptions->getPluginOption(OPTION_TEMPLATE_MESSAGES, QVariant(message)).toString();
	editMessage->setPlainText(message);
	notifyOn = psiOptions->getPluginOption(OPTION_NOTIFICATION_ON, QVariant(notifyOn)).toBool();
	notifyOnButton->setChecked(notifyOn);
	allUnread = psiOptions->getPluginOption(OPTION_SHOW_ALL_MESSAGES, QVariant(allUnread)).toBool();
	if (allUnread){
		allUnreadMsg->setChecked(true);
	}else{
		onlyNewMsg->setChecked(true);
	}
}

void GmailNotifyPlugin::setStanzaSendingHost(StanzaSendingHost *host) {
	stanzaSender = host;
}

bool GmailNotifyPlugin::incomingStanza(int account, const QDomElement& stanza) {
	bool blocked = false;
	if (true == enabled) {
		if (stanza.tagName() == "iq"){
			QDomNode n = stanza.firstChild();
			if (!n.isNull()){
				QDomElement i = n.toElement();
				if (stanza.attribute("type") == "result" && !i.isNull()
					&& i.tagName() == "query" && i.attribute("xmlns") == "http://jabber.org/protocol/disco#info"){
					for (QDomNode child = i.firstChild(); !child.isNull(); child = child.nextSibling()) {
						QDomElement feature = child.toElement();
						if (!feature.isNull() && feature.tagName()=="identity" && feature.attribute("category") != "server") break;
						//If the server supports the Gmail extension
						if( !feature.isNull() && feature.tagName()=="feature"
							&& feature.attribute("var") == "google:mail:notify" && feature.attribute("node") == ""){
							//get jid
							QString jid = stanza.attribute("to").split("/").at(0);
							accounts.insert(account, jid);
							//set "mailnotifications" to "true", the server will send notifications of unread email.
							updateNotifySettings(account);
							if (notifyOn == true){
								QString lastMailTime = lastCheck.value(QString::number(account),"");
								QString lastMailTid = lastTid.value(QString::number(account),"");
								//requests new mail
								requestMail(account, lastMailTime, lastMailTid);
							}
							break;
						}
					}
				} else if ((accounts.empty() || accounts.keys().contains(account)) && stanza.attribute("type") == "set"
							&& !i.isNull() && i.tagName() == "new-mail" && i.attribute("xmlns") == "google:mail:notify"){
					//Server reports new mail
					//send success result
					QString from = stanza.attribute("to");
					QString to = from.split("/").at(0);
					QString iqId = stanza.attribute("id");
					QString reply = QString("<iq type='result' from='%1' to='%2' id='%3' />").arg(from,to,iqId);
					stanzaSender->sendStanza(account, reply);
					QString lastMailTime = lastCheck.value(QString::number(account),"");
					QString lastMailTid = lastTid.value(QString::number(account),"");
					//requests new mail
					requestMail(account, lastMailTime, lastMailTid);
					//block stanza processing
					blocked = true;
				} else if(stanza.attribute("type") == "result" && !i.isNull()
						&& i.tagName() == "mailbox" && i.attribute("xmlns") == "google:mail:notify") {
					//Email Query Response
					lastCheck.insert(QString::number(account), i.attribute("result-time"));
					QString from = stanza.attribute("to");
					QDomElement lastmail = i.firstChildElement("mail-thread-info");
					if (!lastmail.isNull()){
						lastTid.insert(QString::number(account), lastmail.attribute("tid"));
					}
					//save last check values
					if (!allUnread){
						psiOptions->setPluginOption(OPTION_LAST_CHECK_TIME, QVariant(lastCheck.values()));
						psiOptions->setPluginOption(OPTION_LAST_TID, QVariant(lastTid.values()));
						psiOptions->setPluginOption(OPTION_LAST_CHECK_TIME_KEYS, QVariant(lastCheck.keys()));
						psiOptions->setPluginOption(OPTION_LAST_TID_KEYS, QVariant(lastTid.keys()));
					}
					for (QDomNode child = i.lastChild(); !child.isNull(); child = child.previousSibling()) {
						//invalid url
						QString url = child.toElement().attribute("url");
						QString subject = child.firstChildElement("subject").text();
						QString snippet = child.firstChildElement("snippet").text();
						QDomElement senders = child.firstChildElement("senders");
						QString name , email;
						for (QDomNode sender = senders.firstChild(); !sender.isNull(); sender = sender.nextSibling()){
							QDomElement adresser = sender.toElement();
							if (!adresser.isNull() && adresser.attribute("originator")=="1"){
								name = adresser.attribute("name");
								email = adresser.attribute("address");
							}
						}
						QString reply = QString("<message to=\"%1\" id=\"new-message-%2\" >"\
							"<subject>%3</subject><body>%4</body></message>")
							.arg(from,stanzaSender->uniqueId(account))
							.arg((subject=="")?"\n":subject)
							.arg(message.arg(name,email,snippet));
						stanzaSender->sendStanza(account, reply);
					}
				}
			}
		}
	}
	return blocked;
}

bool GmailNotifyPlugin::processEvent(int account, QDomElement& e) {
	if (enabled && accounts.keys().contains(account)) {
		QDomElement msg = e.lastChildElement();
		QString from = msg.attribute("from");
		QString to = msg.attribute("to");
		if ( to == from){
			from.replace(QRegExp("(.*)/.*"),"\\1/gmail");
			msg.setAttribute("from",from);
		}
	}
	return false;
}

bool GmailNotifyPlugin::processMessage(int account, const QString& fromJid, const QString& body, const QString& subject) {
	Q_UNUSED(account);
	Q_UNUSED(fromJid);
	Q_UNUSED(body);
	Q_UNUSED(subject);
	return false;
}

void GmailNotifyPlugin::requestMail(int account,QString time, QString tid) {
	if ("" != time){
		time = QString("newer-than-time='%1'").arg(time);
	}
	if ("" != tid){
		tid = QString("newer-than-tid='%1'").arg(tid);
	}
	QString reply = QString("<iq type='get' to='%1' id='mail-request-%4'>"\
				"<query xmlns='google:mail:notify' %2 %3/></iq>")
				.arg(accounts.value(account),time,tid,stanzaSender->uniqueId(account));
	stanzaSender->sendStanza(account, reply);
}

void GmailNotifyPlugin::updateNotifySettings(int account){
	QString reply = QString("<iq type=\"set\" to=\"%1\" id=\"%2\">"\
			"<usersetting xmlns=\"google:setting\">"\
			"<mailnotifications value=\"%3\"/></usersetting></iq>")
			.arg(accounts.value(account),stanzaSender->uniqueId(account))
			.arg(notifyOn?"true":"false");
	stanzaSender->sendStanza(account, reply);
}

QString GmailNotifyPlugin::pluginInfo() {
	return tr("Author: ") +  "VampiRUS\n"
			+ trUtf8("Shows notifications of new messages in your Gmailbox.\n"
			 "Note: The plugin only checks the root of your Inbox folder in your"
			 " Gmailbox for new messages. When using server side mail filtering, you may not be notified about all new messages.");
}

#include "gmailnotifyplugin.moc"
