MODULE = FFI::Platypus PACKAGE = FFI::Platypus::Function::Function

ffi_pl_function *
new(class, platypus, address, abi, var_fixed_args, return_type, ...)
    const char *class
    SV *platypus
    void *address
    int abi
    int var_fixed_args
    ffi_pl_type *return_type
  PREINIT:
    ffi_pl_function *self;
    int i,n,j;
    SV* arg;
    void *buffer;
    ffi_type *ffi_return_type;
    ffi_type **ffi_argument_types;
    ffi_status ffi_status;
    ffi_abi ffi_abi;
    int extra_arguments;
    dMY_CXT;
  CODE:
    (void)class;
#ifndef FFI_PL_PROBE_VARIADIC
    if(var_fixed_args != -1)
    {
      croak("variadic functions are not supported by some combination of your libffi/compiler/platypus");
    }
#endif
#ifndef FFI_PL_PROBE_RECORDVALUE
    if(return_type->type_code == FFI_PL_TYPE_RECORD_VALUE
    || return_type->type_code == (FFI_PL_TYPE_RECORD_VALUE|FFI_PL_SHAPE_CUSTOM_PERL))
    {
      croak("returning record values is not supported by some combination of your libffi/compiler/platypus");
    }
#endif
    ffi_abi = abi == -1 ? FFI_DEFAULT_ABI : abi;

    for(i=0,extra_arguments=0; i<(items-6); i++)
    {
      ffi_pl_type *arg_type;
      arg = ST(i+6);
      if(!(sv_isobject(arg) && sv_derived_from(arg, "FFI::Platypus::Type")))
      {
        croak("non-type parameter passed in as type");
      }
      arg_type = INT2PTR(ffi_pl_type*, SvIV((SV*) SvRV(arg)));
      if(arg_type->type_code == FFI_PL_TYPE_VOID)
        croak("void not allowed as argument type");
      if((arg_type->type_code & FFI_PL_SHAPE_MASK) == FFI_PL_SHAPE_CUSTOM_PERL)
        extra_arguments += arg_type->extra[0].custom_perl.argument_count;
    }

    Newx(buffer, (sizeof(ffi_pl_function) + sizeof(ffi_pl_type*)*(items-6+extra_arguments)), char);
    self = (ffi_pl_function*)buffer;
    Newx(ffi_argument_types, items-6+extra_arguments, ffi_type*);

    {
      HV *hv;
      SV **sv;
      hv = (HV*) SvRV(platypus);
      sv = hv_fetch(hv, "api", 3, 0);
      self->platypus_api = SvIV(*sv);
    }

    self->address = address;
    self->return_type = return_type;
    ffi_return_type = ffi_pl_type_to_libffi_type(return_type);

    for(i=0,n=0; i<(items-6); i++,n++)
    {
      arg = ST(i+6);
      self->argument_types[n] = INT2PTR(ffi_pl_type*, SvIV((SV*) SvRV(arg)));
      ffi_argument_types[n] = ffi_pl_type_to_libffi_type(self->argument_types[n]);

      if((self->argument_types[n]->type_code & FFI_PL_SHAPE_MASK) == FFI_PL_SHAPE_CUSTOM_PERL
      && self->argument_types[n]->extra[0].custom_perl.argument_count > 0)
      {
        for(j=1; j-1 < self->argument_types[n]->extra[0].custom_perl.argument_count; j++)
        {
          self->argument_types[n+j] = self->argument_types[n];
          ffi_argument_types[n+j] = ffi_pl_type_to_libffi_type(self->argument_types[n]);
        }

        n += self->argument_types[n]->extra[0].custom_perl.argument_count;

      }

      if(
          (self->argument_types[n]->type_code & (FFI_PL_BASE_MASK|FFI_PL_SIZE_MASK)) == FFI_PL_TYPE_LONG_DOUBLE &&
          ((self->argument_types[n]->type_code & FFI_PL_SHAPE_MASK) == FFI_PL_SHAPE_POINTER ||
           (self->argument_types[n]->type_code & FFI_PL_SHAPE_MASK) == FFI_PL_SHAPE_ARRAY)
        )
      {
        /*
         * For historical reasons, we return longdouble pointer and array as Math::LongDouble
         * if it is installed, but we need to load it when the function is created, not on
         * the first call
         */
        if(!MY_CXT.loaded_math_longdouble)
        {
          require_pv("Math/LongDouble.pm");
          if(SvTRUE(ERRSV))
          {
            MY_CXT.loaded_math_longdouble = 2;
          }
          else
          {
            MY_CXT.loaded_math_longdouble = 1;
          }
        }
      }
    }

    if(
        (return_type->type_code & (FFI_PL_BASE_MASK|FFI_PL_SIZE_MASK)) == FFI_PL_TYPE_LONG_DOUBLE
      )
    {
      /*
       * For historical reasons, we return longdouble as Math::LongDouble if it is
       * installed, but we need to load it when the function is created, not on
       * the first call
       */
      if(!MY_CXT.loaded_math_longdouble)
      {
        require_pv("Math/LongDouble.pm");
        if(SvTRUE(ERRSV))
        {
          MY_CXT.loaded_math_longdouble = 2;
        }
        else
        {
          MY_CXT.loaded_math_longdouble = 1;
        }
      }
    }

    if(var_fixed_args == -1)
    {
      ffi_status = ffi_prep_cif(
        &self->ffi_cif,            /* ffi_cif     | */
        ffi_abi,                   /* ffi_abi     | */
        items-6+extra_arguments,   /* int         | argument count */
        ffi_return_type,           /* ffi_type *  | return type */
        ffi_argument_types         /* ffi_type ** | argument types */
      );
    }
    else
    {
#ifdef FFI_PL_PROBE_VARIADIC
      ffi_status = ffi_prep_cif_var(
        &self->ffi_cif,                           /* ffi_cif     | */
        ffi_abi,                                  /* ffi_abi     | */
        var_fixed_args,                           /* int         | fixed argument count */
        items-6+extra_arguments,                  /* int         | total argument count */
        ffi_return_type,                          /* ffi_type *  | return type */
        ffi_argument_types                        /* ffi_type ** | argument types */
      );
#endif
    }

    if(ffi_status != FFI_OK)
    {
      Safefree(self);
      Safefree(ffi_argument_types);
      if(ffi_status == FFI_BAD_TYPEDEF)
        croak("bad typedef");
      else if(ffi_status == FFI_BAD_ABI)
        croak("bad abi");
      else
        croak("unknown error with ffi_prep_cif");
    }

    self->platypus_sv = SvREFCNT_inc_simple_NN(platypus);

    RETVAL = self;
  OUTPUT:
    RETVAL

void
call(self, ...)
    ffi_pl_function *self
  PREINIT:
    int i, n, perl_arg_index;
    SV *arg;
    ffi_pl_arguments *arguments;
    void **argument_pointers;
    dMY_CXT;
  CODE:
#define EXTRA_ARGS 1
    {
#include "ffi_platypus_call.h"
    }

void
_attach(self, perl_name, path_name, proto)
    SV *self
    const char *perl_name
    ffi_pl_string path_name
    ffi_pl_string proto
  PREINIT:
    CV* cv;
    int is_ret_rv;
    ffi_pl_function *f;
  CODE:
    if(!(sv_isobject(self) && sv_derived_from(self, "FFI::Platypus::Function")))
      croak("self is not of type FFI::Platypus::Function");

    f = INT2PTR(ffi_pl_function*, SvIV((SV*) SvRV(self)));
    is_ret_rv = (f->return_type->type_code == FFI_PL_TYPE_RECORD_VALUE) ||
                (f->return_type->type_code == (FFI_PL_TYPE_RECORD_VALUE | FFI_PL_SHAPE_CUSTOM_PERL));

    if(path_name == NULL)
      path_name = "unknown";

    if(proto == NULL)
      cv = newXS(perl_name, is_ret_rv ? ffi_pl_sub_call_rv : ffi_pl_sub_call, path_name);
    else
    {
      /*
       * this ifdef is needed for Perl 5.8.8 support.
       * once we don't need to support 5.8.8 we can
       * remove this workaround (the ndef'd branch)
       */
#ifdef newXS_flags
      cv = newXSproto(perl_name, is_ret_rv ? ffi_pl_sub_call_rv : ffi_pl_sub_call, path_name, proto);
#else
      newXSproto(perl_name, is_ret_rv ? ffi_pl_sub_call_rv : ffi_pl_sub_call, path_name, proto);
      cv = get_cv(perl_name,0);
#endif
    }
    CvXSUBANY(cv).any_ptr = (void *) f;
    /*
     * No coresponding decrement !!
     * once attached, you can never free the function object, or the FFI::Platypus
     * it was created from.
     */
    SvREFCNT_inc_simple_void_NN(self);

SV*
_sub_ref(self, path_name)
    SV *self
    ffi_pl_string path_name
  PREINIT:
    CV* cv;
    SV *ref;
    int is_ret_rv;
    ffi_pl_function *f;
  CODE:
    f = INT2PTR(ffi_pl_function*, SvIV((SV*) SvRV(self)));

    is_ret_rv = (f->return_type->type_code == FFI_PL_TYPE_RECORD_VALUE) ||
                (f->return_type->type_code == (FFI_PL_TYPE_RECORD_VALUE | FFI_PL_SHAPE_CUSTOM_PERL));

    cv = newXS(NULL, is_ret_rv ? ffi_pl_sub_call_rv : ffi_pl_sub_call, path_name);
    CvXSUBANY(cv).any_ptr = (void *) INT2PTR(ffi_pl_function*, SvIV((SV*) SvRV(self)));
    /*
     * No coresponding decrement !!
     * once attached, you can never free the function object, or the FFI::Platypus
     * it was created from.
     */
    SvREFCNT_inc_simple_void_NN(self);
    RETVAL = newRV_inc((SV*)cv);
  OUTPUT:
    RETVAL


void
DESTROY(self)
    ffi_pl_function *self
  CODE:
    SvREFCNT_dec(self->platypus_sv);
    if(!PL_dirty)
    {
      Safefree(self->ffi_cif.arg_types);
      Safefree(self);
    }

MODULE = FFI::Platypus PACKAGE = FFI::Platypus::Function::Wrapper

void
_set_prototype(proto, code)
    SV *proto;
    SV *code;
  PROTOTYPE: $$
  PREINIT:
    SV *cv; /* not CV */
  CODE:
    SvGETMAGIC(code);
    cv = SvRV(code);
    sv_copypv(cv, proto);