#include <EXTERN.h>
#include <perl.h>
#include <perlapi.h>

#include "phpinterp.h"
#include "phpfuncs.h"
#include "zend_interfaces.h"


ZEND_DECLARE_MODULE_GLOBALS(sandwich);
zend_class_entry *pl_ce;
zend_class_entry *plobj_ce;
zend_class_entry *plsv_ce;

static zend_object_handlers plobj_handlers;
static zend_object_handlers plsv_handlers;

static zend_object_value plobj_create_object(zend_class_entry *ce TSRMLS_DC);
static zend_object_value plsv_create_object(zend_class_entry *ce TSRMLS_DC);


PHP_MINFO_FUNCTION(sandwich)
{
  php_info_print_table_start();
  php_info_print_table_row(2, "Loaded Modules", "Perl Sandwich");
  php_info_print_table_end();
}

static void
sw_initglobals(zend_sandwich_globals *swg)
{
  memset(&swg->ps_stats, 0, sizeof(swg->ps_stats));
}

static zval *
sandwich_dim_read(zval *obj, zval *offset, int type TSRMLS_DC)
{
  SV           *value;
  zval         *return_value;
  struct plobj *pl;
  char         *name;

  pTHX;

  pl = zend_object_store_get_object(obj TSRMLS_CC);
#ifdef USE_ITHREADS
  aTHX = pl->perl;
#endif
  value = NULL;
  
  if (Z_TYPE_P(offset) != IS_STRING) {
    goto err_out;
  }

  name = Z_STRVAL_P(offset);
  if (Z_STRLEN_P(offset) < 2) {
    goto err_out;
  }

  switch (name[0]) {
    case '$':
      value = get_sv(name + 1, FALSE);
      break;
    case '@':
      value = (SV *) get_av(name + 1, FALSE);
      break;
    case '%':
      value = (SV *) get_hv(name + 1, FALSE);
      break;
  }

  if (!value) {
    goto err_out;
  }

  return_value = SvZval(value TSRMLS_CC);
  return return_value;
  
err_out:
  MAKE_STD_ZVAL(return_value);
  ZVAL_NULL(return_value);
  return return_value;
}

static void
sandwich_dim_write(zval *obj, zval *offset, zval *value TSRMLS_DC)
{
  return;
}

PHP_METHOD(perl, getvariable)
{
  struct plobj *pl;
  SV           *var;
  zval         *retval;
  char         *name;
  int           namelen;
  pTHX;
  dSP;

  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &namelen) == FAILURE) {
    return;
  }

  if (namelen < 1) {
    RETURN_NULL();
  }
  
  pl = zend_object_store_get_object(getThis() TSRMLS_CC);
#ifdef USE_ITHREADS
  aTHX = pl->perl;
#endif
  var = NULL;
  if(strchr(name, '[') || strchr(name, '{')) {
    ENTER; SAVETMPS;
    PUSHMARK(SP);
    EXTEND(SP, 2);
    PUTBACK;
    var = eval_pv(name, G_VOID);
    SvREFCNT_inc(var);
    FREETMPS; LEAVE;
  } else {
    switch (name[0]) {
      case '$':
        var =  get_sv(name + 1, FALSE);
        break;
      case '@':
        var = (SV *) get_av(name + 1, FALSE);
        break;
      case '%':
        var = (SV *) get_hv(name + 1, FALSE);
        break;
      default:
        RETURN_NULL();
    }
  }
  if (var != NULL) {
    retval = SvZval(var TSRMLS_CC);
    RETURN_ZVAL(retval, 1, 0);
  } else {
    RETURN_NULL();
  }
}

PHP_METHOD(perl, setvariable)
{
  struct plobj *pl;
  SV           *var;
  zval         *retval;
  char         *name;
  int           namelen;
  zval         *param;
  SV           *sparam;
  pTHX;

  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &name, &namelen, &param) == FAILURE) {
    return;
  }

  if (namelen < 1) {
    RETURN_NULL();
  }
  
  pl = zend_object_store_get_object(getThis() TSRMLS_CC);
#ifdef USE_ITHREADS
  aTHX = pl->perl;
#endif
  var = NULL;
  
  switch (name[0]) {
    case '$':
      var =  get_sv(name + 1, FALSE);
      sparam = newSVzval(param, SandwichG(php));
      sv_setsv(var, sparam);
      break;
    case '@':
      /* check type require param IS_ARRAY, maybe IS_OBJECT */
      var = (SV *) get_av(name + 1, FALSE);
      RETURN_NULL();
      break;
    case '%':
      /* check type require param IS_ARRAY, maybe IS_OBJECT */
      var = (SV *) get_hv(name + 1, FALSE);
      RETURN_NULL();
      break;
    default:
      RETURN_NULL();
  }
  RETURN_TRUE;
}

static int _sandwich_call_method(char *method, INTERNAL_FUNCTION_PARAMETERS, int offset)
{
  struct plobj *pl;
  SV           *var;
  zval         *retval;
  char         *name;
  int           namelen;
  zval         *param;
  zval         ***args;
  SV           *sparam;
  int          argc, i;
  SV           *prv;
  pTHX;

  pl = zend_object_store_get_object(getThis() TSRMLS_CC);
#ifdef USE_ITHREADS
  aTHX = pl->perl;
#endif
  {
    int cnt;
    STRLEN n_a;
    dSP;
  
    argc = ZEND_NUM_ARGS();
    args = (zval ***) safe_emalloc(sizeof(zval **), argc, 0);
    if(zend_get_parameters_array_ex(argc, args) == FAILURE) {
      efree(args);
      WRONG_PARAM_COUNT;
    }
    
    pl = zend_object_store_get_object(getThis() TSRMLS_CC);
#ifdef USE_ITHREADS
    aTHX = pl->perl;
#endif  
    ENTER; SAVETMPS;
    PUSHMARK(SP);
    if(offset < argc) EXTEND(SP, argc - offset);
    for(i = offset; i < argc; i++) {
      var = newSVzval(*args[i], SandwichG(php));
      var = sv_2mortal(var);
      XPUSHs(var);
    }
    PUTBACK;
    cnt = call_pv(method, G_SCALAR | G_EVAL | G_KEEPERR);
    SPAGAIN;
    if(cnt == 1) {
      prv = POPs;
      SvREFCNT_inc(prv);
      retval = SvZval(prv TSRMLS_CC);
      RETVAL_ZVAL(retval, 1, 0);
    } else {
      RETVAL_NULL();
    }
    if(SvTRUE(ERRSV)) {
    //  croak(SvPVx(ERRSV, n_a));
    }
    PUTBACK;
    FREETMPS; LEAVE;
    efree(args);
  }
}

static int sandwich_call_method(char *method, INTERNAL_FUNCTION_PARAMETERS)
{
  return _sandwich_call_method(method, INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
}

PHP_METHOD(perl, call)
{
  zval **name[1];
  if(ZEND_NUM_ARGS() < 1) {
    WRONG_PARAM_COUNT;
  }
  if(zend_get_parameters_array_ex(1, name) == FAILURE) {
      WRONG_PARAM_COUNT;
  } else if(Z_TYPE_PP(name[0]) != IS_STRING) {
      WRONG_PARAM_COUNT;
  }
  _sandwich_call_method(Z_STRVAL_PP(name[0]), INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
}

static PHP_FUNCTION(sandwich_method_handler)
{
  _sandwich_call_method(
    ((zend_internal_function*)EG(function_state_ptr)->function)->function_name,
    INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
}

static union _zend_function *sandwich_get_method(zval **object_ptr, char *name, int len TSRMLS_DC)
{
  zval *object = *object_ptr;
  zend_internal_function f, *fptr = NULL;
  union _zend_function *func;
  struct plobj *pl;
  char *lc_method_name;

  lc_method_name = emalloc(len + 1);
  zend_str_tolower_copy(lc_method_name, name, len);
  if (zend_hash_find(&plobj_ce->function_table, lc_method_name, len+1, (void**)&func) == SUCCESS) {
    efree(lc_method_name);
    return func;
  }
  efree(lc_method_name);
  
  f.type = ZEND_OVERLOADED_FUNCTION;
  f.num_args = 0;
  f.arg_info = NULL;
  f.scope = plobj_ce;
  f.fn_flags=0;
  f.function_name= estrndup(name, len);
  f.handler= PHP_FN(sandwich_method_handler);

  func = emalloc(sizeof(*func));
  memcpy(func, &f,sizeof(f));
  return func;
}

PHP_METHOD(perl, getinstance)
{
  struct plobj *pl;

  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "") == FAILURE) {
    return;
  }


  return_value->type = IS_OBJECT;
  return_value->value.obj = plobj_create_object(plobj_ce TSRMLS_CC);
}

SV *my_eval_sv(pTHX_ SV *sv, I32 coe) {
  SV *retval = &PL_sv_undef;
  STRLEN n_a;
  int cnt;
  dSP;
  dMARK;

  ENTER; SAVETMPS;
  eval_sv(sv, G_SCALAR|G_EVAL);
 
  SPAGAIN;
  retval = POPs;
  SvREFCNT_inc(retval);
  PUTBACK;
  FREETMPS; LEAVE;

  if(SvTRUE(ERRSV)) {
    croak(SvPVx(ERRSV, n_a));
  }
  return retval;
}

static SV *sandwich_perl_eval(pTHX_ char *command_in) {
  SV *retval;
  SV *command;
  command = newSVpv(command_in, 0);
  retval = my_eval_sv(aTHX_ command, FALSE);
  return retval;
}

PHP_METHOD(perl, eval)
{
  struct plobj *pl;
  char *func;
  long funclen;
  SV *rv;
  pTHX;

  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &func, &funclen) == FAILURE) {
    return;
  }
  pl = zend_object_store_get_object(getThis() TSRMLS_CC);
#ifdef USE_ITHREADS
  aTHX = pl->perl;
#endif
  if((rv = sandwich_perl_eval(aTHX_ func)) == NULL)  { 
  } else {
    zval *retval = SvZval(rv TSRMLS_CC);
    RETURN_ZVAL(retval, 1, 0);
  }
}

PHP_METHOD(perl, new)
{
  struct plobj *pl;
  SV           *var;
  zval         *retval;
  zval         *param;
  zval         ***args;
  SV           *sparam;
  int          argc, i;
  SV           *prv;
  zval **name[1];

  pTHX;

  if(ZEND_NUM_ARGS() < 1) {
    WRONG_PARAM_COUNT;
  }
  if(zend_get_parameters_array_ex(1, name) == FAILURE) {
      WRONG_PARAM_COUNT;
  } else if(Z_TYPE_PP(name[0]) != IS_STRING) {
      WRONG_PARAM_COUNT;
  }

  pl = zend_object_store_get_object(getThis() TSRMLS_CC);
#ifdef USE_ITHREADS
  aTHX = pl->perl;
#endif
  {
    int cnt;
    STRLEN n_a;
    dSP;
  
    argc = ZEND_NUM_ARGS();
    args = (zval ***) safe_emalloc(sizeof(zval **), argc, 0);
    if(zend_get_parameters_array_ex(argc, args) == FAILURE) {
      efree(args);
      WRONG_PARAM_COUNT;
    }
    
    ENTER; SAVETMPS;
    PUSHMARK(SP);
    EXTEND(SP, argc + 1);
    for(i = 0; i < argc; i++) {
      var = newSVzval(*args[i], SandwichG(php));
      var = sv_2mortal(var);
      XPUSHs(var);
    }
    PUTBACK;
    cnt = call_method("new", G_SCALAR | G_EVAL);
    SPAGAIN;
    if(cnt == 1) {
      prv = POPs;
      SvREFCNT_inc(prv);
      retval = SvZval(prv TSRMLS_CC);
      RETVAL_ZVAL(retval, 1, 0);
    } else {
      RETVAL_NULL();
    }
    if(SvTRUE(ERRSV)) {
    //  croak(SvPVx(ERRSV, n_a));
    }
    PUTBACK;
    FREETMPS; LEAVE;
    efree(args);
  }
}

static zval *
sv_prop_read(zval *obj, zval *member, int type TSRMLS_DC)
{
  SV           *sv, **value;
  HV           *hash;
  zval         *return_value;
  struct plsv  *pl;
  char         *key;
  long         keylen;
  pTHX;

  pl = zend_object_store_get_object(obj TSRMLS_CC);
#ifdef USE_ITHREADS
  aTHX = pl->perl;
#endif
  value = NULL;
  sv = pl->sv;
  if(!SvROK(sv)) RETURN_FALSE;
  sv = SvRV(sv);
  if(SvTYPE(sv) == SVt_PVHV) {
    hash = (HV *) sv;
  } else if(SvTYPE(sv) == SVt_PVGV) {
    hash =  SvSTASH(sv);
  } else {
    goto err_out;
  }
  key = Z_STRVAL_P(member);
  keylen = Z_STRLEN_P(member);

  value = hv_fetch(hash, key, keylen, 1);
  if (!value) {
    goto err_out;
  }
  if(SvMAGICAL(*value)){
    mg_get(*value);
  }
  return_value = SvZval(*value TSRMLS_CC);
  return return_value;
  
err_out:
  MAKE_STD_ZVAL(return_value);
  ZVAL_NULL(return_value);
  return return_value;
}

static void
sv_prop_write(zval *object, zval *member, zval *value TSRMLS_DC)
{
  SV           *sv, **svpp;
  HV           *hash;
  zval         *return_value;
  struct plsv  *pl;
  char         *key;
  long         keylen;
  pTHX;

  pl = zend_object_store_get_object(object TSRMLS_CC);
#ifdef USE_ITHREADS
  aTHX = pl->perl;
#endif
  sv = pl->sv;
  if(!SvROK(sv)) RETURN_FALSE;
  sv = SvRV(sv);
  if(SvTYPE(sv) == SVt_PVHV) {
    hash = (HV *) sv;
  } else if(SvTYPE(sv) == SVt_PVGV) {
    hash =  SvSTASH(sv);
  } else {
    return;
  }
  key = Z_STRVAL_P(member);
  keylen = Z_STRLEN_P(member);
  svpp = hv_store(hash, key, keylen, newSVzval(value, SandwichG(php)), 0);
  if(svpp && SvMAGICAL(*svpp)) {
    mg_set(*svpp);
  }
}

static int
sv_prop_exists(zval *object, zval *member, int has_set_exists TSRMLS_DC)
{
  SV           *sv, **value;
  HV           *hash;
  zval         *return_value;
  struct plsv  *pl;
  char         *key;
  long         keylen;
  pTHX;

  pl = zend_object_store_get_object(object TSRMLS_CC);
#ifdef USE_ITHREADS
  aTHX = pl->perl;
#endif
  value = NULL;
  sv = pl->sv;
  if(!SvROK(sv)) RETURN_FALSE;
  sv = SvRV(sv);
  if(SvTYPE(sv) == SVt_PVHV) {
    hash = (HV *) sv;
  } else {
    return FAILURE;
  }
  key = Z_STRVAL_P(member);
  keylen = Z_STRLEN_P(member);
  return hv_exists(hash, key, keylen);
}

static int _sv_call_method(char *method, INTERNAL_FUNCTION_PARAMETERS, int offset)
{
  struct plsv *pl;
  SV           *var;
  zval         *retval;
  char         *name;
  int           namelen;
  zval         *param;
  zval         ***args;
  SV           *sparam;
  int          argc, i;
  SV           *prv;
  int          cnt;
  pTHX;

  pl = zend_object_store_get_object(getThis() TSRMLS_CC);
#ifdef USE_ITHREADS
  aTHX = pl->perl;
#endif
  {
    STRLEN n_a;
    dSP;
    I32 oldscope = PL_scopestack_ix;
    argc = ZEND_NUM_ARGS();
    args = (zval ***) safe_emalloc(sizeof(zval **), argc, 0);
    if(zend_get_parameters_array_ex(argc, args) == FAILURE) {
      efree(args);
      WRONG_PARAM_COUNT;
    }
    
    pl = zend_object_store_get_object(getThis() TSRMLS_CC);
#ifdef USE_ITHREADS
    aTHX = pl->perl;
#endif  
    ENTER; SAVETMPS;
    PUSHMARK(SP);
    EXTEND(SP, argc);
    SvREFCNT_inc(pl->sv);
    XPUSHs(pl->sv);
    for(i = offset; i < argc; i++) {
      var = newSVzval(*args[i], SandwichG(php));
      var = sv_2mortal(var);
      XPUSHs(var);
    }
    PUTBACK;
    cnt = call_method(method, G_SCALAR | G_EVAL);
    SvREFCNT_dec(pl->sv);
    SPAGAIN;
    if(cnt == 1) {
        prv = POPs;
      /*
      if(coe && SvTRUE(ERRSV)) {
        croak(SvPVx(ERRSV, n_a));
      }
      */
      SvREFCNT_inc(prv);
      retval = SvZval(prv TSRMLS_CC);
      RETVAL_ZVAL(retval, 1, 0);
    } else {
      RETVAL_NULL();
    }
    PUTBACK;
    FREETMPS; LEAVE;
    if(SvTRUE(ERRSV)) {
    //  croak(SvPVx(ERRSV, n_a));
    }
    efree(args);
  }
}

static PHP_FUNCTION(sv_method_handler)
{
  _sv_call_method(
    ((zend_internal_function*)EG(function_state_ptr)->function)->function_name,
    INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
}

static int sv_call_method(char *method, INTERNAL_FUNCTION_PARAMETERS)
{
  return _sv_call_method(method, INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
}

static union _zend_function *sv_get_method(zval **object_ptr, char *name, int len TSRMLS_DC)
{
  zval *object = *object_ptr;
  zend_internal_function f, *fptr = NULL;
  union _zend_function *func;
  struct plobj *pl;
  char *lc_method_name;

  lc_method_name = emalloc(len + 1);
  zend_str_tolower_copy(lc_method_name, name, len);
  if (zend_hash_find(&plobj_ce->function_table, lc_method_name, len+1, (void**)&func) == SUCCESS) {
    efree(lc_method_name);
    return func;
  }
  efree(lc_method_name);
  
  f.type = ZEND_OVERLOADED_FUNCTION;
  f.num_args = 0;
  f.arg_info = NULL;
  f.scope = plobj_ce;
  f.fn_flags=0;
  f.function_name= estrndup(name, len);
  f.handler= PHP_FN(sv_method_handler);

  func = emalloc(sizeof(*func));
  memcpy(func, &f,sizeof(f));
  return func;
}

int sv_get_class_name(zval *obj, char **class_name, zend_uint *class_name_len, int parent TSRMLS_DC) 
{
  if(parent) {
    *class_name_len = strlen("PerlSV") + 1;
    *class_name = estrndup("PerlSV", *class_name_len);
  } else {
    pTHX;
    struct plsv *pl;
    char *tmp;
    pl = (struct plsv *) zend_object_store_get_object(obj TSRMLS_CC);
#ifdef USE_ITHREADS
    aTHX = pl->perl;
#endif
    if(!sv_isobject(pl->sv)) {
      *class_name_len = strlen("PerlSV") + 1;
      *class_name = estrndup("PerlSV", *class_name_len);
    } else {
      tmp = HvNAME((SvSTASH(SvRV(pl->sv))));
      if(!tmp) return FAILURE;
      *class_name_len = strlen(tmp) + sizeof("PerlSV::") - 1;
      *class_name = emalloc(*class_name_len + 1);
      strncpy(*class_name, "PerlSV::", sizeof("PerlSV::"));
      strcat(*class_name, tmp);
    }
  }
  return SUCCESS;
}

static void stderr_dump(char *in, int inlen) {
  fwrite(in, inlen, 1, stderr);
}

PHP_METHOD(perlsv, __tostring)
{
  struct plsv *pl;
  char         *string;
  STRLEN       stringlen;
  pTHX;
  
  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "") == FAILURE) {
    return;
  }
  pl = (struct plsv *) zend_object_store_get_object(getThis() TSRMLS_CC);
#ifdef USE_ITHREADS
  aTHX = pl->perl;
#endif
  string = SvPV(pl->sv, stringlen);
  RETURN_STRINGL(string, stringlen, 1);
}  

struct plsv_iterator {
  zend_object_iterator iter;
  SV *sv;
  HV *hv;
  char *key;
  uint keylen;
  zval *fetch_ahead;
};

static void plsv_iter_dtor(zend_object_iterator *iter TSRMLS_DC)
{
  dTHX;
  struct plsv_iterator *I = (struct plsv_iterator*)iter->data;
  if (I->fetch_ahead) {
    ZVAL_DELREF(I->fetch_ahead);
    I->fetch_ahead = NULL;
  }
  if (I->key) {
    efree(I->key);
  }
  if(I->sv) {
    SvREFCNT_dec(I->sv);
  }
  efree(I);
}

static int plsv_iter_valid(zend_object_iterator *iter TSRMLS_DC)
{
  struct plsv_iterator *I = (struct plsv_iterator*)iter->data;
  if(!I->hv) return FAILURE;
  return I->fetch_ahead ? SUCCESS : FAILURE;
}

static void plsv_iter_get_data(zend_object_iterator *iter, zval ***data TSRMLS_DC)
{
  struct plsv_iterator *I = (struct plsv_iterator*)iter->data;
  zval **ptr_ptr;

  /* sanity */
  if (!I->fetch_ahead) {
      *data = NULL;
      return;
  }

  ptr_ptr = emalloc(sizeof(*ptr_ptr)); /* leaks somewhere */
  *ptr_ptr = I->fetch_ahead;
  ZVAL_ADDREF(I->fetch_ahead);
  *data = ptr_ptr;
}

static int plsv_iter_get_key(zend_object_iterator *iter, char **str_key, uint *str_key_len,
    ulong *int_key TSRMLS_DC)
{
  struct plsv_iterator *I = (struct plsv_iterator*)iter->data;

  if (!I->key) {
      return HASH_KEY_NON_EXISTANT;
  }
  *str_key_len = I->keylen;
  *str_key = estrndup(I->key, I->keylen);
  return HASH_KEY_IS_STRING;
}

static void plsv_iter_move_forwards(zend_object_iterator *iter TSRMLS_DC)
{
  struct plsv_iterator *I = (struct plsv_iterator*)iter->data;
  SV *value;
  char *key;
  long keylen;
  pTHX;
  if(!I->hv) return;
  if (I->fetch_ahead) {
      ZVAL_DELREF(I->fetch_ahead);
      I->fetch_ahead = NULL;
  }
  if(I->key) {
    efree(I->key);
    I->key = NULL;
    I->keylen = 0;
  }
  if((value = hv_iternextsv(I->hv, &key, &keylen)) != NULL) {
      I->key = estrndup(key, keylen);
      I->keylen = keylen + 1;
      I->fetch_ahead = SvZval(value TSRMLS_CC);
  }
}

static void plsv_iter_rewind(zend_object_iterator *iter TSRMLS_DC)
{
  pTHX;
  struct plsv_iterator *I = (struct plsv_iterator*)iter->data;
  if(!I->hv) return;
  hv_iterinit(I->hv);
  plsv_iter_move_forwards(iter TSRMLS_CC);
}

static zend_object_iterator_funcs plsv_iter_funcs = {
    plsv_iter_dtor,
    plsv_iter_valid,
    plsv_iter_get_data,
    plsv_iter_get_key,
    plsv_iter_move_forwards,
    plsv_iter_rewind,
    NULL
};

static zend_object_iterator *plsv_iter_get(zend_class_entry *ce, zval *object TSRMLS_DC)
{
  struct plsv *pl;
  struct plsv_iterator *I;
  SV *sv;
  pTHX;

  pl = (struct plsv *) zend_object_store_get_object(object TSRMLS_CC);

  I = ecalloc(1, sizeof(*I));
  I->iter.funcs = &plsv_iter_funcs;
  I->iter.data = I;
  SvREFCNT_inc(pl->sv);
  sv = pl->sv;
  I->sv = pl->sv;
  if(SvROK(sv)) { sv = SvRV(sv); }
  if(SvTYPE(sv) == SVt_PVHV) {
    I->hv = (HV *) sv;
  } else {
    I->hv = NULL;
    I->fetch_ahead = NULL;
    I->key = NULL;
    SvREFCNT_dec(pl->sv);
    return &I->iter;
  }
  return &I->iter;
}

static function_entry pl_functions[] = {
  PHP_ME(perl, getinstance, NULL, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
  {NULL, NULL, NULL}
};

static function_entry plobj_functions[] = {
  PHP_ME(perl, eval, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(perl, getvariable, NULL, ZEND_ACC_PUBLIC) 
  PHP_ME(perl, setvariable, NULL, ZEND_ACC_PUBLIC) 
  PHP_ME(perl, call, NULL, ZEND_ACC_PUBLIC) 
  PHP_ME(perl, new, NULL, ZEND_ACC_PUBLIC) 
  {NULL, NULL, NULL}
};

static function_entry plsv_functions[] = {
  PHP_ME(perlsv, __tostring, NULL, ZEND_ACC_PUBLIC)
  {NULL, NULL, NULL}
};

static void 
plobj_free(void *obj TSRMLS_DC)
{
  struct plobj *pl;

  pl = (struct plobj *) obj;
  zend_hash_destroy(pl->zo.properties);
  FREE_HASHTABLE(pl->zo.properties);

  efree(obj);
}

static void
plobj_dtor(void *obj, zend_object_handle handle TSRMLS_DC)
{
}

static struct plobj *
plobj_new(zend_class_entry *ce TSRMLS_DC)
{
  struct plobj      *pl;
  zend_object_value  obj;
  dTHX;
  
  pl = ecalloc(1, sizeof(*pl));
  pl->zo.ce = ce;
  
  ALLOC_HASHTABLE(pl->zo.properties);
  zend_hash_init(pl->zo.properties, 0, NULL, ZVAL_PTR_DTOR, 0);

#ifdef USE_ITHREADS
  pl->perl = aTHX;
#endif

  return pl;
}

static void
plobj_clone(void *obj, void **cp TSRMLS_DC)
{
  struct plobj *pl;
  struct plobj *clone;

  pl = (struct plobj *) obj;
  
  clone = plobj_new(pl->zo.ce TSRMLS_CC);
  if (!clone) {
    *cp = NULL;
    return;
  }

  *cp = (void *) clone;
}

static zend_object_value 
plobj_create_object(zend_class_entry *ce TSRMLS_DC)
{
  struct plobj      *pl;
  zend_object_value  obj;
  
  pl = plobj_new(ce TSRMLS_CC);
  
  obj.handle = zend_objects_store_put(pl, plobj_dtor,
      (zend_objects_free_object_storage_t) plobj_free, plobj_clone TSRMLS_CC);
  obj.handlers = (zend_object_handlers *) &plobj_handlers;

  return obj;
}


static void 
plsv_free(void *obj TSRMLS_DC)
{
  struct plsv *pl;

  pl = (struct plsv *) obj;
  zend_hash_destroy(pl->zo.properties);
  FREE_HASHTABLE(pl->zo.properties);

  efree(obj);
}

static void
plsv_dtor(void *obj, zend_object_handle handle TSRMLS_DC)
{
  struct plsv *pl;
  dTHX;

  pl = (struct plsv *) obj;
  SvREFCNT_dec(pl->sv);
}

static struct plsv *
plsv_new(zend_class_entry *ce TSRMLS_DC)
{
  struct plsv      *pl;
  zend_object_value  obj;
  dTHX;
  
  pl = ecalloc(1, sizeof(*pl));
  pl->zo.ce = ce;
  
  ALLOC_HASHTABLE(pl->zo.properties);
  zend_hash_init(pl->zo.properties, 0, NULL, ZVAL_PTR_DTOR, 0);

#ifdef USE_ITHREADS
  pl->perl = aTHX;
#endif

  return pl;
}

static void
plsv_clone(void *obj, void **cp TSRMLS_DC)
{
  struct plsv *pl;
  struct plsv *clone;

  pl = (struct plsv *) obj;
  
  clone = plsv_new(pl->zo.ce TSRMLS_CC);
  if (!clone) {
    *cp = NULL;
    return;
  }

  *cp = (void *) clone;
}

static zend_object_value 
plsv_create_object(zend_class_entry *ce TSRMLS_DC)
{
  struct plsv      *pl;
  zend_object_value  obj;
  
  pl = plsv_new(ce TSRMLS_CC);
  
  obj.handle = zend_objects_store_put(pl, plsv_dtor,
      (zend_objects_free_object_storage_t) plsv_free, plsv_clone TSRMLS_CC);
  obj.handlers = (zend_object_handlers *) &plsv_handlers;

  return obj;
}

void plsv_wrap_sv(zval *retval, SV *sv TSRMLS_DC)
{
  dTHX;
  struct plsv *pl;

  object_init_ex(retval, plsv_ce);
  retval->refcount = 1;
  retval->is_ref = 1;

  pl = (struct plsv *) zend_object_store_get_object(retval TSRMLS_CC);
  SvREFCNT_inc(sv);
  pl->sv = sv;
}
 

PHP_MINIT_FUNCTION(sandwich)
{
  zend_class_entry pl;
  zend_class_entry plobj;
  zend_class_entry plsv;
  
  ZEND_INIT_MODULE_GLOBALS(sandwich, sw_initglobals, NULL);

  INIT_CLASS_ENTRY(pl, "Perl", pl_functions);
  pl_ce = zend_register_internal_class(&pl TSRMLS_CC);
  if (!pl_ce) {
    return FAILURE;
  }

  INIT_CLASS_ENTRY(plobj, "PerlObject", plobj_functions);
  
  plobj.create_object = plobj_create_object;
  plobj_ce = zend_register_internal_class(&plobj TSRMLS_CC);
  if (!plobj_ce) {
    return FAILURE;
  }

  memcpy(&plobj_handlers, zend_get_std_object_handlers(), 
      sizeof(plobj_handlers));

  plobj_handlers.read_dimension = sandwich_dim_read;
  plobj_handlers.write_dimension = sandwich_dim_write;
  plobj_handlers.get_method = sandwich_get_method;
  plobj_handlers.call_method = sandwich_call_method;

  INIT_CLASS_ENTRY(plsv, "PerlSV", plsv_functions);
  plsv_ce = zend_register_internal_class(&plsv TSRMLS_CC);
  plsv_ce->create_object = plsv_create_object;
  plsv_ce->get_iterator = plsv_iter_get;
  zend_class_implements(plsv_ce TSRMLS_CC, 1, zend_ce_traversable);
  if (!plsv_ce) {
    return FAILURE;
  }
  memcpy(&plsv_handlers, zend_get_std_object_handlers(), 
      sizeof(plsv_handlers));

  plsv_handlers.read_property = sv_prop_read;
  plsv_handlers.write_property = sv_prop_write;
  plsv_handlers.has_property = sv_prop_exists;
  plsv_handlers.get_method = sv_get_method;
  plsv_handlers.call_method = sv_call_method;
  plsv_handlers.get_class_name = sv_get_class_name;
  
  return SUCCESS;
}


PHP_FUNCTION(sandwich_test)
{
    RETURN_STRING("mmmm... peanut butter", 1);
}

static function_entry sandwich_functions[] = {
  PHP_FE(sandwich_test, NULL)
  {NULL, NULL, NULL}
};


zend_module_entry sandwich_module_entry = {
  STANDARD_MODULE_HEADER,
  "sandwich",
  sandwich_functions,
  PHP_MINIT(sandwich),
  NULL,
  NULL,
  NULL,
  PHP_MINFO(sandwich),
  NULL,
  STANDARD_MODULE_PROPERTIES
};

/* vim: set sw=2 ts=2 sts=2 ai bs=2 expandtab : */
