/*
 *  Copyright (C) 2001-2002, Richard J. Moore <rich@kde.org>
 *
 *  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., 59 Temple Place - Suite 330,
 *  Boston, MA 02111-1307, USA.
 */

#include <qobject.h>
#include <qobjectlist.h>
#include <qdialog.h>
#include <qmetaobject.h>
#include <qregexp.h>
#include <qsignal.h>
#include <qstrlist.h>
#include <qtimer.h>
#include <qvariant.h>

#include <private/qucomextra_p.h>

#include <kdebug.h>
#include <kjs/interpreter.h>
#include <kjs/types.h>
#include <kjs/ustring.h>

#include "kjsembedpart.h"
#include "securitypolicy.h"

#include "jsobjectproxy.h"

namespace KJSEmbed {

class JSObjectProxyPrivate
{
public:
    const SecurityPolicy *policy;
};

JSObjectProxy::JSObjectProxy( KJS::Interpreter *jsi, QObject *target,
			      QObject *r, const SecurityPolicy *sp )
    : KJS::ObjectImp(), js(jsi), obj(target), root(r)
{
    d = new JSObjectProxyPrivate;
    d->policy = sp ? sp : SecurityPolicy::defaultPolicy();
}

JSObjectProxy::JSObjectProxy( KJS::Interpreter *jsi, QObject *target, QObject *r )
    : KJS::ObjectImp(), js(jsi), obj(target), root(r)
{
    d = new JSObjectProxyPrivate;
    d->policy = SecurityPolicy::defaultPolicy();
}

JSObjectProxy::JSObjectProxy( KJS::Interpreter *jsi, QObject *target )
    : KJS::ObjectImp(), js(jsi), obj(target), root(target)
{
    d = new JSObjectProxyPrivate;
    d->policy = SecurityPolicy::defaultPolicy();
}

void JSObjectProxy::setSecurityPolicy( const SecurityPolicy *pol )
{
    d->policy = pol ? pol : new SecurityPolicy();
}

const SecurityPolicy *JSObjectProxy::securityPolicy() const
{
    return d->policy;
}

KJS::UString JSObjectProxy::toString( KJS::ExecState *state ) const
{
    if ( !state ) {
	kdWarning() << "JS toString with null state, ignoring" << endl;
	return KJS::UString();
    }
    if ( !isAllowed( state->interpreter() ) ) {
	kdWarning() << "JS toString request from unknown interpreter, ignoring" << endl;
	return KJS::UString();
    }

    QString s( "%1 (%2)" );
    s = s.arg( obj ? obj->name() : "Dead Object" );
    s = s.arg( obj ? obj->className() : "" );
    return KJS::UString(s);
}

KJS::Value JSObjectProxy::get( KJS::ExecState *state, const KJS::UString &p ) const
{
    if ( !d->policy->hasCapability( SecurityPolicy::CapabilityGetProperties ) ) {
	return KJS::ObjectImp::get( state, p );
    }

    if ( !isAllowed( state->interpreter() ) ) {
	kdWarning() << "JS get request from unknown interpreter, ignoring" << endl;
	return KJS::Undefined();
    }

    if ( !obj ) {
	kdDebug() << "JS getting '" << p.ascii() << "' but qobj has died" << endl;
	return KJS::ObjectImp::get( state, p );
    }

    // Properties
    QString prop = p.qstring();
    QMetaObject *meta = obj->metaObject();
    
    if ( meta->findProperty( p.ascii(), true ) != -1 ) {
	QVariant val = obj->property( prop.ascii() );
	if ( val.isValid() ) {
	    if ( val.type() == QVariant::String || val.type() == QVariant::CString ||
		 val.type() == QVariant::Date   || val.type() == QVariant::Time    ||
		 val.type() == QVariant::DateTime )
		return KJS::String( val.toString() );
	    else if ( val.type() == QVariant::Int )
		return KJS::Number( val.toInt() );
	    else if ( val.type() == QVariant::UInt )
		return KJS::Number( val.toUInt() );
	    else if ( val.type() == QVariant::Double )
		return KJS::Number( val.toDouble() );
	    else if ( val.type() == QVariant::Bool )
		return KJS::Boolean( val.toBool() );
	    else {
		QString vs( val.toString() );
		if ( !vs.isNull() )
		    return KJS::String( val.toString() );
	    }
	}
    }

    return KJS::ObjectImp::get( state, p );
}

void JSObjectProxy::put( KJS::ExecState *state,
			 const KJS::UString &p, const KJS::Value &v,
			 int attr )
{
    if ( !d->policy->hasCapability( SecurityPolicy::CapabilitySetProperties ) ) {
	KJS::ObjectImp::put( state, p, v, attr );
	return;
    }

    if ( !isAllowed( state->interpreter() ) ) {
	kdWarning() << "JS put request from unknown interpreter, ignoring" << endl;
	return;
    }
    
    if ( !obj ) {
	kdDebug() << "JS setting '" << p.ascii() << "' but qobj has died" << endl;
	KJS::ObjectImp::put( state, p, v, attr );
	return;
    }
    
    QMetaObject *meta = obj->metaObject();
    
    if ( meta->findProperty( p.ascii(), true ) != -1 ) {
	QVariant val;
	
	if ( v.isA( KJS::StringType ) )
	    val = v.toString( state ).qstring();
	else if ( v.isA( KJS::NumberType ) )
	    val = v.toInteger( state );
	else if ( v.isA( KJS::BooleanType ) )
	    val = v.toBoolean( state );

	// kdDebug() << "JS setting '" << p.ascii() << "' ( " << val.typeName() << ") to '"
	//           << val.asString() << "'" << endl;
	if ( val.isValid() )
	    obj->setProperty( p.ascii(), val );
        // kdDebug() << "setProperty returned " << ok << endl;
    }
    else {
	KJS::ObjectImp::put( state, p, v, attr );
    }
}

bool JSObjectProxy::isAllowed( KJS::Interpreter *i ) const
{
    return d->policy->isInterpreterAllowed( this, i );
}

bool JSObjectProxy::isAllowed( QObject *obj ) const
{
    return d->policy->isObjectAllowed( this, obj );
}

bool JSObjectProxy::isAllowed( QObject *obj, const char *name ) const
{
    return d->policy->isPropertyAllowed( this, obj, name );
}

//
// Implementation of JS Method Bindings
//

void JSObjectProxy::addBindings( KJS::Object &object )
{
    addBindings( js->globalExec(), object );
}

void JSObjectProxy::addBindings( KJS::ExecState *state, KJS::Object &object )
{
//    kdDebug() << "JSObjectProxy::addBindings() " << (obj->name() ? obj->name() : "dunno")
//	      << ", class " << obj->className() << endl;

    const SecurityPolicy *pol = d->policy;

    if ( pol->hasCapability( SecurityPolicy::CapabilityTree ) )
	addTreeBindings( state, object );

    if ( pol->hasCapability( SecurityPolicy::CapabilitySlots ) )
	addSlotBindings( state, object );

    if ( pol->hasCapability( SecurityPolicy::CapabilityGetProperties|SecurityPolicy::CapabilitySetProperties ) )
	object.put( state, "properties", KJS::Object(new MethodImp(MethodImp::MethodProps, this)) );

    if ( pol->hasCapability( SecurityPolicy::CapabilityFactory ) )
	addFactoryBindings( state, object );
}

void JSObjectProxy::addTreeBindings( KJS::ExecState *state, KJS::Object &object )
{
    object.put( state, "parent", KJS::Object(new MethodImp(MethodImp::MethodParent, this)) );
    object.put( state, "childCount", KJS::Object(new MethodImp(MethodImp::MethodChildCount, this)) );
    object.put( state, "childAt", KJS::Object(new MethodImp(MethodImp::MethodChildAt, this)) );
    object.put( state, "findChild", KJS::Object(new MethodImp(MethodImp::MethodFindChild, this)) );
    object.put( state, "child", KJS::Object(new MethodImp(MethodImp::MethodChild, this)) );

    object.put( state, "children", KJS::Object(new MethodImp(MethodImp::MethodChildren, this)) );
}

void JSObjectProxy::addSlotBindings( KJS::ExecState *state, KJS::Object &object )
{
    // Get meta data
    QMetaObject *mo = obj->metaObject();
    QStrList slotList( mo->slotNames( true ) );

    int count = 0;
    for ( QStrListIterator iter(slotList); iter.current(); ++iter ) {

	QCString name = iter.current();
	QCString jsname = name;
	jsname.detach();
	jsname.replace( QRegExp("\\([^\\)]*\\)"), "" );

	int slotid = mo->findSlot( name.data(), true );
	if ( (slotid != -1) && (mo->slot( slotid, true )->access == QMetaData::Public) ) {
	    // If takes no args then make a binding
	    if ( name.contains("()") ) {
		MethodImp *imp = new MethodImp( MethodImp::MethodSlot, name, this);
		object.put( state, jsname.data(), KJS::Object(imp) );
		count++;
	    }
	    else if ( name.contains( QRegExp("\\(int\\)") ) ) {
		MethodImp *imp = new MethodImp( MethodImp::MethodSlotInt, name, this);
		object.put( state, jsname.data(), KJS::Object(imp) );
		count++;
	    }
	    else if ( name.contains( QRegExp("\\(bool\\)") ) ) {
		MethodImp *imp = new MethodImp( MethodImp::MethodSlotBool, name, this);
		object.put( state, jsname.data(), KJS::Object(imp) );
		count++;
	    }
	    else if ( name.contains( QRegExp("\\(const QString&\\)") ) ) {
		MethodImp *imp = new MethodImp( MethodImp::MethodSlotString, name, this);
		object.put( state, jsname.data(), KJS::Object(imp) );
		count++;
	    }
	}
    }

    kdDebug() << "Published " << count << " slots" << endl;
    object.put( state, "slots", KJS::Object(new MethodImp(MethodImp::MethodSlots, this)) );
}

void JSObjectProxy::addFactoryBindings( KJS::ExecState *state, KJS::Object &object )
{
    object.put( state, "create", KJS::Object(new MethodImp(MethodImp::MethodCreate, this)) );
}

KJS::Object JSObjectProxy::createSubProxy( QObject *target, KJS::ExecState *state ) const
{
//    kdDebug() << "JSObjectProxy::createSubProxy: For target '" << target->name() << "'" << endl;
    JSObjectProxy *prx = new JSObjectProxy( js, target, root, d->policy );

    KJS::Object proxyObj( prx );
    prx->addBindings( state ? state : js->globalExec(), proxyObj );

    return proxyObj;
}

JSObjectProxy::MethodImp::MethodImp( int mid, const JSObjectProxy *parent )
    : KJS::ObjectImp(), id(mid), proxy(parent)
{
}

JSObjectProxy::MethodImp::MethodImp( int mid, const QCString &name, const JSObjectProxy *parent )
    : KJS::ObjectImp(), id(mid), slotname(name), proxy(parent)
{
}

KJS::Value JSObjectProxy::MethodImp::callChildAt( KJS::ExecState *state, KJS::Object &/*self*/,
						  const KJS::List &args )
{
//    kdDebug() << "JSObjectProxy::Method callChildAt()" << endl;

    QObject *obj = proxy->obj;
    if ( !obj ) {
	kdWarning() << "JSObjectProxy::Method call when object is 0" << endl;
	return KJS::Undefined();
    }

    const QObjectList *kids = obj->children();
    if ( !kids )
	return KJS::Undefined();
    
    QObjectList l( *kids );
    uint i = args[0].toUInt32( state );

    if ( i >= l.count()  )
	return KJS::Undefined();

    QObject *child = l.at( i );
    if ( child && proxy->isAllowed( child ) )
	return proxy->createSubProxy( child, state );
    
    return KJS::Undefined();
}

KJS::Value JSObjectProxy::MethodImp::callFindChild( KJS::ExecState *state, KJS::Object &/*self*/,
						    const KJS::List &args )
{
//    kdDebug() << "JSObjectProxy::Method callFindChild()" << endl;

    QObject *obj = proxy->obj;
    if ( !obj ) {
	kdWarning() << "JSObjectProxy::Method call when object is 0" << endl;
	return KJS::Undefined();
    }

    QString s = args[0].toString( state ).qstring();
    QObject *child = obj->child( s.ascii() );

    if ( child && proxy->isAllowed( child ) )
	return proxy->createSubProxy( child, state );
    
    return KJS::Undefined();
}


KJS::Value JSObjectProxy::MethodImp::call( KJS::ExecState *state, KJS::Object &self,
					   const KJS::List &args )
{
//    kdDebug() << "JSObjectProxy::Method call " << (int) id << endl;

    if ( !proxy->isAllowed(state->interpreter()) ) {
	kdWarning() << "JSObjectProxy::Method call from unknown interpreter!" << endl;
	return KJS::Undefined();
    }

    QObject *obj = proxy->obj;
    if ( !obj ) {
	kdWarning() << "JSObjectProxy::Method call when object is 0" << endl;
	return KJS::Undefined();
    }

    switch( id ) {
	case MethodParent:
	{
	    QObject *po = obj->parent();
	    if ( po && proxy->isAllowed( po ) )
		return proxy->createSubProxy( po, state );

	    return KJS::Undefined();
	}
	break;
	case MethodChildCount:
	{
	    const QObjectList *kids = obj->children();
	    if ( !kids )
		return KJS::Number( 0 );

	    return KJS::Number( kids->count() );
	}
	break;
	case MethodChildAt:
	case MethodFindChild:
	case MethodChild:
	{
	    if ( args[0].isA( KJS::NumberType ) )
		return callChildAt( state, self, args );
	    else
		return callFindChild( state, self, args );
	}
	break;
	case MethodCreate:
	{
	    QString cl = args[0].toString( state ).qstring();
	    QString nm = args[1].toString( state ).qstring();
//	    kdDebug() << "JSObjectProxy: MethodCreate( " << cl << ", " << nm << " )" << endl;

	    const SecurityPolicy *sp = proxy->securityPolicy();
	    if ( !sp->isCreateAllowed(proxy, obj, nm, cl) ) {
		kdWarning() << "JSObjectProxy: Denied by security policy" << endl;
		return KJS::Undefined();
	    }

	    QObject *child = KJSEmbedPart::create( proxy->obj, nm.latin1(), cl.latin1() );
	    if ( child ) {
//		kdDebug() << "JSObjectProxy: Child created" << endl;
		return proxy->createSubProxy( child, state );
	    }

	    return KJS::Undefined();
	}
	break;
	case MethodChildren:
	{
//	    kdDebug() << "JSObjectProxy::Method callChildren()" << endl;

	    QObject *obj = proxy->obj;
	    if ( !obj ) {
		kdWarning() << "JSObjectProxy::Method call when object is 0" << endl;
		return KJS::Undefined();
	    }

	    const QObjectList *kids = obj->children();
	    if ( !kids )
		return KJS::String( "" );
    
	    QObjectList l( *kids );
	    QString items;
	    for ( uint i = 0 ; i < l.count() ; i++ ) {
		QObject *child = l.at( i );
		QCString nm = ( child ? child->name() : "<null>" );
//		kdDebug() << "JSObjectProxy: Adding child " << nm << endl;
		items.append( QString("%1, ").arg(nm) );
	    }
	    items.replace( QRegExp(", $"), "" );

	    return KJS::String( items );
	}
	break;
	case MethodProps:
	{
//	    kdDebug() << "JSObjectProxy::Method callProps()" << endl;

	    QObject *obj = proxy->obj;
	    if ( !obj ) {
		kdWarning() << "JSObjectProxy::Method call when object is 0" << endl;
		return KJS::Undefined();
	    }

	    QString items;
	    QMetaObject *mo = obj->metaObject();
	    QStrList propList( mo->propertyNames( true ) );

	    for ( QStrListIterator iter(propList); iter.current(); ++iter ) {

		QCString name = iter.current();
		int propid = mo->findProperty( name.data(), true );
		if ( propid != -1 ) {
		    items.append( QString("%1, ").arg(name) );
		}
	    }
	    items.replace( QRegExp(", $"), "" );

	    return KJS::String( items );
	}
	break;
	case MethodSlots:
	{
//	    kdDebug() << "JSObjectProxy::Method callSlots()" << endl;

	    QObject *obj = proxy->obj;
	    if ( !obj ) {
		kdWarning() << "JSObjectProxy::Method call when object is 0" << endl;
		return KJS::Undefined();
	    }

	    QString items;
	    QMetaObject *mo = obj->metaObject();
	    QStrList slotList( mo->slotNames( true ) );

	    for ( QStrListIterator iter(slotList); iter.current(); ++iter ) {

		QCString name = iter.current();
		int slotid = mo->findSlot( name.data(), true );
		if ( (slotid != -1) && (mo->slot( slotid, true )->access == QMetaData::Public) ) {
		    // If takes no args then make a binding
		    if ( name.contains("()") || name.contains( QRegExp("\\(int\\)") ) || 
			 name.contains( QRegExp("\\(bool\\)") ) ||
			 name.contains( QRegExp("\\(const QString&\\)") ) ) {
			items.append( QString("%1, ").arg(name) );
		    }
		}
	    }
	    items.replace( QRegExp(", $"), "" );

	    return KJS::String( items );
	}
	break;
	case MethodSlot:
	{
	    int slotid = obj->metaObject()->findSlot( slotname.data(), true );
//	    kdDebug() << "JSProxy: MethodSlot '" << slotname << "' called, id is " << slotid << endl;
	    if ( slotid != -1 ) {
//		kdDebug() << "JSObjectProxy: Method '" << slotname << "' id is " << slotid << endl;
		QUObject uo[ 1 ];
		obj->qt_invoke( slotid, uo );
	    }
	    else {
		kdWarning() << "JSObjectProxy: Slot lookup failed" << endl;
	    }
	    return KJS::Undefined();
	}
	case MethodSlotInt:
	{
	    int slotid = obj->metaObject()->findSlot( slotname.data(), true );
//	    kdDebug() << "JSProxy: MethodSlot '" << slotname << "' called, id is " << slotid << endl;
	    if ( slotid != -1 ) {
//		kdDebug() << "JSObjectProxy: Method '" << slotname << "' id is " << slotid << endl;
		QUObject uo[ 2 ];
		static_QUType_int.set( uo+1, args[0].toInteger( state ) );
		obj->qt_invoke( slotid, uo );
	    }
	    else {
		kdWarning() << "JSObjectProxy: Slot lookup failed" << endl;
	    }
	    return KJS::Undefined();
	}
	break;
	case MethodSlotBool:
	{
	    int slotid = obj->metaObject()->findSlot( slotname.data(), true );
//	    kdDebug() << "JSProxy: MethodSlot '" << slotname << "' called, id is " << slotid << endl;
	    if ( slotid != -1 ) {
//		kdDebug() << "JSObjectProxy: Method '" << slotname << "' id is " << slotid << endl;
		QUObject uo[ 2 ];
		static_QUType_bool.set( uo+1, args[0].toBoolean( state ) );
		obj->qt_invoke( slotid, uo );
	    }
	    else {
		kdWarning() << "JSObjectProxy: Slot lookup failed" << endl;
	    }
	    return KJS::Undefined();
	}
	break;
	case MethodSlotString:
	{
	    int slotid = obj->metaObject()->findSlot( slotname.data(), true );
//	    kdDebug() << "JSProxy: MethodSlot '" << slotname << "' called, id is " << slotid << endl;
	    if ( slotid != -1 ) {
//		kdDebug() << "JSObjectProxy: Method '" << slotname << "' id is " << slotid << endl;
		QUObject uo[ 2 ];
		static_QUType_QString.set( uo+1, args[0].toString(state).qstring() );
		obj->qt_invoke( slotid, uo );
	    }
	    else {
		kdWarning() << "JSObjectProxy: Slot lookup failed" << endl;
	    }
	    return KJS::Undefined();
	}
	break;
	break;
	default:
	    break;
    }

    return KJS::ObjectImp::call( state, self, args );
}

}; // namespace KJSEmbed

// Local Variables:
// c-basic-offset: 4
// End:

