/*############################################################################
 #
 #  Copyright (c) 2003 Zope Foundation and Contributors.
 #  All Rights Reserved.
 #
 #  This software is subject to the provisions of the Zope Public License,
 #  Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
 #  THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
 #  WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 #  WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 #  FOR A PARTICULAR PURPOSE.
 #
 ############################################################################*/

/* _zope_hookable.c

   Provide an efficient implementation for hookable objects

 */

#include "Python.h"
#include "structmember.h"

/* Support for Python < 2.6 */

#ifndef Py_TYPE
  #define Py_TYPE(ob) (((PyObject*)(ob))->ob_type)
#endif

#ifndef cmpfunc
  #define cmpfunc int
#endif

#ifndef PyVarObject_HEAD_INIT
  #define PyVarObject_HEAD_INIT(type, size) \
    PyObject_HEAD_INIT(type) size,
#endif


typedef struct {
	PyObject_HEAD
        PyObject *old;
        PyObject *implementation;
} hookable;

static int
hookable_init(hookable *self, PyObject *args, PyObject *kwds)
{
  static char *kwlist[] = {"implementation", NULL};
  PyObject *implementation;

  if (! PyArg_ParseTupleAndKeywords(args, kwds, "O:hookable", kwlist,
                                    &implementation))
    return -1;

  Py_INCREF(implementation);
  Py_INCREF(implementation);
  Py_XDECREF(self->old);
  self->old = implementation;
  Py_XDECREF(self->implementation);
  self->implementation = implementation;

  return 0;
}

static int
hookable_traverse(hookable *self, visitproc visit, void *arg)
{
  if (self->implementation != NULL && visit(self->implementation, arg) < 0)
    return -1;
  if (self->old != NULL
      && self->old != self->implementation
      && visit(self->old, arg) < 0
      )
    return -1;

  return 0;
}

static int
hookable_clear(hookable *self)
{
  Py_XDECREF(self->old);
  self->old = NULL;
  Py_XDECREF(self->implementation);
  self->implementation = NULL;
  return 0;
}


static void
hookable_dealloc(hookable *self)
{
  PyObject_GC_UnTrack((PyObject *)self);
  Py_XDECREF(self->old);
  Py_XDECREF(self->implementation);
  Py_TYPE(self)->tp_free((PyObject*)self);
}

static PyObject *
hookable_sethook(hookable *self, PyObject *implementation)
{
  PyObject *old;

  old = self->implementation;
  Py_INCREF(implementation);
  self->implementation = implementation;

  if (old == NULL)
    {
      Py_INCREF(Py_None);
      return Py_None;
    }

  return old;
}

static PyObject *
hookable_reset(hookable *self)
{
  Py_XINCREF(self->old);
  Py_XDECREF(self->implementation);
  self->implementation = self->old;
  Py_INCREF(Py_None);
  return Py_None;
}

static struct PyMethodDef hookable_methods[] = {
  {"sethook",	(PyCFunction)hookable_sethook, METH_O,
   "Set the hook implementation for the hookable object"},
  {"reset",	(PyCFunction)hookable_reset, METH_NOARGS,
   "Reset the hook to the original value"},
  {NULL,		NULL}		/* sentinel */
};


static PyObject *
hookable_call(hookable *self, PyObject *args, PyObject *kw)
{
  if (self->implementation != NULL)
    return PyObject_Call(self->implementation, args, kw);
  PyErr_SetString(PyExc_TypeError, "Hookable has no implementation");
  return NULL;
}

static PyMemberDef hookable_members[] = {
  { "original", T_OBJECT_EX, offsetof(hookable, old), READONLY },
  { "implementation", T_OBJECT_EX, offsetof(hookable, implementation), READONLY },
  {NULL}	/* Sentinel */
};


static char Hookabletype__doc__[] =
"Callable objects that support being overridden"
;

static PyTypeObject hookabletype = {
	PyVarObject_HEAD_INIT(NULL, 0)
	/* tp_name           */ "zope.hookable."
                                "hookable",
	/* tp_basicsize      */ sizeof(hookable),
	/* tp_itemsize       */ 0,
	/* tp_dealloc        */ (destructor)&hookable_dealloc,
	/* tp_print          */ (printfunc)0,
	/* tp_getattr        */ (getattrfunc)0,
	/* tp_setattr        */ (setattrfunc)0,
	/* tp_compare        */ 0,
	/* tp_repr           */ (reprfunc)0,
	/* tp_as_number      */ 0,
	/* tp_as_sequence    */ 0,
	/* tp_as_mapping     */ 0,
	/* tp_hash           */ (hashfunc)0,
	/* tp_call           */ (ternaryfunc)hookable_call,
	/* tp_str            */ (reprfunc)0,
        /* tp_getattro       */ (getattrofunc)0,
        /* tp_setattro       */ (setattrofunc)0,
        /* tp_as_buffer      */ 0,
        /* tp_flags          */ Py_TPFLAGS_DEFAULT
				| Py_TPFLAGS_BASETYPE
                                | Py_TPFLAGS_HAVE_GC,
	/* tp_doc            */ Hookabletype__doc__,
        /* tp_traverse       */ (traverseproc)hookable_traverse,
        /* tp_clear          */ (inquiry)hookable_clear,
        /* tp_richcompare    */ (richcmpfunc)0,
        /* tp_weaklistoffset */ (long)0,
        /* tp_iter           */ (getiterfunc)0,
        /* tp_iternext       */ (iternextfunc)0,
        /* tp_methods        */ hookable_methods,
        /* tp_members        */ hookable_members,
        /* tp_getset         */ 0,
        /* tp_base           */ 0,
        /* tp_dict           */ 0, /* internal use */
        /* tp_descr_get      */ (descrgetfunc)0,
        /* tp_descr_set      */ (descrsetfunc)0,
        /* tp_dictoffset     */ 0,
        /* tp_init           */ (initproc)hookable_init,
        /* tp_alloc          */ (allocfunc)0,
        /* tp_new            */ (newfunc)0 /*PyType_GenericNew*/,
	/* tp_free           */ 0/*PyObject_GC_Del*/,
};


#if PY_MAJOR_VERSION >= 3
  #define MOD_ERROR_VAL NULL
  #define MOD_SUCCESS_VAL(val) val
  #define MOD_INIT(name) PyMODINIT_FUNC PyInit_##name(void)
  #define MOD_DEF(ob, name, doc, methods) \
	  static struct PyModuleDef moduledef = { \
	    PyModuleDef_HEAD_INIT, name, doc, -1, methods, }; \
	  ob = PyModule_Create(&moduledef);
#else
  #define MOD_ERROR_VAL
  #define MOD_SUCCESS_VAL(val)
  #define MOD_INIT(name) void init##name(void)
  #define MOD_DEF(ob, name, doc, methods) \
          ob = Py_InitModule3(name, methods, doc);
#endif

static struct PyMethodDef module_methods[] = {
	{NULL, NULL}
};

MOD_INIT(_zope_hookable)
{
  PyObject *m;

  MOD_DEF(m, "_zope_hookable",
    "Provide an efficient implementation for hookable objects",
    module_methods)

  if (m == NULL)
    return MOD_ERROR_VAL;

  hookabletype.tp_new = PyType_GenericNew;
  hookabletype.tp_free = PyObject_GC_Del;

  if (PyType_Ready(&hookabletype) < 0)
    return MOD_ERROR_VAL;

  if (PyModule_AddObject(m, "hookable", (PyObject *)&hookabletype) < 0)
    return MOD_ERROR_VAL;

  return MOD_SUCCESS_VAL(m);

}
