#include <QSpinBox>
#include <QPushButton>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QLabel>
#include <QFileDialog>
#include <QList>
#include <QSound>

#include "view.h"
#include "model.h"
#include "tooltip.h"

#include "psiplugin.h"
#include "stanzafilter.h"
#include "popupaccessor.h"
#include "popupaccessinghost.h"
#include "optionaccessor.h"
#include "optionaccessinghost.h"
#include "iconfactoryaccessor.h"
#include "iconfactoryaccessinghost.h"
#include "menuaccessor.h"
#include "applicationinfoaccessor.h"
#include "applicationinfoaccessinghost.h"
#include "plugininfoprovider.h"


#define constVersion "0.2.7"

#define constSoundFile "sndfl"
#define constInterval "intrvl"
#define constCount "count"
#define constSndFiles "sndfiles"
#define constJids "jids"

class Watcher : public QObject, public PsiPlugin, public PopupAccessor, public MenuAccessor, public PluginInfoProvider,
            public OptionAccessor, public StanzaFilter, public IconFactoryAccessor, public ApplicationInfoAccessor
{
        Q_OBJECT
	Q_INTERFACES(PsiPlugin PopupAccessor OptionAccessor StanzaFilter IconFactoryAccessor
		      PluginInfoProvider MenuAccessor ApplicationInfoAccessor)
public:
        Watcher();
        virtual QString name() const;
        virtual QString shortName() const;
        virtual QString version() const;
        virtual QWidget* options();
        virtual bool enable();
        virtual bool disable();
        virtual void optionChanged(const QString& option);
        virtual void applyOptions();
        virtual void restoreOptions();
        virtual void setPopupAccessingHost(PopupAccessingHost* host);
        virtual void setOptionAccessingHost(OptionAccessingHost* host);
        virtual bool incomingStanza(int account, const QDomElement& xml);
        virtual void setIconFactoryAccessingHost(IconFactoryAccessingHost* host);
        QList < QVariantHash >* getAccountMenuParam();
        QList < QVariantHash >* getContactMenuParam();
        virtual void setApplicationInfoAccessingHost(ApplicationInfoAccessingHost* host);
	virtual QString pluginInfo();

private:
        OptionAccessingHost *psiOptions;
        PopupAccessingHost* popup;
        IconFactoryAccessingHost* IcoHost;
        ApplicationInfoAccessingHost* AppInfoHost;
	bool enabled;
        QString SoundFile;
        QLineEdit *SoundFileWidget;
        void playSound(QString SoundFile);
        void showPopup(QString from, QString status);
        int Interval;
        QSpinBox *IntervalWidget;
        QPushButton *playButton, *getButton;
        QCheckBox *hack;

	QPointer<Viewer> tableView;
	Model *model_;

private slots:
        void checkSound(QModelIndex index = QModelIndex());
        void getSound(QModelIndex index = QModelIndex());
        void addLine();
        void delSelected();
        void Hack();
        void onOptionsClose();
        void addJidFromMenu();
        void checked(QString jid, bool check);
};

Q_EXPORT_PLUGIN(Watcher);

Watcher::Watcher() {
    psiOptions = 0;
    IcoHost = 0;
    popup = 0;
    AppInfoHost = 0;
    enabled = false;
    SoundFile = "sound/watcher.wav";
    SoundFileWidget = 0;
    Interval = 2;
    IntervalWidget = 0;
    hack = 0;

    tableView = 0;
    model_ = 0;
}

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

QString Watcher::shortName() const {
        return "watcher";
}

QString Watcher::version() const {
        return constVersion;
}

bool Watcher::enable(){
    if(psiOptions) {
        enabled = true;        
        SoundFile = psiOptions->getPluginOption(constSoundFile, QVariant(SoundFile)).toString();
        Interval = psiOptions->getPluginOption(constInterval, QVariant(Interval)).toInt();

        QStringList jids = psiOptions->getPluginOption(constJids, QVariant(QStringList())).toStringList();
        QStringList soundFiles = psiOptions->getPluginOption(constSndFiles, QVariant(QStringList())).toStringList();

        if(!model_) {
            model_ = new Model(jids, soundFiles, this);
            connect(model_, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(Hack()));
        }
    }

    return enabled;
}

bool Watcher::disable(){
	if(tableView)
		delete(tableView);
	enabled = false;
        return true;
}

QWidget* Watcher::options(){
        if (!enabled) {
		return 0;
	}
        QWidget *options = new QWidget();
	connect(options, SIGNAL(destroyed()), this, SLOT(onOptionsClose()));

        QVBoxLayout *vbox= new QVBoxLayout(options);

        hack = new QCheckBox(options);
        hack->setVisible(false);

        QPushButton *addButton = new QPushButton(IcoHost->getIcon("psi/addContact"), tr("Add row"));
        QPushButton *delButton = new QPushButton(IcoHost->getIcon("psi/remove"), tr("Delete selected"));
        connect(addButton, SIGNAL(released()), this, SLOT(addLine()));
        connect(delButton, SIGNAL(released()), this, SLOT(delSelected()));
        QHBoxLayout *butLayout = new QHBoxLayout;
        butLayout->addWidget(delButton);
        butLayout->addStretch();
        butLayout->addWidget(addButton);

        SoundFileWidget = new QLineEdit;
        SoundFileWidget->setText(SoundFile);        
        playButton = new QPushButton;
        playButton->setIcon(IcoHost->getIcon("psi/play"));
        getButton = new QPushButton;
        getButton->setIcon(IcoHost->getIcon("psi/browse"));
        playButton->setFixedSize(25,25);
        getButton->setFixedSize(25,25);
        QHBoxLayout *sndLayout = new QHBoxLayout;
        sndLayout->addWidget(new QLabel(tr("Default sound:")));
        sndLayout->addWidget(SoundFileWidget);
        sndLayout->addWidget(getButton);
        sndLayout->addWidget(playButton);

        IntervalWidget = new QSpinBox();
	IntervalWidget->setMinimum(-1);
        IntervalWidget->setValue(Interval);
        QHBoxLayout *IntervalLayout = new QHBoxLayout();
        IntervalLayout->addWidget(new QLabel(tr("Show popup")));
        IntervalLayout->addWidget(IntervalWidget);
	IntervalLayout->addWidget(new QLabel(tr("sec (0=disable, -1=infinite)")));
        IntervalLayout->addStretch();

        QLabel *wikiLink = new QLabel(tr("<a href=\"http://code.google.com/p/psi-dev/wiki/plugins#Watcher_Plugin\">Wiki (Online)</a>"));
        wikiLink->setOpenExternalLinks(true);

	tableView = new Viewer();
        tableView->setModel(model_);
	tableView->init(IcoHost);

        connect(tableView, SIGNAL(checkSound(QModelIndex)), this, SLOT(checkSound(QModelIndex)));
        connect(tableView, SIGNAL(getSound(QModelIndex)), this, SLOT(getSound(QModelIndex)));

        vbox->addWidget(tableView);
        vbox->addLayout(butLayout);
        vbox->addLayout(IntervalLayout);        
        vbox->addLayout(sndLayout);
        vbox->addWidget(wikiLink);

        connect(playButton, SIGNAL(pressed()), this, SLOT(checkSound()));
        connect(getButton, SIGNAL(pressed()), this, SLOT(getSound()));

        return options;
}

void Watcher::addLine() {
    model_->addRow();
    Hack(); //activate apply button
}

void Watcher::delSelected() {
    model_->deleteSelected();
    Hack(); //activate apply button
}

void Watcher::applyOptions() {
    if(!enabled || !SoundFileWidget || !IntervalWidget || !model_) return;

    QVariant vSoundFile(SoundFileWidget->text());
    psiOptions->setPluginOption(constSoundFile, vSoundFile);
    SoundFile = vSoundFile.toString();   

    QVariant vInterval(IntervalWidget->value());
    psiOptions->setPluginOption(constInterval, vInterval);
    Interval = vInterval.toInt();

    model_->apply();
    psiOptions->setPluginOption(constJids, QVariant(model_->getWatchedJids()));
    psiOptions->setPluginOption(constSndFiles, QVariant(model_->getSounds()));
}

void Watcher::restoreOptions() {
    if(!enabled || !SoundFileWidget || !IntervalWidget || !model_) return;

    SoundFileWidget->setText(psiOptions->getPluginOption(constSoundFile, QVariant(SoundFile)).toString());
    IntervalWidget->setValue(psiOptions->getPluginOption(constInterval, QVariant(Interval)).toInt());

    model_->reset();
}

bool Watcher::incomingStanza(int acc, const QDomElement &stanza) {
    Q_UNUSED(acc);
    if(enabled) {
	if(stanza.tagName() == "presence") {
	    if(stanza.attribute("type") == "error")
		return false;

	    QString from = stanza.attribute("from");
	    if(from.isEmpty())
		    return false;

	    bool find = false;
	    if(model_->getWatchedJids().contains(from, Qt::CaseInsensitive))
		    find = true;
	    else {
		    from = from.split("/").takeFirst();
		    if(model_->getWatchedJids().contains(from, Qt::CaseInsensitive))
			    find = true;
	    }
	    if(find) {
                QString status = stanza.firstChildElement("show").text();
                if(status.isEmpty()) {
                    if(stanza.attribute("type") == "unavailable") {
                        status = "offline";
                    } else {
                        status = "online";
                        if(model_->statusByJid(from) != status && psiOptions->getGlobalOption("options.ui.notifications.sounds.enable").toBool()) {
                            QString snd = model_->soundByJid(from);
                            if(!QFile::exists(snd))
                                snd = SoundFile;
                            playSound(snd);
                        }
                    }
                }
                if(model_->statusByJid(from) != status) {
                    showPopup(from, status);
                    model_->setStatusForJid(from, status);
                }
            }
        }
    }

    return false;
}

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

void Watcher::setIconFactoryAccessingHost(IconFactoryAccessingHost* host) {
    IcoHost = host;
}

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

void Watcher::setApplicationInfoAccessingHost(ApplicationInfoAccessingHost* host) {
     AppInfoHost = host;
}

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

void Watcher::playSound(QString f) {
     QFile file(f);
    if(!file.exists()) {
        QString newF = AppInfoHost->appResourcesDir() + "/" + f;
        file.setFileName(newF);
        if(!file.exists()) {
            newF = AppInfoHost->appHomeDir() + "/" + f;
            file.setFileName(newF);
            if(!file.exists())
                return;
        }
    }

    QString str = file.fileName();

    QString player_;
    if (QFile("/proc/asound").exists()) {
        player_ =  "aplay";
    } else {
        player_ =  "play";
    }

#if defined(Q_WS_WIN) || defined(Q_WS_MAC)
        QSound::play(str);
#else
        QString player = psiOptions->getGlobalOption("options.ui.notifications.sounds.unix-sound-player").toString();
        if (player == "") player = player_;
        QStringList args = player.split(' ');
        args += str;
        QString prog = args.takeFirst();
        QProcess::startDetached(prog, args);
#endif

}

void Watcher::getSound(QModelIndex index) {
    if(getButton->isDown()) {
        QString fileName = QFileDialog::getOpenFileName(0,tr("Choose a sound file"),"", tr("Sound (*.wav)"));
        if(fileName.isEmpty()) return;
        SoundFileWidget->setText(fileName);
    } else {
        QString fileName = QFileDialog::getOpenFileName(0,tr("Choose a sound file"),"", tr("Sound (*.wav)"));
        if(fileName.isEmpty()) return;
        const QModelIndex editIndex = model_->index(index.row(), 2, QModelIndex());
        model_->setData(editIndex, QVariant(fileName));
    }
}

void Watcher::checkSound(QModelIndex index) {
    if(playButton->isDown()) {
        playSound(SoundFileWidget->text());
    } else {
        playSound(model_->tmpSoundFile(index));
     }
}

void Watcher::showPopup(QString from, QString status) {    
    if(Interval) {
        QVariant delay(Interval*1000);
        int delay_ = psiOptions->getGlobalOption("options.ui.notifications.passive-popups.delays.status").toInt();
        psiOptions->setGlobalOption("options.ui.notifications.passive-popups.delays.status", delay);

        bool enbl_ = psiOptions->getGlobalOption("options.ui.notifications.passive-popups.enabled").toBool();
        QVariant enbl(true);
        psiOptions->setGlobalOption("options.ui.notifications.passive-popups.enabled", enbl);

        status[0] = status[0].toUpper();
        QString text = from + tr(" change status to ") + status;
        popup->initPopup(text, tr("Watcher Plugin"));

        delay = QVariant(delay_);
        psiOptions->setGlobalOption("options.ui.notifications.passive-popups.delays.status", delay);

        enbl = QVariant(enbl_);
        psiOptions->setGlobalOption("options.ui.notifications.passive-popups.enabled", enbl);
    }
}

void Watcher::Hack() {
    hack->toggle();
}

void Watcher::onOptionsClose() {
	model_->reset();
}

QList < QVariantHash >* Watcher::getAccountMenuParam() {
    return 0;
}

QList < QVariantHash >* Watcher::getContactMenuParam() {
    QVariantHash hash;
        hash["icon"] = QVariant(QString("psi/search"));
        hash["name"] = QVariant(tr("Watch for JID"));
        hash["reciver"] = qVariantFromValue(qobject_cast<QObject *>(this));
        hash["slot"] = QVariant(SLOT(addJidFromMenu()));
        QList< QVariantHash > * l = new QList< QVariantHash >();
        l->push_back(hash);
        return l;
}

void Watcher::addJidFromMenu() {
    QString jid = sender()->property("jid").toString();
    bool checked = model_->getWatchedJids().contains(jid);
    ToolTip *tooltip = new ToolTip(jid, checked, sender());
    connect(tooltip, SIGNAL(check(QString,bool)), this, SLOT(checked(QString,bool)));
    tooltip->show();
}

void Watcher::checked(QString jid, bool check) {
    if(!enabled) return;
    if(check)
       model_->addRow(jid);
    else
        model_->deleteRow(jid);

    psiOptions->setPluginOption(constJids, QVariant(model_->getWatchedJids()));
    psiOptions->setPluginOption(constSndFiles, QVariant(model_->getSounds()));
}

QString Watcher::pluginInfo() {
	return tr("Author: ") +  "Dealer_WeARE\n"
			+ tr("Email: ") + "wadealer@gmail.com\n\n"
			+ trUtf8("This plugin is designed to monitor the status of specific roster contacts.\n"
			 "When the status of such contacts changes a popup window will be shown and when the status changes to online a custom sound can be played.");
}

#include "watcherplugin.moc"

