/*!
 * PHP-Qt - The PHP language bindings for Qt
 *
 * Copyright (C) 2006 - 2007
 * Thomas Moenicke <tm at php-qt.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 <QtCore/QMetaMethod>
#include <QtCore/QHash>
#include <QDebug>

#include "phpqt_internals.h"
#include "functions.h"
#include "InvokeSlot.h"
#include "smoke.h"
#include "php_qt.h"
#include "pDebug.h"
#include "context.h"
#include "smokephp.h"

extern zend_object_handlers php_qt_handler;
extern zend_class_entry* qstring_ce;
extern Smoke::Index cachedQObjectSmokeId;

zend_class_entry* qobject_ce;
QHash<const void*, smokephp_object*> SmokeQtObjects;
QHash<const zend_object_handle, smokephp_object*> obj_x_smokephp;

#define ALLOCA_N(type,n) (type*)alloca(sizeof(type)*(n))

/*!
 * We have two types of Signals and Slots: those already defined in Qt and those the user defines
 * in user space. All of them can arbitrary be combined. The following cases turned out:
 * - User space Signals are called via the proxyMethod and the EmitSignal class, using emit()
 * - Qt Signals are called via qt_methodcall
 * - User space Slots are called via qt_metacall after the related signal was emitted, since PHP-Qt puts them into the QMetaObject
 * - Qt Slots are called here via qt_metacall
 * Algorithm:
 * - check if the method is defined in the user space class, if so it is a user space slot and can be called
 * - if its not defined in the user space class, then it can either be a Qt Slot or a Signal
 *   - if the signature has a slot index in the meta object which is smaller than the offset, it is obviously a Qt slot
 *   - if the signature has a slot index in the meta object that is greater or equal than the offset, it is a user space slot (which
 *     should already be found by the line above)
 *   - if the slot index is -1, its a Signal
 *     - if signals id is smaller than offset, its a C++ one
 *     - if its greater or equal, its a user space one
 * \return true means the job was done, false means smoke will cal l it again on the parent
 */
// TODO check QMultiHash
bool PHPQt::qt_metacall(smokephp_object* o, Smoke::Stack args)
{
    Context::setCallType( Context::SlotCall );
	const QMetaObject* d = o->meta();
	const int _id = args[2].s_int;
	const int offset = d->methodOffset();

	const QByteArray signature( d->method(_id).signature() );
	const QByteArray metaMethodName = signature.left( signature.indexOf("(") );

	if( PHPQt::methodExists( o->ce_ptr() , metaMethodName.constData()) )
	{
		pDebug( PHPQt::Slot ) << " userspace " << signature << o->className();
		const int count = d->method( args[2].s_int ).parameterTypes().count() + 1;
//		zval* zmem = ALLOCA_N(zval, count);
		zval* zmem = (zval*) safe_emalloc( sizeof(zval), count+1, 0);
		// NOTICE is memory allocation safe here?
		InvokeSlot c( o->smoke(), args, o->zval_ptr(), &zmem, _id, d, (void**) args[3].s_voidp );
		c.next();
		efree(zmem);
		return true;

	} else {
		if ( d->indexOfSlot( signature ) != -1 )
		{
			pDebug( PHPQt::Slot ) << " C++ " << signature;
			// return false means this will be done by smoke
			return false;
		} else {
			// TODO error case for undefined methods, see php_qt proxyMethod
			const int i = d->indexOfSignal( signature );
			const int offset = d->methodOffset(); // The offset is the summary of all methods in the class's superclasses
			pDebug( PHPQt::Signal ) << signature << d->className() << i << offset;

/* C++ */	//if( _id < offset ) { qWarning() << "got an id smaller than offset"; }

			/**
			 * We go through QMetaObject::activate, because it can either be connected to a C++ Slot or a user space Slot
			 * cast to a QObject using smoke cast
			 * */
			QObject* ptr = (QObject*) o->smoke()->cast( const_cast<void*>( o->ptr() ), o->classId(), cachedQObjectSmokeId );
			void *_b[] = { 0, ((void**) args[3].s_voidp)[1] };
			d->activate( ptr, d, 0, _b );
			return true; // success
		}
	} // else method exist
	return false;
}

void
PHPQt::destroyHashtable(zend_rsrc_list_entry *rsrc TSRMLS_DC)
{
	pDebug( PHPQt::NoLevel ) << "Hashtable destroyed. Shutdown PHP-Qt.";
}

bool
PHPQt::methodExists(const zend_class_entry* ce_ptr, const char* methodname)
{

	if(ce_ptr == NULL){
		pError() << "no class entry, could not check for message " << methodname;
	}

	char* lcname = zend_str_tolower_dup(methodname, strlen(methodname));

	if(zend_hash_exists(const_cast<HashTable*>(&ce_ptr->function_table), lcname, strlen(methodname)+1)){
		return true;
	}

	efree(lcname);
	return false;

}


zval*
PHPQt::callPHPMethod(const zval* z_this_ptr, const char* methodName, const zend_uint param_count, zval** args)
{

    if(z_this_ptr == NULL){
    	pError() << "could not call PHP method: no related PHP object found.";
    }

    zval *function_name;
    MAKE_STD_ZVAL(function_name);
    ZVAL_STRING(function_name,const_cast<char*>(methodName),1);

    zval* retval;
    MAKE_STD_ZVAL( retval );

    if(call_user_function( EG(function_table), (zval**) &z_this_ptr, function_name, retval, param_count, args ) == FAILURE){
    	smokephp_object* o = PHPQt::getSmokePHPObjectFromZval(z_this_ptr);
    	pError() << "could not call method " << o->ce_ptr()->name << "::" << methodName << "(...)";
    }

    // make sure we return the right object
    if(PHPQt::SmokePHPObjectExists(retval)){
    	smokephp_object* o = PHPQt::getSmokePHPObjectFromZval(retval);
    	retval = const_cast<zval*>(o->zval_ptr());
    }

    efree( function_name );
    return retval;
}

/*!
 *	creates metaObject data for createMetaObject
 *  example: "QWidget\0\0value\0test(int)\0"
 *	@param	zval*				this_ptr	pointer of the zval
 *	@param	char*				classname	name of the class
 *	@param	const QMetaObject*	superdata	superdata
 *	@return	QMetaObject*
 */

bool
PHPQt::getMocData(zval* this_ptr, const char* classname, const QMetaObject* superdata, QString* meta_stringdata, uint* signature){
/*
	// - reads all methods defined in a class
	// - see zend_compile.h for types
	// - case sensitive
	// - types need to be matched

	zend_class_entry* ce_ = Z_OBJCE_P(this_ptr);
	HashTable* function_table = &Z_OBJCE_P(this_ptr)->function_table;
	zend_hash_internal_pointer_reset(function_table);
	zval** prop;
	union _zend_function *fbc;

	if (zend_hash_num_elements( function_table ) > 0) {
    	while(zend_hash_has_more_elements(function_table) == SUCCESS)
    	{
    	    zend_hash_get_current_data(function_table,(void**)&fbc);
            zend_hash_move_forward(function_table);
            QByteArray name( (*fbc).common.function_name );
            if( !name.contains("__destruct") &&
            	!name.contains("__construct") &&
            	!name.contains("__toString") &&
            	!name.contains("proxyMethod") &&
            	!name.contains("staticProxyMethod") &&
            	!name.contains("emit")
            ){
            	for( int i=0; i<(*fbc).common.num_args; i++)
            	{
            		qDebug() <<"++ "<< (*fbc).common.arg_info[i].name <<","<< (*fbc).common.arg_info[i].array_type_hint;
            	}
            	qDebug()  << (*fbc).common.function_name;// << fbc->internal_function->function_name;
            }
    	}
    }
*/

    /// readout the slots table
    zval **slotdata;
    zval *zslot;
    zslot = zend_read_property(Z_OBJCE_P(this_ptr),this_ptr,"slots",5,1);

    zval *zsignal;
    zsignal = zend_read_property(Z_OBJCE_P(this_ptr),this_ptr,"signals",7,1);

    if((zslot)->type==IS_ARRAY && (zsignal)->type==IS_ARRAY )
    {
        HashTable* slots_hash = HASH_OF(zslot);
        HashTable* signals_hash = HASH_OF(zsignal);

        char* assocKey;
        ulong numKey;
        int signaturecount = 2 + strlen(classname);

#ifdef MOC_DEBUG
	QString qr;
	pDebug( PHPQt::Moc ) << "+== begin metaobject dump ==+\n";
	pDebug( PHPQt::Moc ) << "\t" << classname << "\n\t1 0 0 0 " << zend_hash_num_elements(slots_hash)+zend_hash_num_elements(signals_hash) << " 10 0 0 0 0" << endl << endl;
#endif

	/// write class signature
	signature[0] = 1;
	signature[4] = zend_hash_num_elements(slots_hash)+zend_hash_num_elements(signals_hash);
	signature[5] = 10;

	/// write classname
	meta_stringdata->append(classname);
	meta_stringdata->append(QChar::Null);
	meta_stringdata->append(QChar::Null);

	int i = 10;
	zend_hash_internal_pointer_reset(signals_hash);
	while(zend_hash_has_more_elements(signals_hash) == SUCCESS)
	{
	    /// read slot from hashtable
	    zend_hash_get_current_key(signals_hash,&assocKey,&numKey,0);
	    zend_hash_get_current_data(signals_hash,(void**)&slotdata);
#ifdef MOC_DEBUG
	    qr.append(Z_STRVAL_PP(slotdata));
	    qr.append(" ");
	    pDebug( PHPQt::Moc ) << "\t" << signaturecount << "8 8 8 0x05 ::s" << endl;
#endif

	    meta_stringdata->append(Z_STRVAL_PP(slotdata));
	    meta_stringdata->append(QChar::Null);

	    zend_hash_move_forward(signals_hash);

	    /// write signal signature
	    signature[i++] = signaturecount;
	    signature[i++] = 8;
	    signature[i++] = 8;
	    signature[i++] = 8;
	    signature[i++] = 0x05;

	    signaturecount += strlen(Z_STRVAL_PP(slotdata)) + 1;
	}

    	zend_hash_internal_pointer_reset(slots_hash);

	while(zend_hash_has_more_elements(slots_hash) == SUCCESS)
	{
	    /// read slot from hashtable
	    zend_hash_get_current_key(slots_hash,&assocKey,&numKey,0);
	    zend_hash_get_current_data(slots_hash,(void**)&slotdata);

#ifdef MOC_DEBUG
	    qr.append(Z_STRVAL_PP(slotdata));
	    qr.append(" ");
	    pDebug( PHPQt::Moc ) << "\t" << signaturecount << "8 8 8 0x0a ::s" << endl;
#endif
	    meta_stringdata->append(Z_STRVAL_PP(slotdata));
	    meta_stringdata->append(QChar::Null);

	    zend_hash_move_forward(slots_hash);

	    /// write slot signature
	    signature[i++] = signaturecount;
	    signature[i++] = 8;
	    signature[i++] = 8;
	    signature[i++] = 8;
	    signature[i++] = 0x0a;

	    signaturecount += strlen(Z_STRVAL_PP(slotdata)) + 1;
	}
// TODO freeing this crashs
//	   efree( assocKey );
#ifdef MOC_DEBUG
		pDebug( PHPQt::Moc ) << qr << endl;
#endif
		pDebug( PHPQt::Moc ) << "+== end metaobject dump ==+" << endl;
		return true;
    } else {
    	return false;
    }
} // getMocData

// TODO implement operators
const char*
PHPQt::checkForOperator(const char* fname){
	return fname;
}

bool
PHPQt::SmokePHPObjectExists(const zval* this_ptr)
{
	if( this_ptr->type != IS_OBJECT )
		return false;
	return obj_x_smokephp.contains(this_ptr->value.obj.handle);
}

/**
 * fetches the smokephp_object
 */

smokephp_object*
PHPQt::getSmokePHPObjectFromZval(const zval* this_ptr)
{
	if(this_ptr == NULL)
		pError( PHPQt::MapPtr ) << "php object does not exists, smokephp_object could not be fetched, " << Z_OBJCE_P( const_cast<zval*>(this_ptr) )->name;
 	return obj_x_smokephp.value(this_ptr->value.obj.handle);
}

void*
PHPQt::getQtObjectFromZval( const zval* this_ptr ){
	if(this_ptr == NULL)
		pError( PHPQt::MapPtr ) << "php object does not exists, Qt object could not be fetched, " << Z_OBJCE_P( const_cast<zval*>(this_ptr) )->name;
	return getSmokePHPObjectFromZval( this_ptr )->mPtr();
}

smokephp_object*
PHPQt::getSmokePHPObjectFromQt(const void* QtPtr){
	return SmokeQtObjects.value( QtPtr );
}

bool PHPQt::unmapSmokePHPObject(smokephp_object* o)
{
    pDebug( PHPQt::MapPtr ) << "unmapping" << o->ptr() << " o: " << o;
	SmokeQtObjects.remove( o->ptr() );
}

void
PHPQt::setSmokePHPObject(smokephp_object* o){
	pDebug( PHPQt::MapPtr ) << "  mapping" << o->ptr() << " o: " << o;
	SmokeQtObjects.insert( o->ptr(), o );
}

bool
PHPQt::SmokePHPObjectExists(const void* ptr){
	return (SmokeQtObjects.find(ptr) != SmokeQtObjects.end());
}

bool
PHPQt::unmapSmokePHPObject(const zval* zvalue)
{
	pDebug( PHPQt::MapHandle ) << "unmapping" << zvalue->value.obj.handle << PHPQt::getSmokePHPObjectFromZval( zvalue ) << "( zval " << zvalue << ")";
	return obj_x_smokephp.remove( zvalue->value.obj.handle );
}

void
PHPQt::mapSmokePHPObject( const zend_object_handle handle, smokephp_object* o )
{
	pDebug( PHPQt::MapHandle ) << "  mapping" << handle << o;
	obj_x_smokephp.insert( handle, o );
}

/**
 *	marshall_basetypes.h marshall_to_php<SmokeClassWrapper>(Marshall *m)
 */

smokephp_object*
PHPQt::createObject(zval* zval_ptr, const void* ptr, const zend_class_entry* ce, const Smoke::Index classId){

	Q_ASSERT (zval_ptr);
	Q_ASSERT (ptr);

 	if(!ce) {
 		qFatal("no class entry!");
 	}

	if(classId == QSTRING_CLASSID)
	{
		ce = qstring_ce;
	} else if (classId == 0)
	{
		qDebug("\nno class id");
		check_qobject(zval_ptr);
		qFatal("php object creation failed");
	}

	Z_TYPE_P(zval_ptr) = IS_OBJECT;
	object_init_ex(zval_ptr, const_cast<zend_class_entry*>(ce));
	smokephp_object* o = new smokephp_object(PQ::smoke(), classId, ptr, ce, zval_ptr);

	Z_OBJ_HT_P(zval_ptr) = &php_qt_handler;
	setSmokePHPObject(o);
	zval_add_ref(&zval_ptr);

	PHPQt::mapSmokePHPObject( zval_ptr->value.obj.handle, o );

	return o;
}

/**
 * create clone
 */

extern zend_class_entry* php_qt_generic_class;

smokephp_object*
PHPQt::cloneObject(zval* zval_ptr, smokephp_object* so, const void* copyPtr)
{
	Q_ASSERT (zval_ptr);
	Q_ASSERT (copyPtr);

	smokephp_object* o = new smokephp_object(PQ::smoke(), so->classId(), copyPtr, so->ce_ptr(), zval_ptr);
// TODO copy QMultiMap
	Z_OBJ_HT_P(zval_ptr) = &php_qt_handler;
	setSmokePHPObject(o);
#warning check me
//	o->setAllocated( true );
//	obj_x_smokephp.insert(zval_ptr->value.obj.handle, o);
	PHPQt::mapSmokePHPObject( zval_ptr->value.obj.handle, o );

	return o;
}

/**
 * create a new zval that refers to the object. This is neccessary since zvals can be freed when
 * calling virtual methods or custom slots
 */

void
PHPQt::restoreObject( smokephp_object* o )
{
	// check if the CPP side is on the heap or stack, if stack then set refcount to something like 1,
	// example: QVariant
	zval* z = (zval*) emalloc( sizeof(zval) );
    z->type = IS_OBJECT;
    z->refcount = 3;
    z->is_ref = 0;
    z->value.obj.handle = o->handle();
    Z_OBJ_HT_P( z ) = &php_qt_handler;
    o->setZvalPtr( z );
}

smokephp_object*
PHPQt::createOriginal(zval* zval_ptr, void* ptr)
{
	smokephp_object* o = getSmokePHPObjectFromQt(ptr);
 	zval_ptr = const_cast<zval*>(o->zval_ptr());
	zval_add_ref(&zval_ptr);
	return o;
}

void
PHPQt::createMetaObject(smokephp_object* o, zval* this_ptr)
{
    // call metaobject method
    const Smoke::ModuleIndex nameId = o->smoke()->idMethodName("metaObject");
    const Smoke::ModuleIndex classIdx = { o->smoke(), o->classId() };
    const Smoke::ModuleIndex meth = o->smoke()->findMethod( classIdx, nameId );
    const Smoke::Method &methodId = o->smoke()->methods[ o->smoke()->methodMaps[ meth.index ].method ];
    const Smoke::ClassFn fn = o->smoke()->classes[methodId.classId].classFn;
    Smoke::StackItem i[1];
    (*fn)(methodId.method, const_cast<void*>(o->ptr()), i);

    // create the metaobject
    const QMetaObject *superdata = static_cast<QMetaObject *> ( i[0].s_voidp );

    QString phpqt_meta_stringdata;
    uint* phpqt_meta_data = new uint[20*5+10];
    if( PHPQt::getMocData( this_ptr, o->parent_ce_ptr()->name, superdata, &phpqt_meta_stringdata, phpqt_meta_data ) )
    {
    	const char* phpqt_meta_stringdata_ = estrndup(phpqt_meta_stringdata.toAscii(), phpqt_meta_stringdata.size());
    	QMetaObject ob = { { superdata, phpqt_meta_stringdata_, phpqt_meta_data, 0 } };
    	QMetaObject* m = new QMetaObject;
    	memcpy(m, &ob, sizeof(ob));
    	o->setMetaObject(m);
    }
    else {
    	o->setMetaObject(superdata);
    }
// TODO here set QMultiMap
}
/*
void
PHPQt::Context::setActiveCe(zend_class_entry* ce)
{
    extern zend_class_entry* activeCe_;
    activeCe_ = ce;
}

zend_class_entry*
PHPQt::Context::activeCe()
{
    extern zend_class_entry* activeCe_;
    return activeCe_;
}

zval*
PHPQt::Context::activeScope()
{
    extern zval* activeScope_;
    return activeScope_;
}

void
PHPQt::Context::setActiveScope(zval* zval_ptr)
{
    extern zval* activeScope_;
    activeScope_ = zval_ptr;
}

void
PHPQt::Context::setParentCall(bool pc)
{
    extern bool parentCall_;
    parentCall_ = pc;
}

bool
PHPQt::Context::parentCall()
{
    extern bool parentCall_;
    return parentCall_;
}

void
PHPQt::Context::setMethodName(const char* name)
{
    extern QStack<QByteArray*> methodNameStack_;
    methodNameStack_.push( new QByteArray(name) );
}

void
PHPQt::Context::removeMethodName()
{
    extern QStack<QByteArray*> methodNameStack_;
    methodNameStack_.pop();
}

QByteArray*
PHPQt::Context::methodName()
{
    extern QStack<QByteArray*> methodNameStack_;
    return methodNameStack_.top();
}

const char*
PHPQt::Context::methodNameC()
{
    extern QStack<QByteArray*> methodNameStack_;
    return methodNameStack_.top()->constData();
}

*/


/*!
 *	check_qobject()
 *
 */

void PHPQt::check_qobject(zval* zobject)
{
#ifdef PHP_DEBUG
	if(!PHPQt::SmokePHPObjectExists(zobject)) {

		qDebug() << "PHP Object \n(" << endl;

		qDebug() << "\t       zval => " << zobject << endl;
		if(Z_TYPE_P(zobject) == IS_OBJECT)
			qDebug() << "\tclass entry => " << Z_OBJCE_P(zobject)->name << endl;
		qDebug() << "\t  ref count => " << zobject->refcount << endl;
		qDebug() << "\t     is_ref => " << (int) zobject->is_ref << endl;
		qDebug() << "\t       type => " << printType(Z_TYPE_P(zobject)) << endl;

		if(Z_TYPE_P(zobject) == IS_OBJECT)
		{
		 qDebug() <<"\t obj-handle => " << zobject->value.obj.handle << endl;
		}

		qDebug() << ")" << endl;

	} else {

		smokephp_object* o = PHPQt::getSmokePHPObjectFromZval(zobject);

		qDebug() << "PHP-Qt object \n(" << endl;

		qDebug() << "\t       zval => " << zobject << endl;
// 		qDebug() << "\tclass entry => " << Z_OBJCE_P(zobject)->name << endl;
		qDebug() << "\tclass entry => " << o->ce_ptr()->name << endl;
		qDebug() << "\t  ref count => " << zobject->refcount << endl;
		qDebug() << "\t     is_ref => " << (int) zobject->is_ref << endl;
		qDebug() << "\t       type => " << printType(Z_TYPE_P(zobject)) << endl;

		if(Z_TYPE_P(zobject) == 5)
		{
		 qDebug() <<"\t obj-handle => " << zobject->value.obj.handle << endl;
		}

		qDebug() << endl;

		qDebug() << "\t      smokeobj => " << o << endl;
		qDebug() << "\t         Smoke => " << o->smoke() << endl;
		qDebug() << "\t       classId => " << o->classId() << endl;
		qDebug() << "\t        Qt ptr => " << o->ptr() << endl;
		qDebug() << "\t        ce_ptr => " << o->ce_ptr() << endl;
		qDebug() << "\t      zval_ptr => " << o->zval_ptr() << endl;
		qDebug() << "\t  QMetaObject* => " << o->meta() << endl;

		qDebug() << ")" << endl;
	}
#endif
}



const char*
PHPQt::printType(int type)
{
 switch(type){
	case IS_NULL:	return "IS_NULL"; break; // 0
	case IS_LONG:	return "IS_LONG"; break; // 1
	case IS_DOUBLE: return "IS_DOUBLE"; break;	//2
	case IS_BOOL: return "IS_BOOL"; break; //	3
	case IS_ARRAY: return "IS_ARRAY"; break; // 4
	case IS_OBJECT: return "IS_OBJECT"; break; //	5
	case IS_STRING: return "IS_STRING"; break; // 6
	case IS_RESOURCE: return "IS_RESOURCE"; break; // 7
	case IS_CONSTANT: return "IS_CONSTANT"; break; // 8
	case IS_CONSTANT_ARRAY: return "IS_CONSTANT_ARRAY"; break; //	9
 }
 return "unknown";
}
