/*
 *
 * 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 <kservicetypetrader.h>
#include <ktrader.h>
#include <kconfiggroup.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)
{
}

PluginFactory::~PluginFactory()
{
  for(QList<PluginDesc*>::iterator it = _allPlugins.begin(); it != _allPlugins.end(); it++)
      delete(*it);
  _allPlugins.clear();
}

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

    KConfigGroup channel_cfg(cfg, "Channel Plugins");
    _channelPlugins.clear();
    KService::List cplugs = KServiceTypeTrader::self()->query(QString::fromLatin1("kdetv Channel Format"),
                                                       QString("[X-Kdetv-Plugin-Version] == %1" ).arg(KDETV_PLUGIN_VERSION));
    doScan(&channel_cfg, cplugs, _channelPlugins, PluginDesc::CHANNEL);

    KConfigGroup mix_cfg(cfg, "Mixer Plugins");
    _mixerPlugins.clear();
    KService::List mplugs = KServiceTypeTrader::self()->query(QString::fromLatin1("kdetv Audio Mixer"),
                                                       QString("[X-Kdetv-Plugin-Version] == %1" ).arg(KDETV_PLUGIN_VERSION));
    doScan(&mix_cfg, mplugs, _mixerPlugins, PluginDesc::MIXER);

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

    KConfigGroup vbi_cfg(cfg, "VBI Plugins");
    _vbiPlugins.clear();
    KService::List vbiplugs = KServiceTypeTrader::self()->query(QString::fromLatin1("kdetv VBI Decoder"),
                                                         QString("[X-Kdetv-Plugin-Version] == %1" ).arg(KDETV_PLUGIN_VERSION));
    doScan(&vbi_cfg, vbiplugs, _vbiPlugins, PluginDesc::VBI);

    KConfigGroup filter_cfg(cfg, "Filter Plugins");
    _filterPlugins.clear();
    KService::List filterplugs = KServiceTypeTrader::self()->query(QString::fromLatin1("kdetv Image Filter"),
                                                            QString("[X-Kdetv-Plugin-Version] == %1" ).arg(KDETV_PLUGIN_VERSION));
    doScan(&filter_cfg, filterplugs, _filterPlugins, PluginDesc::IMAGEFILTER);
    _postProcessPlugins.clear();
    KService::List postprocessplugs = KServiceTypeTrader::self()->query(QString::fromLatin1("kdetv Postprocess Filter"),
                                                                 QString("[X-Kdetv-Plugin-Version] == %1" ).arg(KDETV_PLUGIN_VERSION));
    doScan(&filter_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--;
    kDebug() << plugin->name << "' refCount: " << plugin->_refCount;
    if ((plugin->_refCount == 0) && plugin->_instance) {
        if (_actionCollection && _factory) {
            plugin->_instance->removeGUIElements(_factory, _actionCollection);
        }
        delete plugin->_instance;
        plugin->_instance = 0L;
    }
}


void PluginFactory::doScan(KConfigGroup* cfg,
                           KService::List& plugs,
                           QList<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 = true;
                // FIXME :
                /* x->enabled = cfg->readEntry(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++;
    kDebug() << plugin->name << "' refCount: " << plugin->_refCount;
    return plugin->_instance;
}

KdetvPluginBase* PluginFactory::doGetPlugin(PluginDesc* plugin, bool qWidgetArg, QObject* o)
{
#warning "Port me against KPluginLoader"
    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->resolveSymbol(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 {
        kWarning() << "**************** PluginFactory: Error loading library kdetv_" << plugin->lib << "*****************";
        kWarning() << "Error: " << loader->lastErrorMessage();
        kWarning() << "kdetv is likely to be crashing soon...";
    }
    return 0L;
}

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

    _factory = guiFactory;
    _actionCollection = actionCollection;

    // allow plugins to add GUI elements to new factory
    if (_actionCollection && _factory) {
        for (QList<PluginDesc*>::iterator it = _allPlugins.begin();
             it != _allPlugins.end();
             ++it) {
            if ((*it)->_instance) {
                (*it)->_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...)
        //        kDebug() << "Warning: undeleted plugin: " << name;
        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;
}

