# The wrapper code for each API set (JudyL, JudySL, and Judy1) is
# contained in two files, a "code" file and an "iter" file.  The code
# file contains most of the code.  The "iter" file contains the code
# for making the forwards and reverse iterators.  The reverse template
# is generated by using string substitutions on the forward iterator.

# For each API set this program reads the template file and makes a set
# of string substitutions depending on if the keys or values are integers
# or Python object.  This lets me generate all 4 JudyL, 2 JudySL and 2
# Judy1 arrays interface with somewhat less need for repetitive typing.

# The substitutions are:
#   <P> -- the name of the new C data type for the PyJudy object
#   <CT> -- the container type for the array type
#   PY_FROM_INDEX -- convert a Python object into the index
#   PY_TO_INDEX -- convert an index into a Python object
#   PY_FROM_PWORD -- convert a pointer to Word_t into a Python object
#   SETITEM -- used in arr[i] = j
#   DELITEM -- used in del arr[i]
#   FREE_ELEMENTS -- code (if needed) to decref array elements
#                       before deletion
#   INCR_KEY -- used in PyJudy1 to change the ref count of a newly added key
#   DECR_KEY -- used in PyJudy1 to change the ref count of a removed key


# The final code is put into "pyjudy.c" along with the Python module
# initialization code.  "pyjudy_common.c" contains a small bit of code
# used by more than one PyJudy class.


## Used to return the templates for the various implementations
import template

## Remember to change the "__version__" in the module initialization!


INT_PY_FROM_INDEX = "PyInt_FromLong(index)"
INT_PY_FROM_PWORD = "PyInt_FromLong((Word_t)(*p))"

INT_PY_TO_INDEX = '''
  if (!PyInt_CheckExact(key)) {
    PyErr_SetString(PyExc_ValueError, "an integer key is required");
    return NULL;
  }
  index = PyInt_AS_LONG(key);
'''

INT_INT_SETITEM = '''
    /* Insert int -> int */
    long index, val;
    PPvoid_t p;

    if (!PyInt_CheckExact(key)) {
        PyErr_SetString(PyExc_ValueError, "an integer key is required");
        return -1;
    }
    index = PyInt_AS_LONG(key);
    if (!PyInt_CheckExact(value)) {
        PyErr_SetString(PyExc_ValueError, "an integer value is required");
        return -1;
    }
    val = PyInt_AS_LONG(value);
       
    p = JudyLIns(&(self->array), (Word_t) index, PJE0);
    if (p == NULL) {
        PyErr_NoMemory();
        return -1;
    }
    *((Word_t *) p) = (Word_t) val;
    return 0;
'''

INT_INT_DELITEM = '''
    /* Delete */
    long index;
    long success;

    if (!PyInt_CheckExact(key)) {
        PyErr_SetString(PyExc_ValueError, "an integer key is required");
        return -1;
    }
    index = PyInt_AS_LONG(key);


    success = JudyLDel(&(self->array), (Word_t) index, PJE0);
    if (success) {
      return 1;
    }
    return 0;
'''

judyl_int_int = {"<P>": "PyJudyLIntInt",
                 "<CT>": "PyJudyL",
                 "PY_FROM_INDEX": INT_PY_FROM_INDEX,
                 "PY_TO_INDEX": INT_PY_TO_INDEX,
                 "PY_FROM_PWORD": INT_PY_FROM_PWORD,
                 "SETITEM": INT_INT_SETITEM,
                 "FREE_ELEMENTS": "",
                 "DELITEM": INT_INT_DELITEM,
                 }
###########################

OBJ_PY_FROM_PWORD = "(Py_INCREF((PyObject *)(*p)), (PyObject *)(*p))"

INT_OBJ_SETITEM = '''
    /* Insert int -> obj */
    long index;
    PPvoid_t p;

    if (!PyInt_CheckExact(key)) {
        PyErr_SetString(PyExc_ValueError, "an integer key is required");
        return -1;
    }
    index = PyInt_AS_LONG(key);
        
    /* Does it already exist? */
    p = JudyLGet(self->array, (Word_t) index, PJE0);
    if (p != NULL) {
      /* Replace the existing value with the new one */
      Py_INCREF(value);
      Py_DECREF(*((PyObject **)p));
      *((PyObject **)p) = value;
      return 0;
    }
    /* Does not exist so add it */
    p = JudyLIns(&(self->array), (Word_t) index, PJE0);
    if (p == NULL) {
      PyErr_NoMemory();
      return -1;
    }
    Py_INCREF(value);
    *((PyObject **)p) = value;
    return 0;
'''

INT_OBJ_FREE_ELEMENTS = '''{
    PPvoid_t p;
    PyObject *obj;
    Word_t i=0;
    p=JudyLFirst(self->array, &i, PJE0);
    while (p != NULL) {
      obj = *((PyObject **)p);
      Py_DECREF(obj);
      p = JudyLNext(self->array, &i, PJE0);
    }
}'''

INT_OBJ_DELITEM = '''
    /* Delete int -> object array */
    long index;
    long success;
    PPvoid_t p;
    PyObject *obj;

    if (!PyInt_CheckExact(key)) {
        PyErr_SetString(PyExc_ValueError, "an integer key is required");
        return -1;
    }
    index = PyInt_AS_LONG(key);

    /* does something already exist? */
    p = JudyLGet(self->array, (Word_t) index, PJE0);
    if (p == NULL) {
      return 0;  /* Let the caller set the exception if appropriate */
    }
    /* decref and remove */
    obj = *((PyObject **)p);
    Py_DECREF(obj);
    
    success = JudyLDel(&(self->array), (Word_t) index, PJE0);
    if (!success) {
      /* is there any other way for delete to fail after a get? */
      PyErr_NoMemory();
      return -1;
    }
    return 1;
'''

judyl_int_obj = {"<P>": "PyJudyLIntObj",
                 "<CT>": "PyJudyL",
                 "PY_FROM_INDEX": INT_PY_FROM_INDEX,
                 "PY_TO_INDEX": INT_PY_TO_INDEX,
                 "PY_FROM_PWORD": OBJ_PY_FROM_PWORD,
                 "SETITEM": INT_OBJ_SETITEM,
                 "FREE_ELEMENTS": INT_OBJ_FREE_ELEMENTS,
                 "DELITEM": INT_OBJ_DELITEM,
                 }

##################################################################

OBJ_PY_FROM_INDEX = "(Py_INCREF((PyObject *)index), (PyObject *)index)"
OBJ_PY_TO_INDEX = "index = (Word_t) key;"

OBJ_OBJ_SETITEM = '''
    /* Insert obj -> obj */
    PPvoid_t p;
    
    /* See if the entry already exists */
    p = JudyLGet(self->array, (Word_t) key, PJE0);
    if (p != NULL) {
        /* no need to touch the refcount on the key */
        Py_INCREF(value);
        Py_DECREF(*((PyObject **)p));
        *((PyObject **)p) = value;
        return 0;
    }
    /* Does not exist so add it */
    p = JudyLIns(&(self->array), (Word_t) key, PJE0);
    if (p == NULL) {
      PyErr_NoMemory();
      return -1;
    }
    Py_INCREF(key);
    Py_INCREF(value);
    *((PyObject **)p) = value;
    return 0;
'''

OBJ_OBJ_FREE_ELEMENTS = '''{
    PPvoid_t p;
    PyObject *obj;
    Word_t i=0;
    p=JudyLFirst(self->array, &i, PJE0);
    while (p != NULL) {
      obj = (PyObject *) i;
      Py_DECREF(obj);
      obj = *((PyObject **)p);
      Py_DECREF(obj);
      p = JudyLNext(self->array, &i, PJE0);
    }
}'''

OBJ_OBJ_DELITEM = '''
    /* Delete object -> object array */
    long success;
    PPvoid_t p;
    PyObject *obj;

    /* does something already exist? */
    p = JudyLGet(self->array, (Word_t) key, PJE0);
    if (p == NULL) {
      return 0;   /* Let the caller set the exception if appropriate */
    }
    /* decref and remove */
    Py_DECREF(key);
    obj = *((PyObject **)p);
    Py_DECREF(obj);
    
    success = JudyLDel(&(self->array), (Word_t) key, PJE0);
    if (!success) {
      /* is there any other way for delete to fail after a get? */
      PyErr_NoMemory();
      return -1;
    }
    return 1;
'''

judyl_obj_obj = {"<P>": "PyJudyLObjObj",
                 "<CT>": "PyJudyL",
                 "PY_FROM_INDEX": OBJ_PY_FROM_INDEX,
                 "PY_TO_INDEX": OBJ_PY_TO_INDEX,
                 "PY_FROM_PWORD": OBJ_PY_FROM_PWORD,
                 "SETITEM": OBJ_OBJ_SETITEM,
                 "FREE_ELEMENTS": OBJ_OBJ_FREE_ELEMENTS,
                 "DELITEM": OBJ_OBJ_DELITEM,
                 }

##################################################################

OBJ_INT_SETITEM = '''
    /* Insert obj -> int */
    PPvoid_t p;
    Word_t val;

    if (!PyInt_CheckExact(value)) {
        PyErr_SetString(PyExc_ValueError, "an integer value is required");
        return -1;
    }
    val = PyInt_AS_LONG(value);
    
    /* See if the entry already exists */
    p = JudyLGet(self->array, (Word_t) key, PJE0);
    if (p != NULL) {
        /* no need to touch the refcount on the key */
        *((Word_t *)p) = val;
        return 0;
    }
    /* Does not exist so add it */
    p = JudyLIns(&(self->array), (Word_t) key, PJE0);
    if (p == NULL) {
      PyErr_NoMemory();
      return -1;
    }
    Py_INCREF(key);
    *((Word_t *)p) = val;
    return 0;
'''

OBJ_INT_FREE_ELEMENTS = '''{
    PPvoid_t p;
    PyObject *obj;
    Word_t i=0;
    p=JudyLFirst(self->array, &i, PJE0);
    while (p != NULL) {
      obj = (PyObject *) i;
      Py_DECREF(obj);
      p = JudyLNext(self->array, &i, PJE0);
    }
}'''

OBJ_INT_DELITEM = '''
    /* Delete object -> int array */
    long success;
    PPvoid_t p;

    /* does something already exist? */
    p = JudyLGet(self->array, (Word_t) key, PJE0);
    if (p == NULL) {
      return 0;   /* Let the caller set the exception if appropriate */
    }
    /* decref and remove */
    Py_DECREF(key);
    
    success = JudyLDel(&(self->array), (Word_t) key, PJE0);
    if (!success) {
      /* is there any other way for delete to fail after a get? */
      PyErr_NoMemory();
      return -1;
    }
    return 1;
'''

judyl_obj_int = {"<P>": "PyJudyLObjInt",
                 "<CT>": "PyJudyL",
                 "PY_FROM_INDEX": OBJ_PY_FROM_INDEX,
                 "PY_TO_INDEX": OBJ_PY_TO_INDEX,
                 "PY_FROM_PWORD": INT_PY_FROM_PWORD,
                 "SETITEM": OBJ_INT_SETITEM,
                 "FREE_ELEMENTS": OBJ_INT_FREE_ELEMENTS,
                 "DELITEM": OBJ_INT_DELITEM,
                 }

##################################################################

judy1_int = {"<P>": "PyJudy1Int",
             "<CT>": "PyJudy1",
             "INCR_KEY": "",
             "DECR_KEY": "",
             "PY_FROM_INDEX": INT_PY_FROM_INDEX,
             "PY_TO_INDEX": INT_PY_TO_INDEX,
             "FREE_ELEMENTS": "",
             }

OBJ_FREE_ELEMENTS = '''{
    int rc;
    PyObject *obj;
    Word_t i=0;
    rc=Judy1First(self->array, &i, PJE0);
    while (rc) {
      obj = (PyObject *) i;
      Py_DECREF(obj);
      rc = Judy1Next(self->array, &i, PJE0);
    }
}'''
judy1_obj = {"<P>": "PyJudy1Obj",
             "<CT>": "PyJudy1",
             "INCR_KEY": "Py_INCREF(key)",
             "DECR_KEY": "Py_DECREF(key)",
             "PY_FROM_INDEX": OBJ_PY_FROM_INDEX,
             "PY_TO_INDEX": OBJ_PY_TO_INDEX,
             "FREE_ELEMENTS": OBJ_FREE_ELEMENTS,
             }
##################################################################

PY_TO_S = '''
  if (check_string(key, &s) != 0)
      return NULL;
'''
S_TO_PY = '''
PyString_FromString(s)
'''


SL_INT_SETITEM = '''
  /* Add the key/value */
  char *s;
  PPvoid_t p;
  long val;

  if (check_string(key, &s) != 0)
      return -1;

  if (!PyInt_CheckExact(value)) {
      PyErr_SetString(PyExc_ValueError, "an integer value is required");
      return -1;
  }
  val = PyInt_AS_LONG(value);

  /* There's no way to tell if the returned reference is to an  */
  /* existing 0 value or to a newly allocated value.  There are */
  /* several ways to work around this.  I chose the simplest;   */
  /* check if the key already exists.  */
  p = JudySLGet(self->array, (uint8_t *)s, PJE0);
  if (p == NULL) {
      /* Not present - can add without checks */
      p = JudySLIns(&(self->array), (uint8_t *)s, PJE0);
      if (p == NULL) {
          /* This only way this can fail */
          PyErr_NoMemory();
          return -1;
      }
      *((Word_t *) p) = (Word_t) val;
      self->count++;
      if (PyString_GET_SIZE(key) > self->max_seen)
          self->max_seen = PyString_GET_SIZE(key);
  } else {
      /* Already present; replace */
      *((Word_t *) p) = (Word_t) val;

      /* No need to change self->count or max_seen */
  }
  
  return 0;
'''

SL_INT_DELITEM = '''{
  int rc;
  rc = JudySLDel(&(self->array), s, PJE0);
  if (rc == 1) {
    self->count--;
  } else if (rc != 0) {
     PyErr_SetString(PyExc_SystemError,
                     "judy error response during deletion");
  }
  return rc;
}'''

    

judysl_int = {"<P>": "PyJudySLInt",
              "<CT>": "PyJudySL",
              "PY_TO_INDEX": PY_TO_S,
              "PY_FROM_INDEX": S_TO_PY,
              "PY_FROM_PWORD": INT_PY_FROM_PWORD,
              "SETITEM": SL_INT_SETITEM,
              "DELITEM": SL_INT_DELITEM,
              "FREE_ELEMENTS": "",
              }

SL_OBJ_SETITEM = '''
  /* Add the key/value */
  char *s;
  PPvoid_t p;

  if (check_string(key, &s) != 0)
    return -1;

  /* Go ahead and insert */
  p = JudySLIns(&(self->array), (uint8_t *)s, PJE0);

  if (*p == NULL) {
      /* No object was there - assign this one */
      *p = value;
      Py_INCREF(value);
      self->count++;
  } else {
      /* Something was already there. Assign the */
      /* new reference before removing the old.  */
      PyObject *old_obj = *p;
      Py_INCREF(value);
      *p = value;
      Py_DECREF(old_obj);
      /* self->count does not change */
  }
  if (PyString_GET_SIZE(key) > self->max_seen)
      self->max_seen = PyString_GET_SIZE(key);
  return 0;
'''

SL_OBJ_DELITEM = '''{
  PPvoid_t p;
  /* Need to decref the object if it is present so */
  /* check if it exists. */
  p = JudySLGet(self->array, (uint8_t *)s, PJE0);
  if (p == NULL) {
      /* Not present, cannot delete. */
      return 0;
  }
  /* Found; delete at will */
  Py_DECREF(*((PyObject **) p));
  JudySLDel(&(self->array), s, PJE0);
  self->count--;
  return 1;
}'''

OBJ_FREE_ELEMENTS = '''{
    PPvoid_t p;
    PyObject *obj;
    index_buffer[0] = '\\0';
    p=JudySLFirst(self->array, index_buffer, PJE0);
    while (p) {
      obj = *((PyObject **) p);
      Py_DECREF(obj);
      p = JudySLNext(self->array, index_buffer, PJE0);
    }
}'''
judysl_obj = {"<P>": "PyJudySLObj",
              "<CT>": "PyJudySL",
              "PY_TO_INDEX": PY_TO_S,
              "PY_FROM_INDEX": S_TO_PY,
              "PY_FROM_PWORD": OBJ_PY_FROM_PWORD,
              "SETITEM": SL_OBJ_SETITEM,
              "DELITEM": SL_OBJ_DELITEM,
              "FREE_ELEMENTS": OBJ_FREE_ELEMENTS,
              }

##################################################################
def do_substitutions(text, subs):
    pos = 0
    fragments = []
    while 1:
        matches = []
        for (substr, replacement) in subs.items():
            i = text.find(substr, pos)
            if i >= 0:
                matches.append( (i, substr, replacement) )
        if not matches:
            break
        matches.sort()
        i, substr, replacement = matches[0]
        fragments.append(text[pos:i])
        fragments.append(replacement)
        pos = i + len(substr)

    fragments.append(text[pos:])
    return "".join(fragments)

def main():
    f = open("pyjudy.c", "w")

    f.write('''
/* Python wrapper to the Judy package. */
/* This is an autogenerated file.  Modifications will be overwritten. */
#include "pyjudy.h"
#include "pyjudy_common.c"
    ''')

    text = template.get_pyjudyl_template()
    for comment, macros in (
        ("/***************** PyJudyLIntInt **************/\n",
         judyl_int_int),
        ("/***************** PyJudyLIntObj **************/\n",
         judyl_int_obj),
        ("/***************** PyJudyLObjObj **************/\n",
         judyl_obj_obj),
        ("/***************** PyJudyLObjInt **************/\n",
         judyl_obj_int),
        ):

        f.write(comment)
        f.write(do_substitutions(text, macros))

    text = template.get_pyjudy1_template()
    for comment, macros in (
        ("/***************** PyJudy1Int **************/\n",
         judy1_int),
        ("/***************** PyJudy1Obj **************/\n",
         judy1_obj),
        ):

        f.write(comment)
        f.write(do_substitutions(text, macros))

    text = template.get_pyjudysl_template()
    for comment, macros in (
        ("/***************** PyJudySLInt **************/\n",
         judysl_int),
        ("/***************** PyJudySLObj **************/\n",
         judysl_obj),
        ):

        f.write(comment)
        f.write(do_substitutions(text, macros))

    
    f.write("\n\n")

    f.write('''

/****** Module initialization ****/

static PyMethodDef module_methods[] = {
  {NULL}  /* Sentinel */
};

PyMODINIT_FUNC
initpyjudy(void)
{
  PyObject *m;

  if (PyType_Ready(&PyJudyLIntIntType) < 0) {
    return ;
    }
  if (PyType_Ready(&PyJudyLIntObjType) < 0) {
    return ;
  }
  if (PyType_Ready(&PyJudyLObjObjType) < 0) {
    return ;
  }
  if (PyType_Ready(&PyJudyLObjIntType) < 0) {
    return ;
  }
  if (PyType_Ready(&PyJudy1IntType) < 0) {
    return ;
  }
  if (PyType_Ready(&PyJudy1ObjType) < 0) {
    return ;
  }
  if (PyType_Ready(&PyJudySLIntType) < 0) {
    return ;
  }
  if (PyType_Ready(&PyJudySLObjType) < 0) {
    return ;
  }
  m = Py_InitModule3("pyjudy", module_methods,
	 	     "pyjudy module docstring");

  Py_INCREF(&PyJudyLIntIntType);
  Py_INCREF(&PyJudyLIntObjType);
  Py_INCREF(&PyJudyLObjObjType);
  Py_INCREF(&PyJudyLObjIntType);
  Py_INCREF(&PyJudy1IntType);
  Py_INCREF(&PyJudy1ObjType);
  Py_INCREF(&PyJudySLIntType);
  Py_INCREF(&PyJudySLObjType);

  PyModule_AddObject(m, "JudyLIntInt", (PyObject *)&PyJudyLIntIntType);
  PyModule_AddObject(m, "JudyLIntObj", (PyObject *)&PyJudyLIntObjType);
  PyModule_AddObject(m, "JudyLObjObj", (PyObject *)&PyJudyLObjObjType);
  PyModule_AddObject(m, "JudyLObjInt", (PyObject *)&PyJudyLObjIntType);
  PyModule_AddObject(m, "Judy1Int", (PyObject *)&PyJudy1IntType);
  PyModule_AddObject(m, "Judy1Obj", (PyObject *)&PyJudy1ObjType);
  PyModule_AddObject(m, "JudySLInt", (PyObject *)&PyJudySLIntType);
  PyModule_AddObject(m, "JudySLObj", (PyObject *)&PyJudySLObjType);

  PyModule_AddStringConstant(m, "__version__", "1.0");
  PyModule_AddIntConstant(m, "MAX_STRING_LEN", MAX_STRING_LEN);
}
''')    
    f.close()

if __name__ == "__main__":
    main()
