/*
 *
 * Copyright (C) 2002 George Staikos <staikos@kde.org>
 *               2003 Dirk Ziegelmeier <dziegel@gmx.de>
 *
 * This library 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 of the License, or (at your option) any later version.
 *
 * This library 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.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <qfile.h>

#include <kdebug.h>
#include <klibloader.h>
#include <kservice.h>
#include <kservicetype.h>
#include <ktrader.h>
#include <kconfig.h>
#include <assert.h>

#include "pluginfactory.h"

#include "kdetvpluginbase.h"
#include "kdetvsrcplugin.h"
#include "kdetvmixerplugin.h"
#include "kdetvosdplugin.h"
#include "kdetvmiscplugin.h"
#include "kdetvchannelplugin.h"
#include "kdetvvbiplugin.h"
#include "kdetvfilterplugin.h"
#include "../kdetv_version.h"


int PluginFactory::_upid = 0;


PluginFactory::PluginFactory(Kdetv *ktv)
    : _ktv(ktv),
      _actionCollection(0L),
      _factory(0L)
{
    _allPlugins.setAutoDelete(true);
}

PluginFactory::~PluginFactory()
{
}

void PluginFactory::scanForPlugins(KConfig* cfg)
{
    cfg->setGroup("Video Plugins");
    _videoPlugins.clear();
    KTrader::OfferList vplugs = KTrader::self()->query(QString::fromLatin1("kdetv Video Source"),
                                                       QString("[X-Kdetv-Plugin-Version] == %1" ).arg(KDETV_PLUGIN_VERSION));
    doScan(cfg, vplugs, _videoPlugins, PluginDesc::VIDEO);

    cfg->setGroup("Channel Plugins");
    _channelPlugins.clear();
    KTrader::OfferList cplugs = KTrader::self()->query(QString::fromLatin1("kdetv Channel Format"),
                                                       QString("[X-Kdetv-Plugin-Version] == %1" ).arg(KDETV_PLUGIN_VERSION));
    doScan(cfg, cplugs, _channelPlugins, PluginDesc::CHANNEL);

    cfg->setGroup("Mixer Plugins");
    _mixerPlugins.clear();
    KTrader::OfferList mplugs = KTrader::self()->query(QString::fromLatin1("kdetv Audio Mixer"),
                                                       QString("[X-Kdetv-Plugin-Version] == %1" ).arg(KDETV_PLUGIN_VERSION));
    doScan(cfg, mplugs, _mixerPlugins, PluginDesc::MIXER);

    cfg->setGroup("Misc Plugins");
    _osdPlugins.clear();
    KTrader::OfferList oplugs = KTrader::self()->query(QString::fromLatin1("kdetv OSD"),
                                                       QString("[X-Kdetv-Plugin-Version] == %1" ).arg(KDETV_PLUGIN_VERSION));
    doScan(cfg, oplugs, _osdPlugins, PluginDesc::OSD);
    _miscPlugins.clear();
    KTrader::OfferList miplugs = KTrader::self()->query(QString::fromLatin1("kdetv Misc"),
                                                        QString("[X-Kdetv-Plugin-Version] == %1" ).arg(KDETV_PLUGIN_VERSION));
    doScan(cfg, miplugs, _miscPlugins, PluginDesc::MISC);

    cfg->setGroup("VBI Plugins");
    _vbiPlugins.clear();
    KTrader::OfferList vbiplugs = KTrader::self()->query(QString::fromLatin1("kdetv VBI Decoder"),
                                                         QString("[X-Kdetv-Plugin-Version] == %1" ).arg(KDETV_PLUGIN_VERSION));
    doScan(cfg, vbiplugs, _vbiPlugins, PluginDesc::VBI);

    cfg->setGroup("Filter Plugins");
    _filterPlugins.clear();
    KTrader::OfferList filterplugs = KTrader::self()->query(QString::fromLatin1("kdetv Image Filter"),
                                                            QString("[X-Kdetv-Plugin-Version] == %1" ).arg(KDETV_PLUGIN_VERSION));
    doScan(cfg, filterplugs, _filterPlugins, PluginDesc::IMAGEFILTER);
    _postProcessPlugins.clear();
    KTrader::OfferList postprocessplugs = KTrader::self()->query(QString::fromLatin1("kdetv Postprocess Filter"),
                                                                 QString("[X-Kdetv-Plugin-Version] == %1" ).arg(KDETV_PLUGIN_VERSION));
    doScan(cfg, postprocessplugs, _postProcessPlugins, PluginDesc::POSTPROCESS);
}

KdetvMixerPlugin*   PluginFactory::getMixerPlugin(PluginDesc* plugin)
{
    if (!plugin) return 0L;

    assert(plugin->type == PluginDesc::MIXER);
    return static_cast<KdetvMixerPlugin*>(getPluginRefCounted(plugin, false, NULL));
}

KdetvOSDPlugin*     PluginFactory::getOSDPlugin(PluginDesc* plugin, QWidget* o)
{
    if (!plugin) return 0L;

    assert(plugin->type == PluginDesc::OSD);
    return static_cast<KdetvOSDPlugin*>(getPluginRefCounted(plugin, true, o));
}

KdetvMiscPlugin*    PluginFactory::getMiscPlugin(PluginDesc* plugin, QWidget* o)
{
    if (!plugin) return 0L;

    assert(plugin->type == PluginDesc::MISC);
    return static_cast<KdetvMiscPlugin*>(getPluginRefCounted(plugin, true, o));
}

KdetvChannelPlugin* PluginFactory::getChannelPlugin(PluginDesc* plugin)
{
    if (!plugin) return 0L;

    assert(plugin->type == PluginDesc::CHANNEL);
    // channel plugins are always enabled
    plugin->enabled = true;
    return static_cast<KdetvChannelPlugin*>(getPluginRefCounted(plugin, false, NULL));
}

KdetvSourcePlugin*  PluginFactory::getSourcePlugin(PluginDesc* plugin, QWidget* o)
{
    if (!plugin) return 0L;

    assert(plugin->type == PluginDesc::VIDEO);
    return static_cast<KdetvSourcePlugin*>(getPluginRefCounted(plugin, true, o));
}

KdetvVbiPlugin*     PluginFactory::getVbiPlugin(PluginDesc* plugin, QObject* parent)
{
    if (!plugin) return 0L;

    assert(plugin->type == PluginDesc::VBI);
    return static_cast<KdetvVbiPlugin*>(getPluginRefCounted(plugin, true, parent));
}

KdetvFilterPlugin*  PluginFactory::getFilterPlugin(PluginDesc* plugin)
{
    if (!plugin) return 0L;

    assert(plugin->type == PluginDesc::IMAGEFILTER);
    return static_cast<KdetvFilterPlugin*>(getPluginRefCounted(plugin, false, NULL));
}

KdetvFilterPlugin*  PluginFactory::getPostProcessPlugin(PluginDesc* plugin)
{
    if (!plugin) return 0L;

    assert(plugin->type == PluginDesc::POSTPROCESS);
    return static_cast<KdetvFilterPlugin*>(getPluginRefCounted(plugin, false, NULL));
}

void PluginFactory::putPlugin(PluginDesc* plugin)
{
    if (!plugin) return;

    if (plugin->_instance)
        plugin->_refCount--;
    kdDebug() << "PluginFactory: putPlugin(): '" << plugin->name << "' refCount: " << plugin->_refCount << endl;
    if ((plugin->_refCount == 0) && plugin->_instance) {
        if (_actionCollection && _factory) {
            plugin->_instance->removeGUIElements(_factory, _actionCollection);
        }
        delete plugin->_instance;
        plugin->_instance = 0L;
    }
}


void PluginFactory::doScan(KConfig* cfg,
                           KService::List& plugs,
                           QPtrList<PluginDesc>& list,
                           PluginDesc::PluginType type)
{
    for (KService::List::ConstIterator it = plugs.constBegin();
         it != plugs.constEnd();
         ++it) {
        KService::Ptr service = *it;
        if(!service->property("X-Kdetv-Ignore-Plugin").toBool()) {
            PluginDesc *x = new PluginDesc(this);
            x->id    = _upid++;
            x->name = service->property("Name").toString();
            x->author = service->property("X-Kdetv-Plugin-Author").toString();
            x->comment = service->property("Comment").toString();
            x->icon = service->property("Icon").toString();
            x->lib = service->property("X-Kdetv-Plugin-Library").toString();
            x->factory = service->property("X-Kdetv-Plugin-Factory").toString();
            x->service = service;
            x->type = type;
            x->configurable = service->property("X-Kdetv-Configurable").toBool();
            if (x->factory.isEmpty())
                x->factory = x->lib;
            if (!x->factory.startsWith("create_"))
                x->factory = "create_" + x->factory;
            
            if (cfg->hasKey(x->name+"-"+x->author))
                x->enabled = cfg->readBoolEntry(x->name+"-"+x->author);
            else x->enabled = service->property("X-Kdetv-Default-Enabled").toBool();
            
            list.append(x);
            _allPlugins.append(x);
        }
    }
}

KdetvPluginBase* PluginFactory::getPluginRefCounted(PluginDesc* plugin, bool qWidgetArg, QObject* o)
{
    if (plugin->_refCount == 0) {
        plugin->_instance = doGetPlugin(plugin, qWidgetArg, o);
    }
    if (plugin->_instance) plugin->_refCount++;
    kdDebug() << "PluginFactory: getPlugin: '" << plugin->name << "' refCount: " << plugin->_refCount << endl;
    return plugin->_instance;
}

KdetvPluginBase* PluginFactory::doGetPlugin(PluginDesc* plugin, bool qWidgetArg, QObject* o)
{
    KLibLoader*   loader = KLibLoader::self();
    KdetvPluginBase* module = 0L;


    if (!plugin->enabled) return 0L;

    KLibrary *lib = loader->library(QFile::encodeName(QString("kdetv_")+plugin->lib));
    if (lib) {
        void* create = lib->symbol(QFile::encodeName(plugin->factory));
        if (create) {
            if (qWidgetArg) {
                KdetvPluginBase* (*func)(Kdetv*, QObject*);
                func = (KdetvPluginBase* (*)(Kdetv*, QObject*)) create;
                module = func(_ktv, o);
            } else {
                KdetvPluginBase* (*func)(Kdetv*);
                func = (KdetvPluginBase* (*)(Kdetv*)) create;
                module = func(_ktv);
            }

            if (module) {
                module->_description = plugin;
                if (_actionCollection && _factory) {
                    module->installGUIElements(_factory, _actionCollection);
                }
                return module;
            }
        }
    } else {
        kdWarning() << "**************** PluginFactory: Error loading library kdetv_" << plugin->lib << "*****************" << endl;
        kdWarning() << "Error: " << loader->lastErrorMessage() << endl;
        kdWarning() << "kdetv is likely to be crashing soon..." << endl;
    }
    return 0L;
}

void PluginFactory::setGUIFactory(KXMLGUIFactory *guiFactory, KActionCollection *actionCollection)
{
    // allow plugins to remove GUI elements from previous factory
    if (_actionCollection && _factory) {
        for (QPtrListIterator<PluginDesc> it(_allPlugins);
             it.current() != 0;
             ++it) {
            if (it.current()->_instance) {
                it.current()->_instance->removeGUIElements(_factory, _actionCollection);
            }
        }
    }

    _factory = guiFactory;
    _actionCollection = actionCollection;

    // allow plugins to add GUI elements to new factory
    if (_actionCollection && _factory) {
        for (QPtrListIterator<PluginDesc> it(_allPlugins);
             it.current() != 0;
             ++it) {
            if (it.current()->_instance) {
                it.current()->_instance->installGUIElements(_factory, _actionCollection);
            }
        }
    }
}

// ---------------------------------------------------------------------------------------------

PluginDesc::PluginDesc(PluginFactory* factory)
    : id(-1),
      type(UNKNOWN),
      configurable(false),
      enabled(true),
      _refCount(0),
      _instance(0L),
      _factory(factory)
{
}

PluginDesc::~PluginDesc()
{
    if (_instance) {
        // For debug (this may confuse users...)
        //        kdDebug() << "PluginDesc: Destructor: Warning: undeleted plugin: " << name << endl;
        delete _instance;
        _instance = 0;
    }
}

int operator>(const PluginDesc& l, const PluginDesc& r)
{
    return (l.id > r.id) ? 1 : 0;
}

int operator<(const PluginDesc& l, const PluginDesc& r)
{
    return (l.id < r.id) ? 1 : 0;
}

int operator==(const PluginDesc& l, const PluginDesc& r)
{
    return (l.id == r.id) ? 1 : 0;
}

int operator!=(const PluginDesc& l, const PluginDesc& r)
{
    return (l.id == r.id) ? 0 : 1;
}

