/*
 * super-call.m -- Finding the right dispatch function for a method-call
 *
 * This file defines a registry that is used to find the right function to
 * call when a method is called. There are 3 variants:
 * - Call the method from python
 * - Call the superclass implementation from python
 * - Call the python implementation of a method from Objective-C
 *
 * The first will almost always be 'execute_and_pythonify_method', the other
 * two classes contain lots of functions because the normal runtime doesn't
 * allow for dynamicly creating these types of calls (see also: register.m)
 */
#include "pyobjc.h"

int PyObjC_MappingCount = 0;

struct registry
{
	PyObjC_CallFunc 	call_to_objc;
	PyObjCFFI_ClosureFunc	call_to_python;
};
	
/* Dict mapping from signature-string to a 'struct registry' */
static PyObject* signature_registry = NULL;

/* List of 3-tuples: (Class, "selector", 'struct registry' */
static PyObject* special_registry  = NULL;

/*
 * Initialize the data structures
 */
static int
init_registry(void)
{
	if (signature_registry == NULL) {
		signature_registry = PyDict_New();
		if (signature_registry == NULL) return -1;
	}

	if (special_registry == NULL) {
		special_registry = PyList_New(0);
		if (special_registry == NULL) return -1;
	}

	return 0;
}


/*
 * Add a custom mapping for a method in a class
 */
int 
PyObjC_RegisterMethodMapping(Class class, SEL sel, 
	PyObjC_CallFunc call_to_objc,
	PyObjCFFI_ClosureFunc call_to_python)
{
	struct registry* v;
	const char* selname = PyObjCRT_SELName(sel);
	PyObject* pyclass;
	PyObject* entry;

	if (signature_registry == NULL) {
		if (init_registry() < 0) return -1;
	}

	if (!call_to_python) {
		PyErr_SetString(PyObjCExc_Error, 
			"PyObjC_RegisterMethodMapping: all functions required");
		return -1;
	}

	if (!call_to_objc) {
		call_to_objc = PyObjCFFI_Caller;
	}

	if (class == nil) {
		pyclass = Py_None; Py_INCREF(Py_None);
	} else {	
		pyclass = PyObjCClass_New(class);
		if (pyclass == NULL) return -1;
	}

	v = PyMem_Malloc(sizeof(*v));
	if (v == NULL) {
		PyErr_NoMemory();
		return -1;
	}
	v->call_to_objc  = call_to_objc;
	v->call_to_python = call_to_python;

	entry = PyTuple_New(3);
	if (entry == NULL) return -1;

	PyTuple_SET_ITEM(entry, 0, pyclass);
	PyTuple_SET_ITEM(entry, 1, PyString_InternFromString(selname));
	PyTuple_SET_ITEM(entry, 2, PyCObject_FromVoidPtr(v, (PyMem_Free)));

	if (PyErr_Occurred()) {
		Py_DECREF(entry);
		return -1;
	}

	if (PyList_Append(special_registry, entry) < 0) {
		Py_DECREF(entry);
		return -1;
	}

	PyObjC_MappingCount += 1;

	return 0;
}

int PyObjC_RegisterSignatureMapping(
	char* signature,
	PyObjC_CallFunc call_to_objc,
	PyObjCFFI_ClosureFunc call_to_python)
{
	struct registry* v;
	PyObject*  entry;
	char signature_buf[1024];
	int r;

	if (special_registry == NULL) {
		if (init_registry() < 0) return -1;
	}

	r = PyObjCRT_SimplifySignature(
			signature, 
			signature_buf, 
			sizeof(signature_buf));
	if (r == -1) {
		PyErr_SetString(PyObjCExc_Error, "cannot simplify signature");
		return -1;
	}	

	if (!call_to_objc || !call_to_python) {
		PyErr_SetString(PyObjCExc_Error, 
		   "PyObjC_RegisterSignatureMapping: all functions required");
		return -1;
	}

	v = PyMem_Malloc(sizeof(*v));
	if (v == NULL) {
		PyErr_NoMemory();
		return -1;
	}
	v->call_to_objc  = call_to_objc;
	v->call_to_python = call_to_python;

	entry = PyCObject_FromVoidPtr(v, (PyMem_Free));
	if (entry == NULL) {
		PyMem_Free(v);
		return -1;
	}

	if (PyDict_SetItemString(signature_registry, signature_buf, entry) < 0){
		Py_DECREF(entry);
		return -1;
	}
	Py_DECREF(entry); 
	PyObjC_MappingCount += 1;

	return 0;
}


static struct registry*
search_special(Class class, SEL sel)
{
	PyObject* result = NULL;
	PyObject* special_class = NULL;
	int special_len;
	int i;

	if (special_registry == NULL) goto error;

	if (class) {
		special_class = PyObjCClass_New(class);
		if (special_class == NULL) goto error;

	}

	special_len = PyList_Size(special_registry);

	for (i = 0; i < special_len; i++) {
		PyObject* entry = PyList_GetItem(special_registry, i);
		PyObject* pyclass = PyTuple_GET_ITEM(entry, 0);
		PyObject* pysel = PyTuple_GET_ITEM(entry, 1);

		if (pyclass == NULL || pysel == NULL) continue;
		
		if (strcmp(PyString_AsString(pysel), 
					PyObjCRT_SELName(sel)) == 0) {

			if (!special_class) {
				special_class = pyclass;
				Py_INCREF(special_class);
				result = PyTuple_GET_ITEM(entry, 2);

			} else if (pyclass == Py_None) {
				Py_DECREF(special_class);
				special_class = pyclass;
				Py_INCREF(special_class);
				result = PyTuple_GET_ITEM(entry, 2);

			} else if (PyType_IsSubtype(
					(PyTypeObject*)special_class,
					(PyTypeObject*)pyclass
				    )) {
				Py_DECREF(special_class);
				special_class = pyclass;
				Py_INCREF(special_class);
				result = PyTuple_GET_ITEM(entry, 2);
			}
		}
	}
	Py_XDECREF(special_class);
	if (!result) goto error;

	return PyCObject_AsVoidPtr(result);

error:
	PyErr_Format(PyObjCExc_Error,
		"PyObjC: don't know how to call method '%s'", 
			PyObjCRT_SELName(sel));
	return NULL;
}


PyObjC_CallFunc 
PyObjC_FindCallFunc(Class class, SEL sel)
{
/*
 * TODO: Should add special case code for NSUndoManager: If this
 * is a selector that is forwarded to the 'target' we should get
 * the caller for the target instead of for this object!
 */
	struct registry* special;

	if (special_registry == NULL) return PyObjCFFI_Caller;

	special = search_special(class, sel);
	if (special) {
		return special->call_to_objc;
	} else {
		PyErr_Clear();
	}

	return PyObjCFFI_Caller;
}

static struct registry*
find_signature(char* signature)
{
	PyObject* o;
	struct registry* r;
	char signature_buf[1024];
	int res;

	res = PyObjCRT_SimplifySignature(
			signature, 
			signature_buf, 
			sizeof(signature_buf));
	if (res == -1) {
		PyErr_SetString(PyObjCExc_Error, "cannot simplify signature");
		return NULL;
	}	

	if (signature_registry == NULL) goto error;
	o = PyDict_GetItemString(signature_registry, signature_buf);
	if (o == NULL) goto error;

	r = PyCObject_AsVoidPtr(o);
	return r;

error:
	PyErr_Format(PyObjCExc_Error,
		"PyObjC: don't know how to call a method with "
		"signature '%s'", signature);
	return NULL;
}

extern IMP 
PyObjC_MakeIMP(Class class, PyObject* sel, PyObject* imp)
{
	struct registry* generic;
	struct registry* special;
	SEL aSelector = PyObjCSelector_GetSelector(sel);
	PyObjCFFI_ClosureFunc func = NULL;
	IMP retval;
	PyObjCMethodSignature* methinfo;

	if (class != nil) {
		special = search_special(class, aSelector);
		if (special) {
			func = special->call_to_python;
		} else {
			PyErr_Clear();
		}
	}

	if (func == NULL) {
		generic = find_signature(PyObjCSelector_Signature(sel));
		if (generic != NULL) {
			func = generic->call_to_python;
		} 
	}

	if (func == PyObjCUnsupportedMethod_IMP) {
		PyErr_Format(PyExc_TypeError,
			"Implementing %s in Python is not supported",
			PyObjCRT_SELName(aSelector));
		return NULL;
	}

	if (func != NULL) {
		methinfo = PyObjCMethodSignature_FromSignature(
				PyObjCSelector_Signature(sel));
		retval = PyObjCFFI_MakeClosure(methinfo, func, imp);
		if (retval != NULL) {
			Py_INCREF(imp);
		}
		PyObjCMethodSignature_Free(methinfo);
		return retval;
	} else {
		/* XXX: To be replaced */
		/* 20040713: But why??? */
		PyErr_Clear();
		retval = PyObjCFFI_MakeIMPForSignature(
				PyObjCSelector_Signature(sel), imp);
		return retval;
	}
}

void  
PyObjCUnsupportedMethod_IMP(
	ffi_cif* cif __attribute__((__unused__)), 
	void* resp __attribute__((__unused__)), 
	void** args, 
	void* userdata __attribute__((__unused__)))
{
	[NSException raise:NSInvalidArgumentException
		format:@"Implementing %s from Python is not supported for %@",
			*(id*)args[0], PyObjCRT_SELName(*(SEL*)args[1])];
}

PyObject* 
PyObjCUnsupportedMethod_Caller(
	PyObject* meth, 
	PyObject* self, 
	PyObject* args __attribute__((__unused__)))
{
	PyObject* repr;

	repr = PyObject_Repr(self);
	if (repr == NULL || !PyString_Check(repr)) {
		Py_XDECREF(repr);
		PyErr_Format(PyExc_TypeError,
			"Cannot call '%s' on instances of '%s' from Python",
			PyObjCRT_SELName(PyObjCSelector_GetSelector(meth)),
			self->ob_type->tp_name);
		return NULL;
	}

	PyErr_Format(PyExc_TypeError,
		"Cannot call '%s' on '%s' from Python",
		PyObjCRT_SELName(PyObjCSelector_GetSelector(meth)),
		PyString_AS_STRING(repr));
	Py_DECREF(repr);
	return NULL;
}
