/****************************************************************************
** $Id: quickcompletion.cpp  beta1   edited Dec 10 13:07 $
**
** Copyright (C) 2001-2002 Trolltech AS.  All rights reserved.
**
** This file is part of the Qt Script for Applications framework (QSA).
**
** This file may be distributed and/or modified under the terms of the
** GNU General Public License version 2 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.
**
** Licensees holding a valid QSA Beta Evaluation Version license may use
** this file in accordance with the QSA Beta Evaluation Version License
** Agreement provided with the Software.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
**   information about QSA Commercial License Agreements.
** See http://www.trolltech.com/gpl/ for GPL licensing information.
**
** Contact info@trolltech.com if any conditions of this licensing are
** not clear to you.
**
*****************************************************************************/

#include "quickcompletion.h"
#include "quicksyntaxhighliter.h"
#include "quickinterpreter.h"
#include "quickobjects.h"
#include "quickclassparser.h"
#include <editor.h>
#include <qobjectlist.h>
#include <qmetaobject.h>
#include <qregexp.h>
#include <private/qcom_p.h>
#include <private/qucom_p.h>
#include <private/qucomextra_p.h>
#include <private/qrichtext_p.h>

QPtrVector<QObject> *interfaceObjects( const QSObject &o )
{
    return &QuickInterpreter::self()->wrapperClass()->objectVector( &o );
}

// =============================================================================
// =============================================================================
// == Functions to set return completion information
// =============================================================================
// =============================================================================

struct Property
{
    QString name;
    QString type;
    bool operator==( const Property &p ) const {
	return name == p.name && type == p.type;
    }
};

static void getSlots( const QMetaObject *meta, QValueList<Property> &result,
		      bool super, bool withArgs, bool sigs )
{
    int nslots = meta->numSlots( super );
    if ( sigs )
	nslots = meta->numSignals( super );
    for ( int i = 0; i < nslots; i++ ) {
 	const QMetaData* md = meta->slot( i, super );
	if ( sigs )
	    md = meta->signal( i, super );
	if ( md->access == QMetaData::Private )
	    continue;
	Property prop;
	QString s = QString::fromLatin1( md->name );
	s = s.left( s.find( '(' ) );
	if ( withArgs ) {
	    s += "(";
	    const QUMethod *m = md->method;
	    for ( int j = 0; j < m->count; ++j ) {
		QUParameter p = m->parameters[j];
		if ( j == 0 && p.inOut == QUParameter::Out ) {
		    prop.type = p.type->desc();
		    QuickInterpreter::cleanType( prop.type );
		    continue;
		}
		QString type = p.type->desc();
		if ( type == "ptr" )
		    type = (const char*)p.typeExtra;
		else if ( type == "varptr" )
		    type = QVariant::typeToName( (QVariant::Type)*(char*)p.typeExtra );
		s += type;
		s += " ";
		s += p.name;
		if ( j < m->count - 1 )
		    s += ",";
	    }
	    s += ")";
	} else {
	    const QUMethod *m = md->method;
	    if ( m->count > 0 && m->parameters[0].inOut == QUParameter::Out ) {
		prop.type = m->parameters[0].type->desc();
		if ( prop.type == "ptr" )
		    prop.type = (const char*)m->parameters[0].typeExtra;
		else if ( prop.type == "varptr" )
		    prop.type = QVariant::typeToName( (QVariant::Type)*(char*)m->parameters[0].typeExtra );
		QuickInterpreter::cleanType( prop.type );
	    }
	}
	prop.name = s;
	if ( result.find( prop ) == result.end() )
	    result << prop;
    }
}

static void addLayoutChildren( QObject *o, QValueList<CompletionEntry> &res )
{
    const QObjectList *l = o->children();
    if ( !l )
	return;
    QObjectListIt it( *l );
    while ( it.current() ) {
	o = it.current();
	++it;
	if ( o->inherits( "QLayoutWidget" ) ||
	     o-> inherits( "QWidgetStack" ) ) {
	    addLayoutChildren( o, res );
	    continue;
	}
	if ( o->inherits( "Spacer" ) ||
	     o->inherits( "QSizeGrip" ) ||
	     o->inherits( "QWidgetStack" ) )
	    continue;
	QString s = o->name();
	if ( s.find( " " ) == -1 && s.find( "qt_" ) == -1 &&
	     s.find( "unnamed" ) == -1 ) {
	    CompletionEntry c;
	    c.type = o->isWidgetType() ? "widget" : "object";
	    c.text = s;
	    c.postfix2 = o->className();
	    if ( !c.postfix2.isEmpty() )
		c.postfix2.prepend( " : " );
	    res << c;
	}
    }
}

static QStringList getArguments( const QString &s )
{
    QString str = s.mid( s.find( "(" ) + 1, s.find( ")" ) - 1 - s.find( "(" ) );
    str = str.simplifyWhiteSpace();
    QStringList lst = QStringList::split( ',', str );
    QStringList res;
    for ( QStringList::Iterator it = lst.begin(); it != lst.end(); ++it ) {
	QString arg = *it;
	arg = arg.replace( QRegExp( "const" ), "" );
	arg = arg.replace( QRegExp( "&" ), "" );
	arg = arg.replace( QRegExp( "*" ), "" );
	arg = arg.simplifyWhiteSpace();
	res << arg;
    }
    return res;
}

void QuickCompletion::completeQSObject( QSObject &obj, QValueList<CompletionEntry> &res )
{
    QStringList funcs = QuickInterpreter::self()->functionsOf( obj, TRUE, TRUE );
    QStringList::Iterator it;
    for ( it = funcs.begin(); it != funcs.end(); ++it ) {
	CompletionEntry c;
	c.type = "function";
	c.text = *it;
	c.prefix = "";
	c.postfix2 = "";
	res << c;
    }
    QStringList vars = QuickInterpreter::self()->variablesOf( obj, TRUE, TRUE );
    for ( it = vars.begin(); it != vars.end(); ++it ) {
	CompletionEntry c;
	c.type = "variable";
	c.text = *it;
	c.prefix = "";
	c.postfix2 = "";
	res << c;
    }
    QStringList classes = QuickInterpreter::self()->classesOf( obj );
    for ( it = classes.begin(); it != classes.end(); ++it ) {
	CompletionEntry c;
	c.type = "class";
	c.text = *it;
	c.prefix = "";
	c.postfix2 = "";
	res << c;
    }
}

void QuickCompletion::completeQMetaObject( const QMetaObject *meta,
					   const QString &object,
					   QValueList<CompletionEntry> &res,
					   int flags,
					   QSObject &obj )
{
    QMap<QString, bool> propMap;
    bool includeSuperClass = (flags & IncludeSuperClass) == IncludeSuperClass;
    bool isUnnamed = (flags & IsUnnamed) == IsUnnamed;

    // properties
    int num = meta->numProperties( includeSuperClass );
    for ( int j = 0; j < num; ++j ) {
	const QMetaProperty *mp = meta->property( j, includeSuperClass );
	if ( !mp->scriptable() || qstrcmp( "name", mp->name() ) == 0 && isUnnamed )
	    continue;
	if ( propMap.find( mp->name() ) != propMap.end() )
	    continue;
	CompletionEntry c;
	propMap.replace( mp->name(), FALSE );
	c.type = "property";
	c.text = mp->name();
	c.prefix = "";
	c.postfix2 = mp->type();
	QuickInterpreter::cleanType( c.postfix2 );
	if ( !c.postfix2.isEmpty() )
	    c.postfix2.prepend( " : " );
	res << c;
    }
    if ( includeSuperClass && obj.isValid() && !obj.isUndefined() ) {
	QStringList vars = QuickInterpreter::self()->variablesOf( obj, TRUE );
	QStringList::Iterator it;
	for ( it = vars.begin(); it != vars.end(); ++it ) {
	    CompletionEntry c;
	    c.type = "variable";
	    c.text = *it;
	    c.prefix = "";
	    c.postfix2 = "";
	    res << c;
	}
    }

    // functions
    QValueList<Property> lst;
    QValueList<Property>::Iterator pit;
    getSlots( meta, lst, includeSuperClass, FALSE, FALSE );
    for ( pit  = lst.begin(); pit != lst.end(); ++pit ) {
	if ( (*pit).name == "deleteLater" )
	    continue;
	CompletionEntry c;
	c.type = "function";
	if ( object == "Application" && (*pit).name == "quit" )
	    c.text = "exit";
	else
	    c.text = (*pit).name;
	c.postfix = "()";
	c.postfix2 = (*pit).type;
	if ( !c.postfix2.isEmpty() )
	    c.postfix2.prepend( " : " );
	res << c;
    }
    if ( includeSuperClass && obj.isValid() && !obj.isUndefined() ) {
	QStringList funcs = QuickInterpreter::self()->functionsOf( obj, TRUE, TRUE );
	QStringList::Iterator it;
	for ( it = funcs.begin(); it != funcs.end(); ++it ) {
	    CompletionEntry c;
	    c.type = "function";
	    c.text = *it;
	    c.prefix = "";
	    c.postfix2 = "";
	    res << c;
	}
    }

    // enum values
    QStrList enums = meta->enumeratorNames( includeSuperClass );
    for ( int k = 0; k < (int)enums.count(); ++k ) {
	const QMetaEnum *me = meta->enumerator( enums.at( k ), includeSuperClass );
	for ( int l = 0; l < (int)me->count; ++l ) {
	    CompletionEntry c;
	    c.type = "enum";
	    c.text = me->items[l].key;
	    c.prefix = "";
	    c.postfix2 = me->name;
	    if ( !c.postfix2.isEmpty() )
		c.postfix2.prepend( " : " );
	    res << c;
	}
    }

    if ( includeSuperClass && obj.isValid() && !obj.isUndefined() ) {
	QStringList classes = QuickInterpreter::self()->classesOf( obj );
	QStringList::Iterator it;
	for ( it = classes.begin(); it != classes.end(); ++it ) {
	    CompletionEntry c;
	    c.type = "class";
	    c.text = *it;
	    c.prefix = "";
	    c.postfix2 = "";
	    res << c;
	}
    }
}

void QuickCompletion::completeQObject( const QPtrVector<QObject> &objects,
				       const QString &object,
				       QValueList<CompletionEntry> &res )
{
    bool deleteList = FALSE;
    for ( uint i = 0; i < objects.count(); i++ ) {
	QObject *qobj = objects[ i ];
	if ( !qobj )
	    continue;
	if ( object != "Qt" && qstrcmp( qobj->className(), "QuickNamespace" ) == 0 )
	    continue;
	// children
	QObjectList *clist = 0;
	QObjectList *l2 = 0;
	if ( qobj == qApp ) {
	    clist = (QObjectList*)QuickInterpreter::self()->topLevelObjects();
	} else {
	    QObjectList *l = (QObjectList*)qobj->children();
	    l2 = extraChildren( qobj );
	    if ( !l ) {
		l = l2;
	    } else {
		deleteList = TRUE;
		l = new QObjectList;
		QObjectListIt it( *qobj->children() );
		while ( it.current() ) {
		    l->append( it.current() );
		    ++it;
		}
		if ( l2 ) {
		    QObjectListIt it( *l2 );
		    while ( it.current() ) {
			if ( ( (QObjectList*)l )->findRef( it.current() ) != -1 ) {
			    ++it;
			    continue;
			}
			( (QObjectList*)l )->append( it.current() );
			++it;
		    }
		}
		delete l2;
		l2 = 0;
	    }
	    clist = l;
	}

	if ( clist ) {
	    QObjectListIt it( *clist );
	    QObject *o;
	    while ( (o=it.current()) ) {
		QString s = o->name();
		if ( o->inherits( "QLayoutWidget" ) ||
		     o-> inherits( "QWidgetStack" ) ) {
		    addLayoutChildren( o, res );
		    ++it;
		    continue;
		}
		if ( o->inherits( "Spacer" ) ||
		     o->inherits( "QSizeGrip" ) ||
		     o->inherits( "QWidgetStack" ) ) {
		    ++it;
		    continue;
		}
		if ( s.find( " " ) == -1 && s.find( "qt_" ) == -1 &&
		     s.find( "unnamed" ) == -1 ) {
		    CompletionEntry c;
		    c.type = o->isWidgetType() ? "widget" : "object";
		    c.text = s;
		    c.postfix2 = o->className();
		    if ( !c.postfix2.isEmpty() )
			c.postfix2.prepend( " : " );
		    res << c;
		}
		++it;
	    }
	}

	if ( deleteList ) {
	    delete clist;
	    clist = 0;
	}

	QSObject qsobj = QuickInterpreter::self()->wrap( qobj );
	int flags = 0;
	if ( i == 0 )
	    flags |= IncludeSuperClass;
	if ( qobj->inherits( "QuickUnnamedObject" ) )
	    flags |= IsUnnamed;
	completeQMetaObject( qobj->metaObject(),
			     object,
			     res,
			     flags,
			     qsobj
	    );

	delete l2;
    }
}

// =============================================================================
// =============================================================================
// == Parser to resolve assignments in Qt Script Code
// =============================================================================
// =============================================================================

QString QuickCompletion::resolveValue( const QString &value, const QValueList<QPair<QString, QString> > &assignements ) const
{
    for ( QValueList<QPair<QString, QString> >::ConstIterator it = assignements.begin(); it != assignements.end(); ++it ) {
	if ( (*it).first == value )
	    return (*it).second;
    }
    return QString::null;
}

QString QuickCompletion::resolveFullyQualifiedValue( const QString &value,
						     const QValueList<QPair<QString, QString> > &assignements ) const
{
    QStringList l = QStringList::split( '.', value );
    QString valuePart;
    for ( QStringList::ConstIterator vit = l.begin(); vit != l.end(); ++vit ) {
	if ( !valuePart.isNull() )
	    valuePart += ".";
	valuePart += (*vit).left( (*vit).find( '(' ) );
	QString replacedValue;
	while ( ( replacedValue = resolveValue( valuePart, assignements ) ) != QString::null )
	    valuePart = replacedValue;
    }
    return valuePart;
}

#define APPEND_PARSED_CHAR( s ) \
do { \
	if ( s == LeftHandSide ) \
	    leftHandBuffer += c; \
	else if ( s == RightHandSight ) \
	    rightHandBuffer += c; \
} while ( FALSE )


QValueList<QPair<QString, QString> > QuickCompletion::parseAssignements( const QString &code ) const
{
    QChar c, last;
    enum State { LeftHandSide, RightHandSight, Comment, String, Parentheses } state = LeftHandSide;
    int parenCount = 0;
    State lastState;
    QChar ignoreEnd[2];
    QString leftHandBuffer, rightHandBuffer;
    QValueList<QPair<QString, QString> > assignements;
    for ( int i = 0; i < (int)code.length(); ++i ) {
	last = c;
	c = code[i];

	if ( state == Comment || state == String || state == Parentheses ) {
	    if ( state == String )
		APPEND_PARSED_CHAR( lastState );
	    if ( c == '(' && state == Parentheses ) {
		parenCount++;
		continue;
	    }
	    if ( ignoreEnd[1] != QChar::null ) {
		if ( last == ignoreEnd[0] && c == ignoreEnd[1] )
		    state = (state == String ? lastState : LeftHandSide);
	    } else if ( c == ignoreEnd[0] ) {
		if ( state == Parentheses ) {
		    parenCount--;
		    if ( parenCount > 0 )
			continue;
		}
		state = ( (state == String || state == Parentheses) ? lastState : LeftHandSide );
	    }
	    continue;
	}

	if ( c == '*' && last == '/' ) {
	    state = Comment;
	    ignoreEnd[0] = '*';
	    ignoreEnd[1] = '/';
	    leftHandBuffer = QString::null;
	    rightHandBuffer = QString::null;
	    continue;
	} else if ( c == '/' && last == '/' ) {
	    state = Comment;
	    ignoreEnd[0] = '\n';
	    ignoreEnd[1] = QChar::null;
	    leftHandBuffer = QString::null;
	    rightHandBuffer = QString::null;
	    continue;
	} else if ( c == '\"' ) {
	    lastState = state;
	    state = String;
	    ignoreEnd[0] = '\"';
	    ignoreEnd[1] = QChar::null;
	    APPEND_PARSED_CHAR( lastState );
	    continue;
	} else if ( c == '\'' ) {
	    lastState = state;
	    state = String;
	    ignoreEnd[0] = '\'';
	    ignoreEnd[1] = QChar::null;
	    APPEND_PARSED_CHAR( lastState );
	    continue;
	} else if ( c == '(' ) {
	    lastState = state;
	    state = Parentheses;
	    ignoreEnd[0] = ')';
	    ignoreEnd[1] = QChar::null;
	    parenCount = 1;
	    continue;
	}

	if ( last.isSpace() ) {
	    if ( code[i-2] != '.' && c != '=' && c != ';' && c != '{' && c != '}' && c != '(' && c != ')' ) {
		if ( state == LeftHandSide )
		    leftHandBuffer = QString::null;
		else if ( state == RightHandSight )
		    rightHandBuffer = QString::null;
	    }
	}


	if ( c == ';' || c == '{' || c == '}' ) {
	    if ( state == LeftHandSide ) {
		leftHandBuffer = QString::null;
	    } else if ( state == RightHandSight ) {
		rightHandBuffer = rightHandBuffer.replace( QRegExp( "\\s" ), "" );
		leftHandBuffer = leftHandBuffer.replace( QRegExp( "\\s" ), "" );
		QPair<QString, QString> p;
		p.first = leftHandBuffer;
		p.second = rightHandBuffer;
		assignements.prepend( p );
		leftHandBuffer = QString::null;
		rightHandBuffer = QString::null;
		state = LeftHandSide;
		continue;
	    }
	}

	if ( c == '=' ) {
	    if ( last == '!' || last == '=' ) {
		leftHandBuffer = QString::null;
		rightHandBuffer = QString::null;
		state = LeftHandSide;
		continue;
	    }
	    if ( state == RightHandSight ) {
		leftHandBuffer = QString::null;
		rightHandBuffer = QString::null;
		state = LeftHandSide;
	    } else if ( state == LeftHandSide ) {
		state = RightHandSight;
	    }
	    continue;
	}

	APPEND_PARSED_CHAR( state );
    }

    for ( QValueList<QPair<QString, QString> >::Iterator it = assignements.begin(); it != assignements.end(); ++it ) {
	QString key = (*it).first;
	QString value = (*it).second;
	QStringList l = QStringList::split( '.', value );
	QString valuePart;
	for ( QStringList::ConstIterator vit = l.begin(); vit != l.end(); ++vit ) {
	    if ( !valuePart.isNull() )
		valuePart += ".";
	    valuePart += *vit;
	    QString replacedValue;
	    while ( ( replacedValue = resolveValue( valuePart, assignements ) ) != QString::null )
		valuePart = replacedValue;
	    (*it).second = valuePart;
	}
    }

    return assignements;
}

// =============================================================================
// =============================================================================
// == Function to return the code which should be parsed for assignments
// =============================================================================
// =============================================================================

QString QuickCompletion::functionCode() const
{
    QTextParagraph *p = curEditor->textCursor()->paragraph();
    static QString validChars = "abcdefghijklmnopqrstuvwxyz0123456789_";
    QString funcName;
    int pos;
    int braceCount = -1;
    while ( p && ( pos = p->string()->toString().find( "function" ) ) == -1 ) {
	QString s = p->string()->toString();
	braceCount += s.contains( '{' );
	braceCount -= s.contains( '}' );
	p = p->prev();
    }

    if ( p && pos != -1 && braceCount >= 0 ) {
	funcName = p->string()->toString().mid( pos + 9 ).simplifyWhiteSpace();
	funcName = funcName.left( funcName.find( '(' ) );
    }

    QuickClassParser parser;
    parser.parse( curEditor->text() );
    QValueList<QuickClass> classes = parser.classes();
    bool global = funcName.isNull() || !p;
    QString code;
    for ( QValueList<QuickClass>::ConstIterator it = classes.begin(); it != classes.end(); ++it ) {
	if ( (*it).type == QuickClass::Global ) {
	    for ( QStringList::ConstIterator vit = (*it).variables.begin(); vit != (*it).variables.end(); ++vit )
		code += *vit + ";\n";
	    code += "\n";
	    if ( global )
		break;
	}

	if ( global )
	    continue;

	for ( QValueList<LanguageInterface::Function>::ConstIterator fit = (*it).functions.begin();
	      fit != (*it).functions.end(); ++fit ) {
	    if ( (*fit).name.left( (*fit).name.find( '(' ) ) == funcName ) {
		if ( (*it).type != QuickClass::Global ) {
		    for ( QStringList::ConstIterator vit = (*it).variables.begin(); vit != (*it).variables.end(); ++vit )
			code += *vit + ";\n";
		    code += "\n";
		}
		code += "\n" + (*fit).body + "\n";
		break;
	    }
	}
    }

    return code;
}

// =============================================================================
// =============================================================================
// == QuickCompletion
// =============================================================================
// =============================================================================

QuickCompletion::QuickCompletion( Editor *e )
    : EditorCompletion( e ),
      thisObject( 0 )
{
    int i = 0;
    while ( QuickSyntaxHighlighter::keywords[ i ] != QString::null )
	addCompletionEntry( QuickSyntaxHighlighter::keywords[ i++ ], 0, FALSE );
    addCompletionEntry( "Application", 0, FALSE );
}

bool QuickCompletion::doObjectCompletion( const QString &obj )
{
    QString object = resolveFullyQualifiedValue( obj, parseAssignements( functionCode() ) );
    QSCompletionObject o = queryObject( object );
    o.resolve();
    if ( o.isNull() )
	return FALSE;

    QValueList<CompletionEntry> res;

    QSObject nullObject;
    switch ( o.type ) {
    case QSCompletionObject::TQSObject:
	completeQSObject( o.qsobj, res );
	break;
    case QSCompletionObject::TQMetaObject:
	completeQMetaObject( o.meta, object, res, IncludeSuperClass, nullObject );
	break;
    case QSCompletionObject::TQObject:
	completeQObject( o.qobj, object, res );
	break;
    case QSCompletionObject::TNull:
	break;
    }

    if ( !res.isEmpty() )
	showCompletion( res );

    return TRUE;
}

QValueList<QStringList> QuickCompletion::functionParameters( const QString &f,
							     QChar &separator,
							     QString &,
							     QString &postfix )
{
     QValueList<QStringList> l;

    separator = ',';

    // some hardcoded ones
    if ( f == "startTimer" ) {
	QStringList args;
	args << "interval : Number";
	args << "callback : Function";
	postfix = " : Number";
	l << args;
	return l;
    } else if ( f == "killTimer" ) {
	QStringList args;
	args << "id : Number";
	l << args;
	return l;
    } else if ( f == "connect" ) {
	QStringList args;
	args << "sender : QObject";
	args << "signal : String";
	args << "receiver : QObject";
	args << "slot : String";
	l << args;
	args.clear();
	args << "sender : QObject";
	args << "signal : String";
	args << "receiver : Function";
	l << args;
	return l;
    }

    QString object = f;
    int pnt = object.findRev( '.' );
    if ( pnt == -1 ) {
	if ( !thisObject )
	    return l;
	object.prepend( "this." );
    }


    QString returnType;
    QString func = object.mid( pnt + 1 );
    object = resolveFullyQualifiedValue( object.left( pnt ), parseAssignements( functionCode() ) );
    QSCompletionObject obj = queryObject( object );
    obj.resolve();
    if ( obj.isNull() || obj.type == QSCompletionObject::TQSObject )
	return l;

    QValueList<Property> res2;
    int i;

    switch ( obj.type ) {
    case QSCompletionObject::TQObject:
	for ( i = obj.qobj.size() - 1; i >= 0; --i )
	    getSlots( obj.qobj[i]->metaObject(), res2, i == 0, TRUE, FALSE );
	break;
    case QSCompletionObject::TQMetaObject:
	getSlots( obj.meta, res2, TRUE, TRUE, FALSE );
	break;
    default:
	break;
    }

    QStringList funcs;
    for ( QValueList<Property>::Iterator it = res2.begin(); it != res2.end(); ++it ) {
	if ( (*it).name.left( (*it).name.find( '(' ) ) == func ) {
	    returnType = (*it).type;
	    funcs << (*it).name;
	}
    }

    if ( funcs.isEmpty() ) {
	if ( f[ 0 ] == '\"' ) {
	    QTextCursor *cursor = curEditor->textCursor();
	    QString line = cursor->paragraph()->string()->toString();
	    if ( line.findRev( "connect(", cursor->index() ) != -1 ) {
		int i = line.findRev( ',' );
		static QString legalChars = "abcdefghijklmnopqrstuvwxyzABSCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.";
		int end;
		for ( end = i; end >= 0; --end ) {
		    if ( legalChars.find( line[ end ] ) != -1 )
			break;
		}
		int begin;
		for ( begin = end; begin >= 0; --begin ) {
		    if ( legalChars.find( line[ begin ] ) == -1 ) {
			begin++;
			break;
		    }
		}
		QString obj_str = line.mid( begin, end - begin + 1 );
		func = f;
		func.remove( 0, 1 );
		func = func.left( func.find( "(" ) );
		obj = queryObject( obj_str );
		obj.resolve();
		if ( obj.isNull() || obj.type == QSCompletionObject::TQSObject )
		    return QValueList<QStringList>();
		QValueList<Property> res2;
		int j;

		switch ( obj.type ) {
		case QSCompletionObject::TQObject:
		    for ( j = obj.qobj.size() - 1; j >= 0; --j ) {
			getSlots( obj.qobj[j]->metaObject(), res2, j == 0, TRUE, FALSE );
			getSlots( obj.qobj[j]->metaObject(), res2, j == 0, TRUE, TRUE );
		    }
		    break;
		case QSCompletionObject::TQMetaObject:
		    getSlots( obj.meta, res2, TRUE, TRUE, FALSE );
		    getSlots( obj.meta, res2, TRUE, TRUE, TRUE );
		    break;
		default:
		    break;
		}

		for ( QValueList<Property>::Iterator it = res2.begin(); it != res2.end(); ++it ) {
		    if ( (*it).name.left( (*it).name.find( '(' ) ) == func ) {
			returnType = (*it).type;
			funcs << (*it).name;
		    }
		}
		if ( funcs.isEmpty() )
		    return QValueList<QStringList>();
	    }
	} else {
	    return QValueList<QStringList>();
	}
    }

    for ( QStringList::Iterator fit = funcs.begin(); fit != funcs.end(); ++fit ) {
	QString fun = *fit;
	QStringList argList = getArguments( fun );

	QStringList res;

	for ( QStringList::Iterator it2 = argList.begin(); it2 != argList.end(); ++it2 ) {
	    QStringList l = QStringList::split( ' ', *it2 );
	    QString type = l[ 0 ];
	    QuickInterpreter::cleanType( type );
	    if ( l.count() > 1 )
		type = l[ 1 ] + " : " + type;
	    res << type;
	}

	if ( !returnType.isEmpty() )
	    postfix = " : " + returnType;

	l << res;
    }

    return l;
}

void QuickCompletion::setContext( QObject *this_ )
{
    thisObject = this_;
}

// =============================================================================
// =============================================================================
// == Functions to query objects, properties, etc.
// =============================================================================
// =============================================================================

static QString vTypeToQSType( const QString &type )
{
    QVariant::Type t = QVariant::nameToType( type );
    switch ( t ) {
    case QVariant::ByteArray:
	return "ByteArray";
    case QVariant::Pixmap:
	return "Pixmap";
    case QVariant::Date:
    case QVariant::Time:
    case QVariant::DateTime:
	return "Date";
    case QVariant::Color:
	return "Color";
    case QVariant::Font:
	return "Font";
    case QVariant::Size:
	return "Size";
    case QVariant::Rect:
	return "Rect";
    case QVariant::Point:
	return "Point";
    case QVariant::StringList:
    case QVariant::List:
    case QVariant::Map:
	return "Array";
    case QVariant::String:
    case QVariant::CString:
	return "String";
    case QVariant::Int:
    case QVariant::UInt:
    case QVariant::Double:
	return "Number";
    case QVariant::Bool:
	return "Boolean";
    default:
	return QString::null;
    }
    return QString::null;
}

static QString uTypeToQSType( QUType *t, const char *extra )
{
    if ( QUType::isEqual( t, &static_QUType_double ) )
	return "Number";
    else if ( QUType::isEqual( t, &static_QUType_int ) )
	return "Number";
    else if ( QUType::isEqual( t, &static_QUType_bool ) )
	return "Boolean";
    else if ( QUType::isEqual( t, &static_QUType_QString ) )
	return "String";
    else if ( QUType::isEqual( t, &static_QUType_charstar ) )
	return "String";
    else if ( QUType::isEqual( t, &static_QUType_QVariant ) ) {
	// ### can't get type without the QUObject
	return "Variant";
    } else if ( QUType::isEqual( t, &static_QUType_varptr ) ) {
	// ### can't get type without the QUObject
	return "Variant";
    } else if ( QUType::isEqual( t, &static_QUType_ptr ) ) {
	return (char*)extra;
    }

    return QString::null;
}

static const QMetaObject *tryMetaObject( const QString &s )
{
    const QMetaObject *m = QMetaObject::metaObject( s );
    if ( m )
	return m;
    m = QMetaObject::metaObject( s + "Ptr" );
    if ( m )
	return m;
    if ( s[0] == 'Q' ) {
	m = QMetaObject::metaObject( s.mid( 1 ) );
	if ( m )
	    return m;
	m = QMetaObject::metaObject( s.mid( 1 ) + "Ptr" );
	if ( m )
	    return m;
	m = QMetaObject::metaObject( "Quick" + s.mid( 1 ) );
	if ( m )
	    return m;
	m = QMetaObject::metaObject( "Quick" + s.mid( 1 ) + "Ptr" );
	if ( m )
	    return m;
	if ( s.left( 4 ) == "QSql" ) {
	    m = QMetaObject::metaObject( "QuickDatabase" + s.mid( 4 ) );
	    if ( m )
		return m;
	    m = QMetaObject::metaObject( "QuickDatabase" + s.mid( 4 ) + "Ptr" );
	    if ( m )
		return m;
	}
    } else {
	m = QMetaObject::metaObject( "Q" + s );
	if ( m )
	    return m;
	m = QMetaObject::metaObject( "Q" + s + "Ptr" );
	if ( m )
	    return m;
    }
    return 0;
}

QSCompletionObject QuickCompletion::queryObject( const QString &object )
{
    QStringList l = QStringList::split( '.', object );

    QSCompletionObject ctx( QuickInterpreter::self()->env()->currentScope() );
    if ( thisObject )
	ctx = QuickInterpreter::self()->wrap( thisObject );

    bool end = FALSE;
    bool first = TRUE;
    for ( QStringList::ConstIterator it = l.begin(); it != l.end(); ) {
	QString s = *it;
	first = it == l.begin();
	++it;
	end = it == l.end();

	if ( first && s == "this" ) {
	    if ( end ) {
		if ( thisObject )
		    return QSCompletionObject( QuickInterpreter::self()->wrap( thisObject ) );
		return QSCompletionObject( QuickInterpreter::self()->env()->currentScope() );
	    }
	    continue;
	}

	QSCompletionObject obj = queryCompletionObject( ctx, s );
	if ( obj.isNull() && first && thisObject ) {
	    ctx = QuickInterpreter::self()->env()->currentScope();
	    obj = queryCompletionObject( ctx, s );
	}

	obj.resolve();
	if ( end )
	    return obj;
	else if ( !obj.isNull() )
	    ctx = obj;
	else
	    return QSCompletionObject();
    }

    return QSCompletionObject();
}

QSCompletionObject QuickCompletion::queryCompletionObject( QSCompletionObject &ctx, const QString &property ) const
{
    QString s = property;

    // this is always an Array
    if ( s.find( '[' ) != -1 )
	return QuickInterpreter::self()->object( "Array" );
    else if ( s == "Application" )
	return *interfaceObjects( QuickInterpreter::self()->wrap( qApp ) );

    const QMetaObject *haveAsMeta = 0;
    if ( ctx.type == QSCompletionObject::TQSObject &&
	 ctx.qsobj.isA( QuickInterpreter::self()->env()->globalClass() ) ) {
	haveAsMeta = tryMetaObject( s );
    }

    QSCompletionObject obj;

    if ( ctx.type == QSCompletionObject::TQSObject ) {
	obj = queryQSObject( ctx.qsobj, property );
	if ( obj.isNull() )
	    obj.resolve();
    }

    if ( ctx.type == QSCompletionObject::TQMetaObject ) {
	obj = queryQMetaObject( ctx.meta, property, TRUE );
	if ( obj.isNull() )
	    obj = queryQSObject( ctx.meta, property, TRUE );
    }

    if ( ctx.type == QSCompletionObject::TQObject ) {
	obj = queryQObject( ctx.qobj, property );
	if ( obj.isNull() )
	    obj = queryQMetaObject( ctx.qobj, property );
	if ( obj.isNull() )
	    obj = queryQSObject( ctx.qobj, property );
    }

    if ( obj.isNull() && ctx.type == QSCompletionObject::TQSObject &&
	 ctx.qsobj.isA( QuickInterpreter::self()->env()->globalClass() ) ) {
	const QMetaObject *meta = tryMetaObject( s );
	if ( meta )
	    return QSCompletionObject( meta );
    }

    if ( haveAsMeta ) {
	QSCompletionObject o = obj;
	o.resolve();
	if ( o.type == QSCompletionObject::TQSObject )
	    return haveAsMeta;
    }

    return obj;
}

QSObject QuickCompletion::queryQSObject( QSObject &ctx, const QString &propety ) const
{
    static QString numbers = "01234567890";
    QString s = propety;

    if ( s.find( '[' ) != -1 )
	s = "Array";
    else if ( numbers.find( s[0] ) != -1 )
	s = "Number";
    else if ( s == "false" || s == "true" )
	s = "Boolean";
    else if ( s[0] == '\'' || s[0] == '\"' )
	s = "String";

    return ctx.get( s );
}

QSObject QuickCompletion::queryQSObject( const QMetaObject *meta, const QString &property, bool includeSuperClass ) const
{
    const QMetaProperty *mp = meta->property( meta->findProperty( property, includeSuperClass ), includeSuperClass );
    if ( mp ) {
	QSObject o = QuickInterpreter::self()->object( vTypeToQSType( mp->type() ) );
	if ( !o.isNull() && !o.isUndefined() )
	    return o;
    }

    int nSlots = meta->numSlots( includeSuperClass );
    for ( int sl = 0; sl < nSlots; ++sl ) {
	QString n = meta->slot( sl, includeSuperClass )->name;
	n = n.left( n.find( '(' ) );
	if ( property != n )
		continue;
	const QMetaData *md = meta->slot( sl, includeSuperClass );
	const QUMethod *m = md->method;
	QUParameter p = m->parameters[0];
	if ( p.inOut == QUParameter::Out ) {
	    QUType *t = p.type;
	    return QuickInterpreter::self()->object( uTypeToQSType( t, (char*)p.typeExtra ) );
	}
    }

    return QSUndefined();
}

QSObject QuickCompletion::queryQSObject( const QPtrVector<QObject> &objects, const QString &property ) const
{
    for ( uint i = 0; i < objects.count(); i++ ) {
	const QMetaObject *mo = objects[i]->metaObject();
	QSObject qso = queryQSObject( mo, property, i == 0 );
	if ( !qso.isNull() && !qso.isUndefined() )
	    return qso;
    }
    return QSUndefined();
}

const QMetaObject *QuickCompletion::queryQMetaObject( const QMetaObject *meta,
						      const QString &property,
						      bool includeSuperClass ) const
{
    int nSlots = meta->numSlots( includeSuperClass );
    for ( int sl = 0; sl < nSlots; ++sl ) {
	QString n = meta->slot( sl, includeSuperClass )->name;
	n = n.left( n.find( '(' ) );
	if ( property != n )
		continue;
	const QMetaData *md = meta->slot( sl, includeSuperClass );
	const QUMethod *m = md->method;
	QUParameter p = m->parameters[0];
	if ( p.inOut == QUParameter::Out ) {
	    QUType *t = p.type;
	    if ( QUType::isEqual( t, &static_QUType_ptr ) ) {
		QString mon = (char*)p.typeExtra;
		const QMetaObject *mo = tryMetaObject( mon );
		if ( mo )
		    return mo;
	    }
	}
    }

    return 0;
}

const QMetaObject *QuickCompletion::queryQMetaObject( const QPtrVector<QObject> &objects, const QString &property ) const
{
    for ( uint i = 0; i < objects.count(); i++ ) {
	const QMetaObject *mo = objects[i]->metaObject();
	const QMetaObject *meta = queryQMetaObject( mo, property, i == 0 );
	if ( meta )
	    return meta;
    }
    return 0;
}

QPtrVector<QObject> QuickCompletion::queryQObject( const QPtrVector<QObject> &objects, const QString &property ) const
{
    QSObject obj = QuickInterpreter::self()->wrap( objects[0] );

    if ( obj.isA( QuickInterpreter::self()->wrapperClass() ) ) {
	QSMember m;
	if ( QuickInterpreter::self()->wrapperClass()->member( &obj, property, &m ) ) {
	    QSObject o = QuickInterpreter::self()->wrapperClass()->fetchValue( &obj, m );
	    if ( o.isA( QuickInterpreter::self()->wrapperClass() ) )
		return *interfaceObjects( o );
	} else if ( QuickInterpreter::self()->applicationClass()->member( &obj, property, &m ) ) {
	    QSObject o = QuickInterpreter::self()->applicationClass()->fetchValue( &obj, m );
	    if ( o.isA( QuickInterpreter::self()->wrapperClass() ) )
		return *interfaceObjects( o );
	}
    }

    return QPtrVector<QObject>();
}

// =============================================================================
// =============================================================================
// == QSCompletionObject
// =============================================================================
// =============================================================================

void QSCompletionObject::resolve()
{
    if ( type == TQSObject && qsobj.isA( QuickInterpreter::self()->wrapperClass() ) ) {
	type = TQObject;
	qobj = *interfaceObjects( qsobj );
    }
}

bool QSCompletionObject::isNull() const
{
    return ( type == TNull ||
	     type == TQSObject && (qsobj.isNull() || qsobj.isUndefined()) ||
	     type == TQMetaObject && !meta ||
	     type == TQObject && qobj.isEmpty() );
}
