/* ============================================================
 * File  : pluginloader.cpp
 * Author: Renchi Raju <renchi@pooh.tam.uiuc.edu>
 * Date  : 2004-02-19
 * Description :
 *
 * Copyright 2004 by Renchi Raju

 * This program is free software; you can redistribute it
 * and/or modify it under the terms of the GNU Library General
 * Public License as published by the Free Software Foundation;
 * either version 2, 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 Library General Public License for more details.
 *
 * ============================================================ */

/** @file pluginloader.cpp */

#include <tqstringlist.h>

#include <ktrader.h>
#include <tdeparts/componentfactory.h>
#include <kdebug.h>
#include <kdialog.h>

#include "plugin.h"
#include "pluginloader.h"
#include "interface.h"
#include <tdeconfig.h>
#include <tqcheckbox.h>
#include <tqlayout.h>

/**
   \author Renchi Raju
   \class KIPI::PluginLoader
   This is the class that will help host applications to load plugins.

   The host application must create an instance of the plugin loader, and
   call the method loadPlugins() to get the plugins loaded. To ensure that
   plugins are correctly removed from menus and toolbars when loaded and
   unloaded after constructions, the application must connect to either the
   signals plug() / unplug() or the signal replug(). These signals are
   emitted when a plugin is to be inserted into the menus.

   If your application is using XMLGUI, the easiest way to get the plugins
   inserted into the menus is by adding an item in the ui.rc file looking
   list this:
      &lt;ActionList name="image_actions"/&gt;

   Then plugin plugins into menus could be done with code similiar to this from KimDaBa:
   \code
    void slotReplug() {
      unplugActionList( TQString::fromLatin1("file_actions") );
      unplugActionList( TQString::fromLatin1("image_actions") );
      unplugActionList( TQString::fromLatin1("tool_actions") );

      TQPtrList<TDEAction> fileActions;
      TQPtrList<TDEAction> imageActions;
      TQPtrList<TDEAction> toolsActions;

      KIPI::PluginLoader::PluginList list = _pluginLoader->pluginList();
      for( KIPI::PluginLoader::PluginList::Iterator it = list.begin(); it != list.end(); ++it ) {
          KIPI::Plugin* plugin = (*it)->plugin;
          if ( !plugin || !(*it)->shouldLoad )
              continue;

          plugin->setup( this );
          TQPtrList<TDEAction>* popup = 0;
          if ( plugin->category() == KIPI::IMAGESPLUGIN )
              popup = &imageActions;

          else if ( plugin->category() == KIPI::EXPORTPLUGIN  ||
                    plugin->category() == KIPI::IMPORTPLUGIN )
              popup = &fileActions;

          else if ( plugin->category() == KIPI::TOOLSPLUGIN )
              popup = &toolsActions;

          if ( popup ) {
              TDEActionPtrList actions = plugin->actions();
              for( TDEActionPtrList::Iterator it = actions.begin(); it != actions.end(); ++it ) {
                  popup->append( *it );
              }
          }
          else {
              kdDebug() << "No menu found\n";
          }
          plugin->actionCollection()->readShortcutSettings();
      }

      // For this to work I need to pass false as second arg for createGUI
      plugActionList( TQString::fromLatin1("file_actions"), fileActions );
      plugActionList( TQString::fromLatin1("image_actions"), imageActions );
      plugActionList( TQString::fromLatin1("tool_actions"), toolsActions );
  }
  \endcode

  To configure which plugins should be loaded, simply call
  PluginLoader::configWidget(), and insert the widget into your normal
  configuration dialog.
*/
namespace KIPI {

//---------------------------------------------------------------------
//
// PluginLoader::Info
//
//---------------------------------------------------------------------
struct PluginLoader::Info::Private {
    TQString m_name;
    TQString m_comment;
    TQString m_library;
    Plugin* m_plugin;
    bool m_shouldLoad;
};


PluginLoader::Info::Info(const TQString& name, const TQString& comment, const TQString& library, bool shouldLoad)
{
    d=new Private;
    d->m_name=name;
    d->m_comment=comment;
    d->m_library=library;
    d->m_plugin=0;
    d->m_shouldLoad=shouldLoad;
}


PluginLoader::Info::~Info()
{
    delete d;
}


TQString PluginLoader::Info::name() const
{
    return d->m_name;
}


TQString PluginLoader::Info::comment() const
{
    return d->m_comment;
}


TQString PluginLoader::Info::library() const
{
    return d->m_library;
}


Plugin* PluginLoader::Info::plugin() const
{
    return d->m_plugin;
}


void PluginLoader::Info::setPlugin(Plugin* plugin)
{
    d->m_plugin=plugin;
}


bool PluginLoader::Info::shouldLoad() const
{
    return d->m_shouldLoad;
}


void PluginLoader::Info::setShouldLoad(bool value)
{
    d->m_shouldLoad=value;
}


//---------------------------------------------------------------------
//
// PluginLoader
//
//---------------------------------------------------------------------
static PluginLoader* s_instance = 0;


struct PluginLoader::Private
{
    PluginList m_pluginList;
    Interface* m_interface;
    TQStringList m_ignores;
};


PluginLoader::PluginLoader( const TQStringList& ignores, Interface* interface )
{
    Q_ASSERT( s_instance == 0 );
    s_instance = this;

    d=new Private;
    d->m_interface = interface;
    d->m_ignores = ignores;

    TDETrader::OfferList offers = TDETrader::self()->query("KIPI/Plugin");
    TDEConfig* config = TDEGlobal::config();
    config->setGroup( TQString::fromLatin1( "KIPI/EnabledPlugin" ) );

    TDETrader::OfferList::ConstIterator iter;
    for(iter = offers.begin(); iter != offers.end(); ++iter) {

        KService::Ptr service = *iter;
        TQString name    = service->name();
        TQString comment = service->comment();
        TQString library = service->library();
        TQStringList reqFeatures = service->property( TQString::fromLatin1( "X-KIPI-ReqFeatures" ) ).toStringList();

        if (library.isEmpty() || name.isEmpty() ) {
            kdWarning( 51001 ) << "KIPI::PluginLoader: Plugin had an empty name or library file - this should not happen." << endl;
            continue;
        }

        if ( d->m_ignores.contains( name ) ) {
            kdDebug( 51001 ) << "KIPI::PluginLoader: plugin " << name << " is in the ignore list for host application" << endl;
            continue;
        }

        bool appHasAllReqFeatures=true;
        for( TQStringList::Iterator featureIt = reqFeatures.begin(); featureIt != reqFeatures.end(); ++featureIt ) {
            if ( !d->m_interface->hasFeature( *featureIt ) ) {
                kdDebug( 51001 ) << "Plugin " << name << " was not loaded because the host application is missing\n"
                                 << "the feature " << *featureIt << endl;
				appHasAllReqFeatures=false;
				break;
            }
        }

        bool load = config->readBoolEntry( name, true );

        if (!appHasAllReqFeatures)
            continue;

        Info* info = new Info( name, comment, library, load );
        d->m_pluginList.append( info );
    }
}

PluginLoader::~PluginLoader()
{
    delete d;
}

void PluginLoader::loadPlugins()
{
    for( PluginList::Iterator it = d->m_pluginList.begin(); it != d->m_pluginList.end(); ++it ) {
        loadPlugin( *it );
    }
    emit replug();
}

void PluginLoader::loadPlugin( Info* info )
{
    if ( info->plugin() == 0 && info->shouldLoad() ) {
        Plugin *plugin = 0;
        int error;
        plugin =  KParts::ComponentFactory
                  ::createInstanceFromLibrary<Plugin>(info->library().local8Bit().data(),
                                                      d->m_interface, 0, TQStringList(), &error);

        if (plugin)
            kdDebug( 51001 ) << "KIPI::PluginLoader: Loaded plugin " << plugin->name()<< endl;
        else
        {
            kdWarning( 51001 ) << "KIPI::PluginLoader:: createInstanceFromLibrary returned 0 for "
                               << info->name()
                               << " (" << info->library() << ")"
                               << " with error number "
                               << error << endl;
            if (error == KParts::ComponentFactory::ErrNoLibrary)
                kdWarning( 51001 ) << "KLibLoader says: "
                                   << KLibLoader::self()->lastErrorMessage() << endl;
        }
        info->setPlugin(plugin);
    }
    if ( info->plugin() ) // Do not emit if we had trouble loading the plugin.
        emit PluginLoader::instance()->plug( info );
}


const PluginLoader::PluginList& PluginLoader::pluginList()
{
    return d->m_pluginList;
}

PluginLoader* PluginLoader::instance()
{
    Q_ASSERT( s_instance != 0);
    return s_instance;
}


//---------------------------------------------------------------------
//
// ConfigWidget
//
//---------------------------------------------------------------------
ConfigWidget* PluginLoader::configWidget( TQWidget* parent )
{
    return new ConfigWidget( parent );
}


class PluginCheckBox :public TQCheckBox
{
public:
    PluginCheckBox( PluginLoader::Info* info, TQWidget* parent )
        : TQCheckBox( info->comment(), parent ), info( info )
        {
            setChecked( info->shouldLoad() );
        }
    PluginLoader::Info* info;
};


struct ConfigWidget::Private
{
    TQValueList< PluginCheckBox* > _boxes;
};


ConfigWidget::ConfigWidget( TQWidget* parent )
    :TQScrollView( parent, "KIPI::PluginLoader::ConfigWidget" )
{
    d=new Private;
    TQWidget* top = new TQWidget( viewport() );
    addChild( top );
    setResizePolicy( AutoOneFit );

    TQVBoxLayout* lay = new TQVBoxLayout( top, KDialog::marginHint(), KDialog::spacingHint() );

    PluginLoader::PluginList list = PluginLoader::instance()->d->m_pluginList;
    for( PluginLoader::PluginList::Iterator it = list.begin(); it != list.end(); ++it ) {
        PluginCheckBox* cb = new PluginCheckBox( *it, top );
        lay->addWidget( cb );
        d->_boxes.append( cb );
    }

    lay->addStretch(10);
}


ConfigWidget::~ConfigWidget()
{
    delete d;
}


void ConfigWidget::apply()
{
    TDEConfig* config = TDEGlobal::config();
    config->setGroup( TQString::fromLatin1( "KIPI/EnabledPlugin" ) );
    bool changes = false;

    for( TQValueList<PluginCheckBox*>::Iterator it = d->_boxes.begin(); it != d->_boxes.end(); ++it ) {
        bool orig = (*it)->info->shouldLoad();
        bool load = (*it)->isChecked();
        if ( orig != load ) {
            changes = true;
            config->writeEntry( (*it)->info->name(), load );
            (*it)->info->setShouldLoad(load);
            if ( load ) {
                PluginLoader::instance()->loadPlugin( (*it)->info);
            }
            else {
                if ( (*it)->info->plugin() ) // Do not emit if we had trouble loading plugin.
                    emit PluginLoader::instance()->unplug( (*it)->info);
            }
        }
    }
    emit PluginLoader::instance()->replug();
}


} // namespace

#include "pluginloader.moc"
