/*
 * attentionplugin.cpp - plugin
 * Copyright (C) 2010  Khryukin Evgeny
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */

#include <QFileDialog>
#include <QDomElement>

#include "psiplugin.h"
#include "stanzafilter.h"
#include "accountinfoaccessor.h"
#include "accountinfoaccessinghost.h"
#include "optionaccessor.h"
#include "optionaccessinghost.h"
#include "activetabaccessinghost.h"
#include "activetabaccessor.h"
#include "stanzasender.h"
#include "stanzasendinghost.h"
#include "iconfactoryaccessor.h"
#include "iconfactoryaccessinghost.h"
#include "toolbariconaccessor.h"
#include "popupaccessor.h"
#include "popupaccessinghost.h"
#include "menuaccessor.h"
#include "applicationinfoaccessor.h"
#include "applicationinfoaccessinghost.h"
#include "plugininfoprovider.h"
#include "soundaccessinghost.h"
#include "soundaccessor.h"

#include "ui_options.h"

#define cVer "0.2.0"
#define constSoundFile "sndfl"
#define constInterval "intrvl"
#define constInfPopup "infPopup"
#define constTimeout "timeout"
#define constDisableDnd "dsbldnd"

#define POPUP_OPTION "Attention Plugin"

class AttentionPlugin: public QObject, public PsiPlugin, public StanzaFilter, public AccountInfoAccessor,
					   public OptionAccessor, public ActiveTabAccessor, public ToolbarIconAccessor, public ApplicationInfoAccessor,
					   public IconFactoryAccessor, public PopupAccessor, public StanzaSender, public MenuAccessor, public PluginInfoProvider,
					   public SoundAccessor
{
	Q_OBJECT
#ifdef HAVE_QT5
	Q_PLUGIN_METADATA(IID "com.psi-plus.AttentionPlugin")
#endif
	Q_INTERFACES(PsiPlugin StanzaFilter AccountInfoAccessor OptionAccessor ActiveTabAccessor ApplicationInfoAccessor
				 ToolbarIconAccessor IconFactoryAccessor PopupAccessor StanzaSender MenuAccessor PluginInfoProvider
				 SoundAccessor)

public:
	AttentionPlugin();
	virtual QString name() const;
	virtual QString shortName() const;
	virtual QString version() const;
	virtual QWidget* options();
	virtual bool enable();
	virtual bool disable();
	virtual void applyOptions();
	virtual void restoreOptions();
	virtual bool incomingStanza(int account, const QDomElement& xml);
	virtual bool outgoingStanza(int account, QDomElement& xml);
	virtual void setAccountInfoAccessingHost(AccountInfoAccessingHost* host);
	virtual void setOptionAccessingHost(OptionAccessingHost* host);
	virtual void optionChanged(const QString& option);
	virtual void setActiveTabAccessingHost(ActiveTabAccessingHost* host);
	virtual void setIconFactoryAccessingHost(IconFactoryAccessingHost* host);
	virtual void setPopupAccessingHost(PopupAccessingHost* host);
	virtual void setStanzaSendingHost(StanzaSendingHost *host);
	virtual QList < QVariantHash > getButtonParam();
	virtual QAction* getAction(QObject* , int , const QString& ) { return 0; };
	virtual QList < QVariantHash > getAccountMenuParam();
	virtual QList < QVariantHash > getContactMenuParam();
	virtual QAction* getContactAction(QObject* , int , const QString& ) { return 0; };
	virtual QAction* getAccountAction(QObject* , int ) { return 0; };
	virtual void setApplicationInfoAccessingHost(ApplicationInfoAccessingHost* host);
	virtual void setSoundAccessingHost(SoundAccessingHost* host);
	virtual QString pluginInfo();
	virtual QPixmap icon() const;

private:
	bool enabled;
	OptionAccessingHost* psiOptions;
	AccountInfoAccessingHost *accInfoHost;
	ActiveTabAccessingHost* activeTab;
	IconFactoryAccessingHost *icoHost;
	PopupAccessingHost* popup;
	StanzaSendingHost *stanzaSender;
	ApplicationInfoAccessingHost* appInfo;
	SoundAccessingHost* sound_;
	QString soundFile;
	int timeout_;
	bool infPopup, disableDnd;
	QTimer *nudgeTimer_;
	QPointer<QWidget> nudgeWindow_;
	QPoint oldPoint_;
	QPointer<QWidget> options_;
	int popupId;


	struct Blocked {
		int Acc;
		QString Jid;
		QDateTime LastMes;
	};
	QVector<Blocked> blockedJids_;

	Ui::Options ui_;


	enum { FakeAccount = 9999 };

	bool findAcc(int account, const QString& Jid, int &i);
	void sendAttention(int account, const QString& yourJid, const QString& jid);
	void nudge();
	void playSound(const QString& soundFile);
	void showPopup(int account, const QString& jid, const QString& text);

private slots:
	void checkSound();
	void getSound();
	void sendAttentionFromTab();
	void sendAttentionFromMenu();
	void nudgeTimerTimeout();
};

#ifndef HAVE_QT5
Q_EXPORT_PLUGIN(AttentionPlugin);
#endif

AttentionPlugin::AttentionPlugin()
	: enabled(false)
	, psiOptions(0)
	, accInfoHost(0)
	, activeTab(0)
	, icoHost(0)
	, popup(0)
	, stanzaSender(0)
	, appInfo(0)
	, sound_(0)
	, soundFile("sound/attention.wav")
	, timeout_(30)
	, infPopup(false)
	, disableDnd(false)
	, nudgeTimer_(0)
	, popupId(0)
{
}

QString AttentionPlugin::name() const {
	return "Attention Plugin";
}

QString AttentionPlugin::shortName() const {
	return "attention";
}

QString AttentionPlugin::version() const {
	return cVer;
}

bool AttentionPlugin::enable() {
	QFile file(":/attentionplugin/attention.png");
	if ( file.open(QIODevice::ReadOnly) ) {
		QByteArray image = file.readAll();
		icoHost->addIcon("attentionplugin/attention",image);
		file.close();
	} else {
		enabled = false;
		return enabled;
	}
	if(psiOptions) {
		blockedJids_.clear();
		enabled = true;
		soundFile = psiOptions->getPluginOption(constSoundFile, QVariant(soundFile)).toString();
		timeout_ = psiOptions->getPluginOption(constTimeout, QVariant(timeout_)).toInt();
		infPopup = psiOptions->getPluginOption(constInfPopup, QVariant(infPopup)).toBool();
		disableDnd = psiOptions->getPluginOption(constDisableDnd, QVariant(disableDnd)).toBool();
		popupId = popup->registerOption(POPUP_OPTION,  psiOptions->getPluginOption(constInterval, QVariant(4000)).toInt()/1000,
										"plugins.options."+shortName()+"."+constInterval);

		QWidgetList wl = qApp->allWidgets();
		foreach(QWidget *w, wl) {
			if(w->objectName() == "MainWin") {
				nudgeWindow_ = w;
				break;
			}
		}
		nudgeTimer_ = new QTimer(this);
		nudgeTimer_->setInterval(50);
		connect(nudgeTimer_, SIGNAL(timeout()), SLOT(nudgeTimerTimeout()));
	}
	return enabled;
}

bool AttentionPlugin::disable() {
	enabled = false;
	nudgeTimer_->stop();
	delete nudgeTimer_;
	nudgeTimer_ = 0;
	popup->unregisterOption(POPUP_OPTION);
	return true;
}

QWidget* AttentionPlugin::options() {
	if(!enabled) {
		return 0;
	}
	options_ = new QWidget();
	ui_.setupUi(options_);

	ui_.tb_open->setIcon(icoHost->getIcon("psi/browse"));
	ui_.tb_test->setIcon(icoHost->getIcon("psi/play"));

	connect(ui_.tb_open, SIGNAL(clicked()), SLOT(getSound()));
	connect(ui_.tb_test, SIGNAL(clicked()), SLOT(checkSound()));

	restoreOptions();

	return options_;
}

bool AttentionPlugin::incomingStanza(int account, const QDomElement& stanza) {
	if (enabled) {
		if(stanza.tagName() == "message"
		   && stanza.attribute("type") == "headline"
		   && !stanza.firstChildElement("attention").isNull()) {

			if(disableDnd && accInfoHost->getStatus(account) == "dnd")
				return false;

			QString from = stanza.attribute("from");

			int i = blockedJids_.size();
			if(findAcc(account, from, i)) {
				Blocked &B = blockedJids_[i];
				if(QDateTime::currentDateTime().secsTo(B.LastMes) > -timeout_) {
					return false;
				} else {
					B.LastMes = QDateTime::currentDateTime();
				}
			} else {
				Blocked B = { account, from, QDateTime::currentDateTime() };
				blockedJids_ << B;
			}

			const QString optAway = "options.ui.notifications.passive-popups.suppress-while-away";
			QVariant suppressAway = psiOptions->getGlobalOption(optAway);
			const QString optDnd = "options.ui.notifications.passive-popups.suppress-while-dnd";
			QVariant suppressDnd = psiOptions->getGlobalOption(optDnd);
			int interval = popup->popupDuration(POPUP_OPTION);
			if(infPopup && (accInfoHost->getStatus(account) == "away" || accInfoHost->getStatus(account) == "xa")) {
				psiOptions->setGlobalOption(optAway, false);
				popup->setPopupDuration(POPUP_OPTION, -1);
			}
			psiOptions->setGlobalOption(optDnd, disableDnd);

			showPopup(account, from.split("/").first(), from + tr(" sends Attention message to you!"));
			psiOptions->setGlobalOption(optAway, suppressAway);
			psiOptions->setGlobalOption(optDnd, suppressDnd);
			popup->setPopupDuration(POPUP_OPTION, interval);

			if(psiOptions->getGlobalOption("options.ui.notifications.sounds.enable").toBool())
				playSound(soundFile);

			/*QTextEdit *te = activeTab->getEditBox();
			  if(te)
			  nudgeWindow_ = te->window();

			  else
			  nudgeWindow_ = qApp->activeWindow();*/



			if(nudgeWindow_ && nudgeWindow_->isVisible())
				nudge();
		}

		else if(stanza.tagName() == "iq" && stanza.attribute("type") == "get")
		{
			QDomElement query = stanza.firstChildElement("query");
			if(!query.isNull() && query.attribute("xmlns") == "http://jabber.org/protocol/disco#info")
			{
				if(query.attribute("node") == "http://psi-dev.googlecode.com/caps#at-pl") {
					QString reply = QString("<iq type=\"result\" to=\"%1\" id=\"%2\">"
											"<query xmlns=\"http://jabber.org/protocol/disco#info\" node=\"http://psi-dev.googlecode.com/caps#at-pl\">"
											"<feature var=\"urn:xmpp:attention:0\"/></query></iq>")
					                        .arg(stanzaSender->escape(stanza.attribute("from")), stanzaSender->escape(stanza.attribute("id")));
					stanzaSender->sendStanza(account, reply);
					return true;
				}
			}
		}
	}
	return false;
}

bool AttentionPlugin::outgoingStanza(int /*account*/, QDomElement& xml) {
	if(enabled) {
		if(xml.tagName() == "iq" && xml.attribute("type") == "result")
		{
			QDomNodeList list = xml.elementsByTagNameNS("http://jabber.org/protocol/disco#info", "query");
			if(!list.isEmpty())
			{
				QDomElement query = list.at(0).toElement();
				if(!query.hasAttribute("node")) {
					QDomDocument doc = xml.ownerDocument();
					QDomElement feature = doc.createElement("feature");
					feature.setAttribute("var", "urn:xmpp:attention:0");
					query.appendChild(feature);
				}
			}
		}
		else if(xml.tagName() == "presence")
		{
			QDomNodeList list = xml.elementsByTagNameNS("http://jabber.org/protocol/caps", "c");
			if(!list.isEmpty())
			{
				QDomElement c = list.at(0).toElement();
				if(c.hasAttribute("ext")) {
					QString ext = c.attribute("ext");
					ext += " at-pl";
					c.setAttribute("ext", ext);
				}
			}
		}
	}
	return false;
}

void AttentionPlugin::applyOptions() {
	if (!options_)
		return;

	soundFile = ui_.le_sound->text();
	psiOptions->setPluginOption(constSoundFile,soundFile);

	timeout_ = ui_.sb_count->value();
	psiOptions->setPluginOption(constTimeout, timeout_);

	infPopup = ui_.cb_dontHide->isChecked();
	psiOptions->setPluginOption(constInfPopup, infPopup);

	disableDnd = ui_.cb_disableDND->isChecked();
	psiOptions->setPluginOption(constDisableDnd, disableDnd);
}

void AttentionPlugin::restoreOptions() {
	if (!options_)
		return;

	ui_.le_sound->setText(soundFile);
	ui_.sb_count->setValue(timeout_);
	ui_.cb_dontHide->setChecked(infPopup);
	ui_.cb_disableDND->setChecked(disableDnd);
}

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

void AttentionPlugin::setAccountInfoAccessingHost(AccountInfoAccessingHost* host) {
	accInfoHost = host;
}

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

void AttentionPlugin::setActiveTabAccessingHost(ActiveTabAccessingHost* host) {
	activeTab = host;
}

void AttentionPlugin::setIconFactoryAccessingHost(IconFactoryAccessingHost *host) {
	icoHost = host;
}

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

void AttentionPlugin::setPopupAccessingHost(PopupAccessingHost* host) {
	popup = host;
}

void AttentionPlugin::setApplicationInfoAccessingHost(ApplicationInfoAccessingHost* host) {
	appInfo = host;
}

void AttentionPlugin::setSoundAccessingHost(SoundAccessingHost *host) {
	sound_ = host;
}

QList < QVariantHash > AttentionPlugin::getButtonParam() {
	QList< QVariantHash >  l;
	QVariantHash hash;
	hash["tooltip"] = QVariant(tr("Send Attention"));
	hash["icon"] = QVariant(QString("attentionplugin/attention"));
	hash["reciver"] = qVariantFromValue(qobject_cast<QObject *>(this));
	hash["slot"] = QVariant(SLOT(sendAttentionFromTab()));
	l.push_back(hash);
	return l;
}

void AttentionPlugin::playSound(const QString& f) {
	sound_->playSound(f);
}

void AttentionPlugin::getSound() {
	QString fileName = QFileDialog::getOpenFileName(0,tr("Choose a sound file"),"", tr("Sound (*.wav)"));
	if(fileName.isEmpty())
		return;
	ui_.le_sound->setText(fileName);
}

void AttentionPlugin::checkSound() {
	playSound(ui_.le_sound->text());
}

void AttentionPlugin::showPopup(int account, const QString &jid, const QString &text) {
	if(account == FakeAccount) {
		popup->initPopup(text, tr("Attention Plugin"), "attentionplugin/attention", popupId);
	}
	else {
		popup->initPopupForJid(account, jid, text, tr("Attention Plugin"), "attentionplugin/attention", popupId);
	}
}

void AttentionPlugin::sendAttention(int account, const QString& yourJid, const QString& jid) {

	if(accInfoHost->getStatus(account) == "offline")
		return;

	QString msg = QString("<message from=\"%1\" to=\"%2\" type=\"headline\"><attention xmlns='urn:xmpp:attention:0'/></message>").arg(yourJid).arg(jid);
	stanzaSender->sendStanza(account, msg);

	showPopup(FakeAccount, QString(), tr("You sent Attention message to %1").arg(jid));
}

void AttentionPlugin::sendAttentionFromTab() {
	if(!enabled) return;
	QString yourJid = activeTab->getYourJid();
	QString jid = activeTab->getJid();
	QString tmpJid("");
	int account = 0;
	while (yourJid != (tmpJid = accInfoHost->getJid(account))){
		++account;
		if (tmpJid == "-1") return;
	}

	sendAttention(account, yourJid, jid);
}

void AttentionPlugin::sendAttentionFromMenu() {
	int acc = sender()->property("account").toInt();
	QString jid = sender()->property("jid").toString();
	QString yourJid = accInfoHost->getJid(acc);

	sendAttention(acc, yourJid, jid);
}

bool AttentionPlugin::findAcc(int account, const QString& Jid, int &i) {
	for(; i > 0;) {
		Blocked Block =	 blockedJids_[--i];
		if(Block.Acc == account && Block.Jid == Jid) {
			return true;
		}
	}
	return false;
}

QList < QVariantHash > AttentionPlugin::getAccountMenuParam() {
	return QList < QVariantHash >();
}

QList < QVariantHash > AttentionPlugin::getContactMenuParam() {
	QVariantHash hash;
	hash["icon"] = QVariant(QString("attentionplugin/attention"));
	hash["name"] = QVariant(tr("Send Attention"));
	hash["reciver"] = qVariantFromValue(qobject_cast<QObject *>(this));
	hash["slot"] = QVariant(SLOT(sendAttentionFromMenu()));
	QList< QVariantHash >  l;
	l.push_back(hash);
	return l;
}

void AttentionPlugin::nudge() {
	if(!nudgeWindow_ || !nudgeTimer_ || nudgeTimer_->isActive())
		return;

	oldPoint_ = nudgeWindow_->pos();
	nudgeTimer_->start();
}

void AttentionPlugin::nudgeTimerTimeout() {
	static uint count = 0;

	if(!nudgeWindow_) {
		nudgeTimer_->stop();
		count = 0;
		return;
	}
	if(count < 40) {
		int rH = qrand()%10, rW = qrand()%10;
		QPoint newPoint(oldPoint_.x()+rH, oldPoint_.y()+rW);
		nudgeWindow_->move(newPoint);
		count++;
	}
	else {
		count = 0;
		nudgeTimer_->stop();
		nudgeWindow_->move(oldPoint_);
	}
}

QString AttentionPlugin::pluginInfo() {
	return tr("Author: ") +	 "Dealer_WeARE\n"
	     + tr("Email: ") + "wadealer@gmail.com\n\n"
	     + trUtf8("This plugin is designed to send and receive special messages such as Attentions.\n"
			      "To work correctly, the plugin requires that the client of the other part supports XEP-0224 (for example: Pidgin, Miranda IM with Nudge plugin).");
}

QPixmap AttentionPlugin::icon() const
{
	return QPixmap(":/attentionplugin/attention.png");
}

#include "attentionplugin.moc"
