// 
//   Copyright (C) 2005, 2006 Free Software Foundation, Inc.
// 
// 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 St, Fifth Floor, Boston, MA  02110-1301  USA
// 
// Linking Gnash statically or dynamically with other modules is making
// a combined work based on Gnash. Thus, the terms and conditions of
// the GNU General Public License cover the whole combination.
// 
// In addition, as a special exception, the copyright holders of Gnash give
// you permission to combine Gnash with free software programs or
// libraries that are released under the GNU LGPL and/or with Mozilla, 
// so long as the linking with Mozilla, or any variant of Mozilla, is
// through its standard plug-in interface. You may copy and distribute
// such a system following the terms of the GNU GPL for Gnash and the
// licenses of the other code concerned, provided that you include the
// source code of that other code when and as the GNU GPL requires
// distribution of source code. 
// 
// Note that people who make modified versions of Gnash are not obligated
// to grant this special exception for their modified versions; it is
// their choice whether to do so.  The GNU General Public License gives
// permission to release a modified version without this exception; this
// exception also makes it possible to release a modified version which
// carries forward this exception.
//
//
// Written by Koos Vriezen <koos ! vriezen ? xs4all ! nl>

#ifdef KDE_USE_FINAL
#undef Always
#endif

#include <config.h>
#include <qcstring.h>
#include <qpopupmenu.h>
#include <qtimer.h>

class KXMLGUIClient; // workaround for kde3.3 on sarge with gcc4, kactioncollection.h does not forward declare KXMLGUIClient
#include <klibloader.h>
#include <kdebug.h>
#include <kconfig.h>
#include <ksimpleconfig.h>
#include <kaction.h>
#include <kapplication.h>
#include <klocale.h>
#include <kinstance.h>
#include <kparts/factory.h>
#include <kstaticdeleter.h>

#include "klash_part.h"

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

class KlashFactory : public KParts::Factory {
public:
    KlashFactory ();
    virtual ~KlashFactory ();
    virtual KParts::Part *createPartObject
        (QWidget *wparent, const char *wname,
         QObject *parent, const char *name,
         const char *className, const QStringList &args);
    static KInstance * instance () { return s_instance; }
private:
    static KInstance * s_instance;
};

K_EXPORT_COMPONENT_FACTORY (libklashpart, KlashFactory)

KInstance *KlashFactory::s_instance = 0;

KDE_NO_CDTOR_EXPORT KlashFactory::KlashFactory () {
    s_instance = new KInstance ("klash");
}

KDE_NO_CDTOR_EXPORT KlashFactory::~KlashFactory () {
    delete s_instance;
}

KDE_NO_EXPORT KParts::Part *KlashFactory::createPartObject
  (QWidget *wparent, const char *wname,
   QObject *parent, const char * name,
   const char * cls, const QStringList & args) {
      //kdDebug() << "KlashFactory::createPartObject " << cls << endl;
      return new KlashPart (wparent, wname, parent, name, args);
}

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

static bool getBoolValue (const QString & value) {
    return (value.lower() != QString::fromLatin1("false") &&
            value.lower() != QString::fromLatin1("off") &&
            value.lower() != QString::fromLatin1("0"));
}

KDE_NO_CDTOR_EXPORT KlashPart::KlashPart (QWidget * wparent, const char *wname,
                    QObject * parent, const char *name, const QStringList &args)
 : KParts::ReadOnlyPart (parent, name),
   //new KSimpleConfig ("klashrc")),
   m_browserextension (new KlashBrowserExtension (this)),
   m_liveconnectextension (new KlashLiveConnectExtension (this)),
   m_process (0L),
   m_width (0),
   m_height (0),
   m_autostart (false),
   m_fullscreen (false),
   m_started_emited (false) {
    //kdDebug () << "KlashPart(" << this << ")::KlashPart ()" << endl;
    bool show_fullscreen = false;
    setInstance (KlashFactory::instance (), true);
    /*KAction *playact =*/ new KAction(i18n("P&lay"), QString ("player_play"), KShortcut (), this, SLOT(play ()), actionCollection (), "play");
    /*KAction *pauseact =*/ new KAction(i18n("&Pause"), QString ("player_pause"), KShortcut (), this, SLOT(pause ()), actionCollection (), "pause");
    /*KAction *stopact =*/ new KAction(i18n("&Stop"), QString ("player_stop"), KShortcut (), this, SLOT(stop ()), actionCollection (), "stop");
    //new KAction (i18n ("Increase Volume"), QString ("player_volume"), KShortcut (), this, SLOT (increaseVolume ()), actionCollection (), "edit_volume_up");
    //new KAction (i18n ("Decrease Volume"), QString ("player_volume"), KShortcut (), this, SLOT (decreaseVolume ()), actionCollection (), "edit_volume_down");
    QStringList::const_iterator end = args.end ();
    for (QStringList::const_iterator it = args.begin () ; it != end; ++it) {
        int equalPos = (*it).find("=");
        if (equalPos > 0) {
            QString name = (*it).left (equalPos).lower ();
            QString value = (*it).right ((*it).length () - equalPos - 1);
            if (value.at(0)=='\"')
                value = value.right (value.length () - 1);
            if (value.at (value.length () - 1) == '\"')
                value.truncate (value.length () - 1);
            //kdDebug () << "name=" << name << " value=" << value << endl;
            if (name == QString::fromLatin1("width")) {
                m_width = value.toInt ();
            } else if (name == QString::fromLatin1("height")) {
                m_height = value.toInt ();
            //} else if (name == QString::fromLatin1("type")) {
            } else if (name == QString::fromLatin1("__khtml__pluginbaseurl")) {
                m_docbase = KURL (value);
            } else if (name == QString::fromLatin1("src")) {
                m_src_url = value;
            } else if (name == QString::fromLatin1 ("fullscreenmode")) {
                show_fullscreen = getBoolValue (value);
            } else if (name == QString::fromLatin1 ("autostart")) {
                bool ok;
                m_autostart = value.toInt (&ok);
                if (!ok)
                    m_autostart = (value.lower () == "false");
	    }
        }
    }
    KParts::Part::setWidget (new KlashView (wparent));
    setXMLFile("klashpartui.rc");
    setProgressInfoEnabled (false);

    if (m_fullscreen)
        fullScreen ();
}

KDE_NO_CDTOR_EXPORT KlashPart::~KlashPart () {
    kdDebug() << "KlashPart::~KlashPart" << endl;
    stop ();
    //delete m_config;
    //m_config = 0L;
}

KDE_NO_EXPORT bool KlashPart::allowRedir (const KURL & url) const {
    return kapp->authorizeURLAction ("redirect", m_docbase, url);
}

KDE_NO_EXPORT void KlashPart::play () {
    stop ();
    if (m_src_url.isEmpty ())
        return;
    m_process = new KProcess;
    m_process->setUseShell (true);
    m_process->setEnvironment (QString::fromLatin1 ("SESSION_MANAGER"), QString::fromLatin1 (""));
    QString dim;
    if (m_width > 0 && m_height > 0)
        dim = QString ("-j ") + QString::number (m_width) +
            QString (" -k ") + QString::number (m_height);
    QString cmd = QString ("gnash -x ") +
        QString::number (static_cast <KlashView *> (widget ())->embedId()) +
        QChar (' ') + dim +
        QChar (' ') + KProcess::quote (m_src_url);
    kdDebug () << cmd << endl;
    *m_process << cmd;
    connect (m_process, SIGNAL (processExited (KProcess *)),
            this, SLOT (processStopped (KProcess *)));
    m_process->start (KProcess::NotifyOnExit, KProcess::NoCommunication);
}

KDE_NO_EXPORT void KlashPart::pause () {
}

KDE_NO_EXPORT void KlashPart::stop () {
    if (m_process) {
        if (m_process->isRunning ()) {
            ; // IPC close
            //m_process->wait(2);
            void (*oldhandler)(int) = signal(SIGTERM, SIG_IGN);
            ::kill (-1 * ::getpid (), SIGTERM);
            signal(SIGTERM, oldhandler);
            m_process->wait(2);
        }
        delete m_process;
        m_process = 0l;
    }
}

bool KlashPart::openFile() {
    if (!m_file.isEmpty ())
        m_src_url = m_file;
    play ();
    return true;
}

KDE_NO_EXPORT bool KlashPart::openURL (const KURL & url) {
    kdDebug () << "KlashPart::openURL " << url.url() << endl;
    emit started (0);
    return KParts::ReadOnlyPart::openURL (url);
}

KDE_NO_EXPORT bool KlashPart::closeURL () {
    return KParts::ReadOnlyPart::closeURL ();
}

KDE_NO_EXPORT void KlashPart::fullScreen () {
}

KDE_NO_EXPORT void KlashPart::setLoaded (int percentage) {
    if (percentage < 100) {
        m_browserextension->setLoadingProgress (percentage);
        m_browserextension->infoMessage
            (QString::number (percentage) + i18n ("% Cache fill"));
    }
}

KDE_NO_EXPORT void KlashPart::playingStarted () {
    //kdDebug () << "KlashPart::processStartedPlaying " << endl;
    //if (m_settings->sizeratio && !m_noresize && m_source->width() > 0 && m_source->height() > 0)
    //    m_liveconnectextension->setSize (m_source->width(), m_source->height());
    m_browserextension->setLoadingProgress (100);
    if (m_started_emited) {
        emit completed ();
        m_started_emited = false;
    }
    m_liveconnectextension->started ();
    m_browserextension->infoMessage (i18n("Klash: Playing"));
}

KDE_NO_EXPORT void KlashPart::playingStopped () {
    if (m_started_emited) {
        m_started_emited = false;
        m_browserextension->setLoadingProgress (100);
        emit completed ();
    }
    m_liveconnectextension->finished ();
    m_browserextension->infoMessage (i18n ("Klash: Stop Playing"));
}

KDE_NO_EXPORT void KlashPart::processStopped (KProcess *) {
    QTimer::singleShot (0, this, SLOT (playingStopped ()));
}

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

KDE_NO_CDTOR_EXPORT KlashBrowserExtension::KlashBrowserExtension (KlashPart * parent)
  : KParts::BrowserExtension (parent, "Klash Browser Extension") {
}

KDE_NO_EXPORT void KlashBrowserExtension::urlChanged (const QString & url) {
    emit setLocationBarURL (url);
}

KDE_NO_EXPORT void KlashBrowserExtension::setLoadingProgress (int percentage) {
    emit loadingProgress (percentage);
}

KDE_NO_EXPORT void KlashBrowserExtension::saveState (QDataStream & stream) {
    stream << static_cast <KlashPart *> (parent ())->source ();
}

KDE_NO_EXPORT void KlashBrowserExtension::restoreState (QDataStream & stream) {
    QString url;
    stream >> url;
    static_cast <KlashPart *> (parent ())->openURL (KURL(url));
}

KDE_NO_EXPORT void KlashBrowserExtension::requestOpenURL (const KURL & url, const QString & target, const QString & service) {
    KParts::URLArgs args;
    args.frameName = target;
    args.serviceType = service;
    emit openURLRequest (url, args);
}

//---------------------------------------------------------------------
/*
 * add
 * .error.errorCount
 * .error.item(count)
 *   .errorDescription
 *   .errorCode
 * .controls.stop()
 * .controls.play()
 */

enum JSCommand {
    notsupported,
    isfullscreen, isloop,
    length, width, height, position, prop_source, source, setsource,
    play, jsc_pause, start, stop,
    prop_volume, volume, setvolume
};

struct KLASH_NO_EXPORT JSCommandEntry {
    const char * name;
    JSCommand command;
    const char * defaultvalue;
    const KParts::LiveConnectExtension::Type rettype;
};

// keep this list in alphabetic order
// http://service.real.com/help/library/guides/realonescripting/browse/htmfiles/embedmet.htm
static const JSCommandEntry JSCommandList [] = {
    { "GetSource", source, 0L, KParts::LiveConnectExtension::TypeString },
    { "GetTitle", notsupported, "title", KParts::LiveConnectExtension::TypeString },
    { "GetVolume", volume, "100", KParts::LiveConnectExtension::TypeNumber },
    { "Pause", jsc_pause, 0L, KParts::LiveConnectExtension::TypeBool },
    { "Play", play, 0L, KParts::LiveConnectExtension::TypeBool },
    { "SetSource", setsource, 0L, KParts::LiveConnectExtension::TypeBool },
    { "SetVolume", setvolume, "true", KParts::LiveConnectExtension::TypeBool },
    { "Start", start, 0L, KParts::LiveConnectExtension::TypeBool },
    { "Stop", stop, 0L, KParts::LiveConnectExtension::TypeBool },
    { "Volume", prop_volume, "100", KParts::LiveConnectExtension::TypeNumber },
    { "pause", jsc_pause, 0L, KParts::LiveConnectExtension::TypeBool },
    { "play", play, 0L, KParts::LiveConnectExtension::TypeBool },
    { "stop", stop, 0L, KParts::LiveConnectExtension::TypeBool },
    { "volume", volume, 0L, KParts::LiveConnectExtension::TypeBool },
};

static const JSCommandEntry * getJSCommandEntry (const char * name, int start = 0, int end = sizeof (JSCommandList)/sizeof (JSCommandEntry)) {
    if (end - start < 2) {
        if (start != end && !strcasecmp (JSCommandList[start].name, name))
            return &JSCommandList[start];
        return 0L;
    }
    int mid = (start + end) / 2;
    int cmp = strcasecmp (JSCommandList[mid].name, name);
    if (cmp < 0)
        return getJSCommandEntry (name, mid + 1, end);
    if (cmp > 0)
        return getJSCommandEntry (name, start, mid);
    return &JSCommandList[mid];
}

KDE_NO_CDTOR_EXPORT KlashLiveConnectExtension::KlashLiveConnectExtension (KlashPart * parent)
  : KParts::LiveConnectExtension (parent), player (parent),
    lastJSCommandEntry (0L),
    m_started (false),
    m_enablefinish (false) {
      connect (parent, SIGNAL (started (KIO::Job *)), this, SLOT (started ()));
}

KDE_NO_CDTOR_EXPORT KlashLiveConnectExtension::~KlashLiveConnectExtension() {
    //kdDebug () << "KlashLiveConnectExtension::~KlashLiveConnectExtension()" << endl;
}

KDE_NO_EXPORT void KlashLiveConnectExtension::started () {
    m_started = true;
}

KDE_NO_EXPORT void KlashLiveConnectExtension::finished () {
    if (m_started && m_enablefinish) {
        KParts::LiveConnectExtension::ArgList args;
        args.push_back (qMakePair (KParts::LiveConnectExtension::TypeString, QString("if (window.onFinished) onFinished();")));
        emit partEvent (0, "eval", args);
        m_started = true;
        m_enablefinish = false;
    }
}

KDE_NO_EXPORT bool KlashLiveConnectExtension::get
  (const unsigned long id, const QString & name,
   KParts::LiveConnectExtension::Type & type,
   unsigned long & rid, QString & rval) {
    const char * str = name.ascii ();
    kdDebug () << "[01;35mget[00m " << str << endl;
    const JSCommandEntry * entry = getJSCommandEntry (str);
    if (!entry)
        return false;
    rid = id;
    type = entry->rettype;
    switch (entry->command) {
        case prop_source:
            type = KParts::LiveConnectExtension::TypeString;
            rval = player->source ();
            break;
        case prop_volume:
            //if (player->view ())
            //    rval = QString::number (??);
            break;
        default:
            lastJSCommandEntry = entry;
            type = KParts::LiveConnectExtension::TypeFunction;
    }
    return true;
}

KDE_NO_EXPORT bool KlashLiveConnectExtension::put
  (const unsigned long, const QString & name, const QString & val) {
    kdDebug () << "[01;35mput[00m " << name << "=" << val << endl;
    const JSCommandEntry * entry = getJSCommandEntry (name.ascii ());
    if (!entry)
        return false;
    switch (entry->command) {
        case prop_source: {
            KURL url (val);
            if (player->allowRedir (url))
                player->openURL (url);
            break;
        }
        case prop_volume:
            //if (player->view ())
            //    ?? (val.toInt ());
            break;
        default:
            return false;
    }
    return true;
}

KDE_NO_EXPORT bool KlashLiveConnectExtension::call
  (const unsigned long id, const QString & name,
   const QStringList & args, KParts::LiveConnectExtension::Type & type,
   unsigned long & rid, QString & rval) {
    const JSCommandEntry * entry = lastJSCommandEntry;
    const char * str = name.ascii ();
    if (!entry || strcmp (entry->name, str))
        entry = getJSCommandEntry (str);
    if (!entry)
        return false;
    kdDebug () << "[01;35mentry[00m " << entry->name << endl;
    for (unsigned int i = 0; i < args.size (); ++i)
        kdDebug () << "      " << args[i] << endl;
    //Klash::View * view = static_cast <Klash::View*> (player->widget ());
    //if (!view)
    //    return false;
    rid = id;
    type = entry->rettype;
    switch (entry->command) {
        case notsupported:
            if (entry->rettype != KParts::LiveConnectExtension::TypeVoid)
                rval = entry->defaultvalue;
            break;
        case play:
            if (args.size ()) {
                KURL url (args.first ());
                if (player->allowRedir (url))
                    ;//player->openNewURL (url);
            } else
                player->play ();
            rval = "true";
            break;
        case start:
            player->play ();
            rval = "true";
            break;
        case stop:
            player->stop ();
            rval = "true";
            break;
        case jsc_pause:
            player->pause ();
            rval = "true";
            break;
        case length:
            rval.setNum (0 /*player->source ()->length ()*/);
            break;
        case width:
            rval.setNum (player->width ());
            break;
        case height:
            rval.setNum (player->height ());
            break;
        case setsource:
            rval ="false";
            if (args.size ()) {
                KURL url (args.first ());
                if (player->allowRedir (url) && player->openURL (url))
                    rval = "true";
            }
            break;
        case setvolume:
            if (!args.size ())
                return false;
            //?? (args.first ().toInt ());
            rval = "true";
            break;
        case source:
            rval = player->url ().url ();
            break;
        case volume:
            if (player->widget ())
                rval = QString::number (0);
            break;
        default:
            return false;
    }
    return true;
}

KDE_NO_EXPORT void KlashLiveConnectExtension::unregister (const unsigned long) {
}

KDE_NO_EXPORT void KlashLiveConnectExtension::setSize (int w, int h) {
    QCString jscode;
    //jscode.sprintf("this.width=%d;this.height=%d;klash", w, h);
    KParts::LiveConnectExtension::ArgList args;
    args.push_back (qMakePair (KParts::LiveConnectExtension::TypeString, QString("width")));
    args.push_back (qMakePair (KParts::LiveConnectExtension::TypeNumber, QString::number (w)));
    emit partEvent (0, "this.setAttribute", args);
    args.clear();
    args.push_back (qMakePair (KParts::LiveConnectExtension::TypeString, QString("height")));
    args.push_back (qMakePair (KParts::LiveConnectExtension::TypeNumber, QString::number (h)));
    emit partEvent (0, "this.setAttribute", args);
}

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

#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
static const int XKeyPress = KeyPress;
#undef KeyPress
#undef Always
#undef Never
#undef Status
#undef Unsorted
#undef Bool

KlashEmbed::KlashEmbed (KlashView * view) : QXEmbed (view), m_view (view) {
    setProtocol (QXEmbed::XPLAIN);
    int scr = DefaultScreen (qt_xdisplay ());
    embed (XCreateSimpleWindow (qt_xdisplay(), view->winId (), 0, 0, width(), height(), 1, BlackPixel (qt_xdisplay(), scr), BlackPixel (qt_xdisplay(), scr)));
    XClearWindow (qt_xdisplay(), embeddedWinId ());
}

KlashEmbed::~KlashEmbed () {}

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

KlashView::KlashView (QWidget * parent)
    : QWidget (parent, "kde_klash_part"), m_embed (new KlashEmbed (this)) {}

KlashView::~KlashView () {}

void KlashView::resizeEvent (QResizeEvent *) {
    m_embed->setGeometry (0, 0, width (), height ());
}

WId KlashView::embedId () {
    return m_embed->embeddedWinId ();
}

#include "klash_part.moc"
