/***************************************************************************
 * plugin.cpp
 * This file is part of the KDE project
 * copyright (C)2007 by Sebastian Sauer (mail@dipe.org)
 *
 * 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 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
 * Library General Public License for more details.
 * You should have received a copy of the GNU Library General Public License
 * along with this program; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 ***************************************************************************/

#include "plugin.h"

#include "../core/manager.h"
#include "../core/object.h"

#include <QByteArray>
#include <QUrl>
#include <QColor>
#include <QRect>
#include <QPoint>
#include <QSize>
#include <QWidget>
#include <QLayout>
#include <QVBoxLayout>
#include <QUiLoader>
#include <QAbstractItemView>
#include <QAbstractItemModel>
#include <QStringListModel>
#include <QScriptClass>
#include <QScriptContext>
#include <QScriptValueIterator>
#include <QDebug>

#include <kurl.h>
#include <klocale.h>

using namespace Kross;

namespace Kross {

    /**
    * The EcmaObject implements QScriptClass and Kross::Object to provide
    * custom behavior to QtScript objects.
    */
    class EcmaObject : public QScriptClass, public Kross::Object
    {
        public:
            explicit EcmaObject(QScriptEngine* engine, const QScriptValue& object = QScriptValue())
                : QScriptClass(engine), Kross::Object(), m_object(object) {}
            virtual ~EcmaObject() {}
            virtual QString name() const { return "KrossObject"; }
            virtual QScriptValue prototype() const { return m_object; }

            virtual QVariant callMethod(const QString& name, const QVariantList& args = QVariantList())
            {
                QScriptValue function = m_object.property(name);
                if( ! function.isFunction() ) {
                    krosswarning( QString("EcmaScript::callFunction No such function \"%1\"").arg(name) );
                    if( QScriptContext* context = engine()->currentContext() )
                        context->throwError(QScriptContext::ReferenceError, i18n("No such function \"%1\"", name));
                    return QVariant();
                }
                QScriptValueList arguments;
                foreach(const QVariant &v, args)
                    arguments << engine()->toScriptValue(v);
                QScriptValue result = function.call(m_object, arguments);
                return result.toVariant();
            }

            virtual QStringList methodNames()
            {
                QStringList methods;
                QScriptValueIterator it( m_object );
                while( it.hasNext() ) {
                    it.next();
                    if( it.value().isFunction() )
                        methods << it.name();
                }
                return methods;
            }

        private:
            QScriptValue m_object;
    };

    QScriptValue toByteArray(QScriptEngine *e, const QByteArray &ba) {
        return ba.isNull() ? e->nullValue() : e->newVariant(ba);
    }
    void fromByteArray(const QScriptValue &v, QByteArray &ba) {
        ba = v.isNull() ? QByteArray() : v.toString().toUtf8();
    }

    QScriptValue toUrl(QScriptEngine *e, const QUrl &url) {
        return e->newVariant( url.toString() );
    }
    void fromUrl(const QScriptValue &v, QUrl &url) {
        url.setUrl( v.toString() );
    }

    QScriptValue toKUrl(QScriptEngine *e, const KUrl &url) {
        return e->newVariant( url.url() );
    }
    void fromKUrl(const QScriptValue &v, KUrl &url) {
        url.setUrl( v.toString() );
    }

    QScriptValue toColor(QScriptEngine *e, const QColor &c) {
        return c.isValid() ? e->newVariant(c.name()) : e->nullValue();
    }
    void fromColor(const QScriptValue &v, QColor &c) {
        c.setNamedColor( v.isNull() ? QString() : v.toString() );
    }

    QScriptValue toRect(QScriptEngine *e, const QRect &r) {
        return e->toScriptValue( QVariantList() << r.x() << r.y() << r.width() << r.height() );
    }
    void fromRect(const QScriptValue &v, QRect &r) {
        r = v.isArray() ? QRect(v.property(0).toInt32(),v.property(1).toInt32(),v.property(2).toInt32(),v.property(3).toInt32()) : QRect();
    }
    QScriptValue toRectF(QScriptEngine *e, const QRectF &r) {
        return e->toScriptValue( QVariantList() << r.x() << r.y() << r.width() << r.height() );
    }
    void fromRectF(const QScriptValue &v, QRectF &r) {
        r = v.isArray() ? QRectF(v.property(0).toNumber(),v.property(1).toNumber(),v.property(2).toNumber(),v.property(3).toNumber()) : QRectF();
    }

    QScriptValue toPoint(QScriptEngine *e, const QPoint &p) {
        return e->toScriptValue( QVariantList() << p.x() << p.y() );
    }
    void fromPoint(const QScriptValue &v, QPoint &p) {
        p = v.isArray() ? QPoint(v.property(0).toInt32(),v.property(1).toInt32()) : QPoint();
    }
    QScriptValue toPointF(QScriptEngine *e, const QPointF &p) {
        return e->toScriptValue( QVariantList() << p.x() << p.y() );
    }
    void fromPointF(const QScriptValue &v, QPointF &p) {
        p = v.isArray() ? QPointF(v.property(0).toNumber(),v.property(1).toNumber()) : QPointF();
    }

    QScriptValue toSize(QScriptEngine *e, const QSize &s) {
        return e->toScriptValue( QVariantList() << s.width() << s.height() );
    }
    void fromSize(const QScriptValue &v, QSize &s) {
        s = v.isArray() ? QSize(v.property(0).toInt32(),v.property(1).toInt32()) : QSize();
    }
    QScriptValue toSizeF(QScriptEngine *e, const QSizeF &s) {
        return e->toScriptValue( QVariantList() << s.width() << s.height() );
    }
    void fromSizeF(const QScriptValue &v, QSizeF &s) {
        s = v.isArray() ? QSizeF(v.property(0).toNumber(),v.property(1).toNumber()) : QSizeF();
    }

    /*
    QScriptValue toVariantList(QScriptEngine *e, const QVariantList &l) {
        const int len = l.size();
        QScriptValue a = e->newArray(len);
        for(int i = 0; i < len; ++i)
            a.setProperty(i, e->toScriptValue(l[i]));
        return a;
    }
    void fromVariantList(const QScriptValue &v, QVariantList &l) {
        l.clear();
        const int len = v.isArray() ? v.property("length").toInt32() : 0;
        for(int i = 0; i < len; ++i)
            l << v.property(i).toVariant();
    }
    */

    QScriptValue toObjPtr(QScriptEngine *e, const Kross::Object::Ptr &ptr) {
        const EcmaObject* obj = dynamic_cast<const EcmaObject*>( ptr.data() );
        return obj ? obj->prototype() : e->nullValue();
    }
    void fromObjPtr(const QScriptValue &v, Kross::Object::Ptr &ptr) {
        ptr = new EcmaObject(v.engine(), v);
    }

    QScriptValue createWidget(QScriptContext *context, QScriptEngine *engine) {
        const QString widgetname = context->callee().prototype().property("className").toString();
        Q_ASSERT( ! widgetname.isEmpty() );
        QWidget *parent = qscriptvalue_cast<QWidget*>(context->argument(0));
        QUiLoader loader;
        QWidget *widget = loader.createWidget(widgetname, parent);
        if( ! widget )
            return context->throwError(QScriptContext::TypeError, QString("No such QWidget \"%1\"").arg(widgetname));
        if( parent && parent->layout() )
            parent->layout()->addWidget(widget);
        QScriptEngine::ValueOwnership owner = parent ? QScriptEngine::QtOwnership : QScriptEngine::ScriptOwnership;
        QScriptValue result = engine->newQObject(widget, owner);
        //result.setPrototype(context->callee().prototype());
        return result;
    }

    QScriptValue addWidgetLayout(QScriptContext *c, QScriptEngine *engine) {
        if( QLayout *layout = dynamic_cast<QLayout*>( qscriptvalue_cast<QObject*>(c->thisObject()) ) ) {
            QGridLayout *gridLayout = dynamic_cast<QGridLayout*>(layout);
            QObject *obj = qscriptvalue_cast<QObject*>( c->argument(0) );
            if( QWidget *w = dynamic_cast<QWidget*>(obj) ) {
                if( gridLayout )
                    gridLayout->addWidget(w, c->argument(1).toInt32(), c->argument(2).toInt32(), (Qt::Alignment)c->argument(3).toInt32());
                else
                    layout->addWidget(w);
            }
            else if( QLayout *l = dynamic_cast<QLayout*>( qscriptvalue_cast<QObject*>(c->argument(0)) ) ) {
                if( gridLayout )
                    gridLayout->addLayout(l, c->argument(1).toInt32(), c->argument(2).toInt32(), (Qt::Alignment)c->argument(3).toInt32());
                else if( QBoxLayout *bl = dynamic_cast<QBoxLayout*>(layout) )
                    bl->addLayout(l);
            }
        }
        return engine->nullValue();
    }
    QScriptValue createLayout(QScriptContext *context, QScriptEngine *engine, QLayout *layout) {
        QObject *parent = qscriptvalue_cast<QObject*>(context->argument(0));
        if( QWidget *parentWidget = dynamic_cast<QWidget*>(parent) )
            parentWidget->setLayout(layout);
        else if( QBoxLayout *parentLayout = dynamic_cast<QBoxLayout*>(parent) )
            parentLayout->addLayout(layout);
        QScriptValue obj = engine->newQObject(layout);
        obj.setProperty("addWidget", engine->newFunction(addWidgetLayout));
        obj.setProperty("addLayout", engine->newFunction(addWidgetLayout));
        return obj;
    }
    QScriptValue createVBoxLayout(QScriptContext *context, QScriptEngine *engine) {
        return createLayout(context, engine, new QVBoxLayout());
    }
    QScriptValue createHBoxLayout(QScriptContext *context, QScriptEngine *engine) {
        return createLayout(context, engine, new QHBoxLayout());
    }
    QScriptValue createGridLayout(QScriptContext *context, QScriptEngine *engine) {
        return createLayout(context, engine, new QGridLayout());
    }
    QScriptValue includeFunction(QScriptContext *context, QScriptEngine *engine) {
        if (context->argumentCount() < 1)
            return engine->nullValue();
        return engine->importExtension(context->argument(0).toString());
    }

    /**
    * Initialize some core functionality like common used types we like
    * to use within scripts.
    */
    void initializeCore(QScriptEngine *engine) {
        QScriptValue global = engine->globalObject();

        // compatibility to kjs/kjsembed
        if( ! global.property("println").isValid() )
            global.setProperty("println", global.property("print"));

        // register common used types
        qScriptRegisterMetaType< QByteArray         >(engine, toByteArray,   fromByteArray);
        qScriptRegisterMetaType< QUrl               >(engine, toUrl,         fromUrl);
        qScriptRegisterMetaType< KUrl               >(engine, toKUrl,        fromKUrl);
        qScriptRegisterMetaType< QColor             >(engine, toColor,       fromColor);
        qScriptRegisterMetaType< QRect              >(engine, toRect,        fromRect);
        qScriptRegisterMetaType< QRectF             >(engine, toRectF,       fromRectF);
        qScriptRegisterMetaType< QPoint             >(engine, toPoint,       fromPoint);
        qScriptRegisterMetaType< QPointF            >(engine, toPointF,      fromPointF);
        qScriptRegisterMetaType< QSize              >(engine, toSize,        fromSize);
        qScriptRegisterMetaType< QSizeF             >(engine, toSizeF,       fromSizeF);

        // we should probably go with an own wrapper for QVariant/QObject...
        //qScriptRegisterMetaType< QVariant           >(engine, toVariant,     fromVariant);
        //qScriptRegisterMetaType< QVariantList       >(engine, toVariantList, fromVariantList);

        // register the Kross::Object::Ptr wrapper
        qScriptRegisterMetaType< Kross::Object::Ptr >(engine, toObjPtr,      fromObjPtr);

        // register the include function that allows to importExtension
        global.setProperty("include", engine->newFunction(includeFunction));
    }

    /**
    * Initialize GUI functionality like widgets the QUiLoader provides
    * and some layout-managers.
    */
    void initializeGui(QScriptEngine *engine) {
        QScriptValue global = engine->globalObject();

        // register UI widgets
        QUiLoader loader;
        foreach(const QString &widgetname, loader.availableWidgets()) {
            QScriptValue proto = engine->newObject();
            proto.setProperty("className", QScriptValue(engine, widgetname));
            QScriptValue func = engine->newFunction(createWidget);
            func.setPrototype(proto);
            global.setProperty(widgetname, func);
        }

        // register layouts
        global.setProperty("QVBoxLayout", engine->newFunction(createVBoxLayout));
        global.setProperty("QHBoxLayout", engine->newFunction(createHBoxLayout));
        global.setProperty("QGridLayout", engine->newFunction(createGridLayout));
    }

}
