#include <cctype>
#include <cstdlib>
#include <cstdio>
#include <map>
#include <string>
#include <setjmp.h>

#include "pydim_utils.h"

extern "C"
{
#include "dic.h"
#include "dis.h"
#include "dim_common.h"
#undef SIZEOF_LONG /* conflicts with Python.h */
#define PY_SSIZE_T_CLEAN
#include <Python.h>
}
#define DIMC_MODULE

#ifndef _WIN32
#include <unistd.h>
#include <pthread.h>
#else
#define HOST_NAME_MAX 256
#include <Winsock2.h>
#include <processthreadsapi.h>
#endif

using namespace std;
static char server_name[HOST_NAME_MAX + 1];
typedef void *func(int *);

/******************************************************************************/
/* DIS containers for callbacks*/
/******************************************************************************/

typedef struct
{
  char *name;
  char *format;
  long pyTag;
  PyObject *pyFunc;
} CmndCallback;
typedef CmndCallback *CmndCallbackPtr;

map<string, CmndCallbackPtr> cmndName2PythonFunc;
map<long, CmndCallbackPtr> cmndUniqueTag2PythonFunc;

typedef struct
{
  char *name;
  char *format;
  char *buffer;
  unsigned int bufferSize;
  bool isUpdated;
  bool firstCall;
  long pyTag;
  PyObject *pyFunc;
} ServiceCallback;

typedef ServiceCallback *ServiceCallbackPtr;
map<unsigned int, ServiceCallbackPtr> serviceID2Callback;

static void dim_callbackCommandFunc(void *, void *, int *);

/******************************************************************************/
/* DIC containers for callbacks and function prototypes                       */
/******************************************************************************/
struct _dic_info_service_callback
{
  PyObject *pyFunc;
  char *name;
  char *format;
  PyObject *pyDefaultArg;
  long pyTag;
  int service_id;
};

struct _dic_cmnd_callback
{
  PyObject *pyFunc;
  long pyTag;
};

unsigned long _dic_cmnd_callback_ID;

static map<string, _dic_info_service_callback *> _dic_info_service_name2Callback;
static map<unsigned int, _dic_info_service_callback *> _dic_info_service_id2Callback;
static map<long, _dic_cmnd_callback *> _dic_cmnd_callback_tag2Callback;

void _dic_error_user_routine_dummy(int, int, char *);
void _dic_info_service_dummy(void *, void *, int *);
void _dic_cmnd_callback_dummy(void *, int *);
void _dic_error_user_routine_dummy(int, int, char *);

/******************************************************************************/

/** \defgroup dim DIM interface functions
 * @{
 */

static PyObject *
dim_dis_start_serving(PyObject * /* self */, PyObject *args)
{
  /** Calls dis_start_serving.
   * @param server_name The name under which the server is going to be
   * registered in the DIM DNS. If not specified the hostname is used.
   */
  char *name = NULL;
  int ret;

  if (!PyArg_ParseTuple(args, "|s", &name))
  {
    PyErr_SetString(PyExc_RuntimeError, "Invalid server name.");
    return NULL;
  }
  if (name)
    strncpy(server_name, name, HOST_NAME_MAX - 1);
  else
  {
    gethostname(server_name, HOST_NAME_MAX);
    debug("No server name specified. Using machine hostname...\n");
  }
  Py_BEGIN_ALLOW_THREADS
      ret = dis_start_serving(server_name);
  Py_END_ALLOW_THREADS

      return Py_BuildValue("i", ret);
}

static PyObject *
dim_dis_stop_serving(PyObject * /* self */, PyObject * /* args */)
{
  /** Calls void dis_stop_serving(void)
   */
  Py_BEGIN_ALLOW_THREADS
  dis_stop_serving();
  Py_END_ALLOW_THREADS
      Py_RETURN_NONE;
}

static PyObject *
dim_dis_set_dns_node(PyObject * /* self */, PyObject *args)
{
  /**
   * Calls dis_set_dns_node(char* node)
   *
   * @param dns_node_name The name of the DNS server
   * @return DIM return code (1 for success)
   */
  char *name = NULL;
  int ret;

  if (!PyArg_ParseTuple(args, "s", &name))
  {
    PyErr_SetString(PyExc_RuntimeError, "Invalid DNS name");
    return NULL;
  }
  ret = dis_set_dns_node(name);

  return Py_BuildValue("i", ret);
}

static PyObject *
dim_dis_get_dns_node(PyObject * /* self */, PyObject * /* args */)
{
  /* calls dis_get_dns_node(char* node)
     the function should return the DNS node
     */
  char names[256];
  if (!dis_get_dns_node(names))
  {
    PyErr_SetString(PyExc_RuntimeError, "Failed to get DNS node name");
    return NULL;
  }
  return Py_BuildValue("s", names);
}

static PyObject *
dim_dis_set_dns_port(PyObject * /* self */, PyObject *args)
{
  /**
   * Calls dis_set_dns_port(int port).
   *
   * @param dim_dns_port (unsigned integer)
   * @return DIM return code (1 for success)
   */
  unsigned int port;
  int ret;

  if (!PyArg_ParseTuple(args, "I", &port))
  {
    PyErr_SetString(PyExc_TypeError,
                    "Argument 'port' must be a pozitive integer");
    return NULL;
  }
  ret = dis_set_dns_port(port);

  return Py_BuildValue("i", ret);
}

static PyObject *
dim_dis_get_dns_port(PyObject * /* self */, PyObject * /* args */)
{
  /**
   * Calls dis_get_dns_port().
   * @return port The DIM DNS port.
   */
  int port;
  port = dis_get_dns_port();
  return Py_BuildValue("i", port);
}

typedef struct
{
  PyObject *self;
  PyObject *func;
} PyCallback; // for Python callbacks

static PyCallback dis_callbackExitHandler_func,
    dis_callbackErrorHandler_func,
    dis_callbackClientExitHandler_func;

static PyCallback _dic_callback_errorHandler_func;

static void
dim_dis_callbackExitHandler(int *code)
{
  /**
   * NOTE: this function does not have the interpretor lock when called.
   * @param code The exit code passed by the DIM library.
   */
  PyObject *arg, *res;
  PyGILState_STATE gstate;

  if (dis_callbackExitHandler_func.func)
  {
    gstate = PyGILState_Ensure();
    arg = Py_BuildValue("i", *code);
    res = PyObject_Call(dis_callbackExitHandler_func.func, arg, NULL);
    Py_DECREF(arg);
    Py_XDECREF(res);
    PyGILState_Release(gstate);
  }
  else
  {
    debug("Could not find any registered Python function. "
          "Dropping DIM exit callback.");
  }
}

static void
dim_dis_callbackErrorHandler(int severity, int error_code, char *message)
{
  /**
   * NOTE: this function does not have the interpretor lock when called.
   * @param code The error code passed by the DIM library.
   */
  PyObject *arg, *res;
  PyGILState_STATE gstate;

  if (dis_callbackErrorHandler_func.func)
  {
    gstate = PyGILState_Ensure();
    arg = Py_BuildValue("iis", severity, error_code, message);
    // res = PyEval_CallObject(dis_callbackErrorHandler_func.func, arg);
    res = PyObject_CallFunctionObjArgs(dis_callbackErrorHandler_func.func, arg, NULL);
    Py_DECREF(arg);
    Py_XDECREF(res);
    PyGILState_Release(gstate);
  }
  else
  {
    debug("Could not find any registered Python function. "
          "Dropping DIM error callback.");
  }
}

static void
dim_dis_callbackClientExitHandler(int *tag)
{
  /*Interface function with signature: void client_exit_user_routine (int* tag)
    Calls the associated Python function.
    */
  PyObject *arg, *res;
  PyGILState_STATE gstate;

  if (dis_callbackClientExitHandler_func.func)
  {
    gstate = PyGILState_Ensure();
    arg = Py_BuildValue("i", *tag);
    res = PyObject_Call(dis_callbackClientExitHandler_func.func, arg, NULL);
    Py_DECREF(arg);
    Py_XDECREF(res);
    PyGILState_Release(gstate);
  }
  else
  {
    debug("Could not find any registered Python function. "
          "Dropping DIM client exit callback.");
  }
}

static PyObject *
dim_dis_add_exit_handler(PyObject *self, PyObject *args)
{
  /**
   * Calls dis_add_exit_handler
   * @param callback A callable Python object.
   */
  PyObject *temp;

  if (!PyArg_ParseTuple(args, "O:set_callback", &temp) ||
      !PyCallable_Check(temp))
  {
    PyErr_SetString(PyExc_TypeError, "Expected a callable Python object");
    return NULL;
  }

  Py_XINCREF(temp);
  Py_XINCREF(self);
  /* Dispose of previous callback */
  Py_XDECREF(dis_callbackExitHandler_func.self);
  Py_XDECREF(dis_callbackExitHandler_func.func);
  dis_callbackExitHandler_func.self = self;
  dis_callbackExitHandler_func.func = temp;
  dis_add_exit_handler(dim_dis_callbackExitHandler);

  Py_RETURN_NONE;
}

static PyObject *
dim_dis_add_error_handler(PyObject *self, PyObject *args)
{
  /**
   * Calls dis_add_error_handler
   * @param callback A callable Python object.
   */
  PyObject *temp;

  if (!PyArg_ParseTuple(args, "O:set_callback", &temp) ||
      !PyCallable_Check(temp))
  {
    PyErr_SetString(PyExc_TypeError, "Expected a callable Python object");
    return NULL;
  }
  /* Add a reference to new callback */
  Py_XINCREF(temp);
  Py_XINCREF(self);
  /* Dispose of previous callback */
  Py_XDECREF(dis_callbackErrorHandler_func.self);
  Py_XDECREF(dis_callbackErrorHandler_func.func);
  dis_callbackErrorHandler_func.self = self;
  dis_callbackErrorHandler_func.func = temp;
  dis_add_error_handler(dim_dis_callbackErrorHandler);

  Py_RETURN_NONE;
}

static PyObject *
dim_dis_add_client_exit_handler(PyObject *self, PyObject *args)
{
  /**
   * Calls dis_add_client_exit_handler
   * @param callback A callable Python object.
   */
  PyObject *temp;

  if (!PyArg_ParseTuple(args, "O:set_callback", &temp) ||
      !PyCallable_Check(temp))
  {
    PyErr_SetString(PyExc_TypeError, "Expected a callable Python object");
    return NULL;
  }
  /* Add a reference to new callback */
  Py_XINCREF(temp);
  Py_XINCREF(self);
  /* Dispose of previous callback */
  Py_XDECREF(dis_callbackClientExitHandler_func.self);
  Py_XDECREF(dis_callbackClientExitHandler_func.func);
  dis_callbackClientExitHandler_func.self = self;
  dis_callbackClientExitHandler_func.func = temp;
  dis_add_client_exit_handler(dim_dis_callbackClientExitHandler);

  Py_RETURN_NONE;
}

static PyObject *
dim_dis_selective_update_service(PyObject * /* self */, PyObject *args)
{
  /**
   *  Calls int dis_selective_update_service (int service_id, int** client_ids)
   */
  int *client_ids = NULL, res;
  PyObject *listOrTuple;
  int service_id;

  if (!PyArg_ParseTuple(args, "iO;list or tuple", &service_id, &listOrTuple))
  {
    PyErr_SetString(PyExc_TypeError,
                    "Invalid arguments: expected and integer and a list/tuple of integers");
    return NULL;
  }
  if (!listOrTuple2Int(listOrTuple, &client_ids))
  {
    PyErr_SetString(PyExc_TypeError,
                    "Second argument must a list/tuple of integers");
    return NULL;
  }
  res = dis_selective_update_service(service_id, client_ids);

  return Py_BuildValue("i", res);
}

static PyObject *
dim_dis_set_quality(PyObject * /* self */, PyObject *args)
{
  /**
   * Calls void dis_set_quality (service_id, quality)
   * @param service_id The service ID returned by dis_add_service().
   * @param quality A flag for the quality of a service.
   */
  unsigned int service_id;
  int quality;

  if (!PyArg_ParseTuple(args, "Ii", &service_id, &quality))
  {
    PyErr_SetString(PyExc_TypeError,
                    "Invalid arguments: expected an unsigned integer and an integer");
    return NULL;
  }
  dis_set_quality(service_id, quality);

  Py_RETURN_NONE;
}

static PyObject *
dim_dis_set_timestamp(PyObject * /* self */, PyObject *args)
{
  /**
   * Calls void dis_set_timestamp(unsigned int service_id,
   *                              int secs,
   *                              int milisecs)
   * @param service_id
   * @param secs
   * @param milisecs
   */
  unsigned int service_id;
  int secs, milisecs;

  if (!PyArg_ParseTuple(args, "Iii", &service_id, &secs, &milisecs))
  {
    PyErr_SetString(PyExc_TypeError,
                    "Invalid arguments: expected an unsigned integer and two integers");
    return NULL;
  }
  dis_set_timestamp(service_id, secs, milisecs);

  Py_RETURN_NONE;
}

static PyObject *
dim_dis_remove_service(PyObject * /* self */, PyObject *args)
{
  /**
   * Call int dis_remove_service (unsigned int service_id)
   * @param service_id
   */
  unsigned int service_id;
  int res;

  if (!PyArg_ParseTuple(args, "I", &service_id))
  {
    PyErr_SetString(PyExc_TypeError,
                    "Invalid argument: expected an unsigned integer");
    return NULL;
  }
  res = dis_remove_service(service_id);

  return Py_BuildValue("i", res);
}

static PyObject *
dim_dis_get_next_cmnd(PyObject * /* self */, PyObject *args)
{
  /**
   * Calls int dis_get_next_cmnd (long* tag, int* buffer, int* size)
   *
   * TODO: Make sure this actually works
   * Problem: the way the results are received is not done in a "pythonic" way.
   * Better to return a tuple with all the results.
   *
   * INFO: the DIM function implementation does not define a value for buffer,
   * size or tag in case a command is not found.
   *
   * @param size The maximum size of the data received by the command
   */
  int res = 0, *buffer, size;
  long tag = 0;
  PyObject *tmp;

  if (!PyArg_ParseTuple(args, "I", &size))
  {
    PyErr_SetString(PyExc_TypeError,
                    "Invalid argument: expected an unsigned integer");
    return NULL;
  }
  buffer = (int *)calloc(size * sizeof(int), 1);
  // bzero(buffer,size);

  res = dis_get_next_cmnd((dim_long *)&tag, buffer, &size);
  printf("res = %d\n", res);
  tmp = Py_BuildValue("(iis#)", res, tag, buffer, size);
  free(buffer);

  return tmp;
}

static PyObject *
dim_dis_get_client(PyObject * /* self */, PyObject *args)
{
  /**
   * Calls: int dis_get_client (char* name)
   */
  char *name;
  int res;

  if (!PyArg_ParseTuple(args, "s", &name))
  {
    PyErr_SetString(PyExc_TypeError, "Invalid argument: expected an string");
    return NULL;
  }
  res = dis_get_client(name);

  return Py_BuildValue("i", res);
}

static PyObject *
dim_dis_get_conn_id(PyObject * /* self */, PyObject * /* args */)
{
  /**
   * Calls: int dis_get_conn_id()
   */
  int res;
  res = dis_get_conn_id();
  return Py_BuildValue("i", res);
}

static PyObject *
dim_dis_get_timeout(PyObject * /* self */, PyObject *args)
{
  /**
   * Calls: int dis_get_timeout (unsigned int service_id, int client_id)
   * @param service_id The service_id returned by dis_add_service or by
   * dis_add_cmnd.
   * @param client_id The connection ID of a DIM client as obtained by
   * the routine dis_get_conn_id.
   */
  unsigned int service_id;
  int client_id, res;

  if (!PyArg_ParseTuple(args, "Ii", &service_id, &client_id))
  {
    PyErr_SetString(PyExc_TypeError,
                    "Invalid argument: expected an unsigned int and an int");
    return NULL;
  }
  res = dis_get_timeout(service_id, client_id);

  return Py_BuildValue("i", res);
}

static PyObject *
dim_dis_get_client_services(PyObject * /* self */, PyObject *args)
{
  /**
   * Calls: char* dis_get_client_services (int conn_id)
   *
   * @param conn_id The connection ID to a client.
   */
  char *res = NULL;
  int conn_id;

  if (!PyArg_ParseTuple(args, "i", &conn_id))
  {
    PyErr_SetString(PyExc_TypeError, "Invalid argument: expected an int");
    return NULL;
  }
  res = dis_get_client_services(conn_id);

  return Py_BuildValue("s", res);
}

static PyObject *
dim_dis_set_client_exit_handler(PyObject * /* self */, PyObject *args)
{
  /**
   * Calls: void dis_set_client_exit_handler (int conn_id, int tag)
   */
  int conn_id, tag;

  if (!PyArg_ParseTuple(args, "ii", &conn_id, &tag))
  {
    PyErr_SetString(PyExc_TypeError, "Invalid argument: expected two ints");
    return NULL;
  }
  dis_set_client_exit_handler(conn_id, tag);

  Py_RETURN_NONE;
}

static PyObject *
dim_dis_get_error_services(PyObject * /* self */, PyObject * /* args */)
{
  /**
   * Calls: char* dis_get_error_services (int conn_id)
   */
  char *res = NULL;
  res = dis_get_error_services();
  return Py_BuildValue("s", res);
}

static PyObject *
dim_dis_add_cmnd(PyObject * /* self */, PyObject *args)
{
  /* @param name
   * @param description
   * @param py_function
   * @param tag
   * @return DIM integer status code (1 for success)
   *
   * Implementation details: in order to preserve the C interface, unique
   * tags are generated for each call. The original tags are preserved with
   * the python function pointer and passed back when doing a callback.
   *
   * Proxy function for:
   *         unsigned int dis_add_cmnd(name,description,cmnd_user_routine,tag)
   * Where:
   *         char* name - the name of the command. Will be used for identifing the
   *                 python callback
   *         char* description - the command format
   *         void cmnd_user_routine (long* tag, int* address, int* size) - The
   *                 long pointer will be transformed in A long Python object
   *                 for the callback. The address will become a string long
   *                 tag - will be passed back to the Python callback
   */
  unsigned int res = 0;
  char *name = NULL, *format = NULL;
  long tag;
  int sizeFormat, sizeName;
  PyObject *pyFunc;
  CmndCallback *callback, *oldCallback;
  string s;

  if (!PyArg_ParseTuple(args, "s#s#O|l",
                        &name, &sizeName, &format, &sizeFormat, &pyFunc, &tag) ||
      !PyCallable_Check(pyFunc))
  {
    PyErr_SetString(PyExc_TypeError,
                    "Invalid arguments: expected two strings, "
                    "a callable object and an integer");
    return NULL;
  }
  debug("Adding command name %s, format %s and tag %p", name, format, tag);
  callback = (CmndCallback *)malloc(sizeof(CmndCallback));
  callback->format = (char *)malloc(sizeof(char) * (sizeFormat + 1));
  callback->name = (char *)malloc(sizeof(char) * (sizeName + 1));
  callback->pyFunc = pyFunc;
  Py_INCREF(callback->pyFunc);
  if (tag)
  {
    callback->pyTag = tag;
  }
  strcpy(callback->format, format);
  strcpy(callback->name, name);
  /* TODO: The following portion is inherently unsafe. If a command comes
   * between the adding the command to the lookup tables and the actual
   * DIM registration of the new command, then results are undefined. The
   * portion of code bellow relies at least that the type of the commands will
   * be the same. The alternative is to become 'smart' and remove the command
   * first using dis_remove_service and then add it again.
   * TODO: check that res below is actually <= 0 if a command is registered
   * twice.
   */
  oldCallback = cmndName2PythonFunc[name];
  cmndUniqueTag2PythonFunc[(long)callback] = callback;
  cmndName2PythonFunc[name] = callback;
  res = dis_add_cmnd(name, format, dim_callbackCommandFunc, (long)callback);
  if (oldCallback && res < 1)
  {
    Py_XDECREF(oldCallback->pyFunc);
    free(oldCallback->name);
    free(oldCallback->format);
    free(oldCallback);
  }

  return Py_BuildValue("i", res);
}

void serviceProxy(void *tagp, void **buf, int *size, int * /*first_time*/)
{
  /** Internal function for returning the data buffer for a service.
   * A DIM service functions in two ways:
   *   - by specifying a pointer to a fixed size structure at creation time
   *   - by specifying a callback that is done when a service is updated
   * In order to pass variable length parameters (the only really useful
   * situation), the second approach is needed. On the other hand, the user code
   * will become harder to understant if the parameters can only be supplied
   * by the means of a callback function. For this a DIM 'extension' was made
   * to allow variable lenght parameters in both cases:
   *   1. When a service is created on the C world, the pointer to the service
   * structure is registered as a tag. The actual tag given in python is saved
   * to the service data structure.
   *   2. In case the arguments are specified when the python function
   * dis_update_service is called, they are converted to a buffer and stored
   * in the service structure. The 'serviceProxy' method is going to be called
   * by the DIM 'dis_update_service' (or more exactly the internal funtion
   * 'execute_service') and 'serviceProxy' is going to take the necesarry steps
   * to return the python objects converted to a buffer
   *
   * @param tagp A pointer to the ServiceCallback structure.
   * @param buffer The buffer that should be sent by DIM
   * @param size The total size of the buffer
   * @param first_time Whether the service is called for the first time or not.
   */

  // the implicit assumption is that tagp is a pointer to a ServiceCallback
  ServiceCallback *svc = (ServiceCallback *)(*(long *)tagp);

  if (!svc->isUpdated || !svc->buffer)
  {
    /* what happens here? we have no data and we can't signal this to
     * DIM.Two options:
     *    - returning NULL + size 0
     *    - return some default data => unpredictable results client side
     */
    print("ERROR: You should not see this message! The service update has failed");
    debug("Could not get data to update service %s, pointer %x. Output buffer is %x with size %d, updated %d",
          svc->name, svc, (long)svc->buffer, svc->bufferSize, svc->isUpdated);
    svc->bufferSize = 0;
    if (svc->buffer)
    {
      free(svc->buffer);
      svc->buffer = NULL;
    }
  }
  else if (svc->isUpdated && svc->buffer)
  {
    /* nothing much to do there, we have everything already */
  }
  *buf = svc->buffer;
  *size = svc->bufferSize;
}

static PyObject *
dim_dis_add_service(PyObject * /* self */, PyObject *args)
{
  /**
   * Proxy function to:
   *        unsigned int dis_add_service (char* name,
   *                                      char* description,
   *                                      int*  address,
   *                                      int   size,
   *                                      func  user_routine,
   *                                      long tag)
   *        func = void user_routine(long* tag, int** address, int* size)
   *
   * Observation: the size of the command is calculated using the
   * description parameter so if size is omitted it is considered 1.
   *
   * @param name
   * @param description
   * @param python_function
   * @param tag
   */

  int name_size, format_size, service_id = 0;
  char *name, *format;
  long pyTag;
  PyObject *pyFunc;
  ServiceCallback *svc;

  if (!PyArg_ParseTuple(args, "s#s#Ol", &name,
                        &name_size,
                        &format,
                        &format_size,
                        &pyFunc,
                        &pyTag) ||
      !PyCallable_Check(pyFunc))
  {
    PyErr_SetString(PyExc_TypeError,
                    "Invalid arguments: expected two strings, a callable object and a long.");
    return NULL;
  }

  Py_INCREF(pyFunc);
  svc = (ServiceCallback *)malloc(sizeof(ServiceCallback));
  svc->name = (char *)malloc(sizeof(char) * (name_size + 1));
  svc->format = (char *)malloc(sizeof(char) * (format_size + 1));
  if (!svc || !svc->name || !svc->format)
    goto noMem;
  strcpy(svc->name, name);
  strcpy(svc->format, format);
  svc->pyTag = pyTag;
  svc->pyFunc = pyFunc;
  svc->buffer = NULL;
  svc->bufferSize = 0;

  service_id = dis_add_service(name,
                               format,
                               NULL,
                               0,
                               serviceProxy,
                               (long)svc);

  if (!service_id)
  {
    PyErr_SetString(PyExc_RuntimeError, "Could not create service");
    return NULL;
  }

  if (serviceID2Callback[service_id])
  {
    /* a service with the same name added again? */
    debug("Replacing service %s", svc->name);
    Py_XDECREF(serviceID2Callback[service_id]->pyFunc);
    free(serviceID2Callback[service_id]->name);
    free(serviceID2Callback[service_id]->format);
    free(serviceID2Callback[service_id]->buffer);
    free(serviceID2Callback[service_id]);
  }
  serviceID2Callback[service_id] = svc;
  debug("Service %s added successfully with pointer %x", svc->name, svc);

  return Py_BuildValue("i", service_id);

noMem:
  PyErr_SetString(PyExc_MemoryError, "Could not allocate memory");
  return NULL;
}

static PyObject *
dim_dis_update_service(PyObject * /* self */, PyObject *args)
{
  /**
   *  Calls: int dis_update_service (int service_id)
   */
  int service_id, res;

  PyObject *svc_args = NULL, *arg;
  ServiceCallbackPtr svc;
  PyGILState_STATE gstate;
  if (!PyArg_ParseTuple(args, "i|O", &service_id, &svc_args))
  {
    // if ( !PyArg_ParseTuple(args, "i", &service_id) ){
    PyErr_SetString(PyExc_TypeError,
                    "Argument error: incorect service ID");
    return NULL;
  }
  svc = serviceID2Callback[service_id];
  if (!svc)
  {
    // Service was not found, already deleted?
    PyErr_SetString(PyExc_RuntimeError,
                    "Service ID doesn't match any service");
    return NULL;
  }
  if (!svc_args)
  {
    if (!svc->pyFunc)
    {
      PyErr_SetString(PyExc_TypeError,
                      "No arguments and no callback function was given");
      return NULL;
    }
    gstate = PyGILState_Ensure();
    arg = Py_BuildValue("(i)", svc->pyTag);
    free(svc_args);
    svc_args = PyObject_Call(svc->pyFunc, arg, NULL);

    Py_DECREF(arg);
    // Py_DECREF(svc_args); // !!!!!
    PyGILState_Release(gstate);
    if (!svc_args)
    {
      /* there was an exception raised when calling the python function
       * returning NULL implies the exception will be propageted
       * back to the interpretor
       */
      print("Error in calling python function %p", svc->pyFunc);
      PyErr_Print();
      return NULL;
    }
  }
  else
  {
    /*
    PyArg_ParseTuple with an O object does NOT increase the reference count of the variable
    bound to it
    */
    Py_INCREF(svc_args);
  }
  /* NOTE: it might not be optimal to allocate a buffer each time */
  if (svc->buffer)
    free(svc->buffer);
  if (!iterator_to_allocated_buffer(svc_args,
                                    svc->format,
                                    &svc->buffer,
                                    &svc->bufferSize))
  {
    PyErr_SetString(PyExc_TypeError,
                    "Arguments do not match initial service format");
    return NULL;
  }
  Py_DECREF(svc_args);
  svc->isUpdated = 1;
  Py_BEGIN_ALLOW_THREADS
      res = dis_update_service(service_id);

  Py_END_ALLOW_THREADS

      return Py_BuildValue("i", res);
}

static void
dim_callbackCommandFunc(void *uTag, void *address, int *size)
{
  /** \brief Proxy function for passing the call to Python.
   * It is registered by default when a command service is
   * created.
   */
  CmndCallbackPtr pycall = (CmndCallbackPtr)(*(long *)uTag);

  PyObject *args, *res, *funargs;
  PyGILState_STATE gstate;

  gstate = PyGILState_Ensure();
  args = dim_buf_to_tuple(pycall->format, (char *)address, *size);
  if (args)
  {
    if (pycall->pyTag)
    {
      funargs = PyTuple_New(2);
      PyTuple_SET_ITEM(funargs, 0, args);
      PyTuple_SET_ITEM(funargs, 1, PyLong_FromLong(pycall->pyTag));
    }
    else
    {
      funargs = PyTuple_New(1);
      PyTuple_SET_ITEM(funargs, 0, args);
    }
    res = PyObject_CallObject(pycall->pyFunc, funargs);
    Py_DECREF(args);
    Py_DECREF(funargs);
    if (!res)
    {
      /* The Python function called threw an exception.
        We can't do much with it so we might as well print it */
      PyErr_Print();
    }
    else
    {
      Py_DECREF(res);
    }
  }
  else
  {
    print("Could not convert received DIM buffer to Python objects");
  }
  PyGILState_Release(gstate);
}

/******************************************************************************/
/* DIC interface functions */
/******************************************************************************/
static PyObject *
dim_dic_set_dns_node(PyObject * /* self */, PyObject *args)
{
  /**
   * Proxy function to:
   *         dic_set_dns_node(char* node)
   * @param dns_name The name of the new DNS.
   * @return ret_code The DIM return code (1 for success).
   */
  char *name = NULL;
  int i;

  if (!PyArg_ParseTuple(args, "s", &name))
  {
    PyErr_SetString(PyExc_TypeError, "Invalid DIM DNS name");
    return NULL;
  }
  i = dic_set_dns_node(name);

  return Py_BuildValue("i", i);
}

static PyObject *
dim_dic_get_dns_node(PyObject * /* self */, PyObject * /* args */)
{
  /* calls dic_get_dns_node(char* node)
     the function should return the DNS node
     */
  char names[256];

  if (!dic_get_dns_node(names))
  {
    PyErr_SetString(PyExc_TypeError,
                    "Could not get DIM DNS node name.");
    return NULL;
  }

  return Py_BuildValue("s", names);
}

static PyObject *
dim_dic_set_dns_port(PyObject * /* self */, PyObject *args)
{
  /**
   * Proxy function to:
   *        dic_set_dns_port(int port)
   * @return return_code The DIM return code (1 for success).
   */
  unsigned int port;
  int i;

  if (!PyArg_ParseTuple(args, "I", &port))
  {
    PyErr_SetString(PyExc_TypeError,
                    "Invalid argument: expected a pozitive integer");
    return NULL;
  }
  i = dic_set_dns_port(port);

  return Py_BuildValue("i", i);
}

static PyObject *
dim_dic_get_dns_port(PyObject * /* self */, PyObject * /* args */)
{
  /**
   * Proxy function to:
   *         dis_get_dns_port().
   * @return dns_port The DIM DNS port.
   */
  int port;
  port = dim_get_dns_port();
  return Py_BuildValue("i", port);
}

static PyObject *
dim_dic_get_id(PyObject * /* self */, PyObject * /* args */)
{
  /** Proxy function to:
   *         int dic_get_id (char* name)
   * @return client_name The client name or an empty string if the
   *  command was not successful.
   */
  char name[256];

  name[0] = 0;
  dic_get_id(name);
  return Py_BuildValue("s", name);
}

static PyObject *
dim_dic_disable_padding(PyObject * /* self */, PyObject * /* args */)
{
  /**
   * Proxy function to:
   *         int dic_disable_padding(void)
   */
  dic_disable_padding();
  Py_RETURN_NONE;
}

static PyObject *
dim_dic_get_quality(PyObject * /* self */, PyObject *args)
{
  /**
   * Proxy function to:
   *         int dic_get_quality (unsigned int service_id);
   * @return service_quality The service quality (int)
   */
  unsigned int service_id;
  int res;

  if (!PyArg_ParseTuple(args, "I", &service_id))
  {
    PyErr_SetString(PyExc_TypeError,
                    "Invalid argument: expected an unsigned integer");
    return NULL;
  }
  res = dic_get_quality(service_id);

  return Py_BuildValue("i", res);
}

static PyObject *
dim_dic_get_timestamp(PyObject * /* self */, PyObject *args)
{
  /**
   * Proxy function to:
   *        int dic_get_timestamp (service_id, secs, milisecs).
   *
   * @param service_id (unsigned int)
   * @return timestamp Python tuple with seconds and miliseconds
   */
  unsigned int service_id;
  int secs, milisecs = 0;

  if (!PyArg_ParseTuple(args, "Iii", &service_id))
  {
    PyErr_SetString(PyExc_TypeError,
                    "service id should be an unsigned integer");
    return NULL;
  }
  dic_get_timestamp(service_id, &secs, &milisecs);

  return Py_BuildValue("ii", secs, milisecs);
}

static PyObject *
dim_dic_get_format(PyObject * /* self */, PyObject *args)
{
  /**
   * Proxy function to:
   *        char *dic_get_format (unsigned int service_id)
   * @return format A string containing the format description.
   */
  unsigned int service_id;
  char *format = NULL;

  if (!PyArg_ParseTuple(args, "I", &service_id))
  {
    PyErr_SetString(PyExc_TypeError,
                    "Service id should be an unsigned integer");
    return NULL;
  }
  format = dic_get_format(service_id);

  return Py_BuildValue("s", format);
}

static PyObject *
dim_dic_release_service(PyObject * /* self */, PyObject *args)
{
  /**
   * Proxy function to:
   *        void dic_release_service (unsigned int service_id)
   */
  unsigned int service_id;
  string cppName;
  _dic_info_service_callback *tmp;

  if (!PyArg_ParseTuple(args, "I", &service_id))
  {
    debug("Invalid service id specified");
    PyErr_SetString(PyExc_TypeError,
                    "Service id should be an unsigned integer");
    return NULL;
  }
  Py_BEGIN_ALLOW_THREADS
      dic_release_service(service_id);
  Py_END_ALLOW_THREADS
      tmp = _dic_info_service_id2Callback[service_id];
  cppName = tmp->name;
  if (!tmp)
  {
    print("Service with id %d is not known", service_id);
    Py_RETURN_NONE;
  }
  _dic_info_service_name2Callback.erase(cppName);
  _dic_info_service_id2Callback.erase(service_id);
  free(tmp->name);
  Py_XDECREF(tmp->pyFunc);
  Py_XDECREF(tmp->pyDefaultArg);
  free(tmp);

  Py_RETURN_NONE;
}

static PyObject *
dim_dic_info_service(PyObject * /* self */, PyObject *args)
{
  /**
   * @param string_name
   * @param string_format
   * @param callbackFunction A callable Python object
   * @param service_type Optional, default=MONITORED (int)
   * @param timeout Optional, default=0 (int)
   * @param tag Optional, default=0 (int)
   * @param default_value  Optional, default=NULL, (Python object)
   * Proxy function to:
   *         unsigned int dic_info_service (char* name         ,
   *                                        int   type         ,
   *                                        int   timeout      ,
   *                                        int*  address      ,
   *                                        int*  size         ,
   *                                        void* user_routine ,
   *                                        long  tag          ,
   *                                        int*  fill_address ,
   *                                        int   fill_size)
   * @return service_id The id of the newly created service.
   */
  char *name;
  char *format = NULL;
  int service_type = MONITORED;
  int timeout = 0;
  int tag = 0;
  int format_size;
  int name_size;
  unsigned int service_id;
  string cppName;
  PyObject *pyFunc = NULL, *default_value = NULL;
  _dic_info_service_callback *tmp = NULL, *svc;
  /*Parse arguments if the format of data is passed
  to the dic_info_service python function
  */
  if (!PyArg_ParseTuple(args, "s#s#O|iiiO",
                        &name, &name_size,
                        &format, &format_size,
                        &pyFunc,
                        &service_type,
                        &timeout,
                        &tag,
                        &default_value) ||
      !PyCallable_Check(pyFunc))
  {
    /*As PyArg_ParseTuple(...) raises an exception when it fails,
    we need to flush the exception system in python by PyErr_Clear().
    If not, the time.sleep(x) called in the Python client program will
    raise the exception and make it crash
    */
    PyErr_Clear();
    /*Parse arguments if the format of data is NOT passed to the dic_info_service
    python function
    */
    if (!PyArg_ParseTuple(args, "s#O|iiiO",
                          &name, &name_size,
                          &pyFunc,
                          &service_type,
                          &timeout,
                          &tag,
                          &default_value) ||
        !PyCallable_Check(pyFunc))
    {
      goto invalid_args;
    }
  }

  /* I am using a map and keys of type char * will not work */
  cppName = name;
  /* increasing the ref count of the python objects */
  Py_INCREF(pyFunc);
  if (default_value)
    Py_INCREF(default_value);
  /* creating a new service structure and storing all the service
   * details inside
   */
  svc = (_dic_info_service_callback *)malloc(sizeof(_dic_info_service_callback));
  if (!svc)
    goto no_memory;

  svc->name = (char *)malloc(sizeof(char) * name_size + 1);
  if (!svc->name)
    goto no_memory;
  svc->pyDefaultArg = default_value;
  svc->pyTag = tag;
  svc->pyFunc = pyFunc;
  strcpy(svc->name, name);

  svc->format = NULL;

  if (format != NULL)
  {
    /* The format has been passed in parameters*/
    svc->format = (char *)malloc(sizeof(char) * format_size + 1);
    if (!svc->format)
    {
      goto no_memory;
    }
    strcpy(svc->format, format);
  }

  Py_BEGIN_ALLOW_THREADS
      /* registering the stub function. The C tag is the actual pointer to the
       * service structure
       */
      service_id = dic_info_service(name,
                                    service_type,
                                    timeout,
                                    0, 0, /* service buffer and size */
                                    _dic_info_service_dummy,
                                    (long)svc,
                                    0, 0); /* default buffer and size */

  Py_END_ALLOW_THREADS if (!service_id)
  {
    print("Service %s creation failed. Received result %d", name, service_id);
    goto dealocate;
  }
  svc->service_id = service_id;
  tmp = _dic_info_service_name2Callback[cppName];
  if (tmp)
  {
    free(tmp->name);
    Py_XDECREF(tmp->pyFunc);
    Py_XDECREF(tmp->pyDefaultArg);
    _dic_info_service_name2Callback.erase(cppName);
    _dic_info_service_id2Callback.erase(tmp->service_id);
    free(_dic_info_service_name2Callback[cppName]);
  }
  _dic_info_service_name2Callback[cppName] = svc;
  _dic_info_service_id2Callback[service_id] = svc;

  return Py_BuildValue("i", service_id);

  /* invalid arguments */
invalid_args:
  PyErr_SetString(PyExc_TypeError,
                  "Invalid parameters. Expected:string name               ,"
                  "                             string format             ,"
                  "                             int service_type          ,"
                  "                             int timeout               ,"
                  "                             PyObject* callbackFunction,"
                  "                             int tag                   ,"
                  "                             PyObject* default_value");
  return NULL;

  /* memory problems */
no_memory:
  PyErr_SetString(PyExc_MemoryError, "Could not allocate memory");
  Py_DECREF(pyFunc);
  return NULL;

  /* invalid service registration */
dealocate:
  Py_XDECREF(tmp->pyFunc);
  _dic_info_service_name2Callback.erase(cppName);
  //_dic_info_service_id2Callback.erase(tmp->buffer);
  free(tmp->format);
  free(tmp->name);
  free(tmp);
  return 0;
}

static PyObject *
dim_dic_info_service_stamped(PyObject *self, PyObject *args)
{
  /* Proxy function to:
   *         to dim_dic_info_service (PyObject* self,
   *                                  PyObject* args,
   *                                  PyObject* keywds
   *                                 )
   * Accepts same arguments and returns the new ID of the created service.
   */
  return dim_dic_info_service(self, args);
}

static PyObject *
dim_dic_get_server(PyObject * /* self */, PyObject *args)
{
  /**
   * Proxy function for:
   *         dic_get_server(char* name)
   * @param server_name string
   * @return server_id
   */
  int service_id;
  char *server_name;

  if (!PyArg_ParseTuple(args, "s", &server_name))
  {
    PyErr_SetString(PyExc_TypeError,
                    "Invalid parameters. Expected argument:string service_name");
    return NULL;
  }
  service_id = dic_get_server(server_name);

  return Py_BuildValue("i", service_id);
}

static PyObject *
dim_dic_get_conn_id(PyObject * /* self */, PyObject * /* args */)
{
  /**
   * Proxy function for:
   *         dic_get_conn_id(char* name)
   * @return connection id
   */
  int service_id;

  service_id = dic_get_conn_id();
  return Py_BuildValue("i", service_id);
}

static PyObject *
dim_dic_get_server_services(PyObject * /* self */, PyObject *args)
{
  /**
   * Proxy function for:
   *         dic_get_server_services(int conn_id)
   * @param connection id
   * @return services_list A Python list of services.
   */
  int conn_id;
  char *server_names = NULL;
  PyObject *ret;

  if (!PyArg_ParseTuple(args, "i", &conn_id))
  {
    PyErr_SetString(PyExc_TypeError,
                    "Invalid parameters. Expected argument:int conn_id");
    return NULL;
  }
  server_names = dic_get_server_services(conn_id);
  ret = stringList_to_tuple(server_names);

  return ret;
}

static PyObject *
dim_dic_get_error_services(PyObject * /* self */, PyObject *args)
{
  /** It is meant to be called inside the error handler to determine
   *  what service originated the error.
   *
   * Proxy function for:
   *         dic_get_error_services(int conn_id)
   *
   * @return service_list a python list of services in error.
   */
  char *server_names = NULL;
  server_names = dic_get_error_services();
  stringList_to_tuple(server_names);
  return args;
}

static PyObject *
dim_dic_add_error_handler(PyObject *self, PyObject *args)
{
  /**
   * @param python callback (callable object)
   * It is a stub function for calling:
   *         dic_add_error_handler ( void* error_routine(int, int, char*) )
   * @returns the python None object
   */
  PyObject *pyFunc;

  if (!PyArg_ParseTuple(args, "O:set_callback", &pyFunc) || !PyCallable_Check(pyFunc))
  {
    PyErr_SetString(PyExc_TypeError,
                    "Invalid parameters. Expected argument: callable object ");
    return NULL;
  }
  Py_XINCREF(pyFunc);
  Py_XINCREF(self);
  /* Dispose of previous callback */
  Py_XDECREF(_dic_callback_errorHandler_func.self);
  Py_XDECREF(_dic_callback_errorHandler_func.func);
  _dic_callback_errorHandler_func.self = self;
  _dic_callback_errorHandler_func.func = pyFunc;

  dic_add_error_handler(_dic_error_user_routine_dummy);

  Py_RETURN_NONE;
}

PyObject *deactivate_command_format_cache(PyObject *, PyObject *args)
{
  deactivate_cmnd_format_cache();
  Py_INCREF(Py_None);
  return Py_None;
}

PyObject *activate_command_format_cache(PyObject *, PyObject *args)
{
  activate_cmnd_format_cache();
  Py_INCREF(Py_None);
  return Py_None;
}

static PyObject *
dim_dic_cmnd_service(PyObject * /* self */, PyObject *args)
{
  /**
   * @param service_name (string),
   * @param command_data (tuple or list),
   * @param format (string)
   * Proxy function for calling:
   *        dic_cmnd_service(char* name, int* address, int size)
   * @return the DIM return code (1 for a successful request)
   */
  char *service_name, *format = NULL, *buffer;
  unsigned int buffer_size;
  int res;
  PyObject *pySeq;

  res = PyArg_ParseTuple(args, "sO|s", &service_name, &pySeq, &format);
  if (!res)
  {
    goto invalid_arguments;
  }

  if (format == NULL)
  {
    /*Format not provided by the user, retrieve it from the cache or from the DNS*/
    get_cmnd_format(service_name, &format);
    if (format == NULL)
      /*Command not found by the DNS, return 0 because command has not been requested*/
      return Py_BuildValue("i", 0);
  }

  /* creating dummy command request */
  if (!iterator_to_allocated_buffer(pySeq, format, &buffer, &buffer_size))
    goto error;

  Py_BEGIN_ALLOW_THREADS
      res = dic_cmnd_service(service_name, buffer, buffer_size);
  Py_END_ALLOW_THREADS
      /* freeing allocated buffer */
      free(buffer);
  return Py_BuildValue("i", res);

invalid_arguments:
  PyErr_SetString(PyExc_TypeError,
                  "Invalid parameters. Expected: string service_name (string), "
                  "update_data (tuple or list), format (DIM format string) (optional)");
  return NULL;

error:
  PyErr_SetString(PyExc_RuntimeError,
                  "Could not serialise provided arguments to a DIM buffer.\n"
                  "Please check that the order/number "
                  "of the argument maches the provided command format.");
  free(buffer);
  return NULL;
}

static PyObject *
dim_dic_cmnd_callback(PyObject * /* self */, PyObject *args)
{
  /**
   * @param service_name (string),
   * @param command_data (tuple or list),
   * @param format (string)
   * @param cmnd_callback (callable object)
   * @param tag (int)
   * Stub function for calling:
   *        dic_cmnd_service(char* name,
   *                         int* address,
   *                         int size,
   *                         void* cmd_callback,
   *                         long tag)
   * @return the DIM return code (int) (1 for successful request)
   */
  char *service_name, *format = NULL, *buffer;
  unsigned int buffer_size;
  int res;
  long pyTag;
  PyObject *pySeq, *pyFunc;
  _dic_cmnd_callback *callback;

  if (!PyArg_ParseTuple(args, "sOsOl",
                        &service_name, &pySeq, &format, &pyFunc, &pyTag) ||
      !PyCallable_Check(pyFunc))
  {
    /*As PyArg_ParseTuple(...) raises an exception when it fails,
    we need to flush the exception system in python by PyErr_Clear().
    If not, the time.sleep(x) called in the Python client program will
    raise the exception and make it crash
    */
    PyErr_Clear();
    if (!PyArg_ParseTuple(args, "sOOl",
                          &service_name, &pySeq, &pyFunc, &pyTag) ||
        !PyCallable_Check(pyFunc))
    {
      goto invalid_arguments;
    }
    get_cmnd_format(service_name, &format);
    if (format == NULL)
      return Py_BuildValue("i", 0);
  }
  /* saving callback */
  Py_INCREF(pyFunc);
  callback = (_dic_cmnd_callback *)malloc(sizeof(_dic_cmnd_callback));
  if (!callback)
    goto memory_error;
  callback->pyFunc = pyFunc;
  callback->pyTag = pyTag;
  /* creating dummy cmnd_callback request */
  if (!iterator_to_allocated_buffer(pySeq, format, &buffer, &buffer_size))
  {
    goto error;
  }
  /* If we don't release the Python lock and if the client is on the
   * same machine we are going to have a deadlock!
   */
  Py_BEGIN_ALLOW_THREADS
      res = dic_cmnd_callback(service_name,
                              buffer,
                              buffer_size,
                              _dic_cmnd_callback_dummy,
                              (long)callback);
  Py_END_ALLOW_THREADS
      /* freeing temporary buffer and returning */
      free(buffer);
  return Py_BuildValue("i", res);

invalid_arguments:
  PyErr_SetString(PyExc_TypeError,
                  "Invalid parameters. "
                  "   Expected: service_name (string), \n"
                  "             command_data (a tuple or list of values), \n"
                  "             format (a DIM format string) (optional), \n"
                  "             function_callback (a Python callable object) \n"
                  "             int tag");
  return NULL;

error:
  PyErr_SetString(PyExc_RuntimeError,
                  "Could not serialise provided arguments to a DIM buffer.\n"
                  "Please check that the order/number "
                  "of the argument maches the provided command format.");
  return NULL;

memory_error:
  PyErr_SetString(PyExc_MemoryError, "Could not allocate memory");
  return NULL;
}

/******************************************************************************/
/* DIC interface internal functions */
/******************************************************************************/
void _dic_cmnd_callback_dummy(void *uTag, int *ret_code)
{
  /**
   * @param uniqueTagPtr is used to identify the actual Python functions that
   * needs to be called. The tag that might have been speciffied in python is
   * substituted before passing the call.
   *
   * Stub function for passing the callback to the error handler to the Python
   * layer. This functions is registered to DIM instead of the Python function
   * and will forward the call to the interpreter.
   *
   * @return no value is returned
   */
  PyObject *res;
  PyGILState_STATE gstate;
  _dic_cmnd_callback *callback = (_dic_cmnd_callback *)(*(long *)uTag);

  /* The python calls to the API are encapsulated by the usual
   * Ensure(), Release()
   */
  gstate = PyGILState_Ensure();
  res = pyCallFunction(callback->pyFunc,
                       Py_BuildValue("li", callback->pyTag, *ret_code));
  Py_DECREF(res);
  Py_DECREF(callback->pyFunc);
  PyGILState_Release(gstate);
  /* delete callback information */
  free(callback);
}

void _dic_error_user_routine_dummy(int severity, int error_code, char *message)
{
  /* Stub function for passing the callback to the error handler to the Python
   * layer.
   * To the callback to the python layer the DIM severity, error_code and
   * message are passed.
   * NOTE: When this function is called it does not have the interpretor lock.
   */

  PyObject *arg, *res;
  PyGILState_STATE gstate;
  if (_dic_callback_errorHandler_func.func)
  {
    gstate = PyGILState_Ensure();
    arg = Py_BuildValue("iis", severity, error_code, message);
    res = PyObject_Call(_dic_callback_errorHandler_func.func, arg, NULL);
    Py_DECREF(arg);
    Py_XDECREF(res);
    PyGILState_Release(gstate);
  }
  else
  {
    debug("Could not find any registered Python function. "
          "Dropping DIM client error callback.");
    PyErr_SetString(PyExc_RuntimeError, "Could not find any registered Python function. "
                                        "Dropping DIM client error callback.");
    return;
  }
}

void _dic_info_service_dummy(void *tag, void *buffer, int *size)
{
  /** Stub function for passing the received data from a service to a python
   *  callback.
   *
   * @param tag A tag that identifies the service. This parameter is used in
   * the wrapper to identify the update service, convert the buffer to Python
   * objects and pass the call to the interpretor.
   * @param buffer The buffer containing the service update data.
   * @param size The size of the buffer.
   *
   * When this function is called we don't have the interpretor lock. We must
   * ensure that before calling any Python API function we have it! Note also
   * that dim_buf_to_tuple creates Python object so the lock must be held.
   */
  PyObject *args, *res;
  _dic_info_service_callback *svc;
  PyGILState_STATE gstate;
  // debug("Received update for a service with size %d, buffer pointer %x, tag %x",
  //	 *size, (long)buffer, (long *)tag);
  svc = (_dic_info_service_callback *)(*(long *)tag);

  gstate = PyGILState_Ensure();

  if (svc->format == NULL)
  {
    svc->format = dic_get_format(0);
  }

  if (!(*size) || svc->format == NULL)
  {
    /* The service update request failed. Passing default argument */
    args = svc->pyDefaultArg;
  }
  else
  {
    args = dim_buf_to_tuple(svc->format, (char *)buffer, *size);
  }
  /*Testing if the returned args are a tuple to avoid a TypeError with dic_sync_info_service*/
  if (args)
  {
    //    Py_INCREF(args);  pydim.dis_update_service(svc3)
    if (args == Py_None)
    {
      /*Can't call PyEval_CallObject with args = PyNone, it must be NULL*/
      args = NULL;
    }
    res = PyObject_Call(svc->pyFunc, args, NULL);
    if (!res)
    {
      if (PyErr_Occurred() != NULL)
      {
        PyErr_Print();
      }
      else
      {
        print("ERROR: Bad call to Python layer");
      }
    }
    else
    {
      /* call succedded */
      Py_DECREF(res);
    }
    Py_XDECREF(args);
  }
  else
  {
    /* service failed and a default value was not specified */
    print("ERROR: Could not get new data to update service");
  }
  PyGILState_Release(gstate);
}

/** @}
 */

static PyMethodDef DimMethods[] = {
    {"dis_start_serving",
     dim_dis_start_serving,
     METH_VARARGS,
     "Start providing DIM service"},
    {"dis_stop_serving",
     dim_dis_stop_serving,
     METH_VARARGS,
     "Stop DIM service"},
    {"dis_set_dns_node",
     dim_dis_set_dns_node,
     METH_VARARGS,
     "Function for setting the DNS node"},
    {"dis_get_dns_node",
     dim_dis_get_dns_node,
     METH_VARARGS,
     "Function for getting the DNS node"},
    {"dis_set_dns_port",
     dim_dis_set_dns_port,
     METH_VARARGS,
     "Function for setting the DNS port"},
    {"dis_get_dns_port",
     dim_dis_get_dns_port,
     METH_VARARGS,
     "Function for getting the DNS port"},
    {"dis_add_exit_handler",
     dim_dis_add_exit_handler,
     METH_VARARGS,
     "Function for setting the DIM exit handler"},
    {"dis_add_error_handler",
     dim_dis_add_error_handler,
     METH_VARARGS,
     "Function for setting the DIM error handler"},
    {"dis_add_client_exit_handler",
     dim_dis_add_client_exit_handler,
     METH_VARARGS,
     "Function for setting the DIM Client exit handler"},
    {"dis_update_service",
     dim_dis_update_service,
     METH_VARARGS,
     "Function for updating the specified service"},
    {"dis_selective_update_service",
     dim_dis_selective_update_service,
     METH_VARARGS,
     "Function for updating the specified service and clients"},
    {"dis_set_quality",
     dim_dis_set_quality,
     METH_VARARGS,
     "Function for setting the quality of a service"},
    {"dis_set_timestamp",
     dim_dis_set_timestamp,
     METH_VARARGS,
     "Function for setting the the timestamp of a service"},
    {"dis_remove_service",
     dim_dis_remove_service,
     METH_VARARGS,
     "Function for removing a service"},
    {"dis_get_next_cmnd",
     dim_dis_get_next_cmnd,
     METH_VARARGS,
     "Get a command from the list of waiting command requests"},
    {"dis_remove_service",
     dim_dis_remove_service,
     METH_VARARGS,
     "Remove a Service from the list of provided services"},
    {"dis_get_client",
     dim_dis_get_client,
     METH_VARARGS,
     "Gets the process identification of the current DIM client"},
    {"dis_get_conn_id",
     dim_dis_get_conn_id,
     METH_VARARGS,
     "Gets the connection ID of the current DIM client"},
    {"dis_get_timeout",
     dim_dis_get_timeout,
     METH_VARARGS,
     "Gets the update rate that was specified by a client for a specific service"},
    {"dis_get_client_services",
     dim_dis_get_client_services,
     METH_VARARGS,
     "Gets the services of a DIM client, which has subscribed to this DIM server"},
    {"dis_set_client_exit_handler",
     dim_dis_set_client_exit_handler,
     METH_VARARGS,
     "Activates the client exit handler for a specific client and a specific service"},
    {"dis_get_error_services",
     dim_dis_get_error_services,
     METH_VARARGS,
     "Gets the names of DIM services that have an error"},
    {"dis_add_cmnd",
     dim_dis_add_cmnd,
     METH_VARARGS,
     "Adds a new command"},
    {"dis_add_service",
     dim_dis_add_service,
     METH_VARARGS,
     "Adds a new service"},
    {"dic_set_dns_node",
     dim_dic_set_dns_node,
     METH_VARARGS,
     "Function for setting the DNS node"},
    {"dic_get_dns_node",
     dim_dic_get_dns_node,
     METH_VARARGS,
     "Function for getting the DNS node"},
    {"dic_set_dns_port",
     dim_dic_set_dns_port,
     METH_VARARGS,
     "Function for setting the DNS port"},
    {"dic_get_dns_port",
     dim_dic_get_dns_port,
     METH_VARARGS,
     "Function for getting the DNS port"},
    {"dic_get_id",
     dim_dic_get_id,
     METH_VARARGS,
     "Gets the process identification of this DIM client."},
    {"dic_disable_padding",
     dim_dic_disable_padding,
     METH_VARARGS,
     "Disable padding of received services."},
    {"dic_get_quality",
     dim_dic_get_quality,
     METH_VARARGS,
     "Gets the quality of  a received service."},
    {"dic_get_timestamp",
     dim_dic_get_timestamp,
     METH_VARARGS,
     "Gets the time stamp of a received service."},
    {"dic_get_format",
     dim_dic_get_format,
     METH_VARARGS,
     "Gets the format description of a received service."},
    {"dic_release_service",
     dim_dic_release_service,
     METH_VARARGS,
     "Called by a client when a service is not needed anymore."},
    {"dic_info_service",
     dim_dic_info_service,
     METH_VARARGS | METH_KEYWORDS,
     "Called by a client for subscribing to a service."},
    {"dic_cmnd_service",
     dim_dic_cmnd_service,
     METH_VARARGS,
     "Request the execution of a command by a server."},
    {"dic_cmnd_callback",
     dim_dic_cmnd_callback,
     METH_VARARGS,
     "Request the execution of a command and registers a completion callback."},
    {"dic_info_service_stamped",
     dim_dic_info_service_stamped,
     METH_VARARGS,
     "Request a time stamped (and quality flagged) information service from a server"},
    {"dic_get_server",
     dim_dic_get_server,
     METH_VARARGS,
     "Gets the process identification of the current DIM server"},
    {"dic_get_conn_id",
     dim_dic_get_conn_id,
     METH_VARARGS,
     "Gets the connection ID of the current DIM server"},
    {"dic_get_server_services",
     dim_dic_get_server_services,
     METH_VARARGS,
     "Gets the services of a DIM server to which the client has subscribed."},
    {"dic_get_error_services",
     dim_dic_get_error_services,
     METH_VARARGS,
     "Gets the names of DIM services that have an error."},
    {"dic_add_error_handler",
     dim_dic_add_error_handler,
     METH_VARARGS,
     "Adds an error handler to this client."},
    {"deactivate_command_format_cache",
     deactivate_command_format_cache,
     METH_VARARGS,
     "Deactivate the command format cache"},
    {"activate_command_format_cache",
     activate_command_format_cache,
     METH_VARARGS,
     "Deactivate the command format cache"},
    {NULL, NULL, 0, NULL} /* Sentinel */
};

#ifndef _WIN32
static pthread_t maintid;
static pthread_mutex_t pydimlock;
#else
static DWORD maintid;
static HANDLE pydimlock;
#endif

static struct PyModuleDef moduledef = {
    PyModuleDef_HEAD_INIT,
    "dimc",        /* m_name */
    "DIM methods", /* m_doc */
    -1,            /* m_size */
    DimMethods,    /* m_methods */
    NULL,          /* m_reload */
    NULL,          /* m_traverse */
    NULL,          /* m_clear */
    NULL,          /* m_free */
};

PyMODINIT_FUNC
PyInit_dimc(void)
{
  PyObject *m;
#ifdef _WIN32
  pydimlock = CreateMutex(NULL, FALSE, NULL);
#else
  pthread_mutex_init(&pydimlock, NULL);
#endif
  // PyEval_InitThreads();
  debug("Initializing the C DIM interface... \n");
  m = PyModule_Create(&moduledef);

  if (m == NULL)
    return NULL;
#ifndef _WIN32
  maintid = pthread_self();
#else
  maintid = GetCurrentThreadId();
#endif
  // Add constants for service type definitions
  PyModule_AddIntConstant(m, "ONCE_ONLY", ONCE_ONLY);
  PyModule_AddIntConstant(m, "TIMED", TIMED);
  PyModule_AddIntConstant(m, "MONITORED", MONITORED);
  PyModule_AddIntConstant(m, "COMMAND", COMMAND);
  PyModule_AddIntConstant(m, "DIM_DELETE", DIM_DELETE);
  PyModule_AddIntConstant(m, "MONIT_ONLY", MONIT_ONLY);
  PyModule_AddIntConstant(m, "UPDATE", UPDATE);
  PyModule_AddIntConstant(m, "TIMED_ONLY", TIMED_ONLY);
  PyModule_AddIntConstant(m, "MONIT_FIRST", MONIT_FIRST);
  PyModule_AddIntConstant(m, "MAX_TYPE_DEF", MAX_TYPE_DEF);
  PyModule_AddIntConstant(m, "STAMPED", STAMPED);

  dic_disable_padding();
  dis_disable_padding();

  return m;
}

#ifndef _WIN32
#ifdef RINUNX
#include <stdio.h>
#include <dlfcn.h>
#include <errno.h>
#include <string.h>
#include <semaphore.h>

extern sem_t DIM_INIT_Sema;

PyGILState_STATE gilstack[GILSTACKSIZE];
int gilstackp = 0;

void dim_lock()
{
  static void (*func)(void);
  int v;

  if (!func)
  {
    func = (void (*)())dlsym(RTLD_NEXT, "dim_lock");
    if (!func)
    {
      printf("Couldn't find dim_lock");
      exit(1);
    }
  }
  if (gilstackp == GILSTACKSIZE)
  {
    printf("ERROR: GILstack overflow");
    exit(1);
  }
  pthread_mutex_lock(&pydimlock);

#ifdef _WIN32
  DWORD mytid = GetCurrentThreadId();
#else
  pthread_t mytid = pthread_self();
#endif
  if ((mytid != maintid))
  {
    gilstack[gilstackp++] = PyGILState_Ensure();
  }
  sem_getvalue(&DIM_INIT_Sema, &v);
  printf("%d\n", v);
  (*func)();
  printf("dim_lock() is called: %d\n", gilstackp);

  pthread_mutex_unlock(&pydimlock);
  return;
}

void dim_unlock()
{

  static void (*func)(void);

  if (!func)
  {
    func = (void (*)())dlsym(RTLD_NEXT, "dim_unlock");
    if (!func)
    {
      printf("Couldn't find dim_unlock");
      exit(1);
    }
  }
  // gilstackp--;
  pthread_mutex_lock(&pydimlock);
  (*func)();
#ifdef _WIN32
  DWORD mytid = GetCurrentThreadId();
#else
  pthread_t mytid = pthread_self();
#endif
  if ((mytid != maintid))
  {
    PyGILState_Release(gilstack[gilstackp--]);
  }
  pthread_mutex_unlock(&pydimlock);
  printf("dim_unlock() is called: %d\n", gilstackp);
  return;
}

#endif
#endif /* _WIN32 */