/*
 * (c) 2006 Kevin Smith
 * (c) 2008 Maciej Niedzielski
 */

#include "pluginhost.h"

#include <QPluginLoader>
#include <QWidget>
#include <QSplitter>
#include <QAction>
#include <QByteArray>
#include <QDomElement>
#include <QKeySequence>
#include <QObject>
#include <QRegExp>
#include <QString>
#include <QStringList>
#include <QTextEdit>
//#include "xmpp_message.h"
#include "psioptions.h"
#include "psiaccount.h"
#include "chatdlg.h"
#include "globalshortcut/globalshortcutmanager.h"
#include "grepshortcutkeydialog.h"
#include "pluginmanager.h"
#include "psiplugin.h"
#include "applicationinfo.h"
#include "stanzasender.h"
#include "stanzafilter.h"
#include "iqfilter.h"
#include "iqnamespacefilter.h"
#include "eventfilter.h"
#include "optionaccessor.h"
#include "shortcutaccessor.h"
#include "iconfactoryaccessor.h"
#include "activetabaccessor.h"
#include "groupchatdlg.h"
#include "tabmanager.h"
#include "popupaccessor.h"
#include "applicationinfoaccessor.h"
#include "accountinfoaccessor.h"
#include "toolbariconaccessor.h"
#include "gctoolbariconaccessor.h"
#include "widgets/iconaction.h"
#include "menuaccessor.h"
#include "contactstateaccessor.h"
#include "plugininfoprovider.h"
#include "psiaccountcontroller.h"
#include "eventcreator.h"
#include "contactinfoaccessor.h"
#include "soundaccessor.h"
#include "textutil.h"
#include "chattabaccessor.h"
#include "pluginaccessor.h"
#include "chatview.h"
#include "webkitaccessor.h"

/**
 * \brief Constructs a host/wrapper for a plugin.
 *
 * PluginHost object manages one plugin. It can load/unload, enable/disable
 * a plugin. It also provides host-side services to the plugin.
 *
 * Constructor attempts to load the plugin, check if it is a valid psi plugin,
 * and cache its name, shortname and version.
 *
 * \param manager PluginManager instance that manages all plugins
 * \param pluginFile path to plugin file
 */
PluginHost::PluginHost(PluginManager* manager, const QString& pluginFile)
    : manager_(manager)
    , plugin_(nullptr)
    , file_(pluginFile)
    , priority_(PsiPlugin::PriorityNormal)
    , loader_(nullptr)
    , iconset_(nullptr)
    , valid_(false)
    , connected_(false)
    , enabled_(false)
    , hasInfo_(false)
    , infoString_(QString())
{
    load();    // reads plugin name, etc
    unload();
}

/**
 * \brief Destroys plugin host.
 *
 * Plugin is disabled and unloaded if needed.
 */
PluginHost::~PluginHost()
{
    disable();
    unload();
    if (loader_) {
        delete loader_;
        loader_ = nullptr;
    }
}

/**
 * \brief Returns true if wrapped file is a valid Psi plugin.
 *
 * Check is done once, in PluginHost constructor.
 */
bool PluginHost::isValid() const
{
    return valid_;
}

/**
 * \brief Returns full path to plugin file.
 */
const QString& PluginHost::path() const
{
    return file_;
}

/**
 * \brief Returns plugin full name.
 *
 * Data is available also when plugin is not loaded.
 */
const QString& PluginHost::name() const
{
    return name_;
}

/**
 * \brief Returns plugin short name.
 *
 * Data is available also when plugin is not loaded.
 */
const QString& PluginHost::shortName() const
{
    return shortName_;
}

/**
 * \brief Returns plugin version string.
 *
 * Data is available also when plugin is not loaded.
 */
const QString& PluginHost::version() const
{
    return version_;
}

/**
 * \brief Returns plugin priority.
 *
 * Data is available also when plugin is not loaded.
 */
int PluginHost::priority() const
{
    return priority_;
}

/**
 * \brief Returns plugin icon.
 *
 * Data is available also when plugin is not loaded.
 */
const QIcon& PluginHost::icon() const
{
    return icon_;
}

QStringList PluginHost::pluginFeatures() const
{
    if (plugin_) {
        return qobject_cast<PsiPlugin*>(plugin_)->pluginFeatures();
    }
    return QStringList();
}

/**
 * \brief Returns plugin options widget.
 *
 * Always returns null if plugin is not currently loaded.
 */
QWidget* PluginHost::optionsWidget() const
{
    QWidget* widget = nullptr;
    if (plugin_) {
        widget = qobject_cast<PsiPlugin*>(plugin_)->options();
    }
    return widget;
}


//-- loading and enabling -------------------------------------------

/**
 * \brief Loads the plugin.
 *
 * Plugin is loaded but not enabled.
 * Does nothing if plugin was already loaded.
 *
 * Will fail if the plugin is not suitable (wrong Qt version,
 * wrong debug setting, wrong Psi plugin interface version etc).
 *
 * \return true if plugin is successfully loaded or if it was already loaded before.
 */
bool PluginHost::load()
{
#ifndef PLUGINS_NO_DEBUG
    qDebug() << "Loading Plugin " << file_;
#endif
    if (plugin_) {
#ifndef PLUGINS_NO_DEBUG
        qDebug() << QString("Plugin %1 was already loaded.").arg(file_);
#endif
    }
    else {
        if (!loader_) {
            loader_ = new QPluginLoader(file_);
            //loader_->setLoadHints(QLibrary::ResolveAllSymbolsHint);
        }

        QObject* plugin = loader_->instance();
        if (!loader_->isLoaded()) {
#ifndef PLUGINS_NO_DEBUG
            qDebug() << "Error loading plugin:" << loader_->errorString();
#endif
            delete loader_;
            loader_ = nullptr;
        }
        else if (plugin) {
#ifndef PLUGINS_NO_DEBUG
            qDebug("Trying to load plugin");
#endif
            //Check it's the right sort of plugin
            PsiPlugin* psiPlugin = qobject_cast<PsiPlugin*>(plugin);
            if (psiPlugin) {
                plugin_ = plugin;
                valid_ = true;
                //loaded_ = true;
                //enabled_ = false;
                name_ = psiPlugin->name();
                shortName_ = psiPlugin->shortName();
                version_ = psiPlugin->version();
                priority_ = psiPlugin->priority();
                icon_ = QIcon(psiPlugin->icon());
                hasToolBarButton_ = qobject_cast<ToolbarIconAccessor*>(plugin_) ? true : false;
                hasGCToolBarButton_ = qobject_cast<GCToolbarIconAccessor*>(plugin_) ? true : false;
                PluginInfoProvider *pip = qobject_cast<PluginInfoProvider*>(plugin_);
                if (pip) {
                    hasInfo_ = true;
                    infoString_ = pip->pluginInfo();
                }
            } else  {
                qWarning("Attempted to load %s, but it is not a valid plugin.", qPrintable(file_));
                if (loader_->isLoaded()) {
                      qWarning("File is a plugin but not for Psi");
                    loader_->unload();
                }
                delete loader_;
                loader_ = nullptr;
                valid_ = false;
            }
        }
    }

    return plugin_ != nullptr;
}

/**
 * \brief Unloads the plugin.
 *
 * Plugin is disabled (if needed) and unloaded.
 * Does nothing if plugin was not loaded yet.
 *
 * If plugin is successfully disabled but unloading fails, it is not enabled again.
 *
 * \return true if plugin is successfully unloaded or if it was not loaded at all.
 */
bool PluginHost::unload()
{
    if (plugin_ && disable()) {
#ifndef PLUGINS_NO_DEBUG
        qDebug("Try to unload plugin %s", qPrintable(name_));
#endif
        if (!loader_) {
            qWarning("Plugin %s's loader wasn't found when trying to unload", qPrintable(name_));
            return false;
        }
        else if (loader_->unload()) {
            //if we're done with the plugin completely and it's unloaded
            // we can delete the loader;
            delete plugin_;
            delete loader_;
            plugin_ = nullptr;
            loader_ = nullptr;
            delete iconset_;
            iconset_ = nullptr;
            connected_ = false;
        }
    }
    return plugin_ == nullptr;
}

/**
 * \brief Returns true if plugin is currently loaded.
 */
bool PluginHost::isLoaded() const
{
    return plugin_ != nullptr;
}

/**
 * \brief Enabled the plugin
 *
 * Plugin is loaded (if needed) and enabled.
 * Does nothing if plugin was already enabled.
 *
 * Before plugin is enabled for the first time,
 * all appropriate set...Host() methods are called.
 *
 * Will fail if the plugin cannot be loaded or if plugin's enable() method fails.
 *
 * \return true if plugin is successfully enabled or if it was already enabled before.
 */
bool PluginHost::enable()
{
    if (!enabled_ && load()) {
        if (!connected_) {
#ifndef PLUGINS_NO_DEBUG
            qDebug() << "connecting plugin " << name_;
#endif

            StanzaSender* s = qobject_cast<StanzaSender*>(plugin_);
            if (s) {
#ifndef PLUGINS_NO_DEBUG
                qDebug("connecting stanza sender");
#endif
                s->setStanzaSendingHost(this);
            }

            IqFilter* f = qobject_cast<IqFilter*>(plugin_);
            if (f) {
#ifndef PLUGINS_NO_DEBUG
                qDebug("connecting iq filter");
#endif
                f->setIqFilteringHost(this);
            }

            OptionAccessor* o = qobject_cast<OptionAccessor*>(plugin_);
            if (o) {
#ifndef PLUGINS_NO_DEBUG
                qDebug("connecting option accessor");
#endif
                o->setOptionAccessingHost(this);
            }

            ShortcutAccessor* sa = qobject_cast<ShortcutAccessor*>(plugin_);
            if (sa) {
#ifndef PLUGINS_NO_DEBUG
                qDebug("connecting shortcut accessor");
#endif
                sa->setShortcutAccessingHost(this);
            }
            PopupAccessor* pa = qobject_cast<PopupAccessor*>(plugin_);
            if (pa) {
#ifndef PLUGINS_NO_DEBUG
                qDebug("connecting popup accessor");
#endif
                pa->setPopupAccessingHost(this);
            }

            IconFactoryAccessor* ia = qobject_cast<IconFactoryAccessor*>(plugin_);
            if (ia) {
#ifndef PLUGINS_NO_DEBUG
                qDebug("connecting iconfactory accessor");
#endif
                ia->setIconFactoryAccessingHost(this);
            }
            ActiveTabAccessor* ta = qobject_cast<ActiveTabAccessor*>(plugin_);
            if (ta) {
#ifndef PLUGINS_NO_DEBUG
                qDebug("connecting activetab accessor");
#endif
                ta->setActiveTabAccessingHost(this);
            }
            ApplicationInfoAccessor* aia = qobject_cast<ApplicationInfoAccessor*>(plugin_);
            if (aia) {
#ifndef PLUGINS_NO_DEBUG
                qDebug("connecting applicationinfo accessor");
#endif
                aia->setApplicationInfoAccessingHost(this);
            }
            AccountInfoAccessor* ai = qobject_cast<AccountInfoAccessor*>(plugin_);
            if (ai) {
#ifndef PLUGINS_NO_DEBUG
                qDebug("connecting accountinfo accessor");
#endif
                ai->setAccountInfoAccessingHost(this);
            }
            ToolbarIconAccessor *tia = qobject_cast<ToolbarIconAccessor*>(plugin_);
            if (tia) {
#ifndef PLUGINS_NO_DEBUG
                qDebug("load toolbaricon param");
#endif
                buttons_ = tia->getButtonParam();
            }
            GCToolbarIconAccessor *gtia = qobject_cast<GCToolbarIconAccessor*>(plugin_);
            if (gtia) {
#ifndef PLUGINS_NO_DEBUG
                qDebug("load gctoolbaricon param");
#endif
                gcbuttons_ = gtia->getGCButtonParam();
            }
            MenuAccessor *ma = qobject_cast<MenuAccessor*>(plugin_);
            if (ma) {
#ifndef PLUGINS_NO_DEBUG
                qDebug("load menu actions param");
#endif
                accMenu_ = ma->getAccountMenuParam();
                contactMenu_ = ma->getContactMenuParam();
            }
            ContactStateAccessor *csa = qobject_cast<ContactStateAccessor*>(plugin_);
            if (csa) {
#ifndef PLUGINS_NO_DEBUG
                qDebug("connecting contactstate accessor");
#endif
                csa->setContactStateAccessingHost(this);
            }
            PsiAccountController *pac = qobject_cast<PsiAccountController*>(plugin_);
            if (pac) {
#ifndef PLUGINS_NO_DEBUG
                qDebug("connectint psiaccount controller");
#endif
                pac->setPsiAccountControllingHost(this);
            }
            EventCreator *ecr = qobject_cast<EventCreator*>(plugin_);
            if (ecr) {
#ifndef PLUGINS_NO_DEBUG
                qDebug("connectint event creator");
#endif
                ecr->setEventCreatingHost(this);
            }
            ContactInfoAccessor *cia = qobject_cast<ContactInfoAccessor*>(plugin_);
            if (cia) {
#ifndef PLUGINS_NO_DEBUG
                qDebug("connecting contactinfo accessor");
#endif
                cia->setContactInfoAccessingHost(this);
            }
            SoundAccessor *soa = qobject_cast<SoundAccessor*>(plugin_);
            if(soa) {
#ifndef PLUGINS_NO_DEBUG
                qDebug("connecting sound accessor");
#endif
                soa->setSoundAccessingHost(this);
            }
            PluginAccessor *pla = qobject_cast<PluginAccessor*>(plugin_);
            if(pla) {
#ifndef PLUGINS_NO_DEBUG
                qDebug("connecting plugin accessor");
#endif
              pla->setPluginAccessingHost(this);
            }
            auto wka = qobject_cast<WebkitAccessor*>(plugin_);
            if (wka) {
                wka->setWebkitAccessingHost(this);
            }

            connected_ = true;
        }

        enabled_ = qobject_cast<PsiPlugin*>(plugin_)->enable();
    }

    return enabled_;
}

/**
 * \brief Disabled the plugin.
 *
 * Plugin is disabled but not unloaded.
 * Does nothing if plugin was not enabled yet.
 *
 * \return true if plugin is successfully disabled or if it was not enabled at all.
 */
bool PluginHost::disable()
{
    if (enabled_) {
        enabled_ = !qobject_cast<PsiPlugin*>(plugin_)->disable();
    }
    return !enabled_;
}

/**
 * \brief Returns true if plugin is currently enabled.
 */
bool PluginHost::isEnabled() const
{
    return enabled_;
}


//-- for StanzaFilter and IqNamespaceFilter -------------------------

/**
 * \brief Give plugin the opportunity to process incoming xml
 *
 * If plugin implements incoming XML filters, they are called in turn.
 * Any handler may then modify the XML and may cause the stanza to be
 * silently discarded.
 * TODO: modification doesn't work
 *
 * \param account Identifier of the PsiAccount responsible
 * \param xml Incoming XML (may be modified)
 * \return Continue processing the XML stanza; true if the stanza should be silently discarded.
 */
bool PluginHost::incomingXml(int account, const QDomElement &e)
{
    bool handled = false;

    // try stanza filter first
    StanzaFilter* sf = qobject_cast<StanzaFilter*>(plugin_);
    if (sf && sf->incomingStanza(account, e)) {
        handled = true;
    }
    // try iq filters
    else if (e.tagName() == "iq") {
        // get iq namespace
        QString ns;
        for (QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) {
            QDomElement i = n.toElement();
            if (!i.isNull() && i.hasAttribute("xmlns")) {
                ns = i.attribute("xmlns");
                break;
            }
        }

        // choose handler function depending on iq type
        bool (IqNamespaceFilter::*handler)(int account, const QDomElement& xml) = 0;
        const QString type = e.attribute("type");
        if (type == "get") {
            handler = &IqNamespaceFilter::iqGet;
        } else if (type == "set") {
            handler = &IqNamespaceFilter::iqSet;
        } else if (type == "result") {
            handler = &IqNamespaceFilter::iqResult;
        } else if (type == "error") {
            handler = &IqNamespaceFilter::iqError;
        }

        if (handler) {
            // normal filters
            foreach (IqNamespaceFilter* f, iqNsFilters_.values(ns)) {
                if ((f->*handler)(account, e)) {
                    handled = true;
                    break;
                }
            }

            // regex filters
            QMapIterator<QRegExp, IqNamespaceFilter*> i(iqNsxFilters_);
            while (!handled && i.hasNext()) {
                if (i.key().indexIn(ns) >= 0 && (i.value()->*handler)(account, e)) {
                    handled = true;
                }
            }
        }
    }

    return handled;
}


bool PluginHost::outgoingXml(int account, QDomElement &e)
{
    bool handled = false;
    StanzaFilter *ef = qobject_cast<StanzaFilter*>(plugin_);
    if (ef && ef->outgoingStanza(account, e)) {
        handled = true;
    }
    return handled;
}

//-- for EventFilter ------------------------------------------------

/**
 * \brief Give plugin the opportunity to process incoming event.
 *
 * If plugin implements EventFilter interface,
 * this will call its processEvent() handler.
 * Handler may then modify the event and may cause the event to be
 * silently discarded.
 *
 * \param account Identifier of the PsiAccount responsible
 * \param e Event XML
 * \return Continue processing the event; true if the stanza should be silently discarded.
 */
bool PluginHost::processEvent(int account, QDomElement& e)
{
    bool handled = false;
    EventFilter *ef = qobject_cast<EventFilter*>(plugin_);
    if (ef && ef->processEvent(account, e)) {
        handled = true;
    }
    return handled;
}

/**
 * \brief Give plugin the opportunity to process incoming message event.
 *
 * If plugin implements EventFilter interface,
 * this will call its processMessage() handler.
 * Handler may then modify the event and may cause the event to be
 * silently discarded.
 * TODO: modification doesn't work
 *
 * \param account Identifier of the PsiAccount responsible
 * \param jidFrom Jid of message sender
 * \param body Message body
 * \param subject Message subject
 * \return Continue processing the event; true if the stanza should be silently discarded.
 */
bool PluginHost::processMessage(int account, const QString& jidFrom, const QString& body, const QString& subject)
{
    bool handled = false;
    EventFilter *ef = qobject_cast<EventFilter*>(plugin_);
    if (ef && ef->processMessage(account, jidFrom, body, subject)) {
        handled = true;
    }
    return handled;
}

bool PluginHost::processOutgoingMessage(int account, const QString& jidTo, QString& body, const QString& type, QString& subject)
{
    bool handled = false;
    EventFilter *ef = qobject_cast<EventFilter*>(plugin_);
    if (ef && ef->processOutgoingMessage(account, jidTo, body, type, subject)) {
        handled = true;
    }
    return handled;
}

void PluginHost::logout(int account)
{
    EventFilter *ef = qobject_cast<EventFilter*>(plugin_);
    if (ef) {
        ef->logout(account);
    }
}

//-- StanzaSender ---------------------------------------------------

/**
 * \brief Sends a stanza from the specified account.
 *
 * \param account Identifier of the PsiAccount responsible
 * \param stanza The stanza to be sent
 */
void PluginHost::sendStanza(int account, const QDomElement& stanza)
{
    QTextStream stream;
    stream.setString(new QString());
    stanza.save(stream, 0);
    manager_->sendXml(account, *stream.string());
}

/**
 * \brief Sends a stanza from the specified account.
 *
 * \param account Identifier of the PsiAccount responsible
 * \param stanza The stanza to be sent.
 */
void PluginHost::sendStanza(int account, const QString& stanza)
{
    manager_->sendXml(account, stanza);
}

/**
 * \brief Sends a message from the specified account.
 *
 * \param account Identifier of the PsiAccount responsible
 * \param to Jid of message addressee
 * \param body Message body
 * \param subject Message type
 * \param type Message type (XMPP message type)
 * \param stanza The stanza to be sent.
 */
void PluginHost::sendMessage(int account, const QString& to, const QString& body, const QString& subject, const QString& type)
{
    //XMPP::Message m;
    //m.setTo(to);
    //m.setBody(body);
    //m.setSubject(subject);
    //if (type =="chat" || type == "error" || type == "groupchat" || type == "headline" || type == "normal") {
    //    m.setType(type);
    //}
    //manager_->sendXml(account, m.toStanza(...).toString());

    //TODO(mck): yeah, that's sick..
    manager_->sendXml(account, QString("<message to='%1' type='%4'><subject>%3</subject><body>%2</body></message>")
              .arg(escape(to)).arg(escape(body)).arg(escape(subject)).arg(escape(type)));

}

/**
 * \brief Returns a unique stanza id in given account XMPP stream.
 *
 * \param account Identifier of the PsiAccount responsible
 * \return Unique stanza id, or empty string if account id is invalid.
 */
QString PluginHost::uniqueId(int account)
{
    return manager_->uniqueId(account);
}

QString PluginHost::escape(const QString &str)
{
    return TextUtil::escape(str);
}

//-- IqFilter -------------------------------------------------------

/**
 * \brief Registers an Iq handler for given namespace.
 *
 * One plugin may register multiple handlers, even for the same namespace.
 * The same handler may be registered for more than one namespace.
 * Attempts to register the same handler for the same namespace will be blocked.
 *
 * When Iq stanza arrives, it is passed in turn to matching handlers.
 * Handler may then modify the event and may cause the event to be
 * silently discarded.
 *
 * Note that iq-result may contain no namespaced element in some protocols,
 * and connection made by this method will not work in such case.
 *
 * \param ns Iq namespace
 * \param filter Filter to be registered
 */
void PluginHost::addIqNamespaceFilter(const QString &ns, IqNamespaceFilter *filter)
{
    if (iqNsFilters_.values(ns).contains(filter)) {
#ifndef PLUGINS_NO_DEBUG
        qDebug("pluginmanager: blocked attempt to register the same filter again");
#endif
    } else {
        iqNsFilters_.insert(ns, filter);
    }
}

/**
 * \brief Registers an Iq handler for given namespace.
 *
 * One plugin may register multiple handlers, even for the same namespace.
 * The same handler may be registered for more than one namespace.
 * Attempts to register the same handler for the same namespace will be blocked.
 *
 * When Iq stanza arrives, it is passed in turn to matching handlers.
 * Handler may then modify the event and may cause the event to be
 * silently discarded.
 *
 * Note that iq-result may contain no namespaced element in some protocols,
 * and connection made by this method will not work in such case.
 *
 * \param ns Iq namespace defined by a regular expression
 * \param filter Filter to be registered
 */
void PluginHost::addIqNamespaceFilter(const QRegExp &ns, IqNamespaceFilter *filter)
{
#ifndef PLUGINS_NO_DEBUG
    qDebug("add nsx");
#endif
    if (iqNsxFilters_.values(ns).contains(filter)) {
#ifndef PLUGINS_NO_DEBUG
        qDebug("pluginmanager: blocked attempt to register the same filter again");
#endif
    } else {
        iqNsxFilters_.insert(ns, filter);
    }
}

/**
 * \brief Unregisters namespace handler.
 *
 * Breaks connection made by addIqNamespaceFilter().
 * Note that \a filter object is never deleted by this function.
 */
void PluginHost::removeIqNamespaceFilter(const QString &ns, IqNamespaceFilter *filter)
{
    iqNsFilters_.remove(ns, filter);
}

/**
 * \brief Unregisters namespace handler.
 *
 * Breaks connection made by addIqNamespaceFilter().
 * Note that \a filter object is never deleted by this function.
 */
void PluginHost::removeIqNamespaceFilter(const QRegExp &ns, IqNamespaceFilter *filter)
{
    iqNsxFilters_.remove(ns, filter);
}


//-- OptionAccessor -------------------------------------------------

/**
 * \brief Sets an option (local to the plugin)
 * The options will be automatically prefixed by the plugin manager, so
 * there is no need to uniquely name the options. In the same way as the
 * main options system, a hierachy is available by dot-delimiting the
 * levels ( e.g. "emoticons.show"). Use this and not setGlobalOption
 * in almost every case.
 * \param  option Option to set
 * \param value New option value
 */
void PluginHost::setPluginOption( const QString& option, const QVariant& value)
{
    // TODO(mck)

    //PsiPlugin* plugin=NULL;
    //
    //if (!plugin)
    //    return;
    QString optionKey=QString("%1.%2.%3").arg(PluginManager::pluginOptionPrefix).arg(shortName()).arg(option);
    PsiOptions::instance()->setOption(optionKey, value);
}

/**
 * \brief Gets an option (local to the plugin)
 * The options will be automatically prefixed by the plugin manager, so
 * there is no need to uniquely name the options. In the same way as the
 * main options system, a hierachy is available by dot-delimiting the
 * levels ( e.g. "emoticons.show"). Use this and not getGlobalOption
 * in almost every case.
 * \param  option Option to set
 * \param value Return value
 */
QVariant PluginHost::getPluginOption(const QString &option, const QVariant &defValue)
{
    QString pluginName = name();
    QString optionKey=QString("%1.%2.%3").arg(PluginManager::pluginOptionPrefix).arg(shortName()).arg(option);
    return PsiOptions::instance()->getOption(optionKey, defValue);
}

/**
 * \brief Sets a global option (not local to the plugin)
 * The options will be passed unaltered by the plugin manager, so
 * the options are presented as they are stored in the main option
 * system. Use setPluginOption instead of this in almost every case.
 * \param  option Option to set
 * \param value New option value
 */
void PluginHost::setGlobalOption(const QString& option, const QVariant& value)
{
    PsiOptions::instance()->setOption(option, value);
}

/**
 * \brief Gets a global option (not local to the plugin)
 * The options will be passed unaltered by the plugin manager, so
 * the options are presented as they are stored in the main option
 * system. Use getPluginOption instead of this in almost every case.
 * \param  option Option to set
 * \param value Return value
 */
QVariant PluginHost::getGlobalOption(const QString& option)
{
    return PsiOptions::instance()->getOption(option);
}

void PluginHost::optionChanged(const QString& option)
{
    OptionAccessor *oa = qobject_cast<OptionAccessor*>(plugin_);
    if(oa)
        oa->optionChanged(option);
}

void PluginHost::applyOptions()
{
    PsiPlugin* pp = qobject_cast<PsiPlugin*>(plugin_);
    if(pp)
        pp->applyOptions();
}

void PluginHost::restoreOptions()
{
    PsiPlugin* pp = qobject_cast<PsiPlugin*>(plugin_);
    if(pp)
        pp->restoreOptions();
}


/**
 * Shortcut accessing host
 */
void PluginHost::setShortcuts()
{
    ShortcutAccessor *sa = qobject_cast<ShortcutAccessor*>(plugin_);
    if (sa) {
        sa->setShortcuts();
    }
}

void PluginHost::connectShortcut(const QKeySequence& shortcut, QObject *receiver, const char* slot)
{
    GlobalShortcutManager::instance()->connect(shortcut, receiver, slot);
}

void PluginHost::disconnectShortcut(const QKeySequence& shortcut, QObject *receiver, const char* slot)
{
    GlobalShortcutManager::instance()->disconnect(shortcut, receiver, slot);
}

void PluginHost::requestNewShortcut(QObject *receiver, const char* slot)
{
    GrepShortcutKeyDialog* grep = new GrepShortcutKeyDialog();
    connect(grep, SIGNAL(newShortcutKey(QKeySequence)), receiver, slot);
    grep->show();
}

/**
 * IconFactory accessing host
 */
QIcon PluginHost::getIcon(const QString& name)
{
    return IconsetFactory::icon(name).icon();
}

void PluginHost::addIcon(const QString& name, const QByteArray& ba)
{
    QPixmap pm;
    pm.loadFromData(ba);
    PsiIcon icon;
    icon.setImpix(pm);
    icon.setName(name);
    if (!iconset_) {
        iconset_ = new Iconset();
    }
    iconset_->setIcon(name,icon);
    iconset_->addToFactory();
}

QTextEdit* PluginHost::getEditBox()
{
    QTextEdit* ed = 0;
    TabbableWidget* tw = findActiveTab();
    if(tw) {
        QWidget* chatEditProxy = tw->findChild<QWidget*>("mle");
        if(chatEditProxy) {
            ed = (QTextEdit *)chatEditProxy->children().at(1);
        }
    }

    return ed;
}

QString PluginHost::getJid()
{
    QString jid;
    TabbableWidget* tw = findActiveTab();
    if(tw) {
        jid = tw->jid().full();
    }

    return jid;
}

QString PluginHost::getYourJid()
{
    QString jid;
    TabbableWidget* tw = findActiveTab();
    if(tw) {
        jid = tw->account()->jid().full();
    }

    return jid;
}

/**
 * ApplicationInfo accessing host
 */
Proxy PluginHost::getProxyFor(const QString &obj)
{
    Proxy prx;
    ProxyItem it = ProxyManager::instance()->getItemForObject(obj);
    prx.type = it.type;
    prx.host = it.settings.host;
    prx.port = it.settings.port;
    prx.user = it.settings.user;
    prx.pass = it.settings.pass;
    return prx;
}

QString PluginHost::appName()
{
    return ApplicationInfo::name();
}

QString PluginHost::appVersion()
{
    return ApplicationInfo::version();
}

QString PluginHost::appCapsNode()
{
    return ApplicationInfo::capsNode();
}

QString PluginHost::appCapsVersion()
{ // this stuff is incompatible with new caps 1.5
    return QString(); // return ApplicationInfo::capsVersion();
}

QString PluginHost::appOsName()
{
    return ApplicationInfo::osName();
}

QString PluginHost::appHomeDir(ApplicationInfoAccessingHost::HomedirType type)
{
    return ApplicationInfo::homeDir((ApplicationInfo::HomedirType)type);
}

QString PluginHost::appResourcesDir()
{
    return ApplicationInfo::resourcesDir();
}

QString PluginHost::appLibDir()
{
    return ApplicationInfo::libDir();
}

QString PluginHost::appProfilesDir(ApplicationInfoAccessingHost::HomedirType type)
{
    return ApplicationInfo::profilesDir((ApplicationInfo::HomedirType)type);
}

QString PluginHost::appHistoryDir()
{
    return ApplicationInfo::historyDir();
}

QString PluginHost::appCurrentProfileDir(ApplicationInfoAccessingHost::HomedirType type)
{
    return ApplicationInfo::currentProfileDir((ApplicationInfo::HomedirType)type);
}

QString PluginHost::appVCardDir()
{
    return ApplicationInfo::vCardDir();
}

//AccountInfoAcsessingHost
QString PluginHost::getStatus(int account)
{
    return manager_->getStatus(account);
}

QString PluginHost::getStatusMessage(int account)
{
    return manager_->getStatusMessage(account);
}

QString PluginHost::proxyHost(int account)
{
    return manager_->proxyHost(account);
}

int PluginHost::proxyPort(int account)
{
    return manager_->proxyPort(account);
}

QString PluginHost::proxyUser(int account)
{
    return manager_->proxyUser(account);
}

QString PluginHost::proxyPassword(int account)
{
    return manager_->proxyPassword(account);
}

QStringList PluginHost::getRoster(int account)
{
    return manager_->getRoster(account);
}

QString PluginHost::getJid(int account)
{
    return manager_->getJid(account);
}

QString PluginHost::getId(int account)
{
    return manager_->getId(account);
}

QString PluginHost::getName(int account)
{
    return manager_->getName(account);
}

int PluginHost::findOnlineAccountForContact(const QString &jid) const
{
    return manager_->findOnlineAccountForContact(jid);
}

bool PluginHost::setActivity(int account, const QString& Jid, QDomElement xml)
{
    return manager_->setActivity(account, Jid, xml);
}

bool PluginHost::setMood(int account, const QString& Jid, QDomElement xml)
{
    return manager_->setMood(account, Jid, xml);
}
bool PluginHost::setTune(int account, const QString& Jid, QString tune)
{
    return manager_->setTune(account, Jid, tune);
}

void PluginHost::addToolBarButton(QObject* parent, QWidget* toolbar, int account, const QString& contact)
{
    ToolbarIconAccessor *ta = qobject_cast<ToolbarIconAccessor*>(plugin_);
    if (ta) {
        if(!buttons_.isEmpty()) {
            for (int i = 0; i < buttons_.size(); ++i) {
                QVariantHash param = buttons_.at(i);
                QString th = param.value("tooltip").value<QString>();
                IconAction *button = new IconAction(th, param.value("icon").value<QString>(), th, 0, parent);
                connect(button, SIGNAL(triggered()),param.value("reciver").value<QObject *>(), param.value("slot").value<QString>().toLatin1());
                toolbar->addAction(button);
            }
        }
        QAction *act = ta->getAction(parent, account, contact);
        if(act) {
            act->setObjectName(shortName_);
            toolbar->addAction(act);
        }
    }
}

bool PluginHost::hasToolBarButton()
{
    return hasToolBarButton_;
}

void PluginHost::addGCToolBarButton(QObject* parent, QWidget* toolbar, int account, const QString& contact)
{
    GCToolbarIconAccessor *ta = qobject_cast<GCToolbarIconAccessor*>(plugin_);
    if (ta) {
        if(!gcbuttons_.isEmpty()) {
            for (int i = 0; i < gcbuttons_.size(); ++i) {
                QVariantHash param = gcbuttons_.at(i);
                QString th = param.value("tooltip").value<QString>();
                IconAction *button = new IconAction(th, param.value("icon").value<QString>(), th, 0, parent);
                connect(button, SIGNAL(triggered()),param.value("reciver").value<QObject *>(), param.value("slot").value<QString>().toLatin1());
                toolbar->addAction(button);
            }
        }
        QAction *act = ta->getGCAction(parent, account, contact);
        if(act)
            toolbar->addAction(act);
    }
}

bool PluginHost::hasGCToolBarButton()
{
    return hasGCToolBarButton_;
}

void PluginHost::initPopup(const QString& text, const QString& title, const QString& icon, int type)
{
    manager_->initPopup(text, title, icon, type);
}

void PluginHost::initPopupForJid(int account, const QString &jid, const QString &text, const QString &title, const QString &icon, int type)
{
    manager_->initPopupForJid(account, jid, text, title, icon, type);
}

int PluginHost::registerOption(const QString& name, int initValue, const QString& path)
{
    return manager_->registerOption(name, initValue, path);
}

void PluginHost::unregisterOption(const QString &name)
{
    manager_->unregisterOption(name);
}

int PluginHost::popupDuration(const QString& name)
{
    return manager_->popupDuration(name);
}

void PluginHost::setPopupDuration(const QString& name, int value)
{
    manager_->setPopupDuration(name, value);
}

void PluginHost::addAccountMenu(QMenu *menu, int account)
{
    MenuAccessor *ma = qobject_cast<MenuAccessor*>(plugin_);
    if(ma) {
        if( !accMenu_.isEmpty()) {
            for (int i = 0; i < accMenu_.size(); ++i) {
                QVariantHash param = accMenu_.at(i);
                IconAction *act = new IconAction(param.value("name").value<QString>(), menu, param.value("icon").value<QString>());
                act->setProperty("account", QVariant(account));
                connect(act, SIGNAL(triggered()), param.value("reciver").value<QObject *>(), param.value("slot").value<QString>().toLatin1());
                menu->addAction(act);
            }
        }
        QAction *act = ma->getAccountAction(menu, account);
        if(act)
            menu->addAction(act);
    }
}

void PluginHost::addContactMenu(QMenu *menu, int account, const QString& jid)
{
    MenuAccessor *ma = qobject_cast<MenuAccessor*>(plugin_);
    if(ma) {
        if(!contactMenu_.isEmpty()) {
            for (int i = 0; i < contactMenu_.size(); ++i) {
                QVariantHash param = contactMenu_.at(i);
                IconAction *act = new IconAction(param.value("name").value<QString>(), menu, param.value("icon").value<QString>());
                act->setProperty("account", QVariant(account));
                act->setProperty("jid", QVariant(jid));
                connect(act, SIGNAL(triggered()), param.value("reciver").value<QObject *>(), param.value("slot").value<QString>().toLatin1());
                menu->addAction(act);
            }
        }
        QAction *act = ma->getContactAction(menu, account, jid);
        if(act)
            menu->addAction(act);
    }
}

void PluginHost::setupChatTab(QWidget* tab, int account, const QString& contact)
{
    ChatTabAccessor *cta = qobject_cast<ChatTabAccessor*>(plugin_);
    if(cta) {
        cta->setupChatTab(tab, account, contact);
    }
}

void PluginHost::setupGCTab(QWidget* tab, int account, const QString& contact)
{
    ChatTabAccessor *cta = qobject_cast<ChatTabAccessor*>(plugin_);
    if(cta) {
        cta->setupGCTab(tab, account, contact);
    }
}

bool PluginHost::appendingChatMessage(int account, const QString &contact,
                      QString &body, QDomElement &html, bool local)
{
    ChatTabAccessor *cta = qobject_cast<ChatTabAccessor*>(plugin_);
    if(cta) {
        return cta->appendingChatMessage(account, contact, body, html, local);
    }
    return false;
}

bool PluginHost::isSelf(int account, const QString& jid)
{
    return manager_->isSelf(account, jid);
}

bool PluginHost::isAgent(int account, const QString& jid)
{
    return manager_->isAgent(account, jid);
}

bool PluginHost::inList(int account, const QString& jid)
{
    return manager_->inList(account, jid);
}

bool PluginHost::isPrivate(int account, const QString& jid)
{
    return manager_->isPrivate(account, jid);
}

bool PluginHost::isConference(int account, const QString& jid)
{
    return manager_->isConference(account, jid);
}

QString PluginHost::name(int account, const QString& jid)
{
    return manager_->name(account, jid);
}

QString PluginHost::status(int account, const QString& jid)
{
    return manager_->status(account, jid);
}

QString PluginHost::statusMessage(int account, const QString& jid)
{
    return manager_->statusMessage(account, jid);
}

QStringList PluginHost::resources(int account, const QString& jid)
{
    return manager_->resources(account, jid);
}

QString PluginHost::realJid(int account, const QString& jid)
{
    return manager_->realJid(account, jid);
}

QStringList PluginHost::mucNicks(int account, const QString &mucJid)
{
    return manager_->mucNicks(account, mucJid);
}

bool PluginHost::hasInfoProvider()
{
    return hasInfo_;
}

QString PluginHost::pluginInfo()
{
    return infoString_;
}

void PluginHost::setStatus(int account, const QString& status, const QString& statusMessage)
{
    manager_->setStatus(account, status, statusMessage);
}

bool PluginHost::appendSysMsg(int account, const QString& jid, const QString& message)
{
    return manager_->appendSysMsg(account, jid, message);
}

bool PluginHost::appendMsg(int account, const QString& jid, const QString& message, const QString& id, bool wasEncrypted)
{
    return manager_->appendMsg(account, jid, message, id, wasEncrypted);
}

void PluginHost::createNewEvent(int account, const QString& jid, const QString& descr, QObject *receiver, const char* slot)
{
    manager_->createNewEvent(account, jid, descr, receiver, slot);
}

void PluginHost::createNewMessageEvent(int account, QDomElement const &element)
{
    manager_->createNewMessageEvent(account, element);
}

void PluginHost::playSound(const QString &fileName)
{
    soundPlay(fileName);
}

/**
 * EncryptionSupport
 */

bool PluginHost::decryptMessageElement(int account, QDomElement &message)
{
    EncryptionSupport *es = qobject_cast<EncryptionSupport*>(plugin_);
    return es && es->decryptMessageElement(account, message);
}

bool PluginHost::encryptMessageElement(int account, QDomElement &message)
{
    EncryptionSupport *es = qobject_cast<EncryptionSupport*>(plugin_);
    return es && es->encryptMessageElement(account, message);
}

/**
 * PluginAccessingHost
 */

QObject* PluginHost::getPlugin(const QString &name)
{
  foreach (PluginHost *plugin, manager_->pluginsByPriority_) {
      if (plugin->name() == name || plugin->shortName() == name) {
          return plugin->plugin_;
      }
  }
  return nullptr;
}

WebkitAccessingHost::RenderType PluginHost::chatLogRenderType() const
{
#ifdef WEBKIT
# ifdef WEBENGINE
    return WebkitAccessingHost::RT_WebEngine;
# else
    return WebkitAccessingHost::RT_WebKit;
# endif
#else
    return WebkitAccessingHost::RT_TextEdit;
#endif
}

QString PluginHost::installChatLogJSDataFilter(const QString &js, PsiPlugin::Priority priority)
{
    return manager_->installChatLogJSDataFilter(js, priority);
}

void PluginHost::uninstallChatLogJSDataFilter(const QString &id)
{
    manager_->uninstallChatLogJSDataFilter(id);
}

void PluginHost::executeChatLogJavaScript(QWidget *log, const QString &js)
{
#ifdef WEBKIT
    auto cv = qobject_cast<ChatView *>(log);
    if (!cv) {
        cv = log->findChild<ChatView *>("log");
    }
    if (cv) {
        cv->sendJsCode(js);
    }
#endif
}

//-- helpers --------------------------------------------------------

static bool operator<(const QRegExp &a, const QRegExp &b)
{
    return a.pattern() < b.pattern();
}
