#include "smokeperl.h"

class SmokePerlTQt : public SmokePerl {
public:
    SmokePerlTQt();
    virtual ~SmokePerlTQt();

    void registerSmoke(const char *name, Smoke *smoke);
    Smoke *getSmoke(const char *name);

    void registerHandlers(TypeHandler *h);

    SmokeObject newObject(void *p, const SmokeClass &c);
    SmokeObject wrapObject(void *p, const SmokeClass &c);
    SmokeObject getObject(void *p);
    SmokeObject getObject(SV *sv);

private:
    HV *_registered_smoke;
    HV *_registered_handlers;
    HV *_remembered_pointers;

    void rememberPointer(SmokeObject &o, const SmokeClass &c, bool remember, void *lastptr = 0);
    void rememberPointer(SmokeObject &o);
    void forgetPointer(SmokeObject &o);
    SmokeObject createObject(void *p, const SmokeClass &c);

    const char *getSmokeName(Smoke *smoke) {
	static const char none[] = "";
	HE *he;

	hv_iterinit(_registered_smoke);
	while(he = hv_iternext(_registered_smoke)) {
	    SV *sv = hv_iterval(_registered_smoke, he);
	    if((Smoke*)SvIV(sv) == smoke) {
		I32 toss;
		return hv_iterkey(he, &toss);
	    }
	}
	return none;
    }

    HV *package(const SmokeClass &c) {
	// for now, we cheat on the class names by assuming they're all TQt::
	if(!strcmp(c.className(), "TQt"))
	    return gv_stashpv(c.className(), GV_ADD);

	SV *name = newSVpv("TQt::", 0);
	sv_catpv(name, c.className() + 1);
	HV *stash = gv_stashpv(SvPV_nolen(name), GV_ADD);
	SvREFCNT_dec(name);

	return stash;
    }
};


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);
   }
};

extern SV *sv_this;
extern void *_current_object;
extern Smoke::Index _current_object_class;
extern int object_count;
extern bool temporary_virtual_function_success;
extern struct mgvtbl vtbl_smoke;

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*)_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());
	    _sp[_cur] = sv_newmortal();
	    (*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 SmokeBindingTQt : public SmokeBinding {
    SmokePerlTQt *_smokeperl;
public:
    SmokeBindingTQt(Smoke *s, SmokePerlTQt *smokeperl) :
	SmokeBinding(s), _smokeperl(smokeperl) {}
    void deleted(Smoke::Index classId, void *ptr) {
	if(do_debug) printf("%p->~%s()\n", ptr, smoke->className(classId));
	object_count--;
	if(do_debug) printf("Remaining objects: %d\n", object_count);
	SV *obj = getPointerObject(ptr);
	smokeperl_object *o = sv_obj_info(obj);
	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) printf("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)   // if not in global destruction
		warn("Cannot find object for virtual method");
	    return false;
	}
	HV *stash = SvSTASH(SvRV(obj));
	if(*HvNAME(stash) == ' ')
	    stash = gv_stashpv(HvNAME(stash) + 1, GV_ADD);
	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;
    }
};

SmokePerlTQt::SmokePerlTQt() {
    _registered_smoke = newHV();
    _registered_handlers = newHV();
    _remembered_pointers = newHV();
}

SmokePerlTQt::~SmokePerlTQt() {
    SvREFCNT_dec((SV*)_registered_smoke);
    SvREFCNT_dec((SV*)_registered_handlers);
    SvREFCNT_dec((SV*)_remembered_pointers);
}

void SmokePerlTQt::registerSmoke(const char *name, Smoke *smoke) {
    hv_store(_registered_smoke, name, strlen(name), newSViv((IV)smoke), 0);

    // This will also need to handle the per-class initialization
    smoke->binding = new SmokeBindingTQt(smoke, this);
}

Smoke *SmokePerlTQt::getSmoke(const char *name) {
    SV **svp = hv_fetch(_registered_smoke, name, strlen(name), 0);
    if(svp && SvOK(*svp))
	return (Smoke*)SvIV(*svp);
    return 0;
}

void SmokePerlTQt::registerHandlers(TypeHandler *h) {
    while(h->name) {
	hv_store(_registered_handlers, h->name, strlen(h->name), newSViv((IV)h->fn), 0);
	h++;
    }
}

SmokeObject SmokePerlTQt::createObject(void *p, const SmokeClass &c) {
    HV *hv = newHV();
    SV *obj = newRV_noinc((SV*)hv);

    Smoke_MAGIC m(p, c);
    sv_magic((SV*)hv, (SV*)newAV(), '~', (char*)&m, sizeof(m));
    MAGIC *mg = mg_find((SV*)hv, '~');
    mg->mg_virtual = &vtbl_smoke;

    sv_bless(obj, package(c));

    SmokeObject o(obj, (Smoke_MAGIC*)mg->mg_ptr);
    SvREFCNT_dec(obj);

    if(c.hasVirtual())
	rememberPointer(o);

    return o;
}

SmokeObject SmokePerlTQt::newObject(void *p, const SmokeClass &c) {
    SmokeObject o = createObject(p, c);

    if(c.isVirtual())
	rememberPointer(o);
    o.setAllocated(true);

    return o;
}

SmokeObject SmokePerlTQt::wrapObject(void *p, const SmokeClass &c) {
    SmokeObject o = createObject(p, c);
    return o;
}

void SmokePerlTQt::rememberPointer(SmokeObject &o, const SmokeClass &c, bool remember, void *lastptr) {
    void *ptr = o.cast(c);
    if(ptr != lastptr) {
	SV *keysv = newSViv((IV)o.ptr());    
	STRLEN klen;
	char *key = SvPV(keysv, klen);

	if(remember)
	    hv_store(_remembered_pointers, key, klen,
		     sv_rvweaken(newSVsv(o.var())), 0);
	else
	    hv_delete(_remembered_pointers, key, klen, G_DISCARD);

	SvREFCNT_dec(keysv);
    }
    for(Smoke::Index *i = c.smoke()->inheritanceList + c.c().parents;
	*i;
	i++)
	rememberPointer(o, SmokeClass(c.smoke(), *i), remember, ptr);
}

void SmokePerlTQt::rememberPointer(SmokeObject &o) {
    rememberPointer(o, o.c(), true);
}

void SmokePerlTQt::forgetPointer(SmokeObject &o) {
    rememberPointer(o, o.c(), false);
}

SmokeObject SmokePerlTQt::getObject(SV *sv) {
    MAGIC *mg = mg_find(SvRV(sv), '~');
    Smoke_MAGIC *m = (Smoke_MAGIC*)mg->mg_ptr;
    return SmokeObject(sv, m);
}

SmokeObject SmokePerlTQt::getObject(void *p) {
    SV *keysv = newSViv((IV)p);
    STRLEN klen;
    char *key = SvPV(keysv, klen);
    SV **svp = hv_fetch(_remembered_pointers, key, klen, 0);
    if(svp && SvROK(*svp))
	return getObject(sv_2mortal(newRV(SvRV(*svp))));   // paranoid copy of a weak ref
    return SmokeObject(&PL_sv_undef, 0);
}

