/**
 * @file
 * Provide functions to interact with Python interpreter from SIC.
 */

/*****************************************************************************
 *                              Dependencies                                 *
 *****************************************************************************/

#undef _DEBUG // for python52 in release mode
#include <Python.h>
#include <string.h>
#include <errcode.h>
#include "gsys/cfc.h"
#include "sic-message-c.h"
#include "gcore/gcomm.h"
#include "ggui/gkbd_line.h"
#include "getcar.h"
#include "gmaster-c.h"

#define gpy_onsicexit     CFC_EXPORT_NAME( gpy_onsicexit )
#define gpy_start         CFC_EXPORT_NAME( gpy_start )
#define gpy_execfile      CFC_EXPORT_NAME( gpy_execfile )
#define gpy_exec          CFC_EXPORT_NAME( gpy_exec )
#define gpy_interact      CFC_EXPORT_NAME( gpy_interact )
#define gpy_findfunc      CFC_EXPORT_NAME( gpy_findfunc )
#define gpy_callfuncd     CFC_EXPORT_NAME( gpy_callfuncd )
#define gpy_callfuncs     CFC_EXPORT_NAME( gpy_callfuncs )
#define gpy_getvar        CFC_EXPORT_NAME( gpy_getvar )
#define gpy_delvar        CFC_EXPORT_NAME( gpy_delvar )

#define sic_getlog_inplace  CFC_EXPORT_NAME( sic_getlog_inplace )
void CFC_API sic_getlog_inplace (CFC_FString symbol CFC_DECLARE_STRING_LENGTH(symbol));

#define enter_program     CFC_EXPORT_NAME( enter_program )
void CFC_API enter_program ();

#define gmaster_clean     CFC_EXPORT_NAME( gmaster_clean )
void CFC_API gmaster_clean (int *);

#define gmaster_get_ismaster CFC_EXPORT_NAME( gmaster_get_ismaster )
#define gpack_get_count      CFC_EXPORT_NAME( gpack_get_count )
#define gpack_get_name       CFC_EXPORT_NAME( gpack_get_name )
#define gmaster_build_sic    CFC_EXPORT_NAME( gmaster_build_sic )
#define sic_verify           CFC_EXPORT_NAME( sic_verify )
#define sic_debug_python     CFC_EXPORT_NAME( sic_debug_python )
int  CFC_API gmaster_get_ismaster ();
int  CFC_API gpack_get_count ();
void CFC_API gpack_get_name (int *id, CFC_FString name, int *error
                              CFC_DECLARE_STRING_LENGTH(name) );
void CFC_API gmaster_build_sic (int *error);
int  CFC_API sic_verify ();
void CFC_API sic_debug_python ();

#ifndef PY_SSIZE_T_MIN
/* In Python <  2.5, PyDict_Next expects as second argument an int,
   in Python >= 2.5, PyDict_Next expects as second argument a Py_ssize_t */
typedef int Py_ssize_t;
#endif

/*****************************************************************************
 *                        Internal function bodies                           *
 *****************************************************************************/

/**
 * Lowercase any C string.
 *
 * @param[in] string is the C string.
 * @return nothing.
 */
static void lowercase(char string[]) {
   int  i = 0;

   while ( string[i] ) {
      string[i] = tolower(string[i]);
      i++;
   }

   return;
}

/**
 * Attempt for a centralized sprint of any PyObject
 */
char *gpy_AsString(PyObject *obj) {
  PyObject *unicod=NULL,*repr=NULL;
  char *string;
  char *nullstr="<NULL>";

  if (obj == NULL) {
    return nullstr;
  }

#if PY_MAJOR_VERSION >= 3
  if (PyUnicode_CheckExact(obj)) {
    /* This is already a PyUnicode */
    Py_INCREF(obj);
    unicod = obj;
  } else {
    unicod = PyObject_Repr(obj);  /* New reference */
    if (unicod == NULL) {
      return nullstr;
    }
  }

  repr = PyUnicode_AsUTF8String(unicod); /* New reference */
  if (repr == NULL) {
    return nullstr;
  }
  Py_XDECREF(unicod);

  string = PyBytes_AsString(repr);

#else

  if (PyString_CheckExact(obj)) {
    /* This is already a PyString */
    Py_INCREF(obj);
    repr = obj;
  } else {
    repr = PyObject_Repr(obj);  /* New reference */
    if (repr == NULL) {
      return nullstr;
    }
  }
  string = PyString_AsString(repr);

#endif

  /* There is probably a major issue here: we decref the object while the
     'string' points to it */
  Py_XDECREF(repr);

  if (string == NULL) {
    return nullstr;
  } else {
    return string;
  }

}

/**
 * Return a new reference to a PyString (Python 2) or PyUnicode (Python 3)
 * from a char* string
 */
PyObject *gpy_FromString(char *string) {

#if PY_MAJOR_VERSION >= 3
  return PyUnicode_FromString(string);
#else
  return PyString_FromString(string);
#endif

}

static int _gpy_getvar_enabled = 0;

void pygildas_set_initialized()
{
    _gpy_getvar_enabled = 1;
}

static int _pygildas_is_initialized()
{
    return _gpy_getvar_enabled;
}


/**
 * Initialize Python if not already initialized.
 * BEWARE THIS IS CALLED EITHER FROM GILDAS MASTER OR GILDAS SLAVE, i.e.
 * we have to be protected against restarting Python if it is master.
 *
 * @return nothing.
 */
void CFC_API gpy_start() {
    int ipack, pack_id, error=0;
    CFC_DECLARE_LOCAL_STRING(f_name);
    char c_name[9];        /* Equivalent to f_name, 8 + 1 for \0 */
    char mod_name[11];     /* 'py' + 'name' + '\0' */
    PyObject *mymod, *mainmod;
    static int pack_is_loaded[32];

    /* If never called before, start Python */
    if (!_pygildas_is_initialized()) {
      for (ipack=0;ipack<32;ipack++) { pack_is_loaded[ipack] = 0; }

      /* Launch Python interpreter, ensure readline is loaded */
      Py_Initialize();
      /* Enable python with threads */
      PyEval_InitThreads();

      printf("Python %s on %s\n",Py_GetVersion(),Py_GetPlatform());
      if (! PyImport_ImportModule("readline")) {
        sic_c_message(seve_w, "PYTHON", "Failed to load Python module 'readline':");
        PyErr_Print();
      }
    }

    /* Import Python module corresponding to current Gildas package. */
    /* Might be different from Gildas master since we can be in the  */
    /* initialization sequence. */
    pack_id = gpack_get_count();
    if (pack_id == 0) {
       sic_c_message(seve_e,"PYTHON","Gildas is not yet initialized (null package number)!");
       return;
    }

    /* If current package is already loaded in Python, return */
    if (pack_is_loaded[pack_id]) { return; }

    CFC_STRING_VALUE(f_name) = (char *)c_name;  /* Make Fortran strings pointing to C ones. */
    CFC_STRING_LENGTH(f_name) = 8;

    gpack_get_name(&pack_id, f_name, &error CFC_PASS_STRING_LENGTH(f_name));
    c_name[8] = '\0';
    CFC_suppressEndingSpaces( c_name);

    strcpy(mod_name,"py");
    strcpy(mod_name+2,c_name);
    if (! (mymod = PyImport_ImportModule(mod_name)) ) {
       sic_c_message(seve_e, "PYTHON", "Failed to load Python module '%s'", mod_name);
       PyErr_Print();
    }
    if (! (mainmod = PyImport_AddModule("__main__")) ) {
       sic_c_message(seve_e, "PYTHON", "Failed to load Python __main__");
       PyErr_Print();
    } else if (PyObject_SetAttrString(mainmod, mod_name, mymod) == -1) {
       sic_c_message(seve_e, "PYTHON", "Could not add '%s' module to Python __main__", mod_name);
    }
    pack_is_loaded[pack_id] = 1;
}

/**
 * Python-is-slave boolean function.
 *
 * @return 0 if Python is master, 1 if slave, -1 if an error occured.
 */
static int pyisslave() {
    int       ret=-1;
    PyObject *module=NULL, *isslave=NULL;

    if (! (module = PyImport_ImportModule("pygildas"))) {
       sic_c_message(seve_e, "PYTHON", "Could not import 'pygildas' module into Python.");
       PyErr_Print();
    } else if (! PyObject_HasAttrString(module, "is_slave")) {
       sic_c_message(seve_e, "PYTHON", "Did not find 'is_slave' attribute of 'pygildas' module.");
    } else if (! (isslave = PyObject_GetAttrString(module, "is_slave"))) {
       sic_c_message(seve_e, "PYTHON", "Failed to load 'is_slave' attribute of 'pygildas' module.");
       PyErr_Print();
    } else if (PyObject_IsTrue(isslave)) {
       ret = 1;
    } else {
       ret = 0;
    }

    Py_XDECREF(module);
    Py_XDECREF(isslave);
    return ret;
}

/**
 * Switch 'pygildas.loop' flag to new value.
 *
 * @param[in] value is the new flag value.
 * @return 0 on success, 1 otherwise.
 */
static int switchflag(int value) {
    int       ret=1;
    PyObject *module;

    if (! (module = PyImport_ImportModule("pygildas"))) {
       sic_c_message(seve_e, "PYTHON", "Could not import 'pygildas' module into Python.");
       PyErr_Print();
    } else if (! PyObject_HasAttrString(module, "loop")) {
       sic_c_message(seve_e, "PYTHON", "Did not find 'loop' attribute of 'pygildas' module.");
    } else {
       Py_XDECREF(PyObject_GetAttrString(module,"loop"));
       PyObject_SetAttrString(module,"loop",PyLong_FromLong((long) value));
       ret = 0;
    }
    Py_XDECREF(module);

    return ret;
}

/**
 * A customized implementation of the Python/C API function
 * PyRun_InteractiveLoopFlags.
 * There are minor differences between the official and this local
 * InteractiveLoop:
 * - 'flags' are always set to NULL (as in PyRun_InteractiveLoop)
 * - 'PRINT_TOTAL_REFS' fprintf to stderr is deactivated
 * - the infinite loop is exited in 3 cases (^D/EOF signal,
 * 'pygildas.loop' flag switched to 0, or to -1.
 *
 * @param[in] fp is a file pointer to an interactive device (usually
 *            stdin).
 * @param[in] filename is the interactive device name (usually "STDIN").
 * @return 0 on standard loop exit, 1 on loop exit that must exit all
 *         the processes, -1 on failure.
 */
static int PyRun_MyInteractiveLoop(FILE *fp, const char *filename) {
    int              signal;
    PyObject        *v, *module;
    PyCompilerFlags *flags, local_flags;

    /* 'flags' always NULL here */
    flags = &local_flags;
    local_flags.cf_flags = 0;

    v = PySys_GetObject("ps1");
    if (v == NULL) {
       PySys_SetObject("ps1", v = PyUnicode_FromString(">>> "));
       Py_XDECREF(v);
    }
    v = PySys_GetObject("ps2");
    if (v == NULL) {
       PySys_SetObject("ps2", v = PyUnicode_FromString("... "));
       Py_XDECREF(v);
    }

    /* Get 'pygildas' module */
    if (! (module = PyImport_ImportModule("pygildas"))) {
       sic_c_message(seve_e, "PYTHON", "Could not import 'pygildas' module into Python.");
       PyErr_Print();
       return -1;
    }

    /* Infinite loop */
    for (;;) {
       signal = PyRun_InteractiveOneFlags(fp, filename, flags);
       /* PRINT_TOTAL_REFS(); */
       /* Command: CTRL-D */
       if (signal == E_EOF) {
          switchflag(-1);
          return 1;
       }
       /* Command: exit() */
       if (PyLong_AsLong(PyObject_GetAttrString(module,"loop")) == -1)
          return 1;
       /* Command: Sic() */
       if (PyLong_AsLong(PyObject_GetAttrString(module,"loop")) == 0)
          return 0;
    }

}

/**
 * Exit Python interactive loop (prompt) when Python is slave, and go
 * to SIC.
 * The 'pygildas.loop' flag is switched to 0, which will end the
 * customized Python InteractiveLoop.
 *
 * @return Py_None.
 */
PyObject* gpy_exitloop(void) {
   PyObject *module;

   /* Set 'pygildas.loop' to 0 */
   if (! (module = PyImport_ImportModule("pygildas"))) {
      fprintf(stderr," E-PYTHON, Could not import 'pygildas' module into Python.\n");
      PyErr_Print();
   } else if (! PyObject_HasAttrString(module, "loop")) {
      fprintf(stderr,"E-PYTHON,  Did not find 'loop' attribute of 'pygildas' module.\n");
   } else {
      Py_DECREF(PyObject_GetAttrString(module,"loop"));
      PyObject_SetAttrString(module,"loop",PyLong_FromLong((long) 0));
   }
   Py_XDECREF(module);

   Py_RETURN_NONE;
}

static void keyboard_activate_prompt( )
{
    char prompt_with_space[PROMPT];

    sic_enable_prompt( );
    strcpy( prompt_with_space, gmaster_c_get_prompt( ));
    strcat( prompt_with_space, " ");
    sic_post_prompt_text( prompt_with_space);
}

/**
 * Jump to SIC prompt when Python is master.
 * 'enter_program()' is an entry to the internal Fortran routine called
 * by SIC to start a new prompt. The 'pygildas.loop' flag is switched to
 * 0, which will indicate that the prompt is SIC.
 *
 * @return Py_None.
 */
PyObject* gpy_enterprog(void) {
   gpy_exitloop(); /* Set also 'pygildas.loop' to 0. */
   keyboard_activate_prompt( );
   run_keyboard( "");
   Py_RETURN_NONE;
}

/**
 * Retrieve a reference to the 'Sic' instance in Python __main__. This
 * reference will have to be XDECREF'd.
 *
 * @return a new reference to the 'Sic' instance, or NULL on failure.
 */
static PyObject* getsicobject() {
   PyObject *module, *Sic=NULL;

   if (! (module = PyImport_AddModule("__main__"))) {
      sic_c_message(seve_e, "PYTHON", "Failed to load Python __main__");
      PyErr_Print();
   } else if (! PyObject_HasAttrString(module, "Sic")) {
      sic_c_message(seve_e, "PYTHON", "Did not find 'Sic' instance in Python __main__");
   } else if (! (Sic = PyObject_GetAttrString(module, "Sic"))) {
      sic_c_message(seve_e, "PYTHON", "Failed to load 'Sic' from Python __main__");
      PyErr_Print();
   }

   return Sic; /* New reference, or NULL on failure. */
}

/**
 * Retrieve a reference to the 'pygildas.dict' dictionary, which stores
 * imported (shared variables). This reference will have to be XDECREF'd.
 *
 * @return a new reference to 'pygildas.dict', or NULL on failure.
 */
static PyObject* getgildasdict() {
   PyObject *pygildas, *gdict=NULL;

   if (! (pygildas = PyImport_ImportModule("pygildas"))) {
      sic_c_message(seve_e, "PYTHON", "Could not import 'pygildas' module into Python");
      PyErr_Print();
   } else if (! PyObject_HasAttrString(pygildas, "dict")) {
      sic_c_message(seve_e, "PYTHON", "Did not find 'dict' attribute of 'pygildas' module");
   } else if (! (gdict = PyObject_GetAttrString(pygildas, "dict"))) {
      sic_c_message(seve_e, "PYTHON", "Failed to load 'dict' attribute of 'pygildas' module");
      PyErr_Print();
   }
   Py_XDECREF(pygildas);

   return gdict; /* New reference, or NULL on failure. */
}

/**
 * Apply 'sic2py' method on a SIC name.
 *
 * @param[in] varname is a string to be passed to 'sic2py'.
 * @return a new reference to a Python string, or NULL on failure.
 */
static PyObject* sic2pyconv(char *varname) {
   PyObject *pgutils=NULL, *pyname=NULL;

   if (! (pgutils = PyImport_ImportModule("pgutils"))) {
      sic_c_message(seve_e, "PYTHON", "Could not import 'pgutils' module into Python");
      PyErr_Print();
   } else if (! PyObject_HasAttrString(pgutils,"sic2py")) {
      sic_c_message(seve_e, "PYTHON", "Did not find 'sic2py' method in 'pgutils' module");
   } else {
      pyname = PyObject_CallMethod(pgutils,"sic2py","(s)",varname);
   }
   Py_XDECREF(pgutils);

   return pyname; /* New reference, or NULL on failure. */
}

/** Return a new reference to the 'Sic.localspaces[level]'
 *
 * @param[in] level is the execution level of the variable.
 * @return a PyObject, or NULL on failure.
 */
static PyObject* getlocalspace(int level) {

   PyObject *localspace=NULL, *Sic=NULL, *tuple=NULL;

   if (! (Sic = getsicobject()) ) { /* New reference */
       ;

   } else if (! PyObject_HasAttrString(Sic, "localspaces")) {
       sic_c_message(seve_e, "PYTHON", "Did not find 'localspaces' array in 'Sic' instance");

   } else if (! (tuple = PyObject_GetAttrString(Sic, "localspaces"))) { /* New ref */
       sic_c_message(seve_e, "PYTHON", "Failed to load 'localspaces' array from 'Sic' instance");
       PyErr_Print();

   } else if (! (localspace = PyTuple_GetItem(tuple,level))) {  /* Borrowed ref */
       sic_c_message(seve_e, "PYTHON", "Failed to load %d-th element of 'localspaces'",level);
       PyErr_Print();

   } else {
       Py_INCREF(localspace);
   }

   Py_XDECREF(Sic);
   Py_XDECREF(tuple);

   return localspace;
}

/**
 * Unsave a variable from 'Sic.localspaces' back to Pygildas internal
 * dictionary. Check if the given object (name and level) exists,
 * reference it in dictionary, and delete it from 'Sic.localspaces'.
 *
 * @param[in] level is the execution level of the variable.
 * @param[in] pyname is the PyString name of the variable.
 * @return 0 on success, 1 otherwise.
 */
static int pyunsavevar(int level, PyObject *pyname) {
   int       ret=1;
   PyObject *gdict=NULL, *instance=NULL, *object=NULL;

   if (! (instance = getlocalspace(level)) ) {
      ;
   } else  if (! (PyObject_HasAttr(instance, pyname))) {
      /* fprintf(stderr,"Did not find '%s' variable from %d-th element of 'localspaces'.\n", gpy_AsString(pyname),level)); */
      ;
   } else if (! (object = PyObject_GetAttr(instance, pyname))) {
      sic_c_message(seve_e, "PYTHON", "Failed to load '%s' variable from %d-th element of 'localspaces'",gpy_AsString(pyname),level);
      PyErr_Print();
   } else if (! (gdict = getgildasdict())) {
      /* No error message, getgildasdict() does. */
      ;
   } else if (PyObject_SetItem(gdict,pyname,object) == -1) {
      sic_c_message(seve_e, "PYTHON", "Failed to add attribute '%s' to Pygildas dictionary",gpy_AsString(pyname));
      PyErr_Print();
   } else if (PyObject_DelAttr(instance, pyname) == -1 ) {
      sic_c_message(seve_e, "PYTHON", "Failed to delete '%s' attribute from %d-th element of 'localspaces'",gpy_AsString(pyname),level);
      PyErr_Print();
   } else {
      ret = 0;
   }

   Py_XDECREF(instance);
   Py_XDECREF(gdict);
   Py_XDECREF(object);

   return ret;
}

/**
 * Return the parent PyObject of the input PyObject.
 * Check if the given PyObject has a dot, and return a new reference
 * the parent PyObject. If name has no dot, return a new reference
 * to the Sic object.
 *
 * @param[in] fullname is the PyString name of the variable.
 * @return a new PyObject reference to the parent,
 *         or a new empty dictionary if parent was not found,
 *         or NULL if an error was encountered.
 */
static PyObject *getparent(PyObject *fullname, int level) {
    PyObject    *parent, *list, *currentname, *current, *pyparlev;
    Py_ssize_t  lsize, i;
    int         parlev,istart;

    /* Default is to return the pygildas.dict dictionary */
    parent = getgildasdict();  /* New reference */

    /* Split fullname with dots as separator. Make this code independent
       between Python 2 (string=bytes) and Python 3 (string=unicode) */
    list = PyObject_CallMethod(fullname,"split","s","."); /* New reference */
    lsize = PyList_Size(list);

    if (lsize == 1) {
      /* Name has no dot: parent is pygildas.dict, return */
      Py_XDECREF(list);
      return parent;
    }

    /* Name has a dot. Special case for top-parent (PyDict_GetItem) */
    currentname = PyList_GetItem(list,0); /* Borrowed reference */
    if ( PyDict_Contains(parent,currentname) ) {
        current = parent;
        parent = PyDict_GetItem(current,currentname); /* Borrowed reference */
        Py_XINCREF(parent);  /* PyDict_GetItem returned a borrowed reference */
        Py_XDECREF(current);  /* getgildasdict() returned a new reference */
    } else {
        /* May be it was not imported, or deleted from python-side. No error. */
        /* Return an empty dictionary */
        Py_XDECREF(list);
        return PyDict_New(); /* New reference */
    }

    /* Does the levels match? Think of this use case:
         def struct s /global
         def real s%a /global
         def real s  ! Local
         del /var s%a
       i.e. "s" is a local variable but "s%a" is attached the "s" the global
       variable */

    pyparlev = PyObject_GetAttrString(parent,"__siclevel__"); /* New reference */
    parlev = PyLong_AsLong(pyparlev);
    Py_DECREF(pyparlev);
    if (parlev == level) {
        istart = 1;  /* We already got the top-parent */
    } else {
        /* The parent we are looking for is not in pygildas.dict, it
           must be in the localspaces */
        Py_DECREF(parent);
        parent = getlocalspace(level);
        istart = 0;  /* We will get the top-parent from localspace[level] */
    }


    for (i=istart; i< lsize-1; i++) {
        /* Standard case: PyObject_GetAttr */
        currentname = PyList_GetItem(list,i); /* Borrowed reference */
        if ( PyObject_HasAttr(parent,currentname) ) {
            current = parent;
            parent = PyObject_GetAttr(current,currentname); /* New reference */
            Py_XDECREF(current);  /* PyDict_GetItem returns a borrowed reference */

        } else {
            /* parent is not a dictionary (it is probably is structure
              instance) and but it has no such attribute. This can happen
              if the structure is partially imported or partially deleted.
              Return an empty dictionary */
            Py_XDECREF(list);
            return PyDict_New(); /* New reference */
        }
    }

    /* Return a new reference to the parent,
       or an empty dictionary if parent was not found,
       or NULL if an error was encountered */
    Py_XDECREF(list);
    return parent;
}


/*****************************************************************************
 *                             Function bodies                               *
 *****************************************************************************/

/**
 * When a Python script is executed from Python, the Gildas-Python programmer
 * has 3 options:
 *  1a) let Python raise its own errors, i.e. do not catch them with
 *      try/except,
 *  1b) raise standard Python errors whenever he wants. The problem is the
 *      lack of control of the traceback (too verbose). The ideal solution
 *      would be to be able to raise silent exceptions. Note that
 *      PyRun_SimpleFileEx can only return a non-null status if the Python
 *      "raise" clause was used...
 *  2)  print its own message, and then return. The problem is that it does
 *      not raise an error in the Sic interpreter (in particular it does not
 *      stop the procedure).
 * So we provide a Python-C function which raises our own flag, which we will
 * test when returning from the Python thread. */
static int _gpy_execfile_error = 0;
void gpy_execfile_error_set(int v) {
  _gpy_execfile_error = v;
}
int gpy_execfile_error_get() {
  return _gpy_execfile_error;
}


/**
 * Called when the exit order comes from SIC (and only from SIC), whatever
 * the master/slave statuses. Finalize Python if it is already initialized.
 *
 * @return nothing.
 */
void CFC_API gpy_onsicexit() {
    int error = 0;

    if (!_pygildas_is_initialized()) return;

    /* See detailed comments in function gpy_onpythonexit */

    if (pyisslave() == 1) {
       Py_Finalize();
       sic_c_message(seve_d,"PYTHON","Python slave interpreter has been finalized");
    } else {
       sic_c_message(seve_d,"PYTHON","Python master exit");
       /* Clean properly SIC slave. We may want to had gmaster_on_exit, which
          calls the optional on_exit routines of the packages. */
       gmaster_clean(&error);
       /* Ask Python master to exit */
       Py_Exit(0);
    }

    _gpy_getvar_enabled = 0;

}

/**
 * Interprets the input Python file.
 * Passing the arguments in a character array (instead of a unique big
 * string) ensures that double quoted strings with arbitrary number of
 * blanks are correctly handled.
 * The input file is interpreted in the __main__ name area (scope).
 * Depending on the 'SIC%VERIFY' flag value, PyRun_SimpleFile (no prints)
 * or 'pexecfile' (custom Python 'execfile' with prints) is called.
 *
 * @param[in] name is the Python file name with full path.
 * @param[in] ln is the Fortran length of this name.
 * @param[in] args is a character array with file name first and then its
 *            arguments.
 * @param[in] largs is an integer array with the effective length of each
 *            'args' array elements.
 * @param[in] la is the Fortran length of the 'args' array elements.
 * @param[in] nargs is the number of effective elements in the 'args' array.
 * @param[out] ier is the error code on return (0 on success, 1 on error)
 * @return nothing.
 */
void CFC_API gpy_execfile(CFC_FString name, int *ln, CFC_FString args, int largs[], int *la, int *nargs, int *ier) {
   PyObject *sysmod, *list=NULL, *pgutils=NULL;
   char     *cname = malloc(*ln + 1);
   char     *carg = malloc(*la + 1);
   int      iarg;

   *ier = 1;

   CFC_f2c_strcpy(cname,name,*ln);

   gpy_start();

   /* Store the arguments in 'sys.argv' */
   if (! (sysmod = PyImport_AddModule("sys"))) {
      sic_c_message(seve_e, "PYTHON", "Failed to load Python module 'sys'");
      PyErr_Print();
   } else if (! (list = PyList_New(*nargs)) ) {
      sic_c_message(seve_e, "PYTHON", "Failed to create the temporary argv list");
      PyErr_Print();
   } else {
      for (iarg=0; iarg<*nargs; iarg++) {
        strncpy( carg, &CFC_f2c_string( args)[*la*iarg], largs[iarg]);
        carg[largs[iarg]] = '\0';
        if (PyList_SetItem(list,iarg,gpy_FromString(carg)) ) {
          sic_c_message(seve_e, "PYTHON", "Failed to copy in the temporary argv list");
          PyErr_Print();
        }
      }
   }
   free(carg);

   if ( PyObject_SetAttrString(sysmod,"argv",list) == -1 ) {
      sic_c_message(seve_e, "PYTHON", "Failed to store arguments into Python 'sys.argv'");
      PyErr_Print();
   }
   Py_XDECREF(list);

   if ( sic_verify() == 0 ) {
      /* 'sic.verify' is 'False', nothing is printed (standard call) */
      FILE *fp = fopen (cname, "r");
      if (fp == NULL) {
         sic_c_message(seve_e, "PYTHON", "Python file '%s' was not found",cname);
      } else {
         gpy_execfile_error_set(0);
         /* 1 below == also close the file */
         *ier = - PyRun_SimpleFileEx(fp,cname,1) || gpy_execfile_error_get();
      }
   /* Else call the 'pexecfile()' method */
   } else if (! (pgutils = PyImport_ImportModule("pgutils"))) {
        sic_c_message(seve_e, "PYTHON", "Could not import 'pgutils' module into Python");
        PyErr_Print();
   } else if (! PyObject_HasAttrString(pgutils, "pexecfile")) {
      sic_c_message(seve_e, "PYTHON", "'pgutils' module has no method 'pexecfile'");
   } else {
      PyObject_CallMethod(pgutils,"pexecfile","(s)",cname);
      if (PyErr_Occurred()) {
         sic_c_message(seve_e, "PYTHON", "An error occurred while executing '%s' file:",cname);
         PyErr_Print();
      } else {
         *ier = 0;
      }
   }
   Py_XDECREF(pgutils);
   free(cname);

   /* Delete the 'sys.argv' attribute */
   if (! (PyObject_HasAttrString(sysmod,"argv"))) {
      /* No matter: it may have been deleted by user. */
      ;
   } else if (PyObject_DelAttrString(sysmod,"argv") == -1) {
      sic_c_message(seve_w, "PYTHON", "Could not delete 'argv' attribute of 'sys' module");
      PyErr_Print();
   }

}

/**
 * Interpret a single Python command line.
 * The input command line is interpreted in the __main__ name area
 * (scope). The carriage return is mandatory at the end of the string
 * to be executed by PyRun_SimpleString. Print the command line if
 * the 'SIC%VERIFY' flag is on, and add it to Python prompt history.
 *
 * @param[in] command is the Python command line.
 * @param[in] l is the Fortran length of the command line.
 * @param[out] ier is the error code on return (0 on success, 1 on error)
 * @return nothing.
 */
void CFC_API gpy_exec(CFC_FString command, int *l, int *ier) {
   PyObject *module;
   char     *formatted = malloc(*l+2);

   *ier = 1;

   CFC_f2c_strcpy(formatted,command,*l);
   formatted[*l]   = '\n';
   formatted[*l+1] = '\0';

   gpy_start();

   if ( sic_verify() != 0 )
      printf(">>> %s",formatted);

   /* Run the command */
   *ier = - PyRun_SimpleString(formatted);

   /* Add the command line to Python history. */
   if (! (module = PyImport_ImportModule("readline"))) {
      /* No error, no warning. */
      ;
   } else {
      formatted[*l] = '\0';
      PyObject_CallMethod(module,"add_history","s",formatted);
   }
   Py_XDECREF(module);
   free(formatted);

}

int start_gpy_interactive_loop( void *data)
{
    int ret = PyRun_MyInteractiveLoop(stdin, "<STDIN>");
    keyboard_activate_prompt( );
    return ret;
}

sic_task_t launch_gpy_interactive_loop( )
{
    sic_disable_prompt( );
    return sic_launch( start_gpy_interactive_loop, NULL);
}

/**
 * Jump to Python prompt.
 * Depending on the slave or master status of Python, this function
 * calls the customized local InteractiveLoop to interact with the
 * Python interpreter started in background, or it return's at once
 * after changing the 'ocode' value.
 *
 * @return nothing.
 */
void CFC_API gpy_interact() {
    int isslave;
    sic_task_t py_ocode;

    gpy_start();
    isslave = pyisslave();

    /* Set 'pygildas.loop' flag to 1 in all cases. */
    if (isslave==-1) {
       sic_c_message(seve_e, "PYTHON", "An error occured while loading 'pygildas.is_slave' flag");
       return;
    } else if ( switchflag(1) == 1 ) {
       sic_c_message(seve_e, "PYTHON", "An error occured while setting 'pygildas.loop' to 1");
       return;
    }

    if (isslave==1) {
       printf("Entering interactive session. Type 'Sic()' or CTRL-D to go back to SIC.\n");
       py_ocode = launch_gpy_interactive_loop( );
    } else {
       keyboard_exit_loop( );
       sic_disable_prompt( );
    }

}

/**
 * Look for a function in Python __main__.
 * Check if the input object exists in Python __main__ and is callable.
 * If yes, adds a reference named 'pygildas.pyfunc' to this function/method.
 *
 * @param[in] fname is the function name.
 * @param[out] l is the Fortran length of the name.
 * @return 0 on success, 1 otherwise.
 */
int CFC_API gpy_findfunc(CFC_FString fname, int *l) {
   int       ret = 1;
   char     *formatted;
   PyObject *module, *func=NULL, *pygildas=NULL;

   if (!_pygildas_is_initialized())  return 1;

   formatted = malloc(*l + 1);
   CFC_f2c_strcpy(formatted,fname,*l);
   lowercase(formatted);

   PyRun_SimpleString("import sys\n");
   PyRun_SimpleString("sys.path.append('')\n");

   if (! (module = PyImport_AddModule("__main__"))) {
      sic_c_message(seve_e, "PYTHON", "Failed to load Python __main__");
      PyErr_Print();
   } else if (! PyObject_HasAttrString(module, formatted)) {
      /* fprintf(stderr,"Did not find '%s' in Python __main__.\n",formatted); */
      ;
   } else if (! (func = PyObject_GetAttrString(module, formatted))) {
      sic_c_message(seve_e, "PYTHON", "Failed to load '%s' from Python __main__",formatted);
      PyErr_Print();
   } else if (! PyCallable_Check(func)) {
      /* fprintf(stderr,"'%s' in Python is not callable.\n",formatted); */
      ;
   } else if (! (pygildas = PyImport_ImportModule("pygildas"))) {
      sic_c_message(seve_e, "PYTHON", "Could not import 'pygildas' module into Python");
      PyErr_Print();
   } else if (PyObject_SetAttrString(pygildas,"pyfunc",func) == -1) {
      sic_c_message(seve_e, "PYTHON", "Failed to add 'pyfunc' reference to 'pygildas' module");
      PyErr_Print();
   } else {
      ret = 0;
   }

   Py_XDECREF(func);
   Py_XDECREF(pygildas);
   free(formatted);

   return ret;
}

/**
 * Call a Python function with double precision.
 * To be callable on any SIC scalar or array, number of elements, strides
 * and size of 'words' are given as input arguments. Output is a scalar
 * as any SIC function.
 *
 * @param[in] nargs is the number of operands.
 * @param[in] index is the number element of the output array that is
 *            computed.
 * @param[in] strides is an array of strides to access in memory elements
 *            of the input operands.
 * @param[in] nwords is the memory location for each operand.
 * @param[in] mem is the reference address.
 * @param[out] output is the ouput value.
 * @return 0 on success, 1 otherwise.
 */
int CFC_API gpy_callfuncd(int *nargs, size_t *index, int strides[],
                          size_t nwords[], double mem[], double *output) {
   int       i, ret=1;
   size_t    shift;
   double    dble;
   PyObject *pygildas, *func=NULL, *args=NULL, *value=NULL;

   if (! (pygildas = PyImport_ImportModule("pygildas")) ) {
      sic_c_message(seve_e, "PYTHON", "Could not import 'pygildas' module into Python");
      PyErr_Print();
   } else if (! (func = PyObject_GetAttrString(pygildas, "pyfunc"))) {
      sic_c_message(seve_e, "PYTHON", "Failed to load Python function (aliased as 'pyfunc') from 'pygildas' module");
      PyErr_Print();
   } else if ( (args = PyTuple_New(*nargs)) ) {
      for (i=0; i<*nargs; i++) {
         shift  = nwords[i]-1;
         dble   = mem[shift/2+(*index-1)*strides[i]];
         /* printf("Valeur de l'arg #%d: %f\n",i,dble); */
         PyTuple_SetItem(args, i, Py_BuildValue("d",dble));
      }
      if (! (value = PyObject_CallObject(func,args))) {
         sic_c_message(seve_e, "PYTHON", "Failed to call Python function (aliased as 'pyfunc')");
         PyErr_Print();
      } else {
         *output = PyFloat_AsDouble(value);
         ret = 0;
      }
   }

   Py_XDECREF(pygildas);
   Py_XDECREF(func);
   Py_XDECREF(args);
   Py_XDECREF(value);

   return ret;
}

/**
 * Call a Python function with single precision.
 * To be callable on any SIC scalar or array, number of elements, strides
 * and size of 'words' are given as input arguments. Output is a scalar
 * as any SIC function.
 *
 * @param[in] nargs is the number of operands.
 * @param[in] index is the number element of the output array that is
 *            computed.
 * @param[in] strides is an array of strides to access in memory elements
 *            of the input operands.
 * @param[in] nwords is the memory location for each operand.
 * @param[in] mem is the reference address.
 * @param[out] output is the ouput value.
 * @return 0 on success, 1 otherwise.
 */
int CFC_API gpy_callfuncs(int *nargs, size_t *index, int strides[],
                          size_t nwords[], float mem[], float *output) {
   int       i, ret=1;
   size_t    shift;
   float     real;
   PyObject *pygildas, *func=NULL, *args=NULL, *value=NULL;

   if (! (pygildas = PyImport_ImportModule("pygildas")) ) {
      sic_c_message(seve_e, "PYTHON", "Could not import 'pygildas' module into Python");
      PyErr_Print();
   } else  if (! (func = PyObject_GetAttrString(pygildas, "pyfunc"))) {
      sic_c_message(seve_e, "PYTHON", "Failed to load Python function (aliased as 'pyfunc') from 'pygildas' module");
      PyErr_Print();
   } else if ( (args = PyTuple_New(*nargs)) ) {
      for (i=0; i<*nargs; i++) {
         shift = nwords[i]-1;
         real = mem[shift+(*index-1)*strides[i]];
         PyTuple_SetItem(args, i, Py_BuildValue("f",real));
      }
      if (! (value = PyObject_CallObject(func,args))) {
         sic_c_message(seve_e, "PYTHON", "Failed to call Python function (aliased as 'pyfunc')");
         PyErr_Print();
      } else {
         *output = (float) PyFloat_AsDouble(value);
         ret = 0;
      }
   }

   Py_XDECREF(pygildas);
   Py_XDECREF(func);
   Py_XDECREF(args);
   Py_XDECREF(value);

   return ret;
}

/**
 * Call the 'get()' method given the input SIC name.
 *
 * @param[in] varname is the name of the variable.
 * @param[in] l is the Fortran length of the string.
 * @return nothing.
 */
void CFC_API gpy_getvar(CFC_FString varname, int *levelin CFC_DECLARE_STRING_LENGTH( varname)) {
   int       siclevel;
   char     *formatted;
   PyObject *pgutils;
   PyGILState_STATE gstate;

   if (!_pygildas_is_initialized())  return;

   gstate = PyGILState_Ensure( );

    /* Variable name: */
    formatted = malloc(CFC_STRING_LENGTH( varname)+1);
    CFC_f2c_strcpy(formatted,varname,CFC_STRING_LENGTH( varname));
    /* Level as int: */
    siclevel = *levelin;

   if (! (pgutils = PyImport_ImportModule("pgutils"))) {
      sic_c_message(seve_e, "PYTHON", "Could not import 'pgutils' module into Python");
      PyErr_Print();
   } else if (! PyObject_CallMethod(pgutils,"get","(si)",formatted,siclevel)) {
      sic_c_message(seve_e, "PYTHON", "Failed to call 'get' method of 'pgutils' module");
      PyErr_Print();
   }

   Py_XDECREF(pgutils);
   free(formatted);

   PyGILState_Release( gstate);
}

/**
 * Get from parent the child named shortname and return it
 * as a PyObject.
 *
 * @param[in] shortname is the child object relatively to the parent.
 * @param[in] parent is the parent.
 * @return a PyObject*.
 */
static PyObject *get_object_from_parent(PyObject *shortname, PyObject *parent) {
    PyObject *child = NULL;

    if (PyDict_Check(parent)) {
      if (! (PyDict_Contains(parent,shortname)) ) {
         /* May be it was not imported, or deleted from python-side. No error. */
         return child;
      } else {
        if (! (child = PyDict_GetItem(parent, shortname))) {
          sic_c_message(seve_e, "PYTHON", "Failed to get '%s' attribute of object '%s'",gpy_AsString(shortname),gpy_AsString(parent));
          /* PyErr_Print(); No exception? */
          return child;
        }
        Py_XINCREF(child); /* PyDict_GetItem returns a borrowed reference */
      }
    } else {
      // check if it's a SicVar or SicStruct
      if (! (PyObject_HasAttrString(parent,"__sicname__")) ) {
        sic_c_message(seve_e, "PYTHON", "Parent object '%s' not a SicVar nor a SicStructure instance", gpy_AsString(parent));
//        PyGILState_Release( gstate);
        return child;
      }
      if (! (child = PyObject_GetAttr(parent,shortname))) {
        sic_c_message(seve_e, "PYTHON", "Failed to load '%s' attribute of object '%s'",gpy_AsString(shortname),gpy_AsString(parent));
        PyErr_Print();
        return child;
      }
    }

    return child;
}

/**
 * Delete variable in Python given the SIC name.
 * Given the SIC name, compute references to the PyObject and its
 * parent, and delete the PyObject. Additionaly, check the
 * 'Sic.localspaces' array and move back an upper-level variable which
 * has the same name, if any.
 *
 * @param[in] varname is the full name of the variable.
 * @param[in] l is the Fortran length of its name.
 * @return nothing.
 */
void CFC_API gpy_delvar(CFC_FString varname, int *levelin CFC_DECLARE_STRING_LENGTH( varname)) {
    int       level;
    PyObject *pyname=NULL, *parent=NULL, *shortname=NULL, *list=NULL;
    PyObject *child=NULL, *sicname=NULL;
    char     *formatted;
    PyGILState_STATE gstate;

    if (!_pygildas_is_initialized())  return;

    gstate = PyGILState_Ensure( );

    /* Variable name: */
    formatted = malloc(CFC_STRING_LENGTH( varname)+1);
    CFC_f2c_strcpy(formatted,varname,CFC_STRING_LENGTH( varname));
    /* Level as int: */
    level = *levelin;


    /* --- 1: Get object name and then get it from the parent --- */

    if (! (pyname = sic2pyconv(formatted))) {
       sic_c_message(seve_e, "PYTHON", "Failed to convert SIC name '%s' to Python name calling 'sic2py'",formatted);
       return;
    } else if (! (parent = getparent(pyname,level))) {
       sic_c_message(seve_e, "PYTHON", "Failed to load '%s' parent object",gpy_AsString(pyname));
       PyGILState_Release( gstate);
       return;
    } else {
       /* Get the short name (attribute) of the object */
       list = PyObject_CallMethod(pyname,"split","s","."); /* New reference */
       shortname = PyList_GetItem(list, PyList_Size(list) - 1);
       Py_XINCREF(shortname); /* GetItem returns a borrowed reference */
       Py_XDECREF(list);
    }

    child = get_object_from_parent(shortname, parent);

    /* --- 3: Check its name against original name --- */

    if (! child) {
       ;
    } else if (! (PyObject_HasAttrString(child,"__sicname__")) ) {
       sic_c_message(seve_w, "PYTHON", "'%s' variable is neither a SicVar nor a SicStructure instance",gpy_AsString(pyname));
       sic_c_message(seve_w, "PYTHON", "It won't be deleted");
    } else if (! (sicname = PyObject_GetAttrString(child,"__sicname__")) ) {
       sic_c_message(seve_e, "PYTHON", "Failed to load '__sicname__' attribute of '%s' variable in Sic object",gpy_AsString(pyname));
       PyErr_Print();
    } else if ( PyUnicode_Compare(PyUnicode_FromString(formatted),sicname) != 0 ) {
       sic_c_message(seve_e, "PYTHON", "%s.__sicname__ value ('%s') and original SIC name ('%s') do not match (deletion failure)",gpy_AsString(pyname),gpy_AsString(sicname),formatted);
    }


    /* --- 4: Delete it --- */

    if (! child) {
       ;

    } else if (PyDict_Check(parent)) {
       if ( PyDict_DelItem(parent,shortname) == -1 ) {
          sic_c_message(seve_e, "PYTHON", "Failed to delete '%s' key of dictionary '%s'",gpy_AsString(shortname),gpy_AsString(parent));
       }

    } else {
       /* Warning: make sure that attribute deletion is enabled in __delattr__ method. */
       if (PyObject_DelAttr(parent,shortname) == -1) {
          sic_c_message(seve_e, "PYTHON", "Failed to delete '%s' attribute of object '%s'",gpy_AsString(shortname),gpy_AsString(parent));
       }
    }


    /* --- 5: Optionally reincarnate saved object --- */

    if (PyLong_AsLong(PyObject_CallMethod(pyname,"find","s",".")) == -1) {
       /* RETRIEVE LOWER OBJECT: Rename lower-level variable which had the same name. */

       if (level==0) { pyunsavevar(0,pyname); } /* GLOBAL: A standard python variable may have been saved. */

       while (level>0) { /* LOCAL: Move upper level variable into global. */
          if (pyunsavevar(level-1,pyname) == 0) {
             level = 0;
          } else {
             level--;
          }
       }
    } else {
       /* Name has a dot: there is nothing to do, because its parent  *
        * structure has been/will be erased, and conflicting name has *
        * already been/will be moved. */
       ;
    }


    Py_XDECREF(pyname);
    Py_XDECREF(parent);
    Py_XDECREF(shortname);
    Py_XDECREF(child);
    Py_XDECREF(sicname);
    free(formatted);

    PyGILState_Release( gstate);
}

#if PY_MAJOR_VERSION >= 3
    #define MOD_DEF(ob, name, doc, methods) \
    static struct PyModuleDef moduledef = { \
        PyModuleDef_HEAD_INIT, NULL, NULL, -1, methods, }; \
    moduledef.m_name = name; moduledef.m_doc = doc; ob = PyModule_Create(&moduledef); 
#else
    #define MOD_DEF(ob, name, doc, methods) \
    ob = Py_InitModule3(name, methods, doc);
#endif
/**
 * Initialization routine called at import time from Python.
 *
 * @param[in] package initialization routine,
 * @param[in] module name,
 * @param[in] string for module __doc__,
 * @return the module (or NULL if an error)
 */
PyObject* gpy_pack_import( void (*fct)( ), char *name, char *doc) {
    PyObject *pgutils, *pygildas, *modu=NULL;
    PyObject *dict, *key, *value, *SicDict=NULL;
    Py_ssize_t pos=0;
    int error=0, pyg_needs_init, pyg_is_master;
    char enter_method[10];
    CFC_DECLARE_LOCAL_STRING(f_symbol);
    char c_symbol[257];        /* Equivalent to f_symbol, 256 + 1 for \0 */
    void gpack_c_import( void (*fct)( ), int debug, int *error);

    /* Import our internal modules. They define and initialize some Python
       objects. */
    if (! (pgutils = PyImport_ImportModule("pgutils"))) {
        sic_c_message(seve_e, "PYTHON", "Could not import 'pgutils' module into Python");
        PyErr_Print();
        return NULL;
    }
    if (! (pygildas = PyImport_ImportModule("pygildas"))) {
        sic_c_message(seve_e, "PYTHON", "Could not import 'pygildas' module into Python");
        PyErr_Print();
        return NULL;
    }

    /* Perform initialization flags on first load of first module */
    /* Special trick here: check the existence of an object pygildas.is_slave
     *  - If it does not exists, we need initialization. Furthermore,
     *    Python is master if gmaster_get_ismaster returns 0, i.e. nothing
     *    was loaded on the SIC side yet. Then we define pygildas.is_slave
     *    according to this master/slave status.
     *  - If it exists, no more initializations are required, and
     *    master/slave status relies on pygildas.is_slave */
    if ( PyObject_HasAttrString(pygildas,"is_slave") == 0 ) {
       pyg_needs_init = 1;
       pyg_is_master  = ( gmaster_get_ismaster() == 0 );
    } else {
       pyg_needs_init = 0;
       pyg_is_master  = PyObject_RichCompareBool(PyObject_GetAttrString(pygildas,"is_slave"),Py_True,Py_NE);
    }

    if (pyg_is_master) {

       /* Perform python-master specific initializations */
       strcpy(enter_method,"enter");

       if (pyg_needs_init) {
          Py_INCREF(Py_False);
          PyModule_AddObject(pygildas,"is_slave",Py_False);
          PyModule_AddObject(pygildas,"loop",PyLong_FromLong((long) 1));

          /* Initialize Gildas with SIC as slave */
          sic_disable_prompt( );
          gmaster_build_sic (&error);
          if (error) {
              sic_c_message(seve_e,"PYTHON","Failed to initialize Gildas");
              return NULL;
          }
       }

    } else {

       /* Perform python-slave specific initializations */
       strcpy(enter_method,"exitloop");

       if (pyg_needs_init) {
          Py_INCREF(Py_True);
          PyModule_AddObject(pygildas,"is_slave",Py_True);
          PyModule_AddObject(pygildas,"loop",PyLong_FromLong((long) 0));

          /* What about exit() ?
           * def exit():
           *    pygildas.loop = -1 */
       }

    }

    /* Import the current package in Gildas */
    Py_BEGIN_ALLOW_THREADS
    gpack_c_import( fct, 0, &error);
    if (pyg_is_master) {
        /* If Python is not master, interpreter is already launched */
        gmaster_launch_interpreter( );
    }
    Py_END_ALLOW_THREADS
    if (error) {
        sic_c_message(seve_e, "PYTHON", "Package could not be imported in Gildas");
        return NULL;
    }

    /* Initialize the current module in Python */
    // I think there's nothing to do with the REFS because 1) it's a new ref
    // 2) in both cases (py2, py3) we don't want it to be g-c'ed
    MOD_DEF(modu, name, doc, NULL)

    /* Add methods to the current module. Need to import them from internal
       modules. 'sic_exports' dictionary summarizes them */
    if (! PyObject_HasAttrString(pgutils, "sic_exports")) {
       sic_c_message(seve_e, "PYTHON", "Did not find 'sic_exports' attribute of 'pgutils' module");
       return NULL;
    } else if (! (dict = PyObject_GetAttrString(pgutils, "sic_exports"))) {
       sic_c_message(seve_e, "PYTHON", "Failed to load 'sic_exports' attribute of 'pgutils' module");
       PyErr_Print();
       return NULL;
    } else if (! (PyDict_Check(dict))) {
       sic_c_message(seve_e, "PYTHON", "'sic_exports' attribute of 'pgutils' module is not a dictionary");
       return NULL;
    } else {
       while (PyDict_Next(dict, &pos, &key, &value)) {
          Py_INCREF(value);
          PyModule_AddObject(modu, gpy_AsString(key), value);
       }
    }

    /* Add the enter method (which leaves Python and (re)enters Gildas) */
    if (! PyObject_HasAttrString(pygildas, enter_method)) {
       sic_c_message(seve_e, "PYTHON", "Did not find '%s' attribute of 'pygildas' module", enter_method);
       return NULL;
    } else if (! (value = PyObject_GetAttrString(pygildas, enter_method))) {
       sic_c_message(seve_e, "PYTHON", "Failed to load '%s' attribute of 'pygildas' module", enter_method);
       PyErr_Print();
       return NULL;
    } else if (! PyCallable_Check(value)) {
       sic_c_message(seve_e, "PYTHON", "'%s' attribute of 'pygildas' module is not callable", enter_method);
       return NULL;
    } else {
       if (PyModule_AddObject(modu, "enter", value)) {  /* Allways call it 'enter' for user */
          sic_c_message(seve_e, "PYTHON", "Could not add 'enter' method for module");
          return NULL;
       }
    }

    /* Add an instance of 'SicDict' */
    if (! PyObject_HasAttrString(pgutils, "SicDict")) {
       sic_c_message(seve_e, "PYTHON", "Did not find 'SicDict' class of 'pgutils' module");
       return NULL;
    } else if (! (SicDict = PyObject_GetAttrString(pgutils, "SicDict"))) {
       sic_c_message(seve_e, "PYTHON", "Failed to load 'SicDict' attribute of 'pgutils' module");
       PyErr_Print();
       return NULL;
//    } else if (! (value = PyInstance_New(SicDict,NULL,NULL))) {
    } else if (! (value = PyObject_CallObject(SicDict ,NULL))) {
       sic_c_message(seve_e, "PYTHON", "Failed to instantiate 'SicDict'");
       PyErr_Print();
       return NULL;
    } else if (PyModule_AddObject(modu, "gdict", value)) {
       sic_c_message(seve_e, "PYTHON", "Could not add 'gdict' instance to module");
       return NULL;
    }

    Py_XDECREF(pygildas);
    Py_XDECREF(pgutils);
    Py_XDECREF(SicDict);

    /* Add the __version__ attribute to current module */
    CFC_STRING_VALUE(f_symbol) = (char *)c_symbol;  /* Make Fortran strings pointing to C ones. */
    CFC_STRING_LENGTH(f_symbol) = 256;
    CFC_c2f_strcpy(f_symbol,256,"GAG_VERSION");

    sic_getlog_inplace(f_symbol CFC_PASS_STRING_LENGTH(f_symbol));
    c_symbol[256] = '\0';
    CFC_suppressEndingSpaces( c_symbol);

    PyModule_AddObject(modu, "__version__", PyUnicode_FromString(c_symbol));

    return modu;
}

/**
 * Add method to the input module after its initialization. Code mostly
 * duplicated from Python sources 'modsupport.c'
 *
 * @param[in] the Python module to be filled,
 * @param[in] the methods list, can be empty with first element NULL,
 * @return    nothing.
 */
void gpy_addmethods(PyObject *m, PyMethodDef *methods) {
  PyObject *d, *v, *n;
  PyMethodDef *ml;

  d = PyModule_GetDict(m);
  if (methods != NULL) {
      n = PyObject_GetAttrString(m,"__name__");
      if (n == NULL)
          return;
      for (ml = methods; ml->ml_name != NULL; ml++) {
          if ((ml->ml_flags & METH_CLASS) ||
              (ml->ml_flags & METH_STATIC)) {
              PyErr_SetString(PyExc_ValueError,
                              "module functions cannot set"
                              " METH_CLASS or METH_STATIC");
              Py_DECREF(n);
              return;
          }
          v = PyCFunction_NewEx(ml, (PyObject *)NULL, n);
          if (v == NULL) {
              Py_DECREF(n);
              return;
          }
          if (PyDict_SetItemString(d, ml->ml_name, v) != 0) {
              Py_DECREF(v);
              Py_DECREF(n);
              return;
          }
          Py_DECREF(v);
      }
      Py_DECREF(n);
  }

}

/**
 * Display compilation vs runtime Python information.
 */
void sic_debug_python() {

  printf("Gildas compiled with Python %s\n",PY_VERSION);

  printf("Gildas running  with Python %s\n",Py_GetVersion());
}
