#include <stdio.h>
#include <ntqglobal.h>
#include <ntqstring.h>
#include <ntqapplication.h>
#include <ntqmetaobject.h>
#include <private/qucomextra_p.h>
#include "smoke.h"

#undef DEBUG
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#ifndef __USE_POSIX
#define __USE_POSIX
#endif
#ifndef __USE_XOPEN
#define __USE_XOPEN
#endif
#ifdef _BOOL
#define HAS_BOOL
#endif
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#ifndef TQT_VERSION_STR
#define TQT_VERSION_STR "Unknown"
#endif

#undef free
#undef malloc

#include "marshall.h"
#include "perlqt.h"
#include "smokeperl.h"

#ifndef IN_BYTES
#define IN_BYTES IN_BYTE
#endif

#ifndef IN_LOCALE
#define IN_LOCALE (PL_curcop->op_private & HINT_LOCALE)
#endif

extern Smoke *qt_Smoke;
extern void init_qt_Smoke();

int do_debug = qtdb_none;

HV *pointer_map = 0;
SV *sv_qapp = 0;
int object_count = 0;
void *_current_object = 0;    // TODO: ask myself if this is stupid

bool temporary_virtual_function_success = false;

static TQAsciiDict<Smoke::Index> *methcache = 0;
static TQAsciiDict<Smoke::Index> *classcache = 0;

SV *sv_this = 0;

Smoke::Index _current_object_class = 0;
Smoke::Index _current_method = 0;
/*
 * Type handling by moc is simple.
 *
 * If the type name matches /^(?:const\s+)?\Q$types\E&?$/, use the
 * static_TQUType, where $types is join('|', qw(bool int double char* TQString);
 *
 * Everything else is passed as a pointer! There are types which aren't
 * Smoke::tf_ptr but will have to be passed as a pointer. Make sure to keep
 * track of what's what.
 */

/*
 * Simply using typeids isn't enough for signals/slots. It will be possible
 * to declare signals and slots which use arguments which can't all be
 * found in a single smoke object. Instead, we need to store smoke => typeid
 * pairs. We also need additional informatation, such as whether we're passing
 * a pointer to the union element.
 */

enum MocArgumentType {
    xmoc_ptr,
    xmoc_bool,
    xmoc_int,
    xmoc_double,
    xmoc_charstar,
    xmoc_TQString
};

struct MocArgument {
    // smoke object and associated typeid
    SmokeType st;
    MocArgumentType argType;
};


extern TypeHandler TQt_handlers[];
void install_handlers(TypeHandler *);

void *sv_to_ptr(SV *sv) {  // ptr on success, null on fail
    smokeperl_object *o = sv_obj_info(sv);
    return o ? o->ptr : 0;
}

bool isTQObject(Smoke *smoke, Smoke::Index classId) {
    if(!strcmp(smoke->classes[classId].className, "TQObject"))
	return true;
    for(Smoke::Index *p = smoke->inheritanceList + smoke->classes[classId].parents;
	*p;
	p++) {
	if(isTQObject(smoke, *p))
	    return true;
    }
    return false;
}

int isDerivedFrom(Smoke *smoke, Smoke::Index classId, Smoke::Index baseId, int cnt) {
    if(classId == baseId)
	return cnt;
    cnt++;
    for(Smoke::Index *p = smoke->inheritanceList + smoke->classes[classId].parents;
	*p;
	p++) {
	if(isDerivedFrom(smoke, *p, baseId, cnt) != -1)
	    return cnt;
    }
    return -1;
}

int isDerivedFrom(Smoke *smoke, const char *className, const char *baseClassName, int cnt) {
    if(!smoke || !className || !baseClassName)
	return -1;
    Smoke::Index idClass = smoke->idClass(className);
    Smoke::Index idBase = smoke->idClass(baseClassName);
    return isDerivedFrom(smoke, idClass, idBase, cnt);
}

SV *getPointerObject(void *ptr) {
    HV *hv = pointer_map;
    SV *keysv = newSViv((IV)ptr);
    STRLEN len;
    char *key = SvPV(keysv, len);
    SV **svp = hv_fetch(hv, key, len, 0);
    if(!svp){
	 SvREFCNT_dec(keysv);
	 return 0;
    }
    if(!SvOK(*svp)){
	hv_delete(hv, key, len, G_DISCARD);
	SvREFCNT_dec(keysv);
	return 0;
    }
    return *svp;
}

void unmapPointer(smokeperl_object *o, Smoke::Index classId, void *lastptr) {
    HV *hv = pointer_map;
    void *ptr = o->smoke->cast(o->ptr, o->classId, classId);
    if(ptr != lastptr) {
	lastptr = ptr;
	SV *keysv = newSViv((IV)ptr);
	STRLEN len;
	char *key = SvPV(keysv, len);
	if(hv_exists(hv, key, len))
	    hv_delete(hv, key, len, G_DISCARD);
	SvREFCNT_dec(keysv);
    }
    for(Smoke::Index *i = o->smoke->inheritanceList + o->smoke->classes[classId].parents;
	*i;
	i++) {
	unmapPointer(o, *i, lastptr);
    }
}

// Store pointer in pointer_map hash : "pointer_to_TQt_object" => weak ref to associated Perl object
// Recurse to store it also as casted to its parent classes.

void mapPointer(SV *obj, smokeperl_object *o, HV *hv, Smoke::Index classId, void *lastptr) {
    void *ptr = o->smoke->cast(o->ptr, o->classId, classId);
    if(ptr != lastptr) {
	lastptr = ptr;
	SV *keysv = newSViv((IV)ptr);
	STRLEN len;
	char *key = SvPV(keysv, len);
	SV *rv = newSVsv(obj);
	sv_rvweaken(rv);		// weak reference!
	hv_store(hv, key, len, rv, 0);
	SvREFCNT_dec(keysv);
    }
    for(Smoke::Index *i = o->smoke->inheritanceList + o->smoke->classes[classId].parents;
	*i;
	i++) {
	mapPointer(obj, o, hv, *i, lastptr);
    }
}

Marshall::HandlerFn getMarshallFn(const SmokeType &type);

class VirtualMethodReturnValue : public Marshall {
    Smoke *_smoke;
    Smoke::Index _method;
    Smoke::Stack _stack;
    SmokeType _st;
    SV *_retval;
public:
    const Smoke::Method &method() { return _smoke->methods[_method]; }
    SmokeType type() { return _st; }
    Marshall::Action action() { return Marshall::FromSV; }
    Smoke::StackItem &item() { return _stack[0]; }
    SV *var() { return _retval; }
    void unsupported() {
	croak("Cannot handle '%s' as return-type of virtual method %s::%s",
		type().name(),
		_smoke->className(method().classId),
		_smoke->methodNames[method().name]);
    }
    Smoke *smoke() { return _smoke; }
    void next() {}
    bool cleanup() { return false; }
    VirtualMethodReturnValue(Smoke *smoke, Smoke::Index meth, Smoke::Stack stack, SV *retval) :
	_smoke(smoke), _method(meth), _stack(stack), _retval(retval) {
	_st.set(_smoke, method().ret);
 	Marshall::HandlerFn fn = getMarshallFn(type());
	(*fn)(this);
   }
};

class VirtualMethodCall : public Marshall {
    Smoke *_smoke;
    Smoke::Index _method;
    Smoke::Stack _stack;
    GV *_gv;
    int _cur;
    Smoke::Index *_args;
    SV **_sp;
    bool _called;
    SV *_savethis;

public:
    SmokeType type() { return SmokeType(_smoke, _args[_cur]); }
    Marshall::Action action() { return Marshall::ToSV; }
    Smoke::StackItem &item() { return _stack[_cur + 1]; }
    SV *var() { return _sp[_cur]; }
    const Smoke::Method &method() { return _smoke->methods[_method]; }
    void unsupported() {
	croak("Cannot handle '%s' as argument of virtual method %s::%s",
		type().name(),
		_smoke->className(method().classId),
		_smoke->methodNames[method().name]);
    }
    Smoke *smoke() { return _smoke; }
    void callMethod() {
	dSP;
	if(_called) return;
	_called = true;
	SP = _sp + method().numArgs - 1;
	PUTBACK;
	int count = call_sv((SV*)GvCV(_gv), G_SCALAR);
	SPAGAIN;
	VirtualMethodReturnValue r(_smoke, _method, _stack, POPs);
	PUTBACK;
	FREETMPS;
	LEAVE;
    }
    void next() {
	int oldcur = _cur;
	_cur++;
	while(!_called && _cur < method().numArgs) {
	    Marshall::HandlerFn fn = getMarshallFn(type());
	    (*fn)(this);
	    _cur++;
	}
	callMethod();
	_cur = oldcur;
    }
    bool cleanup() { return false; }   // is this right?
    VirtualMethodCall(Smoke *smoke, Smoke::Index meth, Smoke::Stack stack, SV *obj, GV *gv) :
	_smoke(smoke), _method(meth), _stack(stack), _gv(gv), _cur(-1), _sp(0), _called(false) {
	dSP;
	ENTER;
	SAVETMPS;
	PUSHMARK(SP);
	EXTEND(SP, method().numArgs);
	_savethis = sv_this;
	sv_this = newSVsv(obj);
	_sp = SP + 1;
	for(int i = 0; i < method().numArgs; i++)
	    _sp[i] = sv_newmortal();
	_args = _smoke->argumentList + method().args;
    }
    ~VirtualMethodCall() {
	SvREFCNT_dec(sv_this);
	sv_this = _savethis;
    }
};

class MethodReturnValue : public Marshall {
    Smoke *_smoke;
    Smoke::Index _method;
    SV *_retval;
    Smoke::Stack _stack;
public:
    MethodReturnValue(Smoke *smoke, Smoke::Index method, Smoke::Stack stack, SV *retval) :
	_smoke(smoke), _method(method), _retval(retval), _stack(stack) {
	Marshall::HandlerFn fn = getMarshallFn(type());
	(*fn)(this);
    }
    const Smoke::Method &method() { return _smoke->methods[_method]; }
    SmokeType type() { return SmokeType(_smoke, method().ret); }
    Marshall::Action action() { return Marshall::ToSV; }
    Smoke::StackItem &item() { return _stack[0]; }
    SV *var() { return _retval; }
    void unsupported() {
	croak("Cannot handle '%s' as return-type of %s::%s",
		type().name(),
		_smoke->className(method().classId),
		_smoke->methodNames[method().name]);
    }
    Smoke *smoke() { return _smoke; }
    void next() {}
    bool cleanup() { return false; }
};

class MethodCall : public Marshall {
    int _cur;
    Smoke *_smoke;
    Smoke::Stack _stack;
    Smoke::Index _method;
    Smoke::Index *_args;
    SV **_sp;
    int _items;
    SV *_retval;
    bool _called;
public:
    MethodCall(Smoke *smoke, Smoke::Index method, SV **sp, int items) :
	_smoke(smoke), _method(method), _sp(sp), _items(items), _cur(-1), _called(false) {
	_args = _smoke->argumentList + _smoke->methods[_method].args;
	_items = _smoke->methods[_method].numArgs;
	_stack = new Smoke::StackItem[items + 1];
	_retval = newSV(0);
    }
    ~MethodCall() {
	delete[] _stack;
	SvREFCNT_dec(_retval);
    }
    SmokeType type() { return SmokeType(_smoke, _args[_cur]); }
    Marshall::Action action() { return Marshall::FromSV; }
    Smoke::StackItem &item() { return _stack[_cur + 1]; }
    SV *var() {
	if(_cur < 0) return _retval;
	SvGETMAGIC(*(_sp + _cur));
	return *(_sp + _cur);
    }
    inline const Smoke::Method &method() { return _smoke->methods[_method]; }
    void unsupported() {
	croak("Cannot handle '%s' as argument to %s::%s",
		type().name(),
		_smoke->className(method().classId),
		_smoke->methodNames[method().name]);
    }
    Smoke *smoke() { return _smoke; }
    inline void callMethod() {
	if(_called) return;
	_called = true;
	Smoke::ClassFn fn = _smoke->classes[method().classId].classFn;
	void *ptr = _smoke->cast(
	    _current_object,
	    _current_object_class,
	    method().classId
	);
	_items = -1;
	(*fn)(method().method, ptr, _stack);
	MethodReturnValue r(_smoke, _method, _stack, _retval);
    }
    void next() {
	int oldcur = _cur;
	_cur++;

	while(!_called && _cur < _items) {
	    Marshall::HandlerFn fn = getMarshallFn(type());
	    (*fn)(this);
	    _cur++;
	}

	callMethod();
	_cur = oldcur;
    }
    bool cleanup() { return true; }
};

class UnencapsulatedTQObject : public TQObject {
public:
    TQConnectionList *public_receivers(int signal) const { return receivers(signal); }
    void public_activate_signal(TQConnectionList *clist, TQUObject *o) { activate_signal(clist, o); }
};

class EmitSignal : public Marshall {
    UnencapsulatedTQObject *_qobj;
    int _id;
    MocArgument *_args;
    SV **_sp;
    int _items;
    int _cur;
    Smoke::Stack _stack;
    bool _called;
public:
    EmitSignal(TQObject *qobj, int id, int items, MocArgument *args, SV **sp) :
	_qobj((UnencapsulatedTQObject*)qobj), _id(id), _items(items), _args(args),
	_sp(sp), _cur(-1), _called(false) {
	_stack = new Smoke::StackItem[_items];
    }
    ~EmitSignal() {
	delete[] _stack;
    }
    const MocArgument &arg() { return _args[_cur]; }
    SmokeType type() { return arg().st; }
    Marshall::Action action() { return Marshall::FromSV; }
    Smoke::StackItem &item() { return _stack[_cur]; }
    SV *var() { return _sp[_cur]; }
    void unsupported() {
	croak("Cannot handle '%s' as signal argument", type().name());
    }
    Smoke *smoke() { return type().smoke(); }
    void emitSignal() {
	if(_called) return;
	_called = true;

	TQConnectionList *clist = _qobj->public_receivers(_id);
	if(!clist) return;

	TQUObject *o = new TQUObject[_items + 1];
	for(int i = 0; i < _items; i++) {
	    TQUObject *po = o + i + 1;
	    Smoke::StackItem *si = _stack + i;
	    switch(_args[i].argType) {
	      case xmoc_bool:
		static_TQUType_bool.set(po, si->s_bool);
		break;
	      case xmoc_int:
		static_TQUType_int.set(po, si->s_int);
		break;
	      case xmoc_double:
		static_QUType_double.set(po, si->s_double);
		break;
	      case xmoc_charstar:
		static_TQUType_charstar.set(po, (char*)si->s_voidp);
		break;
	      case xmoc_TQString:
		static_TQUType_TQString.set(po, *(TQString*)si->s_voidp);
		break;
	      default:
		{
		    const SmokeType &t = _args[i].st;
		    void *p;
		    switch(t.elem()) {
		      case Smoke::t_bool:
			p = &si->s_bool;
			break;
		      case Smoke::t_char:
			p = &si->s_char;
			break;
		      case Smoke::t_uchar:
			p = &si->s_uchar;
			break;
		      case Smoke::t_short:
			p = &si->s_short;
			break;
		      case Smoke::t_ushort:
			p = &si->s_ushort;
			break;
		      case Smoke::t_int:
			p = &si->s_int;
			break;
		      case Smoke::t_uint:
			p = &si->s_uint;
			break;
		      case Smoke::t_long:
			p = &si->s_long;
			break;
		      case Smoke::t_ulong:
			p = &si->s_ulong;
			break;
		      case Smoke::t_float:
			p = &si->s_float;
			break;
		      case Smoke::t_double:
			p = &si->s_double;
			break;
		      case Smoke::t_enum:
			{
			    // allocate a new enum value
			    Smoke::EnumFn fn = SmokeClass(t).enumFn();
			    if(!fn) {
				warn("Unknown enumeration %s\n", t.name());
				p = new int((int)si->s_enum);
				break;
			    }
			    Smoke::Index id = t.typeId();
			    (*fn)(Smoke::EnumNew, id, p, si->s_enum);
			    (*fn)(Smoke::EnumFromLong, id, p, si->s_enum);
			    // FIXME: MEMORY LEAK
			}
			break;
		      case Smoke::t_class:
		      case Smoke::t_voidp:
			p = si->s_voidp;
			break;
		      default:
			p = 0;
			break;
		    }
		    static_TQUType_ptr.set(po, p);
		}
	    }
	}

	_qobj->public_activate_signal(clist, o);
        delete[] o;
    }
    void next() {
	int oldcur = _cur;
	_cur++;

	while(!_called && _cur < _items) {
	    Marshall::HandlerFn fn = getMarshallFn(type());
	    (*fn)(this);
	    _cur++;
	}

	emitSignal();
	_cur = oldcur;
    }
    bool cleanup() { return true; }
};

class InvokeSlot : public Marshall {
    TQObject *_qobj;
    GV *_gv;
    int _items;
    MocArgument *_args;
    TQUObject *_o;
    int _cur;
    bool _called;
    SV **_sp;
    Smoke::Stack _stack;
public:
    const MocArgument &arg() { return _args[_cur]; }
    SmokeType type() { return arg().st; }
    Marshall::Action action() { return Marshall::ToSV; }
    Smoke::StackItem &item() { return _stack[_cur]; }
    SV *var() { return _sp[_cur]; }
    Smoke *smoke() { return type().smoke(); }
    bool cleanup() { return false; }
    void unsupported() {
	croak("Cannot handle '%s' as slot argument\n", type().name());
    }
    void copyArguments() {
	for(int i = 0; i < _items; i++) {
	    TQUObject *o = _o + i + 1;
	    switch(_args[i].argType) {
	      case xmoc_bool:
		_stack[i].s_bool = static_TQUType_bool.get(o);
		break;
	      case xmoc_int:
		_stack[i].s_int = static_TQUType_int.get(o);
		break;
	      case xmoc_double:
		_stack[i].s_double = static_QUType_double.get(o);
		break;
	      case xmoc_charstar:
		_stack[i].s_voidp = static_TQUType_charstar.get(o);
		break;
	      case xmoc_TQString:
		_stack[i].s_voidp = &static_TQUType_TQString.get(o);
		break;
	      default:	// case xmoc_ptr:
		{
		    const SmokeType &t = _args[i].st;
		    void *p = static_TQUType_ptr.get(o);
		    switch(t.elem()) {
		      case Smoke::t_bool:
			_stack[i].s_bool = *(bool*)p;
			break;
		      case Smoke::t_char:
			_stack[i].s_char = *(char*)p;
			break;
		      case Smoke::t_uchar:
			_stack[i].s_uchar = *(unsigned char*)p;
			break;
		      case Smoke::t_short:
			_stack[i].s_short = *(short*)p;
			break;
		      case Smoke::t_ushort:
			_stack[i].s_ushort = *(unsigned short*)p;
			break;
		      case Smoke::t_int:
			_stack[i].s_int = *(int*)p;
			break;
		      case Smoke::t_uint:
			_stack[i].s_uint = *(unsigned int*)p;
			break;
		      case Smoke::t_long:
			_stack[i].s_long = *(long*)p;
			break;
		      case Smoke::t_ulong:
			_stack[i].s_ulong = *(unsigned long*)p;
			break;
		      case Smoke::t_float:
			_stack[i].s_float = *(float*)p;
			break;
		      case Smoke::t_double:
			_stack[i].s_double = *(double*)p;
			break;
		      case Smoke::t_enum:
			{
			    Smoke::EnumFn fn = SmokeClass(t).enumFn();
			    if(!fn) {
				warn("Unknown enumeration %s\n", t.name());
				_stack[i].s_enum = *(int*)p;
				break;
			    }
			    Smoke::Index id = t.typeId();
			    (*fn)(Smoke::EnumToLong, id, p, _stack[i].s_enum);
			}
			break;
		      case Smoke::t_class:
		      case Smoke::t_voidp:
			_stack[i].s_voidp = p;
			break;
		    }
		}
	    }
	}
    }
    void invokeSlot() {
	dSP;
	if(_called) return;
	_called = true;

	SP = _sp + _items - 1;
	PUTBACK;
	int count = call_sv((SV*)GvCV(_gv), G_SCALAR);
	SPAGAIN;
	SP -= count;
	PUTBACK;
	FREETMPS;
	LEAVE;
    }
    void next() {
	int oldcur = _cur;
	_cur++;

	while(!_called && _cur < _items) {
	    Marshall::HandlerFn fn = getMarshallFn(type());
	    (*fn)(this);
	    _cur++;
	}

	invokeSlot();
	_cur = oldcur;
    }
    InvokeSlot(TQObject *qobj, GV *gv, int items, MocArgument *args, TQUObject *o) :
	_qobj(qobj), _gv(gv), _items(items), _args(args), _o(o), _cur(-1), _called(false) {
	dSP;
	ENTER;
	SAVETMPS;
	PUSHMARK(SP);
	EXTEND(SP, items);
	PUTBACK;
	_sp = SP + 1;
	for(int i = 0; i < _items; i++)
	    _sp[i] = sv_newmortal();
	_stack = new Smoke::StackItem[_items];
	copyArguments();
    }
    ~InvokeSlot() {
	delete[] _stack;
    }

};

class TQtSmokeBinding : public SmokeBinding {
public:
    TQtSmokeBinding(Smoke *s) : SmokeBinding(s) {}
    void deleted(Smoke::Index classId, void *ptr) {
	SV *obj = getPointerObject(ptr);
	smokeperl_object *o = sv_obj_info(obj);
	if(do_debug && (do_debug & qtdb_gc)) {
            fprintf(stderr, "%p->~%s()\n", ptr, smoke->className(classId));
        }
	if(!o || !o->ptr) {
	    return;
	}
        unmapPointer(o, o->classId, 0);
	o->ptr = 0;
    }
    bool callMethod(Smoke::Index method, void *ptr, Smoke::Stack args, bool isAbstract) {
	SV *obj = getPointerObject(ptr);
	smokeperl_object *o = sv_obj_info(obj);
	if(do_debug && (do_debug & qtdb_virtual)) fprintf(stderr, "virtual %p->%s::%s() called\n", ptr,
	    smoke->classes[smoke->methods[method].classId].className,
	    smoke->methodNames[smoke->methods[method].name]
        );

	if(!o) {
	    if(!PL_dirty && (do_debug && (do_debug & qtdb_virtual)) )   // if not in global destruction
		fprintf(stderr, "Cannot find object for virtual method\n");
	    return false;
	}
	HV *stash = SvSTASH(SvRV(obj));
	if(*HvNAME(stash) == ' ')
	    stash = gv_stashpv(HvNAME(stash) + 1, TRUE);
	const char *methodName = smoke->methodNames[smoke->methods[method].name];
	GV *gv = gv_fetchmethod_autoload(stash, methodName, 0);
	if(!gv) return false;

	VirtualMethodCall c(smoke, method, args, obj, gv);
	// exception variable, just temporary
	temporary_virtual_function_success = true;
	c.next();
	bool ret = temporary_virtual_function_success;
	temporary_virtual_function_success = true;
	return ret;
    }
    char *className(Smoke::Index classId) {
	const char *className = smoke->className(classId);
	char *buf = new char[strlen(className) + 6];
	strcpy(buf, " TQt::");
	strcat(buf, className + 1);
	return buf;
    }
};

// ----------------   Helpers -------------------

SV *catArguments(SV** sp, int n)
{
    SV* r=newSVpvf("");
    for(int i = 0; i < n; i++) {
        if(i) sv_catpv(r, ", ");
        if(!SvOK(sp[i])) {
            sv_catpv(r, "undef");
        } else if(SvROK(sp[i])) {
            smokeperl_object *o = sv_obj_info(sp[i]);
            if(o)
                sv_catpv(r, o->smoke->className(o->classId));
            else
                sv_catsv(r, sp[i]);
        } else {
            bool isString = SvPOK(sp[i]);
            STRLEN len;
            char *s = SvPV(sp[i], len);
            if(isString) sv_catpv(r, "'");
            sv_catpvn(r, s, len > 10 ? 10 : len);
            if(len > 10) sv_catpv(r, "...");
            if(isString) sv_catpv(r, "'");
        }
    }
    return r;
}

Smoke::Index package_classid(const char *p)
{
     Smoke::Index *item = classcache->find(p);
     if(item)
         return *item;
     char *nisa = new char[strlen(p)+6];
     strcpy(nisa, p);
     strcat(nisa, "::ISA");
     AV* isa=get_av(nisa, true);
     delete[] nisa;
     for(int i=0; i<=av_len(isa); i++) {
         SV** np = av_fetch(isa, i, 0);
         if(np) {
             Smoke::Index ix = package_classid(SvPV_nolen(*np));
             if(ix) {
                 classcache->insert(p, new Smoke::Index(ix));
                 return ix;
             }
         }
     }
     return (Smoke::Index) 0;
}

char *get_SVt(SV *sv)
{
    char *r;
    if(!SvOK(sv))
	r = "u";
    else if(SvIOK(sv))
	r = "i";
    else if(SvNOK(sv))
	r = "n";
    else if(SvPOK(sv))
	r = "s";
    else if(SvROK(sv)) {
	smokeperl_object *o = sv_obj_info(sv);
	if(!o) {
            switch (SvTYPE(SvRV(sv))) {
                case SVt_PVAV:
                  r = "a";
                  break;
//                case SVt_PV:
//                case SVt_PVMG:
//                  r = "p";
                default:
                  r = "r";
            }
        }
	else
	    r = (char*)o->smoke->className(o->classId);
    }
    else
	r = "U";
    return r;
}

SV *prettyPrintMethod(Smoke::Index id) {
    SV *r = newSVpvf("");
    Smoke::Method &meth = qt_Smoke->methods[id];
    const char *tname = qt_Smoke->types[meth.ret].name;
    if(meth.flags & Smoke::mf_static) sv_catpv(r, "static ");
    sv_catpvf(r, "%s ", (tname ? tname:"void"));
    sv_catpvf(r, "%s::%s(", qt_Smoke->classes[meth.classId].className, qt_Smoke->methodNames[meth.name]);
    for(int i = 0; i < meth.numArgs; i++) {
        if(i) sv_catpv(r, ", ");
        tname = qt_Smoke->types[qt_Smoke->argumentList[meth.args+i]].name;
        sv_catpv(r, (tname ? tname:"void"));
    }
    sv_catpv(r, ")");
    if(meth.flags & Smoke::mf_const) sv_catpv(r, " const");
    return r;
}

// --------------- Unary Keywords && Attributes ------------------


// implements unary 'this'
XS(XS_this) {
    dXSARGS;
    ST(0) = sv_this;
    XSRETURN(1);
}

// implements unary attributes: 'foo' means 'this->{foo}'
XS(XS_attr) {
    dXSARGS;
    char *key = GvNAME(CvGV(cv));
    U32 klen = strlen(key);
    SV **svp = 0;
    if(SvROK(sv_this) && SvTYPE(SvRV(sv_this)) == SVt_PVHV) {
	HV *hv = (HV*)SvRV(sv_this);
	svp = hv_fetch(hv, key, klen, 1);
    }
    if(svp) {
	ST(0) = *svp;
	XSRETURN(1);
    }
    XSRETURN_UNDEF;
}

// implements unary SUPER attribute: 'SUPER' means ${(CopSTASH)::_INTERNAL_STATIC_}{SUPER}
XS(XS_super) {
    dXSARGS;
    char *key = "SUPER";
    U32 klen = strlen(key);
    SV **svp = 0;
    if(SvROK(sv_this) && SvTYPE(SvRV(sv_this)) == SVt_PVHV) {
	HV *cs = (HV*)CopSTASH(PL_curcop);
        if(!cs) XSRETURN_UNDEF;
        svp = hv_fetch(cs, "_INTERNAL_STATIC_", 17, 0);
        if(!svp) XSRETURN_UNDEF;
        cs = GvHV((GV*)*svp);
        if(!cs) XSRETURN_UNDEF;
        svp = hv_fetch(cs, "SUPER", 5, 0);
    }
    if(svp) {
	ST(0) = *svp;
	XSRETURN(1);
    }
    XSRETURN_UNDEF;
}

//---------- XS Autoload (for all functions except fully qualified statics & enums) ---------

static inline bool isTQt(char *p) {
	return (p[0] == 'Q' && p[1] && p[1] == 't' && ((p[2] && p[2] == ':') || !p[2]));
}

bool avoid_fetchmethod = false;
XS(XS_AUTOLOAD) {
    // Err, XS autoload is borked. Lets try...
    dXSARGS;
    SV *sv = get_sv("TQt::AutoLoad::AUTOLOAD", TRUE);
    char *package = SvPV_nolen(sv);
    char *method = 0;
    for(char *s = package; *s ; s++)
	if(*s == ':') method = s;
    if(!method) XSRETURN_NO;
    *(method++ - 1) = 0;	// sorry for showing off. :)
    int withObject = (*package == ' ') ? 1 : 0;
    int isSuper = 0;
    if(withObject) {
        package++;
        if(*package == ' ') {
             isSuper = 1;
             char *super = new char[strlen(package) + 7];
             package++;
             strcpy(super, package);
             strcat(super, "::SUPER");
             package = super;
        }
    } else if( isTQt(package) )
	avoid_fetchmethod = true;

    HV *stash = gv_stashpv(package, TRUE);

    if(do_debug && (do_debug & qtdb_autoload))
        warn("In XS Autoload for %s::%s()\n", package, method);

    // check for user-defined methods in the REAL stash; skip prefix
    GV *gv = 0;
    if(avoid_fetchmethod)
	avoid_fetchmethod = false;
    else
	gv = gv_fetchmethod_autoload(stash, method, 0);

    // If we've made it here, we need to set sv_this
    if(gv) {
        if(do_debug && (do_debug & qtdb_autoload))
            warn("\tfound in %s's Perl stash\n", package);

	// call the defined Perl method with new 'this'
	SV *old_this;
	if(withObject && !isSuper) {
	    old_this = sv_this;
	    sv_this = newSVsv(ST(0));
	}

	ENTER;
	SAVETMPS;
	PUSHMARK(SP - items + withObject);
	PUTBACK;
	int count = call_sv((SV*)GvCV(gv), G_SCALAR|G_EVAL);
	SPAGAIN;
	SV *ret = newSVsv(TOPs);
	SP -= count;
	PUTBACK;
	FREETMPS;
	LEAVE;

	if(withObject && !isSuper) {
	    SvREFCNT_dec(sv_this);
	    sv_this = old_this;
	}
        else if(isSuper)
            delete[] package;

        if(SvTRUE(ERRSV))
            croak("%s", SvPV_nolen(ERRSV));
	ST(0) = sv_2mortal(ret);
	XSRETURN(1);
    }
    else if(!strcmp(method, "DESTROY")) {
        SV *old_this;
	if(withObject && !isSuper) {
	    old_this = sv_this;
	    sv_this = newSVsv(ST(0));
 	}
        smokeperl_object *o = sv_obj_info(sv_this);

	if(!(o && o->ptr && (o->allocated || getPointerObject(o->ptr)))) {
	    if(isSuper)
                delete[] package;
	    if(withObject && !isSuper) {
	        SvREFCNT_dec(sv_this);
	        sv_this = old_this;
	    }
            XSRETURN_YES;
        }
        const char *key = "has been hidden";
        U32 klen = 15;
        SV **svp = 0;
        if(SvROK(sv_this) && SvTYPE(SvRV(sv_this)) == SVt_PVHV) {
	    HV *hv = (HV*)SvRV(sv_this);
	    svp = hv_fetch(hv, key, klen, 0);
        }
        if(svp) {
	    if(isSuper)
                delete[] package;
            if(withObject && !isSuper) {
	        SvREFCNT_dec(sv_this);
	        sv_this = old_this;
	    }
	    XSRETURN_YES;
	}
        gv = gv_fetchmethod_autoload(stash, "ON_DESTROY", 0);
	if( !gv )
	    croak( "Couldn't find ON_DESTROY method for %s=%p\n", package, o->ptr);
	PUSHMARK(SP);
	call_sv((SV*)GvCV(gv), G_SCALAR|G_NOARGS);
	SPAGAIN;
	int ret = POPi;
	PUTBACK;
	if(withObject && !isSuper) {
	    SvREFCNT_dec(sv_this);
	    sv_this = old_this;
	}
	if( do_debug && ret && (do_debug & qtdb_gc) )
	    fprintf(stderr, "Increasing refcount in DESTROY for %s=%p (still has a parent)\n", package, o->ptr);
    } else {

        if( items > 18 )  XSRETURN_NO; // current max number of args in TQt is 13.

        // save the stack -- we'll need it
        SV **savestack = new SV*[items+1];
        SV *saveobj = ST(0);
        SV *old_this;

        Copy(SP - items + 1 + withObject, savestack, items-withObject, SV*);

        // Get the classid (eventually converting SUPER to the right TQt class)
        Smoke::Index cid = package_classid(package);
        // Look in the cache
        char *cname = (char*)qt_Smoke->className(cid);
        int lcname = strlen(cname);
        int lmethod = strlen(method);
        char mcid[256];
        strncpy(mcid, cname, lcname);
        char *ptr = mcid + lcname;
	*(ptr++) = ';';
        strncpy(ptr, method, lmethod);
	ptr += lmethod;
        for(int i=withObject ; i<items ; i++)
        {
            *(ptr++) = ';';
	    char *t = get_SVt(ST(i));
	    int tlen = strlen(t);
	    strncpy(ptr, t, tlen );
	    ptr += tlen;
        }
	*ptr = 0;
	Smoke::Index *rcid = methcache->find(mcid);

        if(rcid) {
            // Got a hit
            _current_method = *rcid;
            if(withObject && !isSuper) {
                old_this = sv_this;
                sv_this = newSVsv(ST(0));
            }
        }
        else {

            // Find the C++ method to call. I'll do that from Perl for now

            ENTER;
            SAVETMPS;
            PUSHMARK(SP - items + withObject);
            EXTEND(SP, 3);
            PUSHs(sv_2mortal(newSViv((IV)cid)));
            PUSHs(sv_2mortal(newSVpv(method, 0)));
            PUSHs(sv_2mortal(newSVpv(package, 0)));
            PUTBACK;
            if(withObject && !isSuper) {
                old_this = sv_this;
                sv_this = newSVsv(saveobj);
            }
            call_pv("TQt::_internal::do_autoload", G_DISCARD|G_EVAL);
            FREETMPS;
            LEAVE;

            // Restore sv_this on error, so that eval{ } works
            if(SvTRUE(ERRSV)) {
                if(withObject && !isSuper) {
                        SvREFCNT_dec(sv_this);
                        sv_this = old_this;
                }
                else if(isSuper)
                        delete[] package;
                delete[] savestack;
                croak("%s", SvPV_nolen(ERRSV));
            }

            // Success. Cache result.
            methcache->insert(mcid, new Smoke::Index(_current_method));
        }
        // FIXME: I shouldn't have to set the current object
        {
                smokeperl_object *o = sv_obj_info(sv_this);
                if(o && o->ptr) {
                    _current_object = o->ptr;
                    _current_object_class = o->classId;
                } else {
                    _current_object = 0;
                }
        }
        // honor debugging channels
        if(do_debug && (do_debug & qtdb_calls)) {
            warn("Calling method\t%s\n", SvPV_nolen(sv_2mortal(prettyPrintMethod(_current_method))));
            if(do_debug & qtdb_verbose)
                warn("with arguments (%s)\n", SvPV_nolen(sv_2mortal(catArguments(savestack, items-withObject))));
        }
        MethodCall c(qt_Smoke, _current_method, savestack, items-withObject);
        c.next();
        if(savestack)
            delete[] savestack;

        if(withObject && !isSuper) {
                SvREFCNT_dec(sv_this);
                sv_this = old_this;
        }
        else if(isSuper)
                delete[] package;

        SV *ret = c.var();
        SvREFCNT_inc(ret);
        ST(0) = sv_2mortal(ret);
        XSRETURN(1);
    }
    if(isSuper)
        delete[] package;
    XSRETURN_YES;
}


//----------------- Sig/Slot ------------------


MocArgument *getmetainfo(GV *gv, const char *name, int &offset, int &index, int &argcnt) {
    char *signalname = GvNAME(gv);
    HV *stash = GvSTASH(gv);

    // $meta = $stash->{META}
    SV **svp = hv_fetch(stash, "META", 4, 0);
    if(!svp) return 0;
    HV *hv = GvHV((GV*)*svp);
    if(!hv) return 0;

    // $metaobject = $meta->{object}
    // aka. Class->staticMetaObject
    svp = hv_fetch(hv, "object", 6, 0);
    if(!svp) return 0;
    smokeperl_object *ometa = sv_obj_info(*svp);
    if(!ometa) return 0;
    TQMetaObject *metaobject = (TQMetaObject*)ometa->ptr;

    offset = metaobject->signalOffset();

    // $signals = $meta->{signal}
    U32 len = strlen(name);
    svp = hv_fetch(hv, name, len, 0);
    if(!svp) return 0;
    HV *signalshv = (HV*)SvRV(*svp);

    // $signal = $signals->{$signalname}
    len = strlen(signalname);
    svp = hv_fetch(signalshv, signalname, len, 0);
    if(!svp) return 0;
    HV *signalhv = (HV*)SvRV(*svp);

    // $index = $signal->{index}
    svp = hv_fetch(signalhv, "index", 5, 0);
    if(!svp) return 0;;
    index = SvIV(*svp);

    // $argcnt = $signal->{argcnt}
    svp = hv_fetch(signalhv, "argcnt", 6, 0);
    if(!svp) return 0;
    argcnt = SvIV(*svp);

    // $mocargs = $signal->{mocargs}
    svp = hv_fetch(signalhv, "mocargs", 7, 0);
    if(!svp) return 0;
    MocArgument *args = (MocArgument*)SvIV(*svp);

    return args;
}

MocArgument *getslotinfo(GV *gv, int id, char *&slotname, int &index, int &argcnt, bool isSignal = false) {
    HV *stash = GvSTASH(gv);

    // $meta = $stash->{META}
    SV **svp = hv_fetch(stash, "META", 4, 0);
    if(!svp) return 0;
    HV *hv = GvHV((GV*)*svp);
    if(!hv) return 0;

    // $metaobject = $meta->{object}
    // aka. Class->staticMetaObject
    svp = hv_fetch(hv, "object", 6, 0);
    if(!svp) return 0;
    smokeperl_object *ometa = sv_obj_info(*svp);
    if(!ometa) return 0;
    TQMetaObject *metaobject = (TQMetaObject*)ometa->ptr;

    int offset = isSignal ? metaobject->signalOffset() : metaobject->slotOffset();

    index = id - offset;   // where we at
    // FIXME: make slot inheritance work
    if(index < 0) return 0;
    // $signals = $meta->{signal}
    const char *key = isSignal ? "signals" : "slots";
    svp = hv_fetch(hv, key, strlen(key), 0);
    if(!svp) return 0;
    AV *signalsav = (AV*)SvRV(*svp);
    svp = av_fetch(signalsav, index, 0);
    if(!svp) return 0;
    HV *signalhv = (HV*)SvRV(*svp);
    // $argcnt = $signal->{argcnt}
    svp = hv_fetch(signalhv, "argcnt", 6, 0);
    if(!svp) return 0;
    argcnt = SvIV(*svp);
    // $mocargs = $signal->{mocargs}
    svp = hv_fetch(signalhv, "mocargs", 7, 0);
    if(!svp) return 0;
    MocArgument *args = (MocArgument*)SvIV(*svp);

    svp = hv_fetch(signalhv, "name", 4, 0);
    if(!svp) return 0;
    slotname = SvPV_nolen(*svp);

    return args;
}

XS(XS_signal) {
    dXSARGS;

    smokeperl_object *o = sv_obj_info(sv_this);
    TQObject *qobj = (TQObject*)o->smoke->cast(
	o->ptr,
	o->classId,
	o->smoke->idClass("TQObject")
    );
    if(qobj->signalsBlocked()) XSRETURN_UNDEF;

    int offset;
    int index;
    int argcnt;
    MocArgument *args;

    args = getmetainfo(CvGV(cv), "signal", offset, index, argcnt);
    if(!args) XSRETURN_UNDEF;

    // Okay, we have the signal info. *whew*
    if(items < argcnt)
	croak("Insufficient arguments to emit signal");

    EmitSignal signal(qobj, offset + index, argcnt, args, &ST(0));
    signal.next();

    XSRETURN_UNDEF;
}

XS(XS_qt_invoke) {
    dXSARGS;
    // Arguments: int id, TQUObject *o
    int id = SvIV(ST(0));
    TQUObject *_o = (TQUObject*)SvIV(SvRV(ST(1)));

    smokeperl_object *o = sv_obj_info(sv_this);
    TQObject *qobj = (TQObject*)o->smoke->cast(
	o->ptr,
	o->classId,
	o->smoke->idClass("TQObject")
    );

    // Now, I need to find out if this means me
    int index;
    char *slotname;
    int argcnt;
    MocArgument *args;
    bool isSignal = !strcmp(GvNAME(CvGV(cv)), "qt_emit");
    args = getslotinfo(CvGV(cv), id, slotname, index, argcnt, isSignal);
    if(!args) {
	// throw an exception - evil style
	temporary_virtual_function_success = false;
	XSRETURN_UNDEF;
    }
    HV *stash = GvSTASH(CvGV(cv));
    GV *gv = gv_fetchmethod_autoload(stash, slotname, 0);
    if(!gv) XSRETURN_UNDEF;
    InvokeSlot slot(qobj, gv, argcnt, args, _o);
    slot.next();

    XSRETURN_UNDEF;
}

// -------------------       Tied types        ------------------------

MODULE = TQt   PACKAGE = TQt::_internal::TQString
PROTOTYPES: DISABLE

SV*
FETCH(obj)
    SV* obj
    CODE:
    if (!SvROK(obj))
        croak("?");
    IV tmp = SvIV((SV*)SvRV(obj));
    TQString *s = (TQString*) tmp;
    RETVAL = newSV(0);
    if( s )
    {
        if(!(IN_BYTES))
        {
	    sv_setpv_mg(RETVAL, (const char *)s->utf8());
            SvUTF8_on(RETVAL);
        }
        else if(IN_LOCALE)
            sv_setpv_mg(RETVAL, (const char *)s->local8Bit());
        else
            sv_setpv_mg(RETVAL, (const char *)s->latin1());
    }
    else
        sv_setsv_mg(RETVAL, &PL_sv_undef);
    OUTPUT: 
    RETVAL

void
STORE(obj,what)
   SV* obj
   SV* what
   CODE:
    if (!SvROK(obj))
        croak("?");
    IV tmp = SvIV((SV*)SvRV(obj));
    TQString *s = (TQString*) tmp;
    s->truncate(0);
    if(SvOK(what)) {
        if(SvUTF8(what)) 
	    s->append(TQString::fromUtf8(SvPV_nolen(what)));
        else if(IN_LOCALE)
            s->append(TQString::fromLocal8Bit(SvPV_nolen(what)));
        else
            s->append(TQString::fromLatin1(SvPV_nolen(what)));
    }  

void
DESTROY(obj)
    SV* obj
    CODE:
    if (!SvROK(obj))
        croak("?");
    IV tmp = SvIV((SV*)SvRV(obj));
    TQString *s = (TQString*) tmp;
    delete s;

MODULE = TQt   PACKAGE = TQt::_internal::TQByteArray
PROTOTYPES: DISABLE

SV*
FETCH(obj)
    SV* obj
    CODE:
    if (!SvROK(obj))
        croak("?");
    IV tmp = SvIV((SV*)SvRV(obj));
    TQByteArray *s = (TQByteArray*) tmp;
    RETVAL = newSV(0);
    if( s )
    {
	sv_setpvn_mg(RETVAL, s->data(), s->size());
    }
    else
        sv_setsv_mg(RETVAL, &PL_sv_undef);
    OUTPUT: 
    RETVAL

void
STORE(obj,what)
   SV* obj
   SV* what
   CODE:
    if (!SvROK(obj))
        croak("?");
    IV tmp = SvIV((SV*)SvRV(obj));
    TQByteArray *s = (TQByteArray*) tmp;

    if(SvOK(what)) {
        STRLEN len;
	char* tmp2 = SvPV(what, len); 
        s->resize(len);
	Copy((void*)tmp2, (void*)s->data(), len, char);
    }  else
        s->truncate(0);

void
DESTROY(obj)
    SV* obj
    CODE:
    if (!SvROK(obj))
        croak("?");
    IV tmp = SvIV((SV*)SvRV(obj));
    TQByteArray *s = (TQByteArray*) tmp;
    delete s;

MODULE = TQt   PACKAGE = TQt::_internal::TQRgbStar
PROTOTYPES: DISABLE

SV*
FETCH(obj)
    SV* obj
    CODE:
    if (!SvROK(obj))
        croak("?");
    IV tmp = SvIV((SV*)SvRV(obj));
    TQRgb *s = (TQRgb*) tmp;
    AV* ar = newAV();
    RETVAL = newRV_noinc((SV*)ar);
    for(int i=0; s[i] ; i++)
    {
	SV *item = newSViv((IV)s[i]);
	if(!av_store(ar, (I32)i, item))
	    SvREFCNT_dec( item );
    }
    OUTPUT: 
    RETVAL

void
STORE(obj,sv)
   SV* obj
   SV* sv
   CODE:
    if (!SvROK(obj))
        croak("?");
    IV tmp = SvIV((SV*)SvRV(obj));
    TQRgb *s = (TQRgb*) tmp;
    if(!SvROK(sv) || SvTYPE(SvRV(sv)) != SVt_PVAV ||
	av_len((AV*)SvRV(sv)) < 0) {
	 s = new TQRgb[1];
	 s[0] = 0; 
	 sv_setref_pv(obj, "TQt::_internal::TQRgbStar", (void*)s);
	 return;
    }
    AV *list = (AV*)SvRV(sv);
    int count = av_len(list);
    s = new TQRgb[count + 2];
    int i;
    for(i = 0; i <= count; i++) {
	SV **item = av_fetch(list, i, 0);
	if(!item || !SvOK(*item)) {
	    s[i] = 0;
	    continue;
	}
	s[i] = SvIV(*item);
    }
    s[i] = 0;
    sv_setref_pv(obj, "TQt::_internal::TQRgbStar", (void*)s); 

void
DESTROY(obj)
    SV* obj
    CODE:
    if (!SvROK(obj))
        croak("?");
    IV tmp = SvIV((SV*)SvRV(obj));
    TQRgb *s = (TQRgb*) tmp;
    delete[] s;

# --------------- XSUBS for TQt::_internal::* helpers  ----------------


MODULE = TQt   PACKAGE = TQt::_internal
PROTOTYPES: DISABLE

void
getMethStat()
    PPCODE:
    XPUSHs(sv_2mortal(newSViv((int)methcache->size())));
    XPUSHs(sv_2mortal(newSViv((int)methcache->count())));

void
getClassStat()
    PPCODE:
    XPUSHs(sv_2mortal(newSViv((int)classcache->size())));
    XPUSHs(sv_2mortal(newSViv((int)classcache->count())));

void
getIsa(classId)
    int classId
    PPCODE:
    Smoke::Index *parents =
	qt_Smoke->inheritanceList +
	qt_Smoke->classes[classId].parents;
    while(*parents)
	XPUSHs(sv_2mortal(newSVpv(qt_Smoke->classes[*parents++].className, 0)));

void
dontRecurse()
    CODE:
    avoid_fetchmethod = true;

void *
sv_to_ptr(sv)
    SV* sv

void *
allocateMocArguments(count)
    int count
    CODE:
    RETVAL = (void*)new MocArgument[count + 1];
    OUTPUT:
    RETVAL

void
setMocType(ptr, idx, name, static_type)
    void *ptr
    int idx
    char *name
    char *static_type
    CODE:
    Smoke::Index typeId = qt_Smoke->idType(name);
    if(!typeId) XSRETURN_NO;
    MocArgument *arg = (MocArgument*)ptr;
    arg[idx].st.set(qt_Smoke, typeId);
    if(!strcmp(static_type, "ptr"))
	arg[idx].argType = xmoc_ptr;
    else if(!strcmp(static_type, "bool"))
	arg[idx].argType = xmoc_bool;
    else if(!strcmp(static_type, "int"))
	arg[idx].argType = xmoc_int;
    else if(!strcmp(static_type, "double"))
	arg[idx].argType = xmoc_double;
    else if(!strcmp(static_type, "char*"))
	arg[idx].argType = xmoc_charstar;
    else if(!strcmp(static_type, "TQString"))
	arg[idx].argType = xmoc_TQString;
    XSRETURN_YES;

void
installsignal(name)
    char *name
    CODE:
    char *file = __FILE__;
    newXS(name, XS_signal, file);

void
installqt_invoke(name)
    char *name
    CODE:
    char *file = __FILE__;
    newXS(name, XS_qt_invoke, file);

void
setDebug(on)
    int on
    CODE:
    do_debug = on;

int
debug()
    CODE:
    RETVAL = do_debug;
    OUTPUT:
    RETVAL

char *
getTypeNameOfArg(method, idx)
    int method
    int idx
    CODE:
    Smoke::Method &m = qt_Smoke->methods[method];
    Smoke::Index *args = qt_Smoke->argumentList + m.args;
    RETVAL = (char*)qt_Smoke->types[args[idx]].name;
    OUTPUT:
    RETVAL

int
classIsa(className, base)
    char *className
    char *base
    CODE:
    RETVAL = isDerivedFrom(qt_Smoke, className, base, 0);
    OUTPUT:
    RETVAL

void
insert_pclassid(p, ix)
    char *p
    int ix
    CODE:
    classcache->insert(p, new Smoke::Index((Smoke::Index)ix));

int
find_pclassid(p)
    char *p
    CODE:
    Smoke::Index *r = classcache->find(p);
    if(r)
        RETVAL = (int)*r;
    else
        RETVAL = 0;
    OUTPUT:
    RETVAL

void
insert_mcid(mcid, ix)
    char *mcid
    int ix
    CODE:
    methcache->insert(mcid, new Smoke::Index((Smoke::Index)ix));

int
find_mcid(mcid)
    char *mcid
    CODE:
    Smoke::Index *r = methcache->find(mcid);
    if(r)
        RETVAL = (int)*r;
    else
        RETVAL = 0;
    OUTPUT:
    RETVAL

char *
getSVt(sv)
    SV *sv
    CODE:
    RETVAL=get_SVt(sv);
    OUTPUT:
    RETVAL

void *
make_TQUParameter(name, type, extra, inout)
    char *name
    char *type
    SV *extra
    int inout
    CODE:
    TQUParameter *p = new TQUParameter;
    p->name = new char[strlen(name) + 1];
    strcpy((char*)p->name, name);
    if(!strcmp(type, "bool"))
	p->type = &static_TQUType_bool;
    else if(!strcmp(type, "int"))
	p->type = &static_TQUType_int;
    else if(!strcmp(type, "double"))
	p->type = &static_QUType_double;
    else if(!strcmp(type, "char*") || !strcmp(type, "const char*"))
	p->type = &static_TQUType_charstar;
    else if(!strcmp(type, "TQString") || !strcmp(type, "TQString&") ||
	    !strcmp(type, "const TQString") || !strcmp(type, "const TQString&"))
	p->type = &static_TQUType_TQString;
    else
	p->type = &static_TQUType_ptr;
    // Lacking support for several types. Evil.
    p->inOut = inout;
    p->typeExtra = 0;
    RETVAL = (void*)p;
    OUTPUT:
    RETVAL

void *
make_TQMetaData(name, method)
    char *name
    void *method
    CODE:
    TQMetaData *m = new TQMetaData;		// will be deleted
    m->name = new char[strlen(name) + 1];
    strcpy((char*)m->name, name);
    m->method = (TQUMethod*)method;
    m->access = TQMetaData::Public;
    RETVAL = m;
    OUTPUT:
    RETVAL

void *
make_TQUMethod(name, params)
    char *name
    SV *params
    CODE:
    TQUMethod *m = new TQUMethod;			// permanent memory allocation
    m->name = new char[strlen(name) + 1];	// this too
    strcpy((char*)m->name, name);
    m->count = 0;
    m->parameters = 0;
    if(SvOK(params) && SvRV(params)) {
	AV *av = (AV*)SvRV(params);
	m->count = av_len(av) + 1;
	if(m->count > 0) {
	    m->parameters = new TQUParameter[m->count];
	    for(int i = 0; i < m->count; i++) {
		SV *sv = av_shift(av);
		if(!SvOK(sv))
		    croak("Invalid paramater for TQUMethod\n");
		TQUParameter *p = (TQUParameter*)SvIV(sv);
		SvREFCNT_dec(sv);
		((TQUParameter*)m->parameters)[i] = *p;
		delete p;
	    }
	} else
	    m->count = 0;
    }
    RETVAL = m;
    OUTPUT:
    RETVAL

void *
make_TQMetaData_tbl(list)
    SV *list
    CODE:
    RETVAL = 0;
    if(SvOK(list) && SvRV(list)) {
	AV *av = (AV*)SvRV(list);
	int count = av_len(av) + 1;
	TQMetaData *m = new TQMetaData[count];
	for(int i = 0; i < count; i++) {
	    SV *sv = av_shift(av);
	    if(!SvOK(sv))
		croak("Invalid metadata\n");
	    TQMetaData *old = (TQMetaData*)SvIV(sv);
	    SvREFCNT_dec(sv);
	    m[i] = *old;
	    delete old;
	}
	RETVAL = (void*)m;
    }
    OUTPUT:
    RETVAL

SV *
make_metaObject(className, parent, slot_tbl, slot_count, signal_tbl, signal_count)
    char *className
    SV *parent
    void *slot_tbl
    int slot_count
    void *signal_tbl
    int signal_count
    CODE:
    smokeperl_object *po = sv_obj_info(parent);
    if(!po || !po->ptr) croak("Cannot create metaObject\n");
    TQMetaObject *meta = TQMetaObject::new_metaobject(
	className, (TQMetaObject*)po->ptr,
	(const TQMetaData*)slot_tbl, slot_count,	// slots
	(const TQMetaData*)signal_tbl, signal_count,	// signals
	0, 0,	// properties
	0, 0,	// enums
	0, 0);

    // this object-creation code is so, so wrong here
    HV *hv = newHV();
    SV *obj = newRV_noinc((SV*)hv);

    smokeperl_object o;
    o.smoke = qt_Smoke;
    o.classId = qt_Smoke->idClass("TQMetaObject");
    o.ptr = meta;
    o.allocated = true;
    sv_magic((SV*)hv, sv_qapp, '~', (char*)&o, sizeof(o));
    MAGIC *mg = mg_find((SV*)hv, '~');
    mg->mg_virtual = &vtbl_smoke;
    char *buf = qt_Smoke->binding->className(o.classId);
    sv_bless(obj, gv_stashpv(buf, TRUE));
    delete[] buf;
    RETVAL = obj;
    OUTPUT:
    RETVAL

void
dumpObjects()
    CODE:
    hv_iterinit(pointer_map);
    HE *e;
    while(e = hv_iternext(pointer_map)) {
	STRLEN len;
	SV *sv = HeVAL(e);
	printf("key = %s, refcnt = %d, weak = %d, ref? %d\n", HePV(e, len), SvREFCNT(sv), SvWEAKREF(sv), SvROK(sv)?1:0);
	if(SvRV(sv))
	    printf("REFCNT = %d\n", SvREFCNT(SvRV(sv)));
	//SvREFCNT_dec(HeVAL(e));
	//HeVAL(e) = &PL_sv_undef;
    }

void
dangle(obj)
    SV *obj
    CODE:
    if(SvRV(obj))
	SvREFCNT_inc(SvRV(obj));

void
setAllocated(obj, b)
    SV *obj
    bool b
    CODE:
    smokeperl_object *o = sv_obj_info(obj);
    if(o) {
	o->allocated = b;
    }

void
setqapp(obj)
    SV *obj
    CODE:
    if(!obj || !SvROK(obj))
        croak("Invalid TQt::Application object. Couldn't set TQt::app()\n");
    sv_qapp = SvRV(obj);

void
setThis(obj)
    SV *obj
    CODE:
    sv_setsv_mg(sv_this, obj);

void
deleteObject(obj)
    SV *obj
    CODE:
    smokeperl_object *o = sv_obj_info(obj);
    if(!o) { XSRETURN_EMPTY; }
    TQObject *qobj = (TQObject*)o->smoke->cast(o->ptr, o->classId, o->smoke->idClass("TQObject"));
    delete qobj;

void
mapObject(obj)
    SV *obj
    CODE:
    smokeperl_object *o = sv_obj_info(obj);
    if(!o)
        XSRETURN_EMPTY;
    SmokeClass c( o->smoke, o->classId );
    if(!c.hasVirtual() ) {
	XSRETURN_EMPTY;
   }
    mapPointer(obj, o, pointer_map, o->classId, 0);

bool
isTQObject(obj)
    SV *obj
    CODE:
    RETVAL = 0;
    smokeperl_object *o = sv_obj_info(obj);
    if(o && isTQObject(o->smoke, o->classId))
	RETVAL = 1;
    OUTPUT:
    RETVAL

bool
isValidAllocatedPointer(obj)
    SV *obj
    CODE:
    RETVAL = 0;
    smokeperl_object *o = sv_obj_info(obj);
    if(o && o->ptr && o->allocated)
	RETVAL = 1;
    OUTPUT:
    RETVAL

SV*
findAllocatedObjectFor(obj)
    SV *obj
    CODE:
    RETVAL = &PL_sv_undef;
    smokeperl_object *o = sv_obj_info(obj);
    SV *ret;
    if(o && o->ptr && (ret = getPointerObject(o->ptr)))
        RETVAL = ret;
    OUTPUT:
    RETVAL

SV *
getGV(cv)
    SV *cv
    CODE:
    RETVAL = (SvROK(cv) && (SvTYPE(SvRV(cv))==SVt_PVCV) ?
              SvREFCNT_inc(CvGV((CV*)SvRV(cv))) : &PL_sv_undef);
    OUTPUT:
    RETVAL

int
idClass(name)
    char *name
    CODE:
    RETVAL = qt_Smoke->idClass(name);
    OUTPUT:
    RETVAL

int
idMethodName(name)
    char *name
    CODE:
    RETVAL = qt_Smoke->idMethodName(name);
    OUTPUT:
    RETVAL

int
idMethod(idclass, idmethodname)
    int idclass
    int idmethodname
    CODE:
    RETVAL = qt_Smoke->idMethod(idclass, idmethodname);
    OUTPUT:
    RETVAL

void
findMethod(c, name)
    char *c
    char *name
    PPCODE:
    Smoke::Index meth = qt_Smoke->findMethod(c, name);
//    printf("DAMNIT on %s::%s => %d\n", c, name, meth);
    if(!meth) {
	// empty list
    } else if(meth > 0) {
	Smoke::Index i = qt_Smoke->methodMaps[meth].method;
	if(!i) {		// shouldn't happen
	    croak("Corrupt method %s::%s", c, name);
	} else if(i > 0) {	// single match
	    PUSHs(sv_2mortal(newSViv(
		(IV)qt_Smoke->methodMaps[meth].method
	    )));
	} else {		// multiple match
	    i = -i;		// turn into ambiguousMethodList index
	    while(qt_Smoke->ambiguousMethodList[i]) {
		PUSHs(sv_2mortal(newSViv(
		    (IV)qt_Smoke->ambiguousMethodList[i]
		)));
		i++;
	    }
	}
    }

void
findMethodFromIds(idclass, idmethodname)
    int idclass
    int idmethodname
    PPCODE:
    Smoke::Index meth = qt_Smoke->findMethod(idclass, idmethodname);
    if(!meth) {
	// empty list
    } else if(meth > 0) {
	Smoke::Index i = qt_Smoke->methodMaps[meth].method;
	if(i >= 0) {	// single match
	    PUSHs(sv_2mortal(newSViv((IV)i)));
	} else {		// multiple match
	    i = -i;		// turn into ambiguousMethodList index
	    while(qt_Smoke->ambiguousMethodList[i]) {
		PUSHs(sv_2mortal(newSViv(
		    (IV)qt_Smoke->ambiguousMethodList[i]
		)));
		i++;
	    }
	}
    }

# findAllMethods(classid [, startingWith]) : returns { "mungedName" => [index in methods, ...], ... }

HV*
findAllMethods(classid, ...)
    SV* classid
    CODE:
    RETVAL=newHV();
    if(SvIOK(classid)) {
        Smoke::Index c = (Smoke::Index) SvIV(classid);
        char * pat = 0L;
        if(items > 1 && SvPOK(ST(1)))
            pat = SvPV_nolen(ST(1));
        Smoke::Index imax = qt_Smoke->numMethodMaps;
        Smoke::Index imin = 0, icur = -1, methmin = 0, methmax = 0;
        int icmp = -1;
        while(imax >= imin) {
            icur = (imin + imax) / 2;
            icmp = qt_Smoke->leg(qt_Smoke->methodMaps[icur].classId, c);
            if(!icmp) {
                Smoke::Index pos = icur;
                while(icur && qt_Smoke->methodMaps[icur-1].classId == c)
                    icur --;
                methmin = icur;
                icur = pos;
                while(icur < imax && qt_Smoke->methodMaps[icur+1].classId == c)
                    icur ++;
                methmax = icur;
                break;
            }
            if (icmp > 0)
		imax = icur - 1;
	    else
		imin = icur + 1;
        }
        if(!icmp) {
            for(Smoke::Index i=methmin ; i <= methmax ; i++) {
                Smoke::Index m = qt_Smoke->methodMaps[i].name;
                if(!pat || !strncmp(qt_Smoke->methodNames[m], pat, strlen(pat))) {
                    Smoke::Index ix= qt_Smoke->methodMaps[i].method;
                    AV* meths = newAV();
                    if(ix >= 0) {	// single match
                        av_push(meths, newSViv((IV)ix));
                    } else {		// multiple match
                        ix = -ix;		// turn into ambiguousMethodList index
                        while(qt_Smoke->ambiguousMethodList[ix]) {
                          av_push(meths, newSViv((IV)qt_Smoke->ambiguousMethodList[ix]));
                          ix++;
                        }
                    }
                    hv_store(RETVAL, qt_Smoke->methodNames[m],strlen(qt_Smoke->methodNames[m]),newRV_inc((SV*)meths),0);
                }
            }
        }
    }
    OUTPUT:
    RETVAL

SV *
dumpCandidates(rmeths)
    SV *rmeths
    CODE:
    if(SvROK(rmeths) && SvTYPE(SvRV(rmeths)) == SVt_PVAV) {
        AV *methods = (AV*)SvRV(rmeths);
        SV *errmsg = newSVpvf("");
        for(int i = 0; i <= av_len(methods); i++) {
                sv_catpv(errmsg, "\t");
                IV id = SvIV(*(av_fetch(methods, i, 0)));
                Smoke::Method &meth = qt_Smoke->methods[id];
                const char *tname = qt_Smoke->types[meth.ret].name;
                if(meth.flags & Smoke::mf_static) sv_catpv(errmsg, "static ");
                sv_catpvf(errmsg, "%s ", (tname ? tname:"void"));
                sv_catpvf(errmsg, "%s::%s(", qt_Smoke->classes[meth.classId].className, qt_Smoke->methodNames[meth.name]);
                for(int i = 0; i < meth.numArgs; i++) {
                        if(i) sv_catpv(errmsg, ", ");
                        tname = qt_Smoke->types[qt_Smoke->argumentList[meth.args+i]].name;
                        sv_catpv(errmsg, (tname ? tname:"void"));
                }
                sv_catpv(errmsg, ")");
                if(meth.flags & Smoke::mf_const) sv_catpv(errmsg, " const");
                sv_catpv(errmsg, "\n");
        }
        RETVAL=errmsg;
    }
    else
        RETVAL=newSVpvf("");
    OUTPUT:
    RETVAL

SV *
catArguments(r_args)
    SV* r_args
    CODE:
    RETVAL=newSVpvf("");
    if(SvROK(r_args) && SvTYPE(SvRV(r_args)) == SVt_PVAV) {
        AV* args=(AV*)SvRV(r_args);
        for(int i = 0; i <= av_len(args); i++) {
            SV **arg=av_fetch(args, i, 0);
	    if(i) sv_catpv(RETVAL, ", ");
	    if(!arg || !SvOK(*arg)) {
		sv_catpv(RETVAL, "undef");
	    } else if(SvROK(*arg)) {
		smokeperl_object *o = sv_obj_info(*arg);
		if(o)
		    sv_catpv(RETVAL, o->smoke->className(o->classId));
		else
		    sv_catsv(RETVAL, *arg);
	    } else {
		bool isString = SvPOK(*arg);
		STRLEN len;
		char *s = SvPV(*arg, len);
		if(isString) sv_catpv(RETVAL, "'");
		sv_catpvn(RETVAL, s, len > 10 ? 10 : len);
		if(len > 10) sv_catpv(RETVAL, "...");
		if(isString) sv_catpv(RETVAL, "'");
	    }
	}
    }
    OUTPUT:
    RETVAL

SV *
callMethod(...)
    PPCODE:
    if(_current_method) {
	MethodCall c(qt_Smoke, _current_method, &ST(0), items);
	c.next();
	SV *ret = c.var();
	SvREFCNT_inc(ret);
	PUSHs(sv_2mortal(ret));
    } else
	PUSHs(sv_newmortal());

bool
isObject(obj)
    SV *obj
    CODE:
    RETVAL = sv_to_ptr(obj) ? TRUE : FALSE;
    OUTPUT:
    RETVAL

void
setCurrentMethod(meth)
    int meth
    CODE:
    // FIXME: damn, this is lame, and it doesn't handle ambiguous methods
    _current_method = meth;  //qt_Smoke->methodMaps[meth].method;

SV *
getClassList()
    CODE:
    AV *av = newAV();
    for(int i = 1; i <= qt_Smoke->numClasses; i++) {
//printf("%s => %d\n", qt_Smoke->classes[i].className, i);
	av_push(av, newSVpv(qt_Smoke->classes[i].className, 0));
//	hv_store(hv, qt_Smoke->classes[i].className, 0, newSViv(i), 0);
    }
    RETVAL = newRV((SV*)av);
    OUTPUT:
    RETVAL

void
installthis(package)
    char *package
    CODE:
    if(!package) XSRETURN_EMPTY;
    char *name = new char[strlen(package) + 7];
    char *file = __FILE__;
    strcpy(name, package);
    strcat(name, "::this");
    // *{ $name } = sub () : lvalue;
    CV *thissub = newXS(name, XS_this, file);
    sv_setpv((SV*)thissub, "");    // sub this () : lvalue;
    delete[] name;

void
installattribute(package, name)
    char *package
    char *name
    CODE:
    if(!package || !name) XSRETURN_EMPTY;
    char *attr = new char[strlen(package) + strlen(name) + 3];
    sprintf(attr, "%s::%s", package, name);
    char *file = __FILE__;
    // *{ $attr } = sub () : lvalue;
    CV *attrsub = newXS(attr, XS_attr, file);
    sv_setpv((SV*)attrsub, "");
    CvLVALUE_on(attrsub);
    CvNODEBUG_on(attrsub);
    delete[] attr;

void
installsuper(package)
    char *package
    CODE:
    if(!package) XSRETURN_EMPTY;
    char *attr = new char[strlen(package) + 8];
    sprintf(attr, "%s::SUPER", package);
    char *file = __FILE__;
    CV *attrsub = newXS(attr, XS_super, file);
    sv_setpv((SV*)attrsub, "");
    delete[] attr;

void
installautoload(package)
    char *package
    CODE:
    if(!package) XSRETURN_EMPTY;
    char *autoload = new char[strlen(package) + 11];
    strcpy(autoload, package);
    strcat(autoload, "::_UTOLOAD");
    char *file = __FILE__;
    // *{ $package."::AUTOLOAD" } = XS_AUTOLOAD
    newXS(autoload, XS_AUTOLOAD, file);
    delete[] autoload;

# ----------------- XSUBS for TQt:: -----------------

MODULE = TQt   PACKAGE = TQt

SV *
this()
    CODE:
    RETVAL = newSVsv(sv_this);
    OUTPUT:
    RETVAL

SV *
app()
    CODE:
    RETVAL = newRV_inc(sv_qapp);
    OUTPUT:
    RETVAL

SV *
version()
    CODE:
    RETVAL = newSVpv(TQT_VERSION_STR,0);
    OUTPUT:
    RETVAL

BOOT:
    init_qt_Smoke();
    qt_Smoke->binding = new TQtSmokeBinding(qt_Smoke);
    install_handlers(TQt_handlers);
    pointer_map = newHV();
    sv_this = newSV(0);
    methcache = new TQAsciiDict<Smoke::Index>(1187);
    classcache = new TQAsciiDict<Smoke::Index>(827);
    methcache->setAutoDelete(1);
    classcache->setAutoDelete(1);
