MODULE = FFI::Platypus PACKAGE = FFI::Platypus::TypeParser

BOOT:
{
  HV *bt = get_hv("FFI::Platypus::TypeParser::basic_type", GV_ADD);
  hv_stores(bt, "void",       newSViv(FFI_PL_TYPE_VOID));
  hv_stores(bt, "sint8",      newSViv(FFI_PL_TYPE_SINT8));
  hv_stores(bt, "sint16",     newSViv(FFI_PL_TYPE_SINT16));
  hv_stores(bt, "sint32",     newSViv(FFI_PL_TYPE_SINT32));
  hv_stores(bt, "sint64",     newSViv(FFI_PL_TYPE_SINT64));
  hv_stores(bt, "uint8",      newSViv(FFI_PL_TYPE_UINT8));
  hv_stores(bt, "uint16",     newSViv(FFI_PL_TYPE_UINT16));
  hv_stores(bt, "uint32",     newSViv(FFI_PL_TYPE_UINT32));
  hv_stores(bt, "uint64",     newSViv(FFI_PL_TYPE_UINT64));

  hv_stores(bt, "float",      newSViv(FFI_PL_TYPE_FLOAT));
  hv_stores(bt, "double",     newSViv(FFI_PL_TYPE_DOUBLE));
  hv_stores(bt, "string",     newSViv(FFI_PL_TYPE_STRING));
  hv_stores(bt, "opaque",     newSViv(FFI_PL_TYPE_OPAQUE));
#ifdef FFI_PL_PROBE_LONGDOUBLE
  hv_stores(bt, "longdouble", newSViv(FFI_PL_TYPE_LONG_DOUBLE));
#endif
#ifdef FFI_PL_PROBE_COMPLEX
  hv_stores(bt, "complex_float", newSViv(FFI_PL_TYPE_COMPLEX_FLOAT));
  hv_stores(bt, "complex_double", newSViv(FFI_PL_TYPE_COMPLEX_DOUBLE));
#endif
}

ffi_pl_type *
create_type_basic(self, type_code)
    SV *self
    int type_code
  PREINIT:
    ffi_pl_type *type;
  CODE:
    (void)self;
    type = ffi_pl_type_new(0);
    type->type_code |= type_code;
    RETVAL = type;
  OUTPUT:
    RETVAL

ffi_pl_type *
create_type_record(self, is_by_value, size, record_class=NULL, meta=NULL)
    SV *self
    int is_by_value
    size_t size
    ffi_pl_string record_class
    void *meta
  PREINIT:
    ffi_pl_type *type;
  CODE:
    (void)self;
    type = ffi_pl_type_new(sizeof(ffi_pl_type_extra_record));
    type->type_code |= is_by_value ? FFI_PL_TYPE_RECORD_VALUE : FFI_PL_TYPE_RECORD;
    type->extra[0].record.size = size;
    if(record_class != NULL)
    {
      size = strlen(record_class)+1;
      type->extra[0].record.class = malloc(size);
      memcpy(type->extra[0].record.class, record_class, size);
    }
    else
    {
      type->extra[0].record.class = NULL;
    }
    type->extra[0].record.meta = meta;
    RETVAL = type;
  OUTPUT:
    RETVAL

ffi_pl_type*
create_type_object(self, type_code, class)
    SV *self
    int type_code
    ffi_pl_string class
  PREINIT:
    ffi_pl_type *type;
    size_t size;
  CODE:
    (void)self;
    type = ffi_pl_type_new(sizeof(ffi_pl_type_extra_object));
    size = strlen(class)+1;
    type->extra[0].object.class = malloc(size);
    memcpy(type->extra[0].object.class, class, size);
    type->type_code |= type_code;
    type->type_code |= FFI_PL_SHAPE_OBJECT;
    RETVAL = type;
  OUTPUT:
    RETVAL

ffi_pl_type *
create_type_string(self, rw)
    SV *self
    int rw
  PREINIT:
    ffi_pl_type *type;
  CODE:
    (void)self;
    type = ffi_pl_type_new(0);
    type->type_code = FFI_PL_TYPE_STRING;
    if(rw)
      type->sub_type = FFI_PL_TYPE_STRING_RW;
    else
      type->sub_type = FFI_PL_TYPE_STRING_RO;
    RETVAL = type;
  OUTPUT:
    RETVAL

ffi_pl_type *
create_type_array(self, type_code, size)
    SV *self
    int type_code
    size_t size
  PREINIT:
    ffi_pl_type *type;
  CODE:
    (void)self;
    type = ffi_pl_type_new(sizeof(ffi_pl_type_extra_array));
    type->type_code |= FFI_PL_SHAPE_ARRAY | type_code;
    type->extra[0].array.element_count = size;
    RETVAL = type;
  OUTPUT:
    RETVAL

ffi_pl_type*
create_type_pointer(self, type_code)
    SV *self
    int type_code
  PREINIT:
    ffi_pl_type *type;
  CODE:
    (void)self;
    type = ffi_pl_type_new(0);
    type->type_code |= FFI_PL_SHAPE_POINTER | type_code;
    RETVAL = type;
  OUTPUT:
    RETVAL

ffi_pl_type *
_create_type_custom(self, basis, perl_to_native, native_to_perl, perl_to_native_post, argument_count)
    SV *self
    ffi_pl_type* basis
    SV *perl_to_native
    SV *native_to_perl
    SV *perl_to_native_post
    int argument_count
  PREINIT:
    ffi_pl_type *type;
    int type_code;
    ffi_pl_type_extra_custom_perl *custom;
    ffi_pl_type_extra_record *record;
    size_t size;
  CODE:
    (void)self;
    type = ffi_pl_type_new(sizeof(ffi_pl_type_extra_custom_perl));
    type->type_code = FFI_PL_SHAPE_CUSTOM_PERL | basis->type_code;

    type->extra[0].record.class = NULL;
    if( (basis->type_code & FFI_PL_BASE_MASK) == (FFI_PL_TYPE_RECORD & FFI_PL_BASE_MASK)
    ||  (basis->type_code & FFI_PL_BASE_MASK) == (FFI_PL_TYPE_RECORD_VALUE & FFI_PL_BASE_MASK))
    {
      type->extra[0].record.size = basis->extra[0].record.size;
      type->extra[0].record.meta = basis->extra[0].record.meta;
      if(basis->extra[0].record.class)
      {
        size = strlen(basis->extra[0].record.class) + 1;
        type->extra[0].record.class = malloc(size);
        memcpy(type->extra[0].record.class, basis->extra[0].record.class, size);
      }
    }

    custom = &type->extra[0].custom_perl;
    custom->perl_to_native = SvOK(perl_to_native) ? SvREFCNT_inc_simple_NN(perl_to_native) : NULL;
    custom->perl_to_native_post = SvOK(perl_to_native_post) ? SvREFCNT_inc_simple_NN(perl_to_native_post) : NULL;
    custom->native_to_perl = SvOK(native_to_perl) ? SvREFCNT_inc_simple_NN(native_to_perl) : NULL;
    custom->argument_count = argument_count-1;

    RETVAL = type;
  OUTPUT:
    RETVAL


ffi_pl_type *
create_type_closure(self, abi, return_type, ...)
    SV *self
    int abi
    ffi_pl_type *return_type
  PREINIT:
    ffi_pl_type *type;
    int i;
    SV *arg;
    ffi_type *ffi_return_type;
    ffi_type **ffi_argument_types;
    ffi_status ffi_status;
  CODE:
    (void)self;
    switch(return_type->type_code)
    {
      case FFI_PL_TYPE_VOID:
        ffi_return_type = &ffi_type_void;
        break;
      case FFI_PL_TYPE_SINT8:
        ffi_return_type = &ffi_type_sint8;
        break;
      case FFI_PL_TYPE_SINT16:
        ffi_return_type = &ffi_type_sint16;
        break;
      case FFI_PL_TYPE_SINT32:
        ffi_return_type = &ffi_type_sint32;
        break;
      case FFI_PL_TYPE_SINT64:
        ffi_return_type = &ffi_type_sint64;
        break;
      case FFI_PL_TYPE_UINT8:
        ffi_return_type = &ffi_type_uint8;
        break;
      case FFI_PL_TYPE_UINT16:
        ffi_return_type = &ffi_type_uint16;
        break;
      case FFI_PL_TYPE_UINT32:
        ffi_return_type = &ffi_type_uint32;
        break;
      case FFI_PL_TYPE_UINT64:
        ffi_return_type = &ffi_type_uint64;
        break;
      case FFI_PL_TYPE_FLOAT:
        ffi_return_type = &ffi_type_float;
        break;
      case FFI_PL_TYPE_DOUBLE:
        ffi_return_type = &ffi_type_double;
        break;
      case FFI_PL_TYPE_OPAQUE:
        ffi_return_type = &ffi_type_pointer;
        break;
      case FFI_PL_TYPE_RECORD_VALUE:
        if(return_type->extra[0].record.meta == NULL)
          croak("Only native types are supported as closure return types (%d)", return_type->type_code);
        if(!return_type->extra[0].record.meta->can_return_from_closure)
          croak("Record return type contains types that cannot be returned from a closure");
        ffi_return_type = &return_type->extra[0].record.meta->ffi_type;
        break;
      default:
        croak("Only native types are supported as closure return types (%d)", return_type->type_code);
        break;
    }

    Newx(ffi_argument_types, items-3, ffi_type*);
    type = ffi_pl_type_new(sizeof(ffi_pl_type_extra_closure) + sizeof(ffi_pl_type)*(items-3));
    type->type_code = FFI_PL_TYPE_CLOSURE;

    type->extra[0].closure.return_type = return_type;
    type->extra[0].closure.flags = 0;

    for(i=0; i<(items-3); i++)
    {
      arg = ST(3+i);
      type->extra[0].closure.argument_types[i] = INT2PTR(ffi_pl_type*, SvIV((SV*)SvRV(arg)));
      switch(type->extra[0].closure.argument_types[i]->type_code)
      {
        case FFI_PL_TYPE_VOID:
          ffi_argument_types[i] = &ffi_type_void;
          break;
        case FFI_PL_TYPE_SINT8:
          ffi_argument_types[i] = &ffi_type_sint8;
          break;
        case FFI_PL_TYPE_SINT16:
          ffi_argument_types[i] = &ffi_type_sint16;
          break;
        case FFI_PL_TYPE_SINT32:
          ffi_argument_types[i] = &ffi_type_sint32;
          break;
        case FFI_PL_TYPE_SINT64:
          ffi_argument_types[i] = &ffi_type_sint64;
          break;
        case FFI_PL_TYPE_UINT8:
          ffi_argument_types[i] = &ffi_type_uint8;
          break;
        case FFI_PL_TYPE_UINT16:
          ffi_argument_types[i] = &ffi_type_uint16;
          break;
        case FFI_PL_TYPE_UINT32:
          ffi_argument_types[i] = &ffi_type_uint32;
          break;
        case FFI_PL_TYPE_UINT64:
          ffi_argument_types[i] = &ffi_type_uint64;
          break;
        case FFI_PL_TYPE_FLOAT:
          ffi_argument_types[i] = &ffi_type_float;
          break;
        case FFI_PL_TYPE_DOUBLE:
          ffi_argument_types[i] = &ffi_type_double;
          break;
        case FFI_PL_TYPE_OPAQUE:
        case FFI_PL_TYPE_STRING:
        case FFI_PL_TYPE_RECORD:
          ffi_argument_types[i] = &ffi_type_pointer;
          break;
        case FFI_PL_TYPE_RECORD_VALUE:
          if(type->extra[0].closure.argument_types[i]->extra[0].record.meta == NULL)
          {
            Safefree(ffi_argument_types);
            croak("Only native types and strings are supported as closure argument types (%d)", type->extra[0].closure.argument_types[i]->type_code);
          }
          ffi_argument_types[i] = &type->extra[0].closure.argument_types[i]->extra[0].record.meta->ffi_type;
          break;
        default:
          Safefree(ffi_argument_types);
          croak("Only native types and strings are supported as closure argument types (%d)", type->extra[0].closure.argument_types[i]->type_code);
          break;
      }
    }

    ffi_status = ffi_prep_cif(
      &type->extra[0].closure.ffi_cif,
      abi == -1 ? FFI_DEFAULT_ABI : abi,
      items-3,
      ffi_return_type,
      ffi_argument_types
    );

    if(ffi_status != FFI_OK)
    {
      Safefree(type);
      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");
    }

    if( items-3 == 0 )
    {
      type->extra[0].closure.flags |= G_NOARGS;
    }

    if(type->extra[0].closure.return_type->type_code == FFI_PL_TYPE_VOID)
    {
      type->extra[0].closure.flags |= G_DISCARD | G_VOID;
    }
    else
    {
      type->extra[0].closure.flags |= G_SCALAR;
    }

    RETVAL = type;

  OUTPUT:
    RETVAL