/* Copyright (c) 1996 by Lele Gaifax.  All Rights Reserved
 *
 * This software may be used and distributed freely for any purpose
 * provided that this notice is included unchanged on any and all
 * copies. The author does not warrant or guarantee this software in
 * any way.
 *
 * This file is part of the PyObjC package.
 *
 * $RCSfile: objc_support.m,v $
 * $Revision: 1.8 $
 * $Date: 1996/11/15 02:33:46 $
 *
 * Created Tue Sep 10 14:16:02 1996.
 */

#ifdef NeXT
#include <bsd/libc.h>
#endif

#include "ObjC.h"
#include "objc_support.h"
#include "myctype.h"

#ifndef GNU_RUNTIME

#ifndef MAX
#define MAX(x,y) ({ unsigned int __x=(x), __y=(y); (__x > __y ? __x : __y); })
#define MIN(x,y) ({ unsigned int __x=(x), __y=(y); (__x < __y ? __x : __y); })
#endif

inline static const int
ROUND(int v, int a)
{
  return a * ((v+a-1)/a);
}

const char * 
objc_skip_typespec (const char *type)
{
  type = objc_skip_type_qualifiers (type);

  switch (*type)
    {
      /* The following are one character type codes */
    case _C_ID:

    case _C_CLASS:
    case _C_SEL:
    case _C_CHR:
    case _C_UCHR:
    case _C_CHARPTR:
    case _C_SHT:
    case _C_USHT:
    case _C_INT:
    case _C_UINT:
    case _C_LNG:
    case _C_ULNG:
    case _C_FLT:
    case _C_DBL:
    case _C_VOID:
      return ++type;
      break;

    case _C_ARY_B:
      /* skip digits, typespec and closing ']' */
    
      while (isdigit (*++type));
      type = objc_skip_typespec (type);
      //assert (*type == _C_ARY_E);
      return ++type;
      break;
      
    case _C_STRUCT_B:
      /* skip name, and elements until closing '}'  */
    
      while (*type != _C_STRUCT_E && *type++ != '=');
      while (*type != _C_STRUCT_E)
	type = objc_skip_typespec (type);
      return ++type;

    case _C_UNION_B:
      /* skip name, and elements until closing ')'  */
      type++;
      while (*type != _C_UNION_E) { type = objc_skip_typespec (type); }
      return ++type;
      
    case _C_PTR:
      /* Just skip the following typespec */
      return objc_skip_typespec (++type);
    
    default:
      fprintf (stderr, "objc_skip_typespec: Unhandled type '%c'\n", *type);
      abort();
    }
}

/*
  Return the alignment of an object specified by type 
*/

static int
objc_alignof_type (const char *type)
{
  switch (*type)
    {
    case _C_ID:
      return __alignof__ (id);
      break;

    case _C_CLASS:
      return __alignof__ (Class);
      break;
    
    case _C_SEL:
      return __alignof__ (SEL);
      break;

    case _C_CHR:
      return __alignof__ (char);
      break;
    
    case _C_UCHR:
      return __alignof__ (unsigned char);
      break;

    case _C_SHT:
      return __alignof__ (short);
      break;

    case _C_USHT:
      return __alignof__ (unsigned short);
      break;

    case _C_INT:
      return __alignof__ (int);
      break;

    case _C_UINT:
      return __alignof__ (unsigned int);
      break;

    case _C_LNG:
      return __alignof__ (long);
      break;

    case _C_ULNG:
      return __alignof__ (unsigned long);
      break;

    case _C_FLT:
      return __alignof__ (float);
      break;

    case _C_DBL:
      return __alignof__ (double);
      break;

    case _C_CHARPTR:
      return __alignof__ (char *);
      break;

    case _C_PTR:
      return __alignof__ (void *);
      break;
      
    case _C_ARY_B:
      while (isdigit(*++type)) /* do nothing */;
      return objc_alignof_type (type);
      
    case _C_STRUCT_B:
      {
	struct { int x; double y; } fooalign;
	while(*type != _C_STRUCT_E && *type++ != '=') /* do nothing */;
	if (*type != _C_STRUCT_E)
	  return MAX (objc_alignof_type (type), __alignof__ (fooalign));
	else
	  return __alignof__ (fooalign);
      }

    case _C_UNION_B:
      {
	int maxalign = 0;
	type++;
	while (*type != _C_UNION_E)
	  {
	    maxalign = MAX (maxalign, objc_alignof_type (type));
	    type = objc_skip_typespec (type);
	  }
	return maxalign;
      }
    
    default:
      fprintf (stderr, "objc_align_type: Unhandled type '%c'\n", *type);
      abort();
    }
}

/*
  The aligned size if the size rounded up to the nearest alignment.
*/

static int
objc_aligned_size (const char *type)
{
  static int objc_sizeof_type (const char *type);
  
  int size = objc_sizeof_type (type);
  int align = objc_alignof_type (type);
  return ROUND (size, align);
}

/*
  return the size of an object specified by type 
*/

int
objc_sizeof_type (const char *type)
{
  switch (*type)
    {
    case _C_VOID:
      return 0;
      
    case _C_ID:
      return sizeof(id);
      break;
      
    case _C_CLASS:
      return sizeof(Class);
      break;
      
    case _C_SEL:
      return sizeof(SEL);
      break;
      
    case _C_CHR:
      return sizeof(char);
      break;
      
    case _C_UCHR:
      return sizeof(unsigned char);
      break;
      
    case _C_SHT:
      return sizeof(short);
      break;
      
    case _C_USHT:
      return sizeof(unsigned short);
      break;
      
    case _C_INT:
      return sizeof(int);
      break;
      
    case _C_UINT:
      return sizeof(unsigned int);
      break;
      
    case _C_LNG:
      return sizeof(long);
      break;
      
    case _C_ULNG:
      return sizeof(unsigned long);
      break;
      
    case _C_FLT:
      return sizeof(float);
      break;
      
    case _C_DBL:
      return sizeof(double);
      break;
      
    case _C_PTR:
    case _C_CHARPTR:
      return sizeof(char*);
      break;
      
    case _C_ARY_B:
      {
	int len = atoi(type+1);
	while (isdigit(*++type));
	return len*objc_aligned_size (type);
      }
    break; 
    
    case _C_STRUCT_B:
      {
	int acc_size = 0;
	int align;
	while (*type != _C_STRUCT_E && *type++ != '='); /* skip "<name>=" */
	while (*type != _C_STRUCT_E)
	  {
	    align = objc_alignof_type (type);       /* padd to alignment */
	    acc_size = ROUND (acc_size, align);
	    acc_size += objc_sizeof_type (type);   /* add component size */
	    type = objc_skip_typespec (type);	         /* skip component */
	  }
	return acc_size;
      }
    
    case _C_UNION_B:
      {
	int max_size = 0;
	type++;
	while (*type != _C_UNION_E)
	  {
	    max_size = MAX (max_size, objc_sizeof_type (type));
	    type = objc_skip_typespec (type);
	  }
	return max_size;
      }
    
    default:
      fprintf (stderr, "objc_sizeof_type: Unhandled type '%c'\n", *type);
      abort();
    }
}

#endif

/*#F Returns a tuple of objects representing the content of a C array
  of type @var{type} pointed by @var{datum}. */
static PyObject *
pythonify_c_array (const char *type, void *datum, ObjCMethod *meth)
{
  PyObject *ret;
  unsigned int nitems, offset, itemidx, sizeofitem;
  
  nitems = atoi (type+1);
  while (isdigit (*++type));
  sizeofitem = objc_sizeof_type (type);

  ret = PyTuple_New (nitems);
  if (!ret)
    return NULL;

  for (offset=itemidx=0; itemidx < nitems; itemidx++)
    {
      PyObject *pyitem = NULL;

      pyitem = pythonify_c_value (type, datum+offset, meth);

      if (pyitem)
	PyTuple_SET_ITEM (ret, itemidx, pyitem);
      else
	{
	  Py_DECREF(ret);
	  return NULL;
	}

      offset += sizeofitem;
    }
  
  return ret;
}

/*#F Returns a tuple of objects representing the content of a C structure
  of type @var{type} pointed by @var{datum}. */
static PyObject *
pythonify_c_struct (const char *type, void *datum, ObjCMethod *meth)
{
  PyObject *ret;
  unsigned int nitems, offset, itemidx;
  const char *item;

  while (*type != _C_STRUCT_E && *type++ != '='); /* skip "<name>=" */
  for (item=type, nitems=0; *item != _C_STRUCT_E; item = objc_skip_typespec (item))
    nitems++;

  ret = PyTuple_New (nitems);
  if (!ret)
    return NULL;

  for (item=type, offset=itemidx=0; *item != _C_STRUCT_E; item = objc_skip_typespec (item))
    {
      PyObject *pyitem;

      pyitem = pythonify_c_value (item, datum+offset, meth);

      if (pyitem)
	{
	  PyTuple_SET_ITEM (ret, itemidx, pyitem);
	}
      else
	{
	  Py_DECREF(ret);
	  return NULL;
	}

      itemidx++;
      offset += objc_sizeof_type (item);
    }
  
  return ret;
}

/*#F Extracts the elements from the tuple @var{arg} and fills a C array
  of type @var{type} pointed by @var{datum}. Returns an error message, or
  NULL on success. */
static const char *
depythonify_c_array (const char *type, PyObject *arg, void *datum)
{
  unsigned int nitems, offset, itemidx, sizeofitem;

  nitems = atoi (type+1);
  while (isdigit (*++type));
  sizeofitem = objc_sizeof_type (type);

  if (nitems != PyTuple_Size (arg))
    {
#define ERRMSG "a tuple of %d items, got one of %d"
      static char errmsg[sizeof ERRMSG + 4];

      sprintf (errmsg, ERRMSG, nitems, PyTuple_Size (arg));
      return errmsg;
#undef ERRMSG
    }

  for (offset=itemidx=0; itemidx < nitems; itemidx++)
    {
      PyObject *pyarg = PyTuple_GetItem (arg, itemidx);
      const char *error;

      error = depythonify_c_value (type, pyarg, datum+offset);
      if (error)
	return error;
      
      offset += sizeofitem;
    }

  return NULL;
}

/*#F Extracts the elements from the tuple @var{arg} and fills a C structure
  of type @var{type} pointed by @var{datum}. Returns an error message, or
  NULL on success. */
static const char *
depythonify_c_struct (const char *types, PyObject *arg, void *datum)
{
  unsigned int nitems, offset, itemidx;
  const char *type;

  while (*types != _C_STRUCT_E && *types++ != '='); /* skip "<name>=" */
  for (type=types, nitems=0; *type != _C_STRUCT_E; type = objc_skip_typespec (type))
    nitems++;

  if (nitems != PyTuple_Size (arg))
    {
#define ERRMSG "a tuple of %d items, got one of %d"
      static char errmsg[sizeof ERRMSG + 4];

      sprintf (errmsg, ERRMSG, nitems, PyTuple_Size (arg));
      return errmsg;
#undef ERRMSG
    }

  for (type=types, offset=itemidx=0; *type != _C_STRUCT_E; type = objc_skip_typespec (type))
    {
      PyObject *argument = PyTuple_GetItem (arg, itemidx);
      const char *error;

      error = depythonify_c_value (type, argument, datum+offset);
      if (error)
	return error;
      
      itemidx++;
      offset += objc_sizeof_type (type);
    }
  return NULL;
}

PyObject *
pythonify_c_value (const char *type, void *datum, ObjCMethod *meth)
{
  PyObject *retobject = NULL;

  type = objc_skip_type_qualifiers (type);

  switch (*type)
    {
    case _C_CHR:
    case _C_UCHR:
      retobject = (PyObject *) PyInt_FromLong ((int) (*(char **) datum));
      break;

    case _C_CHARPTR:
      {
	char *cp = *(char **) datum;

	if (! cp)
	  cp = "(null pointer)"; /* XXX */
	retobject = (PyObject *) PyString_FromString (cp);
	break;
      }

    case _C_INT:
    case _C_UINT:
      retobject = (PyObject *) PyInt_FromLong (*(int *) datum);
      break;

    case _C_SHT:
    case _C_USHT:
      retobject = (PyObject *) PyInt_FromLong (*(short *) datum);
      break;

    case _C_LNG:
    case _C_ULNG:
      retobject = (PyObject *) PyInt_FromLong (*(long *) datum);
      break;

    case _C_FLT:
      retobject = (PyObject *) PyFloat_FromDouble (*(float *) datum);
      break;

    case _C_DBL:
      retobject = (PyObject *) PyFloat_FromDouble (*(double *) datum);
      break;
      
    case _C_ID:
      {
	id obj = *(id *) datum;

	if (obj == nil)
	  {
	    /* XXX What should I do in this case? I used to treat it
	       as an error, but for example `-superclass' on Object
	       returns nil... */
	    // retobject = NULL;
	    retobject = Py_None;
	    Py_INCREF (retobject);
	  }
	else if (meth && obj == meth->obj->oc_object)
	  {
	    retobject = (PyObject *) meth->obj;
	    Py_INCREF(retobject);
	  }
	else
	  {
	    if ([obj isKindOf:[OC_PythonObject class]])
	      {
		retobject = [obj object];
		Py_INCREF(retobject);
	      }
	    else if ([obj isKindOf:[OC_Stream class]])
	      retobject = (PyObject *) ObjCStream_new ((OC_Stream *) obj);
	    else
	      retobject = (PyObject *) ObjCObject_new (obj);
	  }
	break;
      }

    case _C_SEL:
      retobject = (PyObject *) ObjCMethod_new_with_selector (meth->obj,
							     *(SEL *) datum);
      break;

    case _C_CLASS:
      {
	Class c = *(Class *) datum;

	if (c == Nil)
	  {
	    retobject = Py_None;
	    Py_INCREF (retobject);
	  }
	else
	  retobject = (PyObject *) ObjCObject_new (c);
	break;
      }

    case _C_PTR:
#ifndef WITH_FOUNDATION
#define NXSTREAM_TYPE @encode (NXStream *)
#define ALT_NXSTREAM_TYPE "^{?=I**iilii^{stream_functions}^v}"
      if (!strncmp (type, NXSTREAM_TYPE, sizeof (NXSTREAM_TYPE)-1) ||
	  !strncmp (type, ALT_NXSTREAM_TYPE, sizeof (ALT_NXSTREAM_TYPE)-1))
	retobject = (PyObject *) ObjCStream_new_from_stream (*(NXStream **) datum);
      else
#undef NXSTREAM_TYPE
#undef ALT_NXSTREAM_TYPE
#endif /* WITH_FOUNDATION */
	retobject = (PyObject *) ObjCPointer_new (*(void **) datum, type+1);
      break;
      
    case _C_UNION_B:
      {
	unsigned int size = objc_sizeof_type (type);
	char *buffer = alloca (size);

	memcpy (buffer, (void *) datum, size);
	retobject = PyString_FromStringAndSize (buffer, size);
	break;
      }
    
    case _C_STRUCT_B:
      retobject = pythonify_c_struct (type, datum, meth);
      break;

    case _C_ARY_B:
      retobject = pythonify_c_array (type, datum, meth);
      break;

    case _C_VOID:
      retobject = Py_None;
      Py_INCREF (retobject);
      break;

    default:
      {
#ifdef GNU_RUNTIME
	[NSException raise:NSInternalInconsistencyException 
		     format:@"unhandled value type (%c)", *type];
#else
#define ERRMSG "pythonify_c_value: unhandled value type (%c)"
	char msg[sizeof ERRMSG];
	sprintf (msg, ERRMSG, *type);
	PyErr_SetString (ObjC_Error, msg);
#undef ERRMSG
#endif
	break;
      }
    }

  return retobject;
}

#ifdef WITH_THREAD
#warning Does not support multiple threads yet.
#endif

/* This is used as an array of pointers: both the memory for the array
   and that for each array's slot is dinamically allocated with malloc().
   We use it when we have to give an ObjC method a pointer to some datum:
   since from the Python point of view we always work on values, not pointers,
   when an ObjC does actually want a pointer, we allocate memory in the
   next free slot (eventually growing the array), depythonify the argument
   in that space and feed it to the method.
   On the next method call this space will be freed. */

void **arguments_arena;

/* Count of used slots in arguments_arena. */
static unsigned int arguments_arena_current_count;

/* Total number of slots in arguments_arena. */
static unsigned int arguments_arena_slots;

static inline void
xfree (void *ptr)
{
  if (ptr)
    free (ptr);
}

static inline void *
xmalloc (unsigned int size)
{
  void *ptr = malloc (size);

  if (ptr == 0)
    {
#define ERRMSG "xmalloc: memory exausted"
      write (fileno (stderr), ERRMSG, sizeof (ERRMSG)-1);
      exit (1);
#undef ERRMSG
    }
  return ptr;
}

static inline void *
xrealloc (void *ptr, unsigned int size)
{
  if (ptr == 0)
    return xmalloc (size);
  ptr = realloc (ptr, size);
  if (ptr == 0)
    {
#define ERRMSG "xrealloc: memory exausted"
      write (fileno (stderr), ERRMSG, sizeof (ERRMSG)-1);
      exit (1);
#undef ERRMSG
    }
  return ptr;
}

/* If the arena is not empty, free it. Then initialize counters. */
static inline void
initialize_arguments_arena()
{
  if (arguments_arena)
    {
      while (arguments_arena_current_count--)
	xfree (arguments_arena[arguments_arena_current_count]);

      xfree (arguments_arena);
    }
  arguments_arena_current_count = arguments_arena_slots = 0;
  arguments_arena = NULL;
}

/* Calculates the space needed to keep a value of type TYPE, then if
   the arena isn't big enough reallocates a bigger array for it;
   allocates the needed amount of memory for the value recording
   its address in the next free slot and returns that pointer. */ 
static void *
get_space_on_arena_for_type (const char *type)
{
  unsigned int needed = objc_sizeof_type (type);
  
  if (arguments_arena_current_count == arguments_arena_slots)
    {
      if (arguments_arena_slots)
	arguments_arena_slots *= 2;
      else
	arguments_arena_slots = 2;
      arguments_arena = xrealloc (arguments_arena,
				  arguments_arena_slots * sizeof (arguments_arena[0]));
    }
  return arguments_arena[arguments_arena_current_count++] = xmalloc (needed);
}

const char *
depythonify_c_value (const char *type, PyObject *argument, void *datum)
{
  const char *error = NULL;

  type = objc_skip_type_qualifiers (type);
  
  switch (*type)
    {
    case _C_CHR:
      if (! PyString_Check (argument) &&
	  ! PyInt_Check (argument))
	error = "a string or an integer";
      else
	if (PyInt_Check (argument))
	  *(char *) datum = PyInt_AsLong (argument);
	else
	  *(char *) datum = PyString_AsString (argument)[0];
      break;

    case _C_UCHR:
      if (! PyString_Check (argument) &&
	  ! PyInt_Check (argument))
	error = "a string or an integer";
      else
	if (PyInt_Check (argument))
	  *(unsigned char *) datum = PyInt_AsLong (argument);
	else
	  *(unsigned char *) datum = PyString_AsString (argument)[0];
      break;

    case _C_CHARPTR:
      if (! PyString_Check (argument) && argument != Py_None)
	error = "a string or None";
      else
	if (argument == Py_None)
	  *(char **) datum = NULL;
	else
	  *(char **) datum = PyString_AsString (argument);
      break;

    case _C_INT:
      if (! PyInt_Check (argument))
	error = "an integer";
      else
	*(int *) datum = PyInt_AsLong (argument);
      break;

    case _C_SHT:
      if (! PyInt_Check (argument))
	error = "an integer";
      else
	*(short *) datum = PyInt_AsLong (argument);
      break;

    case _C_UINT:
      if (! PyInt_Check (argument))
	error = "an integer";
      else
	*(unsigned int *) datum = PyInt_AsLong (argument);
      break;

    case _C_USHT:
      if (! PyInt_Check (argument))
	error = "an integer";
      else
	*(unsigned short *) datum = PyInt_AsLong (argument);
      break;

    case _C_LNG:
      if (! PyInt_Check (argument))
	error = "an integer";
      else
	*(long *) datum = PyInt_AsLong (argument);
      break;

    case _C_ULNG:
      if (! PyInt_Check (argument))
	error = "an integer";
      else
	*(unsigned long *) datum = PyInt_AsLong (argument);
      break;

    case _C_ID:
      if (argument == Py_None)
	*(id *) datum = nil;
      else if (ObjCObject_Check (argument))
	*(id *) datum = ((ObjCObject *) argument)->oc_object;
      else if (ObjCStream_Check (argument))
	*(id *) datum = ((ObjCStream *) argument)->stream;
      else
	*(id *) datum = [OC_PythonObject newWithObject:argument];
      break;

    case _C_CLASS:
      if (! (ObjCObject_Check (argument) &&
	     ISCLASS(((ObjCObject *) argument)->oc_object))
	  && argument != Py_None)
	error = "a ObjC class or None";
      else
	*(Class *) datum = (argument == Py_None
			    ? nil
			    : ((ObjCObject *)(argument))->oc_object);
      break;

    case _C_SEL:
      if (! ObjCMethod_Check (argument) && ! PyString_Check (argument))
	error = "a ObjC method or a string";
      else
	if (ObjCMethod_Check (argument))
	  *(SEL *) datum = ((ObjCMethod *) argument)->sel;
	else
	  {
	    char *selname = PyString_AsString (argument);
	    SEL sel = SELUID (selname);

	    /* XXX this is questionable */
	    if (sel)
	      *(SEL *) datum = sel;
	    else
	      *(char **) datum = selname;
	  }
      break;

    case _C_PTR:
#ifndef WITH_FOUNDATION
#define NXSTREAM_TYPE @encode (NXStream *)
#define ALT_NXSTREAM_TYPE "^{?=I**iilii^{stream_functions}^v}"
      if ((!strncmp (type, NXSTREAM_TYPE, sizeof (NXSTREAM_TYPE)-1) ||
	   !strncmp (type, ALT_NXSTREAM_TYPE, sizeof (ALT_NXSTREAM_TYPE)-1)) &&
	  ObjCStream_Check (argument))
	*(NXStream **) datum = ObjCStream_stream ((ObjCStream *) argument);
      else
#undef NXSTREAM_TYPE
#undef ALT_NXSTREAM_TYPE
#endif /* WITH_FOUNDATION */
      if (PyString_Check (argument))
	{
	  unsigned int expected_size = objc_sizeof_type (++type);
	  
	  if (expected_size != PyString_Size (argument))
	    {
#define ERRMSG "a string of size %d instead of %d"
	      static char errmsg[sizeof (ERRMSG)+6+6];

	      sprintf (errmsg, ERRMSG, expected_size, PyString_Size (argument));
	      error = errmsg;
#undef ERRMSG
	    }
	  else
	    *(void **) datum = PyString_AS_STRING ((PyStringObject *) argument);
	}
      else if (ObjCPointer_Check (argument))
	*(void **) datum = ((ObjCPointer *) argument)->ptr;
      else	       
	{
	  *(void **) datum = get_space_on_arena_for_type (++type);
	  error = depythonify_c_value (type, argument, *(void **) datum);
	}
      break;

    case _C_FLT:
      if (PyFloat_Check (argument))
	*(float *) datum = (float) PyFloat_AsDouble (argument);
      else if (PyInt_Check (argument))
	*(float *) datum = (float) PyInt_AsLong (argument);
      else
	error = "a float or an integer";
      break;

    case _C_DBL:
      if (PyFloat_Check (argument))
	*(double *) datum = PyFloat_AsDouble (argument);
      else if (PyInt_Check (argument))
	*(double *) datum = (double) PyInt_AsLong (argument);
      else
	error = "a float or an integer";
      break;

    case _C_UNION_B:
      if (PyString_Check (argument))
	{
	  unsigned int expected_size = objc_sizeof_type (type);
	  
	  if (expected_size != PyString_Size (argument))
	    {
#define ERRMSG "a string of size %d instead of %d"
	      static char errmsg[sizeof (ERRMSG)+6+6];

	      sprintf (errmsg, ERRMSG, expected_size, PyString_Size (argument));
	      error = errmsg;
#undef ERRMSG
	    }
	  else
	    memcpy ((void *) datum, PyString_AS_STRING ((PyStringObject *) argument), expected_size);
	}
      else
	error = "a string";
      break;

    case _C_STRUCT_B:
      if (! PyTuple_Check (argument))
	error = "a tuple";
      else
	error = depythonify_c_struct (type, argument, datum);
      break;

    case _C_ARY_B:
      if (! PyTuple_Check (argument))
	error = "a tuple";
      else
	error = depythonify_c_array (type, argument, datum);
      break;

    default:
      {
#define ERRMSG "unhandled typespec %c"
	static char msg[sizeof ERRMSG];

	sprintf (msg, ERRMSG, *type);
	error = msg;
	break;
#undef ERRMSG
      }
    }

  return error;
}

PyObject *
execute_and_pythonify_objc_method (ObjCMethod *meth, PyObject *args)
{
  PyObject *retobject = NULL;
  METHOD methinfo;
  unsigned int argcount;

  initialize_arguments_arena();
  
  if (ISCLASS(meth->obj->oc_object))
    methinfo = CLASSMETH(meth->obj->oc_object, meth->sel);
  else
    methinfo = INSTMETH(meth->obj->oc_object, meth->sel);

  if (!methinfo)
    {
#define ERRORMSG " does not recognize "
      const char *whoiam = NAMEOF(meth->obj->oc_object);
      const char *selname = SELNAME(meth->sel);
      char buffer[strlen (whoiam) + sizeof ERRORMSG + 1 + strlen (selname) + 1];
      
      strcpy (buffer, whoiam);
      strcat (buffer, ERRORMSG);
      strcat (buffer, (ISCLASS(meth->obj->oc_object) ? "+" : "-"));
      strcat (buffer, selname);
      PyErr_SetString (ObjC_Error, buffer);
#undef ERRORMSG
      return NULL;
    }
      
  argcount = METHNARGS(methinfo);
  if (PyTuple_Size (args) != argcount-2)
    {
      char error[100];

      sprintf (error, "wrong number of arguments, expected to be %d", argcount-2);
      PyErr_SetString (ObjC_Error, error);
      return NULL;
    }
  else
    {

#ifdef GNU_RUNTIME
  
      void decoder (int argn, void *datum, const char *type)
	{
	  if (argn == 0)
	    *(id *)datum = meth->obj->oc_object;
	  else if (argn == 1)
	    *(SEL *)datum = meth->sel;
	  else
	    {
	      PyObject *argument;
	      const char *error = NULL;
	      
	      argument = PyTuple_GET_ITEM (args, argn-2);
	      error = depythonify_c_value (type, argument, datum);
	      if (error)
		[NSException raise:NSInvalidArgumentException
			     format:@"expected %s for argument %d", error, argn-2];
	    }
	}

      void encoder (int argn, void *datum, const char *type, int flags)
	{
	  if (argn == -1)	// return value
	    retobject = pythonify_c_value (type, datum, meth);

	  // XXX What if argn >= 0? 
	}

      NS_DURING
	
	mframe_do_call (methinfo->method_types, decoder, encoder);
      
      NS_HANDLER
	
	PyErr_SetString (ObjC_Error, (char *) [[localException reason] cString]);
      
      NS_ENDHANDLER
      
#else /* NeXT' runtime */
	
      unsigned int argsize;
      char *argbuffer;
      const char *type;
      int offset, i;
      const char *error = NULL;
      id receiver;
      
      argsize = METHSARGS(methinfo);
      argbuffer = alloca (argsize);

      receiver = meth->obj->oc_object;

      method_getArgumentInfo (methinfo, 0, &type, &offset);
      marg_setValue (argbuffer, offset, id, receiver);

      method_getArgumentInfo (methinfo, 1, &type, &offset);
      marg_setValue (argbuffer, offset, SEL, meth->sel);
  
      for (i = 2; i < argcount; i++)
	{
	  PyObject *argument;

	  method_getArgumentInfo (methinfo, i, &type, &offset);

	  argument = PyTuple_GET_ITEM (args, i-2);

	  error = depythonify_c_value (type, argument, argbuffer+offset);
	    
	  if (error)
	    break;
	}

      if (error)
	{
	  const char *typeend = objc_skip_typespec (type);
#define ERRMSG "expected %s for argument %d: its type is `%.*s'"
	  char errmsg[sizeof ERRMSG + strlen (error) + typeend-type];

	  sprintf (errmsg, ERRMSG, error, i-2, (int) (typeend-type), type);
	  PyErr_SetString (ObjC_Error, errmsg);
#undef ERRMSG
	  return NULL;
	}

      PyErr_Clear();
      type = objc_skip_type_qualifiers (methinfo->method_types);
      switch (*type)
	{
	case _C_DBL:
	  {
	    double ddatum = ((double (*)()) objc_msgSendv) (receiver, meth->sel, argsize, argbuffer);

	    if (!PyErr_Occurred())
	      retobject = pythonify_c_value (methinfo->method_types, &ddatum, meth);
	    break;
	  }

	case _C_FLT:
	  {
	    float fdatum = ((float (*)()) objc_msgSendv) (receiver, meth->sel, argsize, argbuffer);

	    if (!PyErr_Occurred())
	      retobject = pythonify_c_value (methinfo->method_types, &fdatum, meth);
	    break;
	  }

	default:
	  {
	    unsigned int ret_size = objc_sizeof_type (type);
	    
	    if (ret_size <= sizeof (void *))
	      {
		void *datum = objc_msgSendv (receiver, meth->sel, argsize, argbuffer);
		
		if (!PyErr_Occurred())
		  retobject = pythonify_c_value (methinfo->method_types, &datum, meth);
	      }
	    else
	      {
#if 0
/* this generates a
	 ``cc: Internal compiler error: program cc1obj got fatal signal 6''*/

		struct dummy
		  {
		    void *p;
		    typeof (char [ret_size - sizeof (void *)]) items;
		  } ldatum;

		ldatum = ((typeof (ldatum) (*)()) objc_msgSendv) (receiver, meth->sel, argsize, argbuffer);

		if (!PyErr_Occurred())
		  retobject = pythonify_c_value (methinfo->method_types, ldatum, meth);
#else		  
		  PyErr_SetString (ObjC_Error, "Not able to handle big structures");
#endif
	      }
	    break;
	  }
	}
#endif
    }
  return retobject;
}

/*
** Local Variables:
** change-log-default-name:"../ChangeLog.PyObjC"
** End:
*/
