/* 

$Id: array.c,v 1.10 2005/05/24 09:54:57 dk Exp $ 

Implemenmtation of PHP::TieHash and PHP::TieArray methods

*/

#include "PHP.h"

#ifdef __cplusplus
extern "C" {
#endif

XS(PHP_ArrayHandle_new)
{
	dXSARGS;
	STRLEN na;
	zval * array;
	
	if ( items != 1)
		croak("PHP::ArrayHandle::new: 1 parameter expected");

	SP -= items;

	MAKE_STD_ZVAL( array);
	array_init( array);

	XPUSHs( sv_2mortal( Entity_create( SvPV( ST(0), na), array)));
	PUTBACK;
	ZVAL_DELREF( array);
	return;
}

XS( PHP_TieHash_EXISTS)
{
	dXSARGS;
	char * key;
	STRLEN na, klen;
	zval * array;

#define METHOD "PHP::TieHash::EXISTS"
	if ( items != 2) 
		croak("%s: expect 2 parameters", METHOD);

	if (( array = SV2ZANY( ST(0))) == NULL)
		croak("%s: (%s) is not a PHP array", METHOD, SvPV(ST(0), na));

	key = SvPV( ST(1), klen);
	DEBUG("exists 0x%x->{%s}", array, key);

	SP -= items;
	PUTBACK;

	return XSRETURN_IV( zend_hash_exists( HASH_OF(array), key, klen + 1));
#undef METHOD
}

XS( PHP_TieHash_FETCH)
{
	dXSARGS;
	char * key;
	STRLEN na, klen;
	zval * array, **zobj;
	SV * retsv;

#define METHOD "PHP::TieHash::FETCH"
	if ( items != 2) 
		croak("%s: expect 2 parameters", METHOD);

	if (( array = SV2ZANY( ST(0))) == NULL)
		croak("%s: (%s) is not a PHP array", METHOD, SvPV(ST(0), na));

	key = SvPV( ST(1), klen);
	DEBUG("fetch 0x%x->{%s}", array, key);

	SP -= items;

	if ( zend_hash_find( HASH_OF(array), key, klen + 1, (void**) &zobj) == FAILURE) {
		XPUSHs( &PL_sv_undef);
		PUTBACK;
		return;
	}

	if ( !( retsv = zval2sv( *zobj))) {
		warn("%s: value cannot be converted\n", METHOD);
		retsv = &PL_sv_undef;
	}
	retsv = sv_2mortal( retsv);
	XPUSHs( retsv);

#undef METHOD
	PUTBACK;

	return;
}

XS( PHP_TieHash_STORE)
{
	dXSARGS;
	char * key;
	STRLEN na, klen;
	zval * array, *zobj;
	SV * val;

#define METHOD "PHP::TieHash::STORE"
	if ( items != 3) 
		croak("%s: expect 3 parameters", METHOD);

	if (( array = SV2ZANY( ST(0))) == NULL)
		croak("%s: (%s) is not a PHP array", METHOD, SvPV(ST(0), na));

	key = SvPV( ST(1), klen);
	DEBUG("store 0x%x->{%s}=%s", array, key, SvPV( ST(2), na));

	MAKE_STD_ZVAL( zobj);
	zobj-> type = IS_NULL;
	if ( !sv2zval( val = ST(2), zobj, -1)) {
		zval_ptr_dtor( &zobj);
		croak("%s: scalar (%s) type=%d cannot be converted", 
			METHOD, SvPV( val, na), SvTYPE( val));
	}

	if ( zend_hash_update( 
		HASH_OF(array), key, klen + 1,
		(void *)&zobj, sizeof(zval *), NULL
		) == FAILURE) {
		zval_ptr_dtor( &zobj);
		croak("%s: failed", METHOD);
	}

#undef METHOD
	SP -= items;
	PUTBACK;
	XSRETURN_EMPTY;
}

XS( PHP_TieHash_DELETE)
{
	dXSARGS;
	char * key;
	STRLEN na, klen;
	zval * array;

#define METHOD "PHP::TieHash::DELETE"
	if ( items != 2) 
		croak("%s: expect 2 parameters", METHOD);

	if (( array = SV2ZANY( ST(0))) == NULL)
		croak("%s: (%s) is not a PHP array", METHOD, SvPV(ST(0), na));

	key = SvPV( ST(1), klen);
	DEBUG("delete 0x%x->{%s}", array, key);

	SP -= items;
	PUTBACK;

	zend_hash_del( HASH_OF(array), key, klen + 1);

	XSRETURN_EMPTY;
#undef METHOD
}

XS( PHP_TieHash_CLEAR)
{
	dXSARGS;
	STRLEN na;
	zval * array;

#define METHOD "PHP::TieHash::CLEAR"
	if ( items != 1) 
		croak("%s: expect 1 parameter", METHOD);

	if (( array = SV2ZANY( ST(0))) == NULL)
		croak("%s: (%s) is not a PHP array", METHOD, SvPV(ST(0), na));

	DEBUG("clear 0x%x", array);

	SP -= items;
	PUTBACK;

	zend_hash_clean( HASH_OF(array));

	XSRETURN_EMPTY;
#undef METHOD
}

/* for internal use by FIRSTKEY and NEXTKEY - construct return value and advance zhash ptr  */
static SV *
do_zenum( 
	char * method,
	zval * array,
	HashPosition * hpos
) {
	SV * ret;
	int rettype, klen;
	unsigned long numkey;
	char * key;

	if ( ( rettype = zend_hash_get_current_key_ex( HASH_OF(array), 
		&key, &klen, &numkey, 0, hpos)) == HASH_KEY_NON_EXISTANT) {
		DEBUG( "%s: enum stop", method);
		return &PL_sv_undef;
	}
	
	if ( rettype == HASH_KEY_IS_STRING) {
		ret = newSVpvn( key, klen - 1); 
		DEBUG( "%s: enum %s", method, key);
	} else {
		ret = newSViv( numkey); 
		DEBUG( "%s: enum index %d", method, numkey);
	}

	return sv_2mortal( ret);
}

XS( PHP_TieHash_FIRSTKEY)
{
	dXSARGS;
	zval * array;
	STRLEN na;
	SV * hash_position, * perl_obj;
	HashPosition hpos_buf, *hpos;

#define METHOD "PHP::TieHash::FIRSTKEY"
	if ( items != 1) 
		croak("%s: expect 1 parameter", METHOD);

	if (( array = SV2ZANY( perl_obj = ST(0))) == NULL)
		croak("%s: (%s) is not a PHP array", METHOD, SvPV( perl_obj, na));

	DEBUG("firstkey 0x%x", array);

	hash_position = newSV( sizeof( HashPosition));
        sv_setpvn( hash_position, ( char *) &hpos_buf, sizeof( hpos_buf));
	hpos = ( HashPosition*) SvPV( hash_position, na);
	hv_store((HV *) SvRV( perl_obj), "__ENUM__", 8, hash_position, 0);

	zend_hash_internal_pointer_reset_ex( HASH_OF(array), hpos); 

	SP -= items;

	XPUSHs( do_zenum( METHOD, array, hpos));
	PUTBACK;

#undef METHOD
	return;
}

XS( PHP_TieHash_NEXTKEY)
{
	dXSARGS;
	zval * array;
	STRLEN na;
	SV ** hash_position, * perl_obj;
	HashPosition *hpos;

#define METHOD "PHP::TieHash::NEXTKEY"
	if ( items != 2) 
		croak("%s: expect 2 parameters", METHOD);

	if (( array = SV2ZANY( perl_obj = ST(0))) == NULL)
		croak("%s: (%s) is not a PHP array", METHOD, SvPV( perl_obj, na));

	DEBUG("nextkey 0x%x", array);

	if ( !( hash_position = hv_fetch(( HV *) SvRV( perl_obj), "__ENUM__", 8, 0)))
		croak("%s: Internal inconsistency", METHOD);
	hpos = ( HashPosition*) SvPV( *hash_position, na);
	
	zend_hash_move_forward_ex( HASH_OF(array), hpos);
	
	SP -= items;
	XPUSHs( do_zenum( METHOD, array, hpos));
	PUTBACK;
		
#undef METHOD
	return;
}

XS( PHP_TieArray_EXISTS)
{
	dXSARGS;
	long key;
	STRLEN na;
	zval * array;

#define METHOD "PHP::TieArray::EXISTS"
	if ( items != 2) 
		croak("%s: expect 2 parameters", METHOD);

	if (( array = SV2ZANY( ST(0))) == NULL)
		croak("%s: (%s) is not a PHP array", METHOD, SvPV(ST(0), na));

	key = SvIV( ST(1));
	DEBUG("exists 0x%x->[%d]", array, key);

	SP -= items;
	PUTBACK;

	return XSRETURN_IV( zend_hash_index_exists( HASH_OF(array), key));
#undef METHOD
}

XS( PHP_TieArray_FETCH)
{
	dXSARGS;
	long key;
	STRLEN na;
	zval * array, **zobj;
	SV * retsv;

#define METHOD "PHP::TieArray::FETCH"
	if ( items != 2) 
		croak("%s: expect 2 parameters", METHOD);

	if (( array = SV2ZANY( ST(0))) == NULL)
		croak("%s: (%s) is not a PHP array", METHOD, SvPV(ST(0), na));

	key = SvIV( ST(1));
	DEBUG("fetch 0x%x->[%d]", array, key);

	SP -= items;

	if ( zend_hash_index_find( HASH_OF(array), key, (void**) &zobj) == FAILURE) {
		XPUSHs( &PL_sv_undef);
		PUTBACK;
		return;
	}

	if ( !( retsv = zval2sv( *zobj))) {
		warn("%s: value cannot be converted\n", METHOD);
		retsv = &PL_sv_undef;
	}
	if ( retsv != &PL_sv_undef) 
		retsv = sv_2mortal( retsv);
	XPUSHs( retsv);

#undef METHOD
	PUTBACK;

	return;
}

XS( PHP_TieArray_STORE)
{
	dXSARGS;
	long key;
	STRLEN na;
	zval * array, *zobj;
	SV * val;

#define METHOD "PHP::TieArray::STORE"
	if ( items != 3) 
		croak("%s: expect 3 parameters", METHOD);

	if (( array = SV2ZANY( ST(0))) == NULL)
		croak("%s: (%s) is not a PHP array", METHOD, SvPV(ST(0), na));

	key = SvIV( ST(1));
	DEBUG("store 0x%x->[%d]=%s", array, key, SvPV( ST(2), na));

	MAKE_STD_ZVAL( zobj);
	zobj-> type = IS_NULL;
	if ( !sv2zval( val = ST(2), zobj, -1)) {
		zval_ptr_dtor( &zobj);
		croak("%s: scalar (%s) type=%d cannot be converted", 
			METHOD, SvPV( val, na), SvTYPE( val));
	}

	if ( zend_hash_index_update( 
		HASH_OF(array), key,
		(void *)&zobj, sizeof(zval *), NULL
		) == FAILURE) {
		zval_ptr_dtor( &zobj);
		croak("%s: failed", METHOD);
	}

#undef METHOD
	SP -= items;
	PUTBACK;
	XSRETURN_EMPTY;
}

XS( PHP_TieArray_DELETE)
{
	dXSARGS;
	long key;
	STRLEN na;
	zval * array;

#define METHOD "PHP::TieArray::DELETE"
	if ( items != 2) 
		croak("%s: expect 2 parameters", METHOD);

	if (( array = SV2ZANY( ST(0))) == NULL)
		croak("%s: (%s) is not a PHP array", METHOD, SvPV(ST(0), na));

	key = SvIV( ST(1));
	DEBUG("delete 0x%x->[%d]", array, key);

	SP -= items;
	PUTBACK;

	zend_hash_index_del( HASH_OF(array), key);

	XSRETURN_EMPTY;
#undef METHOD
}

/* 
   Retrieve index of the last item in the array; return -1 if the array is empty .
*/
static long 
array_last_index( HashTable * array)
{
	int klen;
	char * key;
	HashPosition hp;
	unsigned long numkey;
	long last = -1;

	zend_hash_internal_pointer_reset_ex( array, &hp); 
	while ( 1) {
		switch( zend_hash_get_current_key_ex( array, 
			&key, &klen, &numkey, 0, &hp)) 
		{
		case HASH_KEY_NON_EXISTANT:
			return last;
		case HASH_KEY_IS_LONG:
			if ( last < (long) numkey) last = numkey;
			break;
		}
		zend_hash_move_forward_ex( array, &hp);
	}
}

XS( PHP_TieArray_FETCHSIZE)
{
	dXSARGS;
	STRLEN na;
	zval * array;

#define METHOD "PHP::TieArray::FETCHSIZE"
	if ( items != 1) 
		croak("%s: expect 1 parameter", METHOD);

	if (( array = SV2ZANY( ST(0))) == NULL)
		croak("%s: (%s) is not a PHP array", METHOD, SvPV(ST(0), na));

	DEBUG("fetchsize 0x%x", array);

	SP -= items;
	PUTBACK;

	XSRETURN_IV( 1 + array_last_index( HASH_OF(array)));
#undef METHOD
}

XS( PHP_TieArray_PUSH)
{
	dXSARGS;
	STRLEN na;
	long i, pos;
	zval * array, * zobj;
	SV * val;

#define METHOD "PHP::TieArray::PUSH"
	if ( items < 1) 
		croak("%s: expect at least 1 parameter", METHOD);

	if (( array = SV2ZANY( ST(0))) == NULL)
		croak("%s: (%s) is not a PHP array", METHOD, SvPV(ST(0), na));

	pos = array_last_index( HASH_OF(array));
	
	DEBUG("push 0x%x %d items after %d", array, items - 1, pos);
	
	for ( i = 1; i < items; i++) {
		DEBUG("push %s", SvPV( ST(i), na));

		MAKE_STD_ZVAL( zobj);
		zobj-> type = IS_NULL;
		if ( !sv2zval( val = ST(i), zobj, -1)) {
			zval_ptr_dtor( &zobj);
			croak("%s: scalar (%s) type=%d cannot be converted", 
				METHOD, SvPV( val, na), SvTYPE( val));
		}

		if ( zend_hash_index_update( 
			HASH_OF(array), pos + i,
			(void *)&zobj, sizeof(zval *), NULL
			) == FAILURE) {
			zval_ptr_dtor( &zobj);
			croak("%s: failed", METHOD);
		}
	}

	SP -= items;
	PUTBACK;

	XSRETURN_IV( pos + items);
	
#undef METHOD
}

XS( PHP_TieArray_POP)
{
	dXSARGS;
	STRLEN na;
	zval * array, **zobj;
	SV * retsv;
	long pos;

#define METHOD "PHP::TieArray::POP"
	if ( items != 1) 
		croak("%s: expect 1 parameters", METHOD);

	if (( array = SV2ZANY( ST(0))) == NULL)
		croak("%s: (%s) is not a PHP array", METHOD, SvPV(ST(0), na));

	pos = array_last_index( HASH_OF(array));

	DEBUG("pop 0x%x at %d", array, pos);

	SP -= items;
	
	if ( pos == -1) { /* empty array */
		XPUSHs( &PL_sv_undef);
		PUTBACK;
		return;
	}

	if ( zend_hash_index_find( HASH_OF(array), pos, (void**) &zobj) == FAILURE) {
		XPUSHs( &PL_sv_undef);
		PUTBACK;
		return;
	}
	
	if ( !( retsv = zval2sv( *zobj))) {
		warn("%s: value cannot be converted\n", METHOD);
		retsv = &PL_sv_undef;
	}

	zend_hash_index_del( HASH_OF(array), pos);

	if ( retsv != &PL_sv_undef) 
		retsv = sv_2mortal( retsv);
	XPUSHs( retsv);

#undef METHOD
	PUTBACK;

	return;
}

void
register_PHP_Array()
{
	newXS( "PHP::ArrayHandle::new", PHP_ArrayHandle_new, "PHP::ArrayHandle");

	newXS( "PHP::TieHash::EXISTS",	PHP_TieHash_EXISTS,	"PHP::TieHash");
	newXS( "PHP::TieHash::FETCH",	PHP_TieHash_FETCH,	"PHP::TieHash");
	newXS( "PHP::TieHash::STORE",	PHP_TieHash_STORE,	"PHP::TieHash");
	newXS( "PHP::TieHash::DELETE",	PHP_TieHash_DELETE,	"PHP::TieHash");
	newXS( "PHP::TieHash::CLEAR",	PHP_TieHash_CLEAR,	"PHP::TieHash");
	newXS( "PHP::TieHash::FIRSTKEY",PHP_TieHash_FIRSTKEY,	"PHP::TieHash");
	newXS( "PHP::TieHash::NEXTKEY",	PHP_TieHash_NEXTKEY,	"PHP::TieHash");

	newXS( "PHP::TieArray::FETCHSIZE",PHP_TieArray_FETCHSIZE,"PHP::TieArray");
	newXS( "PHP::TieArray::EXISTS",	PHP_TieArray_EXISTS,	"PHP::TieArray");
	newXS( "PHP::TieArray::FETCH",	PHP_TieArray_FETCH,	"PHP::TieArray");
	newXS( "PHP::TieArray::STORE",	PHP_TieArray_STORE,	"PHP::TieArray");
	newXS( "PHP::TieArray::DELETE",	PHP_TieArray_DELETE,	"PHP::TieArray");
	newXS( "PHP::TieArray::CLEAR",	PHP_TieHash_CLEAR,	"PHP::TieArray");
	newXS( "PHP::TieArray::PUSH",	PHP_TieArray_PUSH,	"PHP::TieArray");
	newXS( "PHP::TieArray::POP",	PHP_TieArray_POP,	"PHP::TieArray");
}

#ifdef __cplusplus
}
#endif