/*

  This is an encapsulation of the  Netscape plugin API.


  Copyright (c) 2000 Matthias Hoelzer-Kluepfel <hoelzer@kde.org>
                     Stefan Schimanski <1Stein@gmx.de>
  Copyright (c) 2002-2005 George Staikos <staikos@kde.org>

  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.

  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 General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

*/

#include "nspluginloader.h"
#include "nspluginloader.moc"

#include <QDir>
#include <QGridLayout>
#include <QResizeEvent>


#include <kapplication.h>
#include <kprocess.h>
#include <kdebug.h>
#include <kglobal.h>
#include <klocale.h>
#include <kstandarddirs.h>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <QLayout>
#include <QObject>
#include <QPushButton>
#include <QtGui/QX11EmbedContainer>
#include <QTextStream>
#include <QRegExp>
#include <QTimer>
#include <QFrame>

#include "nsplugins_class_interface.h"
#include "nsplugins_instance_interface.h"
#include "nsplugins_viewer_interface.h"

#include <kdefakes.h>

#include <X11/Xlib.h>
#include <unistd.h>


NSPluginLoader *NSPluginLoader::s_instance = 0;
int NSPluginLoader::s_refCount = 0;


NSPluginInstance::NSPluginInstance(QWidget *parent, const QString& viewerDBusId, const QString& id, const KUrl& baseUrl)
  : EMBEDCLASS(parent), _loader(0), inited(false), haveSize(false), _frame(0)
{
    setWindowTitle("nsp.host"); // for debugging..
    _instanceInterface = new org::kde::nsplugins::Instance( viewerDBusId, id, QDBusConnection::sessionBus() );

    QGridLayout *_layout = new QGridLayout(this);
    _layout->setMargin(1);
    _layout->setSpacing(1);
    KConfig _cfg( "kcmnspluginrc" );
    KConfigGroup cfg(&_cfg, "Misc");
    if (cfg.readEntry("demandLoad", false)) {
        KSharedConfigPtr config = KSharedConfig::openConfig("konquerorrc");
        KConfigGroup settings(config, "Java/JavaScript Settings");
        if (settings.readEntry("PluginDomains", QStringList()).contains(baseUrl.host())) {
            KConfigGroup pluginDomains(config, baseUrl.host());
            if (pluginDomains.readEntry("plugins.EnablePlugins", false)) {
                return;
            }
        }
        _frame = new QFrame(this);
        _frame->setFrameShape(QFrame::Box);
        _frame->setFrameShadow(QFrame::Plain);
        _frame->setLineWidth(1);
        _layout->addWidget(_frame, 0, 0);
        QVBoxLayout *vlay = new QVBoxLayout(_frame);
        QPushButton *startPluginButton = new QPushButton(i18n("Start Plugin"), _frame);
        vlay->addWidget(startPluginButton);
        connect(startPluginButton, SIGNAL(clicked()), this, SLOT(loadPlugin()));
        show();
    }
}

void NSPluginInstance::loadPlugin()
{
    delete _frame;
    _frame = 0;
    doLoadPlugin(width(), height());
}

void NSPluginInstance::doLoadPlugin(int w, int h) {
    if (!inited && !_frame) {
        _loader = NSPluginLoader::instance();
        // resize before showing, some plugins are stupid and can't handle repeated
        // NPSetWindow() calls very well (viewer will avoid the call if not shown yet)
        qApp->syncX();
        _instanceInterface->setupWindow(winId(), w, h);
        inited = true;
    }
}

NSPluginInstance::~NSPluginInstance()
{
   kDebug() << "-> NSPluginInstance::~NSPluginInstance";
   _instanceInterface->shutdown();
   kDebug() << "release";
   if (_loader)
        _loader->release();
   kDebug() << "<- NSPluginInstance::~NSPluginInstance";
}


void NSPluginInstance::windowChanged(WId w)
{
    if (w == 0) {
        // FIXME: Put a notice here to tell the user that it crashed.
        repaint();
    }
}

void NSPluginInstance::embedIfNeeded(int w, int h)
{
    if (isVisible()) {
        if (haveSize && !inited)
            doLoadPlugin(w, h);
        else if (inited)
            resizePlugin(w, h);
    }
}

void NSPluginInstance::resizeEvent(QResizeEvent *event)
{
    kDebug() << width() << height() << isVisible() << haveSize << inited;
    EMBEDCLASS::resizeEvent(event);
    haveSize = true;

    embedIfNeeded(width(), height());
}

void NSPluginInstance::showEvent(QShowEvent *event)
{
    kDebug() << width() << height() << isVisible() << haveSize << inited;
    EMBEDCLASS::showEvent(event);

    embedIfNeeded(width(), height());
}

void NSPluginInstance::javascriptResult(int id, const QString &result)
{
    _instanceInterface->javascriptResult( id, result );
}

void NSPluginInstance::focusInEvent( QFocusEvent* event )
{
  Q_UNUSED( event );
  _instanceInterface->gotFocusIn();
}

void NSPluginInstance::focusOutEvent( QFocusEvent* event )
{
  Q_UNUSED( event );
  _instanceInterface->gotFocusOut();
}

void NSPluginInstance::resizePlugin( int w, int h )
{
  qApp->syncX();
  _instanceInterface->resizePlugin( clientWinId(), w, h );
}

/*******************************************************************************/


NSPluginLoader::NSPluginLoader()
   : QObject(), _mapping(), _viewer(0)
{
  scanPlugins();
}


NSPluginLoader *NSPluginLoader::instance()
{
  if (!s_instance)
    s_instance = new NSPluginLoader;

  s_refCount++;
  kDebug() << "NSPluginLoader::instance -> " <<  s_refCount;

  return s_instance;
}


void NSPluginLoader::release()
{
   s_refCount--;
   kDebug() << "NSPluginLoader::release -> " <<  s_refCount;

   if (s_refCount==0)
   {
      delete s_instance;
      s_instance = 0;
   }
}


NSPluginLoader::~NSPluginLoader()
{
   kDebug() << "-> NSPluginLoader::~NSPluginLoader";
   unloadViewer();
   kDebug() << "<- NSPluginLoader::~NSPluginLoader";
}


void NSPluginLoader::scanPlugins()
{
  QRegExp version(";version=[^:]*:");

  // open the cache file
  QFile cachef(KStandardDirs::locate("data", "nsplugins/cache"));
  if (!cachef.open(QIODevice::ReadOnly)) {
      kDebug() << "Could not load plugin cache file!";
      return;
  }

  QTextStream cache(&cachef);

  // read in cache
  QString line, plugin;
  while (!cache.atEnd()) {
      line = cache.readLine();
      if (line.isEmpty() || (line.left(1) == "#"))
        continue;

      if (line.left(1) == "[")
        {
          plugin = line.mid(1,line.length()-2);
          continue;
        }

      QStringList desc = line.split(':', QString::KeepEmptyParts);
      // avoid crash on broken lines
      if (desc.size()<2)
        continue;

      QString mime = desc[0].trimmed();
      QStringList suffixes;
      // If there are no suffixes, this would cause a crash
      if (desc.count() > 1)
        suffixes = desc[1].trimmed().split(',');
      if (!mime.isEmpty())
        {
          // insert the mimetype -> plugin mapping
          _mapping.insert(mime, QString(plugin));

          // insert the suffix -> mimetype mapping
          QStringList::Iterator suffix;
          for (suffix = suffixes.begin(); suffix != suffixes.end(); ++suffix) {

              // strip whitspaces and any preceding '.'
              QString stripped = (*suffix).trimmed();

              int p=0;
              for ( ; p<stripped.length() && stripped[p]=='.'; p++ ) {
                  ;
              }
              stripped = stripped.right( stripped.length()-p );

              // add filetype to list
              if ( !stripped.isEmpty() && !_filetype.contains(stripped) )
                  _filetype.insert( stripped, QString(mime));
          }
        }
    }
}


QString NSPluginLoader::lookupMimeType(const QString &url)
{
   QString result;
   QHashIterator<QString, QString> dit2(_filetype);
   while ( dit2.hasNext() ) {
    dit2.next();
    QString ext = QString(".")+dit2.key();
    if (url.right(ext.length()) == ext) {
        result = dit2.value();
        break;
    }
    }
    
   return result;
}


QString NSPluginLoader::lookup(const QString &mimeType)
{
    QString plugin;
    if (  _mapping.contains(mimeType) )
        plugin = _mapping.value(mimeType);

  kDebug() << "Looking up plugin for mimetype " << mimeType << ": " << plugin;

  return plugin;
}


bool NSPluginLoader::loadViewer()
{
   kDebug() << "NSPluginLoader::loadViewer";

   _process.clearProgram();
   // get the dbus app id
   int pid = (int)getpid();
   QString tmp;
   tmp.sprintf("org.kde.nspluginviewer-%d",pid);
   _viewerDBusId =tmp.toLatin1();

   connect( &_process, SIGNAL(finished(int, QProcess::ExitStatus)),
            this, SLOT(processTerminated()) );

    QTime t;
    t.start();

   // find the external viewer process
   QString viewer = KGlobal::dirs()->findExe("nspluginviewer");
   if (viewer.isEmpty())
   {
      kDebug() << "can't find nspluginviewer";
      return false;
   }

   bool runInValgrind = !qgetenv("VALGRIND_NSPLUGINVIEWER").isEmpty();
   bool runInGdb      = !qgetenv("GDB_NSPLUGINVIEWER").isEmpty();

   if (runInValgrind || runInGdb) {
      _process << "konsole";
      _process << "--nofork";
      _process << "--noclose";   
      _process << "-e";

      if (runInValgrind) {
         _process << "valgrind";
         _process << "--num-callers=50";
      } else {
         _process << "gdb";
         _process << "--args";
      }
   }

   _process << viewer;

   _process << "-dbusservice";
   _process << _viewerDBusId;

   // run the process
   kDebug() << "Running nspluginviewer";
   _process.start();

   // wait for the process to run
   int cnt = 0;
   while (!QDBusConnection::sessionBus().interface()->isServiceRegistered(_viewerDBusId))
   {
      //kapp->processEvents(); // would lead to recursive calls in khtml
      usleep( 50*1000 );
      cnt++;
      if ( cnt >= 100 && !runInValgrind && !runInGdb )
      {
         kDebug() << "timeout";
         _process.kill();
         return false;
      }

      if (_process.state() == QProcess::NotRunning)
      {
         kDebug() << "nspluginviewer terminated";
         return false;
      }
   }

   // get viewer dcop interface
   _viewer = new org::kde::nsplugins::Viewer( _viewerDBusId, "/Viewer", QDBusConnection::sessionBus() );

   // make sure we have the types setup
   kdeNsPluginViewer::initDBusTypes();

   kDebug() << "nspluginviewer startup took:" << t.elapsed() << "ms";

   return _viewer!=0;
}


void NSPluginLoader::unloadViewer()
{
   kDebug() << "-> NSPluginLoader::unloadViewer";

   if ( _viewer )
   {
      _viewer->shutdown();
      kDebug() << "Shutdown viewer";
      delete _viewer;
      _process.kill();
      _viewer = 0;
   }

   kDebug() << "<- NSPluginLoader::unloadViewer";
}




void NSPluginLoader::processTerminated()
{
      kDebug() << "Viewer process  terminated";
      delete _viewer;
      _viewer = 0;
}


NSPluginInstance *NSPluginLoader::newInstance(QWidget *parent, const QString& url,
                                              const QString& mimeType, bool embed,
                                              const QStringList& _argn, const QStringList& _argv,
                                              const QString& ownDBusId, const QString& callbackId, bool reload )
{
   kDebug() << "-> NSPluginLoader::NewInstance( parent=" << (void*)parent << ", url=" << url << ", mime=" << mimeType << ", " << _argn << _argv << ")";

   if ( !_viewer )
   {
      // load plugin viewer process
      loadViewer();

      if ( !_viewer )
      {
         kDebug() << "No viewer dbus stub found";
         return 0;
      }
   }

   kDebug() << "-> ownID" << ownDBusId << " viewer ID:" << _viewerDBusId;

   QStringList argn( _argn );
   QStringList argv( _argv );

   // check the mime type
   QString mime = mimeType;
   if (mime.isEmpty())
   {
      mime = lookupMimeType( url );
      argn << "MIME";
      argv << mime;
   }
   if (mime.isEmpty())
   {
      kDebug() << "Unknown MimeType";
      return 0;
   }

   // lookup plugin for mime type
   QString plugin_name = lookup(mime);
   if (plugin_name.isEmpty())
   {
      kDebug() << "No suitable plugin";
      return 0;
   }

   // get plugin class object
   QDBusObjectPath cls_ref = _viewer->newClass( plugin_name, ownDBusId );
   if ( cls_ref.path() == QLatin1String("/null") || cls_ref.path().isEmpty() )
   {
      kDebug() << "Couldn't create plugin class";
      return 0;
   }

   org::kde::nsplugins::Class* cls = new org::kde::nsplugins::Class( _viewerDBusId, cls_ref.path(), QDBusConnection::sessionBus() );

   // handle special plugin cases
   if ( mime=="application/x-shockwave-flash" )
       embed = true; // flash doesn't work in full mode :(


   // get plugin instance
   QDBusObjectPath inst_ref = cls->newInstance( url, mime, embed, argn, argv, ownDBusId, callbackId, reload );
   if ( inst_ref.path() == QLatin1String("/null") || inst_ref.path().isEmpty() )
   {
      kDebug() << "Couldn't create plugin instance";
      delete cls;
      return 0;
   }

   KUrl baseUrl;
   const int indexOfBaseurl = argn.indexOf( "__KHTML__PLUGINBASEURL" );
   if ( indexOfBaseurl > 0 ) {
      baseUrl = argv.at( indexOfBaseurl );
   }

   NSPluginInstance *plugin = new NSPluginInstance( parent, _viewerDBusId, inst_ref.path(), baseUrl);

   kDebug() << "<- NSPluginLoader::NewInstance = " << (void*)plugin;

   delete cls;
   return plugin;
}

// vim: ts=4 sw=4 et
// kate: indent-width 4; replace-tabs on; tab-width 4; space-indent on;
