#include "apricot.h"
#include "Object.h"
#include <Object.inc>

#ifdef __cplusplus
extern "C" {
#endif


#undef  my
#define my  ((( PObject) self)-> self)
#define var (( PObject) self)

Handle
Object_create( char *className, HV * profile)
{
	dSP;
	Handle self = 0;

	SV *xmate;
	SV *profRef;

	if ( primaObjects == nil)
		return nilHandle;

	ENTER;
	SAVETMPS;
	PUSHMARK( sp);
	XPUSHs( sv_2mortal( newSVpv( className, 0)));
	PUTBACK;
	PERL_CALL_METHOD( "CREATE", G_SCALAR);
	SPAGAIN;
	xmate = newRV_inc( SvRV( POPs));
	self = create_mate( xmate);
	var-> mate = xmate;
	var-> stage = csDeadInInit;
	PUTBACK;
	FREETMPS;
	LEAVE;

	profRef = newRV_inc(( SV *) profile);
	my-> profile_add( self, profRef);
	SPAGAIN;
	{
		dG_EVAL_ARGS;
		ENTER;
		SAVETMPS;
		PUSHMARK( sp);
		XPUSHs( var-> mate);
		sp = push_hv_for_REDEFINED( sp, profile);
		PUTBACK;

		OPEN_G_EVAL;
		PERL_CALL_METHOD( "init", G_VOID|G_DISCARD|G_EVAL);
		if ( SvTRUE( GvSV( PL_errgv))) {
			CLOSE_G_EVAL;
			OPEN_G_EVAL;
			Object_destroy( self);
			CLOSE_G_EVAL;
			croak( "%s", SvPV_nolen( GvSV( PL_errgv)));
		}
		CLOSE_G_EVAL;
		SPAGAIN;
		FREETMPS;
		LEAVE;
	}
	if ( primaObjects)
		hash_store( primaObjects, &self, sizeof( self), (void*)1);
	SvREFCNT_dec( profRef);
	if ( var-> stage > csConstructing) {
		if ( var-> mate && ( var-> mate != nilSV) && SvRV( var-> mate))
			--SvREFCNT( SvRV( var-> mate));
		return nilHandle;
	}
	var-> stage = csNormal;
	my-> setup( self);
	return self;
}

#define csHalfDead csFrozen

static void
protect_chain( Handle self, int direction)
{
	while ( self) {
		var-> destroyRefCount += direction;
		self = var-> owner;
	}
}

void
Object_destroy( Handle self)
{
	SV *mate, *object = nil;
	int enter_stage = var-> stage;

	if ( var-> stage == csDeadInInit) {
		/* lightweight destroy */
		if ( is_opt( optInDestroyList)) {
			list_delete( &postDestroys, self);
			opt_clear( optInDestroyList);
		}
		if ( primaObjects)
			hash_delete( primaObjects, &self, sizeof( self), false);
		mate = var-> mate;
		var-> stage = csDead;
		var-> mate = nilSV;
		if ( mate && object) sv_free( mate);
		return;
	}

	if ( var-> stage > csNormal && var-> stage != csHalfDead)
		return;

	if ( var-> destroyRefCount > 0) {
		if ( !is_opt( optInDestroyList)) {
			opt_set( optInDestroyList);
			list_add( &postDestroys, self);
		}
		return;
	}

	if ( var-> stage == csHalfDead) {
		Handle owner;
		if ( !var-> mate || ( var-> mate == nilSV))
			return;
		object = SvRV( var-> mate);
		if ( !object)
			return;
		var-> stage = csFinalizing;
		recursiveCall++;
		protect_chain( owner = var-> owner, 1);
		my-> done( self);
		protect_chain( owner, -1);
		recursiveCall--;
		if ( is_opt( optInDestroyList)) {
			list_delete( &postDestroys, self);
			opt_clear( optInDestroyList);
		}
		if ( primaObjects)
			hash_delete( primaObjects, &self, sizeof( self), false);
		var-> stage = csDead;
		return;
	}
	var-> stage = csDestroying;
	mate = var-> mate;
	if ( mate && ( mate != nilSV)) {
		object = SvRV( mate);
		if ( object) ++SvREFCNT( object);
	}
	if ( object) {
		Handle owner;
		var-> stage = csHalfDead;
		recursiveCall++;
		/*  ENTER;
		SAVEINT recursiveCall; */
		protect_chain( owner = var-> owner, 1);
		if ( enter_stage > csConstructing)
			my-> cleanup( self);
		else if ( enter_stage == csConstructing && var-> transient_class)
			((PObject_vmt)var-> transient_class)-> cleanup( self);
		if ( var-> stage == csHalfDead) {
			var-> stage = csFinalizing;
			my-> done( self);
			if ( primaObjects)
				hash_delete( primaObjects, &self, sizeof( self), false);
			if ( is_opt( optInDestroyList)) {
				list_delete( &postDestroys, self);
				opt_clear( optInDestroyList);
			}
		}
		protect_chain( owner, -1);
		/*  LEAVE; */
		recursiveCall--;
	}
	var-> stage = csDead;
	var-> mate = nilSV;
	if ( mate && object) sv_free( mate);

	while (( recursiveCall == 0) && ( postDestroys. count > 0)) {
		Handle last = postDestroys. items[ 0];
		recursiveCall++;
		Object_destroy( postDestroys. items[ 0]);
		recursiveCall--;
		if ( postDestroys. count == 0) break;
		if ( postDestroys. items[ 0] != last) continue;
		if ( postDestroys. count == 1)
			croak("Zombie detected: %p", (void*)last);
		else {
			list_delete_at( &postDestroys, 0);
			list_add( &postDestroys, last);
		}
	}
}

XS( Object_alive_FROMPERL)
{
	dXSARGS;
	Handle _c_apricot_self_;
	int ret;

	if ( items != 1)
		croak("Invalid usage of Prima::Object::%s", "alive");
	_c_apricot_self_ = gimme_the_real_mate( ST( 0));
	SPAGAIN;
	SP -= items;
	if ( _c_apricot_self_ != nilHandle) {
		switch ((( PObject) _c_apricot_self_)-> stage) {
		case csDeadInInit:
		case csConstructing:
			ret = 2;
			break;
		case csNormal:
			ret = 1;
			break;
		default:
			ret = 0;
		}
	} else
		ret = 0;
	XPUSHs( sv_2mortal( newSViv( ret)));
	PUTBACK;
	return;
}


void Object_done    ( Handle self) {}

void Object_init    ( Handle self, HV * profile)
{
	if ( var-> stage != csDeadInInit) croak( "Unexpected call of Object::init");
	var-> stage = csConstructing;
	CORE_INIT_TRANSIENT(Object);
}

void Object_cleanup ( Handle self) {}
void Object_setup( Handle self) {}

#ifdef __cplusplus
}
#endif
