/***************************************************************************
                          pluginmanager.cpp  -  description
                             -------------------
    begin                : Mon Apr 28 2003
    copyright            : (C) 2003 by Martin Witte
    email                : witte@kawo1.rwth-aachen.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "include/plugins.h"
#include "include/pluginmanager.h"
#include "include/pluginmanager-configuration.h"
#include "include/plugin_configuration_dialog.h"
#include "include/kradioapp.h"

#include <kiconloader.h>
#include <kdialogbase.h>
#include <klocale.h>
#include <kconfig.h>
#include <kprogress.h>

#include <tqlayout.h>
#include <tqframe.h>
#include <tqmenudata.h>

#include "include/debug-profiler.h"

PluginManager::PluginManager(
    const TQString &name,
    KRadioApp     *app,
    const TQString &configDialogTitle,
    const TQString &aboutDialogTitle)
 : m_Name(name),
   m_Application(app),
   m_showProgressBar(true),
   m_configDialog (NULL),
   m_pluginManagerConfiguration(NULL),
   m_aboutDialog(NULL),
   m_configDialogTitle(configDialogTitle),
   m_aboutDialogTitle (aboutDialogTitle)
{
}


PluginManager::~PluginManager()
{
    delete m_pluginManagerConfiguration;
    m_pluginManagerConfiguration = NULL;

    // config Dialog must be deleted first, so we can clear m_configPages
    // without problems (this is the only place where our config dialog is deleted)
    // Without clearing this list, those pages would be deleted, but
    // we would try to delete them another time when the associated plugin is
    // deleted, because m_configPages is out of date.
    if (m_configDialog) {
        m_configDialog->cancel();
        delete m_configDialog;
    }
    m_configPages.clear();
    m_configPageFrames.clear();
    m_configDialog = NULL;

    if (m_aboutDialog)
        delete m_aboutDialog;
    m_aboutPages.clear();
    m_aboutPageFrames.clear();
    m_aboutDialog = NULL;

    while (PluginBase *p = m_plugins.getFirst()) {
        deletePlugin(p);
    }
}


void PluginManager::noticeLibrariesChanged()
{
    if (m_pluginManagerConfiguration)
        m_pluginManagerConfiguration->noticePluginLibrariesChanged();
}


void PluginManager::unloadPlugins(const TQString &class_name)
{
    PluginList plugins = m_plugins;
    for (PluginIterator it(plugins); it.current(); ++it) {
        PluginBase *p = it.current();
        if (p->pluginClassName() == class_name) {
            deletePlugin(p);
        }
    }
}


void PluginManager::addWidgetPluginMenuItems(TQMenuData *menu, TQMap<WidgetPluginBase *,int> &map) const
{
    map.clear();

    for (PluginIterator it(m_plugins); it.current(); ++it) {
        WidgetPluginBase *b = dynamic_cast<WidgetPluginBase*>(it.current());
        if (!b) continue;

        int id = menu->insertItem("dummy", b->getWidget(), TQT_SLOT(toggleShown()));
        map.insert(b, id);
        updateWidgetPluginMenuItem(b, menu, map, b->isReallyVisible());
    }
}


void PluginManager::updateWidgetPluginMenuItem(WidgetPluginBase *b, TQMenuData *menu, TQMap<WidgetPluginBase *,int> &map, bool shown) const
{
    if (!b || !map.contains(b))
        return;

    const TQString &name = b->description();
    TQString text = (shown ? i18n("Hide %1") : i18n("Show %1")).arg(name);

    menu->changeItem(map[b],
                     TQIconSet(SmallIconSet(!shown ? "kradio_show" : "kradio_hide")),
                     text);
}


void PluginManager::noticeWidgetPluginShown(WidgetPluginBase *p, bool shown)
{
    for (PluginIterator it(m_plugins); it.current(); ++it) {
        it.current()->noticeWidgetPluginShown(p, shown);
    }
}


PluginBase *PluginManager::getPluginByName(const TQString &name) const
{
    for (PluginIterator it(m_plugins); it.current(); ++it) {
        if (it.current()->name() == name)
            return it.current();
    }
    return NULL;
}


void PluginManager::insertPlugin(PluginBase *p)
{
    BlockProfiler profiler("PluginManager::insertPlugin");

    if (p) {
        BlockProfiler profiler_cfg("PluginManager::insertPlugin - about/config");

        /*kdDebug() << TQDateTime::currentDateTime().toString(Qt::ISODate)
                  << " Debug: Adding Plugin: " << p->name() << "\n";*/

        if (!m_configDialog)
            createConfigDialog(m_configDialogTitle);
        if (!m_aboutDialog)
            createAboutDialog(m_aboutDialogTitle);

        m_plugins.append(p);
        p->setManager(this);

        addConfigurationPage (p, p->createConfigurationPage());
        addAboutPage         (p, p->createAboutPage());

        profiler_cfg.stop();
        BlockProfiler profiler_connect("PluginManager::insertPlugin - connect");

        // connect plugins with each other
        for (PluginIterator it(m_plugins); it.current(); ++it) {
            if (it.current() != p) {
                /*kdDebug() << TQDateTime::currentDateTime().toString(Qt::ISODate)
                          << " Debug: connecting with " << it.current()->name() << "\n";*/
                p->connectI(it.current());
            }
        }

        // perhaps some existing config pages will profit from new plugin
        // example: timecontrol profits from radio plugin
        for (TQWidgetDictIterator it(m_configPages); it.current(); ++it) {
            Interface *i = dynamic_cast<Interface *>(it.current());
            if (i)
                i->connectI(p);
        }

        profiler_connect.stop();
        BlockProfiler profiler_widget("PluginManager::insertPlugin - notifywidgets");

        WidgetPluginBase *w1 = dynamic_cast<WidgetPluginBase*>(p);
        for (PluginIterator it(m_plugins); it.current(); ++it) {
            it.current()->noticePluginsChanged(m_plugins);
            if (w1)
                it.current()->noticeWidgetPluginShown(w1, w1->isReallyVisible());

            WidgetPluginBase *w2 = dynamic_cast<WidgetPluginBase*>(it.current());
            if (w2)
                p->noticeWidgetPluginShown(w2, w2->isReallyVisible());
        }

        if (m_pluginManagerConfiguration)
            m_pluginManagerConfiguration->noticePluginsChanged();

        profiler_widget.stop();
    }
}


void PluginManager::deletePlugin(PluginBase *p)
{
    if (p && m_plugins.contains(p)) {
        removePlugin(p);
        delete p;
    }
}


void PluginManager::removePlugin(PluginBase *p)
{
    if (p && m_plugins.contains(p)) {

        for (PluginIterator it(m_plugins); it.current(); ++it) {
            if (it.current() != p) {
                // workaround for buggy compilers/libstdc++
                if (p->destructorCalled()) {
                    p->PluginBase::disconnectI(it.current());
                } else {
                    p->disconnectI(it.current());
                }
            }
        }

        // remove config page from config dialog, only chance is to delete it
        // plugin will be notified automatically (mechanism implemented by
        // PluginBase)
        while (TQFrame *f = m_configPageFrames.find(p)) {
            m_configPageFrames.remove(p);
            m_configPages.remove(p);
            delete f;
        }
        while (TQFrame *f = m_aboutPageFrames.find(p)) {
            m_aboutPageFrames.remove(p);
            m_aboutPages.remove(p);
            delete f;
        }

        // remove bindings between me and plugin
        m_plugins.remove(p);
        p->unsetManager();

        p->noticePluginsChanged(PluginList());
        for (PluginIterator it(m_plugins); it.current(); ++it) {
            it.current()->noticePluginsChanged(m_plugins);
        }

        if (m_pluginManagerConfiguration)
            m_pluginManagerConfiguration->noticePluginsChanged();
    }
}


void PluginManager::addConfigurationPage (PluginBase *forWhom,
                                          const ConfigPageInfo &info)
{
    if (!forWhom || !m_plugins.containsRef(forWhom) || !info.page)
        return;
    TQFrame *f = addConfigurationPage(info);

    // register this frame and config page
    m_configPageFrames.insert(forWhom, f);
    m_configPages.insert(forWhom, info.page);

    // perhaps new config page profits from existing plugins
    // example: timecontrol profits from radio plugin
    Interface *i = dynamic_cast<Interface *>(info.page);
    if (i) {
        for (PluginIterator it(m_plugins); it.current(); ++it)
            i->connectI(it.current());
    }
}


TQFrame *PluginManager::addConfigurationPage (const ConfigPageInfo &info)
{
    if (!m_configDialog)
        createConfigDialog(i18n(m_configDialogTitle.ascii()));

    // create empty config frame
    TQFrame *f = m_configDialog->addPage(
      info.itemName,
      info.pageHeader,
      TDEGlobal::instance()->iconLoader()->loadIcon( info.iconName, KIcon::NoGroup, KIcon::SizeMedium )
    );

    // fill config frame with layout ...
    TQGridLayout *l = new TQGridLayout(f);
    l->setSpacing( 0 );
    l->setMargin( 0 );

    // ... and externally created config page
    info.page->reparent (f, TQPoint(0,0), true);
    l->addWidget( info.page, 0, 0 );

    // make sure, that config page receives ok, apply and cancel signals
    TQObject::connect(this,           TQT_SIGNAL(sigConfigOK()),   info.page, TQT_SLOT(slotOK()));
    TQObject::connect(m_configDialog, TQT_SIGNAL(cancelClicked()), info.page, TQT_SLOT(slotCancel()));

    return f;
}


void PluginManager::createConfigDialog(const TQString &title)
{
    if (m_configDialog) delete m_configDialog;
    m_configDialog = NULL;

    PluginConfigurationDialog *cfg = new PluginConfigurationDialog(
        KDialogBase::IconList,
        title,
        KDialogBase::Apply|KDialogBase::Ok|KDialogBase::Cancel,
        KDialogBase::Ok,
        /*parent = */ NULL,
        title.ascii(),
        /*modal = */ false,
        true);

    m_configDialog = cfg;

    TQObject::connect(m_configDialog, TQT_SIGNAL(okClicked()),     this, TQT_SLOT(slotConfigOK()));
    TQObject::connect(m_configDialog, TQT_SIGNAL(applyClicked()),  this, TQT_SLOT(slotConfigOK()));

    insertPlugin(cfg);

    addConfigurationPage(createOwnConfigurationPage());

    for (PluginIterator i(m_plugins); m_configDialog && i.current(); ++i) {
        addConfigurationPage(i.current(),
                             i.current()->createConfigurationPage());
    }
}


ConfigPageInfo PluginManager::createOwnConfigurationPage()
{
    m_pluginManagerConfiguration = new PluginManagerConfiguration(NULL, m_Application, this);
    return ConfigPageInfo (m_pluginManagerConfiguration,
                           i18n("Plugins"),
                           i18n("Plugin Library Configuration"),
                           "kradio_plugins");
}





void PluginManager::addAboutPage (PluginBase *forWhom,
                                  const AboutPageInfo &info)
{
    if (!m_aboutDialog)
        createAboutDialog(i18n(m_aboutDialogTitle.ascii()));

    if (   !forWhom || !m_plugins.containsRef(forWhom)
        || !m_aboutDialog || !info.page)
        return;


    // create empty about frame
    TQFrame *f = m_aboutDialog->addPage(
      info.itemName,
      info.pageHeader,
      TDEGlobal::instance()->iconLoader()->loadIcon( info.iconName, KIcon::NoGroup, KIcon::SizeMedium )
    );

    // register this frame and config page
    m_aboutPageFrames.insert(forWhom, f);
    m_aboutPages.insert(forWhom, info.page);

    // fill config frame with layout ...
    TQGridLayout *l = new TQGridLayout(f);
    l->setSpacing( 0 );
    l->setMargin( 0 );

    // ... and externally created config page
    info.page->reparent (f, TQPoint(0,0), true);
    l->addWidget( info.page, 0, 0 );
}


void PluginManager::createAboutDialog(const TQString &title)
{
    if (m_aboutDialog) delete m_aboutDialog;
    m_aboutDialog = NULL;

    m_aboutDialog = new KDialogBase(KDialogBase::IconList,
                                    title,
                                    KDialogBase::Close,
                                    KDialogBase::Close,
                                    /*parent = */ NULL,
                                    title.ascii(),
                                    /*modal = */ false,
                                    true);

    for (PluginIterator i(m_plugins); m_aboutDialog && i.current(); ++i) {
        addAboutPage(i.current(),
                     i.current()->createAboutPage());
    }
}


void PluginManager::saveState (TDEConfig *c) const
{
    c->setGroup("PluginManager-" + m_Name);
    c->writeEntry("show-progress-bar", m_showProgressBar);
    int n = 0;
    for (PluginIterator it(m_plugins); it.current(); ++it) {
        TQString class_name  = it.current()->pluginClassName();
        TQString object_name = it.current()->name();
        if (class_name.length() && object_name.length() &&
            m_Application->getPluginClasses().contains(class_name))
        {
            ++n;
            c->writeEntry("plugin_class_" + TQString::number(n), class_name);
            c->writeEntry("plugin_name_"  + TQString::number(n), object_name);
        }
    }
    c->writeEntry("plugins", n);

    for (PluginIterator i(m_plugins); i.current(); ++i) {
        i.current()->saveState(c);
    }
}


void PluginManager::restoreState (TDEConfig *c)
{
    BlockProfiler profile_all("PluginManager::restoreState");
    c->setGroup("PluginManager-" + m_Name);
    m_showProgressBar = c->readBoolEntry("show-progress-bar", true);
    int n = c->readNumEntry("plugins", 0);

    KProgressDialog  *progress = NULL;
    if (m_showProgressBar) {
        progress = new KProgressDialog(NULL, NULL, i18n("Starting Plugins"));
        progress->setMinimumWidth(400);
        progress->setAllowCancel(false);
        progress->show();
        progress->progressBar()->setTotalSteps(2*n);
    }

    for (int i = 1; i <= n; ++i) {
        c->setGroup("PluginManager-" + m_Name);
        TQString class_name  = c->readEntry("plugin_class_" + TQString::number(i));
        TQString object_name = c->readEntry("plugin_name_"  + TQString::number(i));

        if (m_showProgressBar)
            progress->TQWidget::setCaption(i18n("Creating Plugin %1").arg(class_name));
        if (class_name.length() && object_name.length())
            m_Application->CreatePlugin(this, class_name, object_name);
        if (m_showProgressBar)
            progress->progressBar()->setProgress(i);
    }

    if (m_Application && n == 0) {
        const TQMap<TQString, PluginClassInfo> &classes = m_Application->getPluginClasses();
        TQMapConstIterator<TQString, PluginClassInfo> end = classes.end();
        n = classes.count();
        if (m_showProgressBar)
            progress->progressBar()->setTotalSteps(2*n);
        int idx = 1;
        for (TQMapConstIterator<TQString, PluginClassInfo> it=classes.begin(); it != end; ++it, ++idx) {
            const PluginClassInfo &cls = *it;
            if (m_showProgressBar)
                progress->TQWidget::setCaption(i18n("Creating Plugin %1").arg(cls.class_name));
            m_Application->CreatePlugin(this, cls.class_name, m_Name + "-" + cls.class_name);
            if (m_showProgressBar)
                progress->progressBar()->setProgress(idx);
        }
        m_configDialog->show();
    }

    BlockProfiler  profile_plugins("PluginManager::restoreState -  plugins");

    int idx = n;
    for (PluginIterator i(m_plugins); i.current(); ++i, ++idx) {
        BlockProfiler profile_plugin("PluginManager::restoreState - " + i.current()->pluginClassName());
        if (m_showProgressBar)
            progress->TQWidget::setCaption(i18n("Initializing Plugin %1").arg(i.current()->pluginClassName()));
        i.current()->restoreState(c);
        if (m_showProgressBar)
            progress->progressBar()->setProgress(idx+1);
    }
    if (m_showProgressBar)
        delete progress;
}

PluginConfigurationDialog *PluginManager::getConfigDialog()
{
    if (!m_configDialog)
        createConfigDialog(m_configDialogTitle);
    return m_configDialog;
}

KDialogBase               *PluginManager::getAboutDialog()
{
    if (!m_aboutDialog)
        createAboutDialog();
    return m_aboutDialog;
}



void PluginManager::slotConfigOK()
{
    emit sigConfigOK();
    if (m_Application)
        m_Application->saveState(TDEGlobal::config());
}


void  PluginManager::startPlugins()
{
    for (PluginIterator i(m_plugins); i.current(); ++i) {
        i.current()->startPlugin();
    }
}

void  PluginManager::aboutToQuit()
{
    for (PluginIterator i(m_plugins); i.current(); ++i) {
        i.current()->aboutToQuit();
    }
}


#include "pluginmanager.moc"