/*
----------------------------------------------------------------------------

    Devel::MRO/mro_compat.h - Provides mro functions for XS modules

    Copyright (c) 2008-2009, Goro Fuji (gfx) <gfuji(at)cpan.org>.

    This program is free software; you can redistribute it and/or
    modify it under the same terms as Perl itself.

----------------------------------------------------------------------------

Usage:
	#include "mro_compat.h"

Functions:
	AV*  mro_get_linear_isa(HV* stash)
	UV   mro_get_pkg_gen(HV* stash)
	void mro_method_changed_in(HV* stash)


    See "perldoc Devel::MRO" for details.
 */


#ifdef mro_get_linear_isa /* >= 5.10.0 */

/* NOTE:
	Because ActivePerl 5.10.0 does not provide Perl_mro_meta_init(), 
	which is used in HvMROMETA() macro, this mro_get_pkg_gen() refers
	to xhv_mro_meta directly.
*/
/* compatible with &mro::get_pkg_gen() */
#ifndef mro_get_pkg_gen
#define mro_get_pkg_gen(stash) (HvAUX(stash) ? HvAUX(stash)->xhv_mro_meta->pkg_gen : (U32)0)
#endif

#ifndef mro_get_cache_gen
#define mro_get_cache_gen(stash) (HvAUX(stash) ? HvAUX(stash)->xhv_mro_meta->cache_gen : (U32)0)
#endif

/* pkg_gen + cache_gen */
#ifndef mro_get_gen
#define mro_get_gen(stash) (HvAUX(stash) ? (HvAUX(stash)->xhv_mro_meta->pkg_gen + HvAUX(stash)->xhv_mro_meta->cache_gen) : (U32)0)
#endif

#else /* < 5.10.0  */
#define mro_get_linear_isa(stash) my_mro_get_linear_isa(aTHX_ stash)

#define mro_method_changed_in(stash) ((void)stash, (void)PL_sub_generation++)
#define mro_get_pkg_gen(stash)   ((void)stash, PL_sub_generation)
#define mro_get_cache_gen(stash) ((void)stash, (I32)0) /* ??? */
#define mro_get_gen(stash)       ((void)stash, PL_sub_generation)


#if defined(NEED_mro_get_linear_isa) && !defined(NEED_mro_get_linear_isa_GLOBAL)
static AV* my_mro_get_linear_isa(pTHX_ HV* const stash);
static
#else
extern AV* my_mro_get_linear_isa(pTHX_ HV* const stash);
#endif /* !NEED_mro_get_linear_isa */

#if defined(NEED_mro_get_linear_isa) || defined(NEED_mro_get_linear_isa_GLOBAL)
#define ISA_CACHE "::Devel::MRO::"

/* call &mro::get_linear_isa, which is actually &MRO::Compat::__get_linear_isa */
AV*
my_mro_get_linear_isa(pTHX_ HV* const stash){
	GV* cachegv;
	AV* isa;  /* linearized ISA cache */
	SV* gen;  /* package generation */
	CV* get_linear_isa;

	assert(stash);
	assert(SvTYPE(stash) == SVt_PVHV);
	assert(HvNAME(stash));

	/* PerlIO_printf(Perl_debug_log, "mro_get_linear_isa(%s)\n", HvNAME(stash)); //*/

	cachegv = *(GV**)hv_fetchs(stash, ISA_CACHE, TRUE);
	if(!isGV(cachegv))
		gv_init(cachegv, stash, ISA_CACHE, sizeof(ISA_CACHE)-1, GV_ADD);

	isa = GvAVn(cachegv);
#ifdef GvSVn
	gen = GvSVn(cachegv);
#else
	gen = GvSV(cachegv);
#endif

	if(SvIOK(gen) && SvIVX(gen) == (IV)mro_get_pkg_gen(stash)){
		return isa; /* returns the cache if available */
	}

	get_linear_isa = get_cv("mro::get_linear_isa", FALSE);
	if(!get_linear_isa){
		ENTER;
		SAVETMPS;

		Perl_load_module(aTHX_ PERL_LOADMOD_NOIMPORT, newSVpvs("MRO::Compat"), NULL, NULL);
		get_linear_isa = get_cv("mro::get_linear_isa", TRUE);

		FREETMPS;
		LEAVE;
	}

	{
		SV* avref;
		dSP;

		ENTER;
		SAVETMPS;

		PUSHMARK(SP);
		mXPUSHp(HvNAME(stash), strlen(HvNAME(stash)));
		PUTBACK;

		call_sv((SV*)get_linear_isa, G_SCALAR);

		SPAGAIN;
		avref = POPs;
		PUTBACK;

		if(SvROK(avref) && SvTYPE(SvRV(avref)) == SVt_PVAV){
			AV* const av  = (AV*)SvRV(avref);
			I32 const len = AvFILLp(av) + 1;
			I32 i;

			SvREADONLY_off(isa);
			av_fill(isa, len-1);

			for(i = 0; i < len; i++){
				HV* const st = gv_stashsv(AvARRAY(av)[i], TRUE); /* to normalize the stash name */
				SV* const sv = *av_fetch(isa, i, TRUE);

				sv_setpv(sv, HvNAME(st));
			}
			SvREADONLY_on(isa);
		}
		else{
			Perl_croak(aTHX_ "panic: mro::get_linear_isa() didn't return an ARRAY reference");
		}

		FREETMPS;
		LEAVE;
	}

	sv_setiv(gen, (IV)mro_get_pkg_gen(stash));
	return isa;
}
#undef ISA_CACHE

#endif /* !(defined(NEED_mro_get_linear_isa) || defined(NEED_mro_get_linear_isa_GLOBAL)) */

#endif /* end of the file */
