/* SWIG (www.swig.org) interface file for the dbf interface of shapelib
 *
 * At the moment (Dec 2000) this file is only useful to generate Python
 * bindings. Invoke swig as follows:
 *
 *	swig -python -shadow dbflib.i
 *
 * to generate dbflib_wrap.c and dbflib.py. dbflib_wrap.c defines a
 * bunch of Python-functions that wrap the appripriate dbflib functions
 * and dbflib.py contains an object oriented wrapper around
 * dbflib_wrap.c.
 *
 * This module defines one object type: DBFFile.
 */
/* this is the dbflib module */
%module dbflib
/* first a %{,%} block. These blocks are copied verbatim to the
 * dbflib_wrap.c file and are not parsed by SWIG. This is the place to
 * import headerfiles and define helper-functions that are needed by the
 * automatically generated wrappers.
 */
%{
#include "shapefil.h"
/* Read one attribute from the dbf handle and return it as a new python object
 *
 * If an error occurs, set the appropriate Python exception and return
 * NULL.
 *
 * Assume that the values of the record and field arguments are valid.
 * The name argument will be passed to DBFGetFieldInfo as is and should
 * thus be either NULL or a pointer to an array of at least 12 chars
 */
static PyObject *
do_read_attribute(DBFInfo * handle, int record, int field, char * name)
{
    int type, width;
    PyObject *value;
    type = DBFGetFieldInfo(handle, field, name, &width, NULL);
    /* For strings NULL and the empty string are indistinguishable
     * in DBF files. We prefer empty strings instead for backwards
     * compatibility reasons because older wrapper versions returned
     * emtpy strings as empty strings.
     */
    if (type != FTString && DBFIsAttributeNULL(handle, record, field))
    {
	value = Py_None;
	Py_INCREF(value);
    }
    else
    {
	switch (type)
	{
	case FTString:
	{
	    const char * temp = DBFReadStringAttribute(handle, record, field);
	    if (temp)
	    {
		value = PyString_FromString(temp);
	    }
	    else
	    {
		PyErr_Format(PyExc_IOError,
			     "Can't read value for row %d column %d",
			     record, field);
		value = NULL;
	    }
	    break;
	}
	case FTInteger:
	    value = PyInt_FromLong(DBFReadIntegerAttribute(handle, record,
							   field));
	    break;
	case FTDouble:
	    value = PyFloat_FromDouble(DBFReadDoubleAttribute(handle, record,
							      field));
	    break;
	default:
	    PyErr_Format(PyExc_TypeError, "Invalid field data type %d",
			 type);
	    value = NULL;
	}
    }
    if (!value)
	return NULL;
    return value;
}    
/* the read_attribute method. Return the value of the given record and
 * field as a python object of the appropriate type.
 * 
 * In case of error, set a python exception and return NULL. Since that
 * value will be returned to the python interpreter as is, the
 * interpreter should recognize the exception.
 */
static PyObject *
DBFInfo_read_attribute(DBFInfo * handle, int record, int field)
{
    if (record < 0 || record >= DBFGetRecordCount(handle))
    {
	PyErr_Format(PyExc_ValueError,
		     "record index %d out of bounds (record count: %d)",
		     record, DBFGetRecordCount(handle));
	return NULL;
    }
    if (field < 0 || field >= DBFGetFieldCount(handle))
    {
	PyErr_Format(PyExc_ValueError,
		     "field index %d out of bounds (field count: %d)",
		     field, DBFGetFieldCount(handle));
	return NULL;
    }
    return do_read_attribute(handle, record, field, NULL);
}
    
/* the read_record method. Return the record record as a dictionary with
 * whose keys are the names of the fields, and their values as the
 * appropriate Python type.
 * 
 * In case of error, set a python exception and return NULL. Since that
 * value will be returned to the python interpreter as is, the
 * interpreter should recognize the exception.
 */
static PyObject *
DBFInfo_read_record(DBFInfo * handle, int record)
{
    int num_fields;
    int i;
    int type, width;
    char name[12];
    PyObject *dict;
    PyObject *value;
    if (record < 0 || record >= DBFGetRecordCount(handle))
    {
	PyErr_Format(PyExc_ValueError,
		     "record index %d out of bounds (record count: %d)",
		     record, DBFGetRecordCount(handle));
	return NULL;
    }
    dict = PyDict_New();
    if (!dict)
	return NULL;
	
    num_fields = DBFGetFieldCount(handle);
    for (i = 0; i < num_fields; i++)
    {
	value = do_read_attribute(handle, record, i, name);
	if (!value)
	    goto fail;
	PyDict_SetItemString(dict, name, value);
	Py_DECREF(value);
    }
    return dict;
 fail:
    Py_XDECREF(dict);
    return NULL;
}
/* the write_record method. Write the record record given wither as a
 * dictionary or a sequence (i.e. a list or a tuple).
 *
 * If it's a dictionary the keys must be the names of the fields and
 * their value must have a suitable type. Only the fields actually
 * contained in the dictionary are written. Fields for which there's no
 * item in the dict are not modified.
 *
 * If it's a sequence, all fields must be present in the right order.
 *
 * In case of error, set a python exception and return NULL. Since that
 * value will be returned to the python interpreter as is, the
 * interpreter should recognize the exception.
 *
 * The method is implemented with two c-functions, write_field to write
 * a single field and DBFInfo_write_record as the front-end.
 */
/* write a single field of a record. */
static int
write_field(DBFHandle handle, int record, int field, int type,
	    PyObject * value)
{
    char * string_value;
    int int_value;
    double double_value;
    if (value == Py_None)
    {
	if (!DBFWriteNULLAttribute(handle, record, field))
	{
	    PyErr_Format(PyExc_IOError,
			 "can't write NULL field %d of record %d",
			 field, record);
	    return 0;
	}
    }
    else
    {
	switch (type)
	{
	case FTString:
	    string_value = PyString_AsString(value);
	    if (!string_value)
		return 0;
	    if (!DBFWriteStringAttribute(handle, record, field, string_value))
	    {
		PyErr_Format(PyExc_IOError,
			     "can't write field %d of record %d",
			     field, record);
		return 0;
	    }
	    break;
	case FTInteger:
	    int_value = PyInt_AsLong(value);
	    if (int_value == -1 && PyErr_Occurred())
		return 0;
	    if (!DBFWriteIntegerAttribute(handle, record, field, int_value))
	    {
		PyErr_Format(PyExc_IOError,
			     "can't write field %d of record %d",
			     field, record);
		return 0;
	    }
	    break;
	case FTDouble:
	    double_value = PyFloat_AsDouble(value);
	    if (double_value == -1 && PyErr_Occurred())
		return 0;
	    if (!DBFWriteDoubleAttribute(handle, record, field, double_value))
	    {
		PyErr_Format(PyExc_IOError,
			     "can't write field %d of record %d",
			     field, record);
		return 0;
	    }
	    break;
	default:
	    PyErr_Format(PyExc_TypeError, "Invalid field data type %d", type);
	    return 0;
	}
    }
    return 1;
}
static
PyObject *
DBFInfo_write_record(DBFHandle handle, int record, PyObject *record_object)
{
    int num_fields;
    int i, length;
    int type, width;
    char name[12];
    PyObject * value = NULL;
    num_fields = DBFGetFieldCount(handle);
    /* We used to use PyMapping_Check to test whether record_object is a
     * dictionary like object instead of PySequence_Check to test
     * whether it's a sequence. Unfortunately in Python 2.3
     * PyMapping_Check returns true for lists and tuples too so the old
     * approach doesn't work anymore.
     */
    if (PySequence_Check(record_object))
    {
	/* It's a sequence object. Iterate through all items in the
	 * sequence and write them to the appropriate field.
	 */
	length = PySequence_Length(record_object);
	if (length != num_fields)
	{
	    PyErr_SetString(PyExc_TypeError,
			    "record must have one item for each field");
	    goto fail;
	}
	for (i = 0; i < length; i++)
	{
	    type = DBFGetFieldInfo(handle, i, name, &width, NULL); 
	    value = PySequence_GetItem(record_object, i);
	    if (value)
	    {
		if (!write_field(handle, record, i, type, value))
		    goto fail;
		Py_DECREF(value);
	    }
	    else
	    {
		goto fail;
	    }
	}
    }
    else
    {
	/* It's a dictionary-like object. Iterate over the names of the
         * known fields and write the corresponding item
	 */
	for (i = 0; i < num_fields; i++)
	{
	    type = DBFGetFieldInfo(handle, i, name, &width, NULL);
	    /* if the dictionary has the key name write that object to
	     * the appropriate field, other wise just clear the python
	     * exception and do nothing.
	     */
	    value = PyMapping_GetItemString(record_object, name);
	    if (value)
	    {
		if (!write_field(handle, record, i, type, value))
		    goto fail;
		Py_DECREF(value);
	    }
	    else
	    {
		PyErr_Clear();
	    }
	}
    }
    Py_INCREF(Py_None);
    return Py_None;
 fail:
    Py_XDECREF(value);
    return NULL;
}
%}
/* The commit method implementation
 *
 * The method relies on the DBFUpdateHeader method which is not
 * available in shapelib <= 1.2.10.  setup.py defines
 * HAVE_UPDATE_HEADER's value depending on whether the function is
 * available in the shapelib version the code is compiled with.
 */
%{
static
void
DBFInfo_commit(DBFHandle handle)
{
#if HAVE_UPDATE_HEADER
    DBFUpdateHeader(handle);
#endif
}
%} 
/*
 * The SWIG Interface definition.
 */
/* include some common SWIG type definitions and standard exception
   handling code */
%include typemaps.i
%include exception.i
/* As for ShapeFile in shapelib.i, We define a new C-struct that holds
 * the DBFHandle. This is mainly done so we can separate the close()
 * method from the destructor but it also helps with exception handling.
 *
 * After the DBFFile has been opened or created the handle is not NULL.
 * The close() method closes the file and sets handle to NULL as an
 * indicator that the file has been closed.
 */
%{
    typedef struct {
	DBFHandle handle;
    } DBFFile;
%}
/* The first argument to the DBFFile methods is a DBFFile pointer.
 * We have to check whether handle is not NULL in most methods but not
 * all. In the destructor and the close method, it's OK for handle to be
 * NULL. We achieve this by checking whether the preprocessor macro
 * NOCHECK_$name is defined. SWIG replaces $name with the name of the
 * function for which the code is inserted. In the %{,%}-block below we
 * define the macros for the destructor and the close() method.
 */
%typemap(python,check) DBFFile *{
%#ifndef NOCHECK_$name
    if (!$target || !$target->handle)
	SWIG_exception(SWIG_TypeError, "dbffile already closed");
%#endif
}
%{
#define NOCHECK_delete_DBFFile
#define NOCHECK_DBFFile_close
%}
/* An exception handle for the constructor and the module level open()
 * and create() functions.
 *
 * Annoyingly, we *have* to put braces around the SWIG_exception()
 * calls, at least in the python case, because of the way the macro is
 * written. Of course, always putting braces around the branches of an
 * if-statement is often considered good practice.
 */
%typemap(python,except) DBFFile * {
    $function;
    if (!$source)
    {
    	SWIG_exception(SWIG_MemoryError, "no memory");
    }
    else if (!$source->handle)
    {
	SWIG_exception(SWIG_IOError, "$name failed");
    }
}
/* Exception handler for the add_field method */
%typemap(python,except) int DBFFile_add_field {
    $function;
    if ($source < 0)
    {
    	SWIG_exception(SWIG_RuntimeError, "add_field failed");
    }
}
/* define and use some typemaps for the field_info() method whose
 * C-implementation has three output parameters that are returned
 * through pointers passed into the function. SWIG already has
 * definitions for common types such as int* and we can use those for
 * the last two parameters:
 */
%apply int * OUTPUT { int * output_width }
%apply int * OUTPUT { int * output_decimals }
/* the fieldname has to be defined manually: */
%typemap(python,ignore) char *fieldname_out(char temp[12]) {
    $target = temp;
}
%typemap(python,argout) char *fieldname_out() {
    PyObject * string = PyString_FromString($source);
    $target = t_output_helper($target,string);
}
/*
 * The SWIG-version of the DBFFile struct 
 */
typedef	struct
{
    %addmethods {
	DBFFile(const char *file, const char * mode = "rb") {
	    DBFFile * self = malloc(sizeof(DBFFile));
	    if (self)
		self->handle = DBFOpen(file, mode);
	    return self;
	}
	    
	~DBFFile() {
	    if (self->handle)
		DBFClose(self->handle);
	    free(self);
	}
	void close() {
	    if (self->handle)
		DBFClose(self->handle);
	    self->handle = NULL;
	}
	int field_count() {
	    return DBFGetFieldCount(self->handle);
	}
	int record_count() {
	    return DBFGetRecordCount(self->handle);
	}
	int field_info(int iField, char * fieldname_out,
		       int * output_width, int * output_decimals) {
	    return DBFGetFieldInfo(self->handle, iField, fieldname_out,
				   output_width, output_decimals);
	}
	    
	PyObject * read_record(int record) {
	    return DBFInfo_read_record(self->handle, record);
	}
	PyObject * read_attribute(int record, int field) {
	    return DBFInfo_read_attribute(self->handle, record, field);
	}
	int add_field(const char * pszFieldName, DBFFieldType eType,
		      int nWidth, int nDecimals) {
	    return DBFAddField(self->handle, pszFieldName, eType, nWidth,
			       nDecimals);
	}
	PyObject *write_record(int record, PyObject *dict_or_sequence) {
	    return DBFInfo_write_record(self->handle, record,
					dict_or_sequence);
	}
	void commit() {
	    DBFInfo_commit(self->handle);
	}
	/* Delete the commit method from the class if it doesn't have a
	 * real implementation.
	 */
	%pragma(python) addtomethod="__class__:if not dbflibc._have_commit: del commit"
	/* The __del__ method generated by the old SWIG version we're
	 * tries to access self.thisown which may not be set at all when
	 * there was an exception during construction.  Therefore we
	 * override it with our own version.
	 * FIXME: It would be better to upgrade to a newer SWIG version
	 * or to get rid of SWIG entirely.
	 */
	%pragma(python) addtoclass = "
    def __del__(self,dbflibc=dbflibc):
        if getattr(self, 'thisown', 0):
            dbflibc.delete_DBFFile(self)
    "
    }
} DBFFile;
/*
 * Two module level functions, open() and create() that correspond to
 * DBFOpen and DBFCreate respectively. open() is equivalent to the
 * DBFFile constructor.
 */
%{
    DBFFile * open_DBFFile(const char * file, const char * mode)
    {
	DBFFile * self = malloc(sizeof(DBFFile));
	if (self)
	    self->handle = DBFOpen(file, mode);
	return self;
    }
%}
%name(open) %new DBFFile * open_DBFFile(const char * file,
					const char * mode = "rb");
%{
    DBFFile * create_DBFFile(const char * file)
    {
	DBFFile * self = malloc(sizeof(DBFFile));
	if (self)
	    self->handle = DBFCreate(file);
	return self;
    }
%}
%name(create) %new DBFFile * create_DBFFile(const char * file);
/* constant definitions copied from shapefil.h */
typedef enum {
  FTString,
  FTInteger,
  FTDouble,
  FTInvalid
} DBFFieldType;
/* Put the value of the HAVE_UPDATE_HEADER preprocessor macro into the
 * wrapper so that the __class__ pragma above knows when to remove the
 * commit method
 */
const int _have_commit = HAVE_UPDATE_HEADER;