/* 
   Unix SMB/CIFS implementation.

   Swig interface to tdb.

   Copyright (C) 2004-2006 Tim Potter <tpot@samba.org>
   Copyright (C) 2007 Jelmer Vernooij <jelmer@samba.org>

     ** NOTE! The following LGPL license applies to the tdb
     ** library. This does NOT imply that all of Samba is released
     ** under the LGPL
   
   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 3 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/

%define DOCSTRING
"TDB is a simple key-value database similar to GDBM that supports multiple writers."
%enddef

%module(docstring=DOCSTRING) tdb

%{

/* This symbol is used in both includes.h and Python.h which causes an
   annoying compiler warning. */

#ifdef HAVE_FSTAT
#undef HAVE_FSTAT
#endif

/* Include tdb headers */
#include <stdint.h>
#include <signal.h>
#include <tdb.h>
#include <fcntl.h>

typedef TDB_CONTEXT tdb;
%}

/* The tdb functions will crash if a NULL tdb context is passed */

%import exception.i
%import stdint.i

%typemap(check,noblock=1) TDB_CONTEXT* {
	if ($1 == NULL)
		SWIG_exception(SWIG_ValueError, 
			"tdb context must be non-NULL");
}

/* In and out typemaps for the TDB_DATA structure.  This is converted to
   and from the Python string type which can contain arbitrary binary
   data.. */

%typemap(in,noblock=1) TDB_DATA {
    if ($input == Py_None) {
        $1.dsize = 0;
        $1.dptr = NULL;
    } else if (!PyString_Check($input)) {
		PyErr_SetString(PyExc_TypeError, "string arg expected");
		return NULL;
	} else {
        $1.dsize = PyString_Size($input);
        $1.dptr = (uint8_t *)PyString_AsString($input);
    }
}

%typemap(out,noblock=1) TDB_DATA {
	if ($1.dptr == NULL && $1.dsize == 0) {
		$result = Py_None;
	} else {
		$result = PyString_FromStringAndSize((const char *)$1.dptr, $1.dsize);
		free($1.dptr);
    }
}

/* Treat a mode_t as an unsigned integer */
typedef int mode_t;

/* flags to tdb_store() */
%constant int REPLACE = TDB_REPLACE;
%constant int INSERT = TDB_INSERT;
%constant int MODIFY = TDB_MODIFY;

/* flags for tdb_open() */
%constant int DEFAULT = TDB_DEFAULT;
%constant int CLEAR_IF_FIRST = TDB_CLEAR_IF_FIRST;
%constant int INTERNAL = TDB_INTERNAL;
%constant int NOLOCK = TDB_NOLOCK;
%constant int NOMMAP = TDB_NOMMAP;
%constant int CONVERT = TDB_CONVERT;
%constant int BIGENDIAN = TDB_BIGENDIAN;

enum TDB_ERROR {
     TDB_SUCCESS=0, 
     TDB_ERR_CORRUPT, 
     TDB_ERR_IO, 
     TDB_ERR_LOCK, 
     TDB_ERR_OOM, 
     TDB_ERR_EXISTS, 
     TDB_ERR_NOLOCK, 
     TDB_ERR_LOCK_TIMEOUT,
     TDB_ERR_NOEXIST, 
     TDB_ERR_EINVAL, 
     TDB_ERR_RDONLY
};

%rename(lock_all) tdb_context::lockall;
%rename(unlock_all) tdb_context::unlockall;

%rename(read_lock_all) tdb_context::lockall_read;
%rename(read_unlock_all) tdb_context::unlockall_read;

%typemap(default,noblock=1) int tdb_flags {
    $1 = TDB_DEFAULT;
}

%typemap(default,noblock=1) int flags {
    $1 = O_RDWR;
}

%typemap(default,noblock=1) int hash_size {
    $1 = 0;
}

%typemap(default,noblock=1) mode_t mode {
    $1 = 0600;
}

%typemap(default,noblock=1) int flag {
    $1 = TDB_REPLACE;
}

%rename(Tdb) tdb_context;
%feature("docstring") tdb_context "A TDB file.";
%typemap(out,noblock=1) tdb * {
    /* Throw an IOError exception from errno if tdb_open() returns NULL */
    if ($1 == NULL) {
        PyErr_SetFromErrno(PyExc_IOError);
        SWIG_fail;
    }
    $result = SWIG_NewPointerObj($1, $1_descriptor, 0);
}

typedef struct tdb_context {
    %extend {
        %feature("docstring") tdb "S.__init__(name,hash_size=0,tdb_flags=TDB_DEFAULT,flags=O_RDWR,mode=0600)\n"
                                  "Open a TDB file.";
        tdb(const char *name, int hash_size, int tdb_flags, int flags, mode_t mode) {
            return tdb_open(name, hash_size, tdb_flags, flags, mode);
        }
        %feature("docstring") error "S.error() -> int\n"
                                    "Find last error number returned by operation on this TDB.";
        enum TDB_ERROR error();
        ~tdb() { tdb_close($self); }
        %feature("docstring") close "S.close() -> None\n"
                                    "Close the TDB file.";
        int close();
        int append(TDB_DATA key, TDB_DATA new_dbuf);
        %feature("docstring") errorstr "S.errorstr() -> errorstring\n"
                                        "Obtain last error message.";
        const char *errorstr();
        %rename(get) fetch;
        %feature("docstring") fetch "S.fetch(key) -> value\n"
                                        "Fetch a value.";
        TDB_DATA fetch(TDB_DATA key);
        %feature("docstring") delete "S.delete(key) -> None\n"
                                        "Delete an entry.";
        int delete(TDB_DATA key);
        %feature("docstring") store "S.store(key, value, flag=TDB_REPLACE) -> None\n"
                                        "Store an entry.";
        int store(TDB_DATA key, TDB_DATA dbuf, int flag);
        %feature("docstring") exists "S.exists(key) -> bool\n"
                                        "Check whether key exists in this database.";
        int exists(TDB_DATA key);
        %feature("docstring") firstkey "S.firstkey() -> data\n"
                                        "Return the first key in this database.";
        TDB_DATA firstkey();
        %feature("docstring") nextkey "S.nextkey(prev) -> data\n"
                                        "Return the next key in this database.";
        TDB_DATA nextkey(TDB_DATA key);
        %feature("docstring") lockall "S.lockall() -> bool";
        int lockall();
        %feature("docstring") unlockall "S.unlockall() -> bool";
        int unlockall();
        %feature("docstring") unlockall "S.lockall_read() -> bool";
        int lockall_read();
        %feature("docstring") unlockall "S.unlockall_read() -> bool";
        int unlockall_read();
        %feature("docstring") reopen "S.reopen() -> bool\n"
                                        "Reopen this file.";
        int reopen();
        %feature("docstring") transaction_start "S.transaction_start() -> None\n"
                                        "Start a new transaction.";
        int transaction_start();
        %feature("docstring") transaction_commit "S.transaction_commit() -> None\n"
                                        "Commit the currently active transaction.";
        int transaction_commit();
        %feature("docstring") transaction_cancel "S.transaction_cancel() -> None\n"
                                        "Cancel the currently active transaction.";
        int transaction_cancel();
        int transaction_recover();
        %feature("docstring") hash_size "S.hash_size() -> int";
        int hash_size();
        %feature("docstring") map_size "S.map_size() -> int";
        size_t map_size();
        %feature("docstring") get_flags "S.get_flags() -> int";
        int get_flags();
        %feature("docstring") set_max_dead "S.set_max_dead(int) -> None";
        void set_max_dead(int max_dead);
        %feature("docstring") name "S.name() -> path\n" \
                                   "Return filename of this TDB file.";
        const char *name();
    }

    %pythoncode {
    def __repr__(self):
        return "Tdb('%s')" % self.name()

    # Random access to keys, values
    def __getitem__(self, key):
        result = self.get(key)
        if result is None:
            raise KeyError, '%s: %s' % (key, self.errorstr())
        return result

    def __setitem__(self, key, item):
        if self.store(key, item) == -1:
            raise IOError, self.errorstr()

    def __delitem__(self, key):
        if not self.exists(key):
            raise KeyError, '%s: %s' % (key, self.errorstr())
        self.delete(key)

    def __contains__(self, key):
        return self.exists(key) != 0

    def has_key(self, key):
        return self.exists(key) != 0

    def fetch_uint32(self, key):
        data = self.get(key)
        if data is None:
            return None
        import struct
        return struct.unpack("<L", data)[0]

    def fetch_int32(self, key):
        data = self.get(key)
        if data is None:
            return None
        import struct
        return struct.unpack("<l", data)[0]

    # Tdb iterator
    class TdbIterator:
        def __init__(self, tdb):
            self.tdb = tdb
            self.key = None

        def __iter__(self):
            return self
            
        def next(self):
            if self.key is None:
                self.key = self.tdb.firstkey()
                if self.key is None:
                    raise StopIteration
                return self.key
            else:
                self.key = self.tdb.nextkey(self.key)
                if self.key is None:
                    raise StopIteration
                return self.key

    def __iter__(self):
        return self.TdbIterator(self)

    # Implement other dict functions using TdbIterator

    def keys(self):
        return [k for k in iter(self)]

    def values(self):
        return [self[k] for k in iter(self)]

    def items(self):
        return [(k, self[k]) for k in iter(self)]

    def __len__(self):
        return len(self.keys())

    def clear(self):
        for k in iter(self):
            del(self[k])

    def iterkeys(self):
        for k in iter(self):
            yield k
       
    def itervalues(self):
        for k in iter(self):
            yield self[k]

    def iteritems(self):
        for k in iter(self):
            yield (k, self[k])

    # TODO: any other missing methods for container types
    }
} tdb;