/* 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.m,v $
 * $Revision: 1.9 $
 * $Date: 1996/11/15 02:33:42 $
 *
 * Created Tue Jun 18 12:28:42 1996.
 */

/* Long time ago, Jon M. Kutemeier wrote a module to interface Python
   with Objective-C and for a while Guido van Rossum "revamped and
   maintained" it. Sadly, on Mon, 22 Jul 1996 Guido announced the he
   will drop that module from the Python distribution. Since I'm a
   NeXTSTEP fan(atic), I considered that a bad news: for months I dreamed
   about working on its beautiful UI from Python; more than that, the
   two languages are so similar that Objective-C may be viewed as the
   compiled cousin of Guido's creation, at least while we all wait for
   a Python compiler...

   So I rewrote it: it provides an easier way of sending messages to
   Objective-C objects, and (will) support(s) the GNU Objective-C runtime.

   Now I just hope Guido will reconsider his decision... */

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

#ifndef STRINGIFY		// GNUstep defines it.
#define STRINGIFY(a) _STRINGIFY(a)
#define _STRINGIFY(a) #a
#endif

static char ObjC_doc[] = "\
Objective-C Interface module Version " STRINGIFY(PyObjC_VERSION) ".\n\
Copyright (C) 1996 - Lele Gaifax <lele@nautilus.eclipse.it>\n\n\
This module implements an Objective-C interface layer, allowing the use\n\
of Objective-C functionalities from Python scripts.\n\n\
You can access an Objective-C class with the `.lookup_class' method,\n\
that returns a Python wrapper around it. Then you can send Objective-C\n\
message to it, for example to get an instance of the class:\n\
\tSyntax A:\t\t\t\tSyntax B:\n\
\tList = ObjC.lookup_class ('List')\tList = ObjC.runtime.List\n\
\talist = List.alloc().init()\t\talist = List()\n\
\tObject = ObjC.lookup_class ('Object')\tObject = ObjC.runtime.Object\n\
\tobj1 = Object.alloc().init()\t\tobj1 = Object()\n\
\talist.addObject__ (obj1)\t\t...\n\
\talist.addObject__ ('Ciao')\n\
\talist.count()\n\
\t>>> 2\n\
\talist[1]\n\
\t>>> 'Ciao'\n\n\
Since Python has a different calling syntax, Objective-C method names\n\
change slightly: given the expression\n\
\tmyself = [[User alloc] initId:1 group:1 andName:\"Lele Gaifax\"];\n\n\
to obtain the same in Python you should use\n\
\tUser = ObjC.runtime.User\n\
\tmyself = User.alloc().initId__group__andName__ (1, 1, \"Lele Gaifax\")\n\n\
or more concisely\n\
\tmyself=ObjC.runtime.User(1,1,\"Lele Gaifax\",init=\"initId:group:andName:\")\n\
\n\
In other words, if the signature of an Objective-C method is\n\
`someMethod:withSecondArg:andAnother:', which takes three arguments, the\n\
Python equivalent results by replacing each colon `:' with a double underscore\n\
`_', then feeding the arguments in row, as usual.\n\
";

char ObjC_load_object_files_doc[] =
FUNDOC("Dynamically load a list of object files",
       ".load_object_files (LIST)",
       "LIST\t: list of filenames to load",
       "None");
PyObject *
ObjC_load_object_files (PyObject *self, PyObject *args)
{
#ifdef GNU_RUNTIME
  
#define errorStream stderr
#define streamBuf "Unable to dynamically load modules"
#define NXPutc(s,c)
#define NXGetMemoryBuffer(a,b,c,d)
#define NXCloseMemory(a,b)
  
#else
  
  NXStream *errorStream;
  char *streamBuf;
  int len, maxLen;
  
#endif
  const char **filenames;
  long ret;
  PyObject *filelist, *file;
  int listsize, i;

  if (!PyArg_ParseTuple (args, "O!;list of filenames", &PyList_Type, &filelist))
    return NULL;

  listsize = PyList_Size (filelist);
  filenames = alloca ((listsize+1) * sizeof (*filenames));

#ifndef GNU_RUNTIME
  errorStream = NXOpenMemory (NULL, 0, NX_WRITEONLY);
#endif
  
  for (i = 0; i < listsize; i++)
    {
      file = PyList_GetItem (filelist, i);

      if (!PyString_Check (file))
	{
	  PyErr_SetString (ObjC_Error,
			   "all list items must be strings");
	  return NULL;
	}
      
      filenames[i] = PyString_AsString (file);
    }

  filenames[listsize] = NULL;

  ret = objc_loadModules (filenames, errorStream, NULL, NULL, NULL);

  /* extract the error messages for the exception */
  if (ret)
    {
      NXPutc (errorStream, '\0');

      NXGetMemoryBuffer (errorStream, &streamBuf, &len, &maxLen);
      PyErr_SetString (ObjC_Error, streamBuf);
    }

  NXCloseMemory (errorStream, NX_FREEBUFFER);

  if (ret)
    return NULL;
  else
    {
      Py_INCREF (Py_None);
      return Py_None;
    }
}

static char ObjC_lookup_class_doc[] =
FUNDOC("Locate a class in the Objective-C runtime",
       ".lookup_class (CLASS)",
       "CLASS\t: the name of an Objective-C class",
       "An ObjCObject wrapping the given class");
static PyObject *
ObjC_lookup_class (PyObject *self, PyObject *args)
{
  char *classname;
  id    class;

  if (!PyArg_ParseTuple (args, "s;class name", &classname))
    return NULL;

  if (!(class = LOOKUPCLASS(classname)))
    {
#define ERRMSG "Objective-C class `%s' is unknown to the runtime"
      char errmsg[sizeof ERRMSG + strlen (classname)];

      sprintf (errmsg, ERRMSG, classname);
      PyErr_SetString (ObjC_Error, errmsg);
#undef ERRMSG
      return NULL;
    }

  return (PyObject *) ObjCObject_new (class);
}

char ObjC_list_classes_doc[] =
FUNDOC("Get a list of all known classes names",
       ".list_classes()",
       "none",
       "A list of class names");
PyObject *
ObjC_list_classes (PyObject *self, PyObject *args)
{
  Class classid;
  PyObject *list;
#ifdef GNU_RUNTIME

  void *es = NULL;
#define ENUMERATE_CLASSES(c) (c = objc_next_class (&es))

#else

  NXHashTable *class_hash = objc_getClasses();
  NXHashState state = NXInitHashState (class_hash);
#define ENUMERATE_CLASSES(c) (NXNextHashState (class_hash, &state, (void**) &c))

#endif
  
  if (!PyArg_ParseTuple (args, ""))
    return NULL;

  list = PyList_New (0);
  if (list == NULL)
    return NULL;
  
  while (ENUMERATE_CLASSES(classid))
    {
      ObjCObject *item = ObjCObject_new (classid);
      
      if (item == NULL || PyList_Append (list, (PyObject *)item) < 0)
	{
	  Py_XDECREF(item);
	  Py_DECREF(list);
	  return NULL;
	}
      Py_DECREF(item);
    }

  return list;
}

static char ObjC_make_pointer_doc[] =
FUNDOC("Cast an integer value to a generic pointer.",
       ".make_pointer (INT)",
       "INT\t: the integer value",
       "An ObjCPointer instance");
static PyObject *
ObjC_make_pointer (PyObject *self, PyObject *args)
{
  long value;

  if (PyArg_ParseTuple (args, "l;an integer", &value))
    return (PyObject *) ObjCPointer_new ((void *) value, @encode (void));

  return NULL;
}
			    
static PyMethodDef ObjC_methods[] =
{
  { "load_object_files",	(PyCFunction) ObjC_load_object_files,	METH_VARARGS,	ObjC_load_object_files_doc },
  { "lookup_class",		(PyCFunction) ObjC_lookup_class,	METH_VARARGS,	ObjC_lookup_class_doc },
  { "list_classes",		(PyCFunction) ObjC_list_classes,	METH_VARARGS,	ObjC_list_classes_doc },
  { "make_pointer",		(PyCFunction) ObjC_make_pointer,	METH_VARARGS,	ObjC_make_pointer_doc },
  { 0, 0, 0, 0 }
};

PyObject *ObjC_Error;

#include "OC_PythonBundle.h"
#include "OC_Pasteboard.h"

void
initObjC()
{
  PyObject *m, *d;
  extern void initObjCStreams (void);
  
  m = Py_InitModule4 ("ObjC", ObjC_methods, ObjC_doc, NULL, PYTHON_API_VERSION);
  d = PyModule_GetDict (m);

  ObjC_Error = PyString_FromString ("ObjC.error");
  PyDict_SetItemString (d, "error", ObjC_Error);

  PyDict_SetItemString (d, "runtime", (PyObject *) ObjCRuntime_new());
  PyDict_SetItemString (d, "__version__", PyFloat_FromDouble (PyObjC_VERSION));

  /**
  *** Force +initialize on these classes before any others due to an
  *** initialization order dependency in the 3.3 [and possibly other]
  *** AppKit
  **/
  [LOOKUPCLASS("Application") class];

  // Force the load of these classes/categories
  [OC_PythonBundle class];
  oc_pasteboard_ensure_link();
  
  // Initialize the ObjCStreams module...
  initObjCStreams();

  // ...and insert it as ``streams'' in the module's dictionary
  m = PyImport_ImportModule ("ObjCStreams");
  if (m)
    {
      PyDict_SetItemString (d, "streams", m);
      Py_DECREF (m);
    }
  
  if (PyErr_Occurred())
    Py_FatalError ("can't initialize module ObjC");
  
#if defined(WITH_THREAD) && !defined(GNU_RUNTIME)
  objc_setMultithreaded (1);
#endif
}

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