/*
 * glue code for calling PYTHON from within DPUSER
 *
 * In order to use this, DPUSER has to be compiled with -DHAS_PYTHON
 * no: DPUSER has to be started with LD_PRELOAD=...python.so dpuser
 */

#include "dpuserType.h"
#include "dpuser_utils.h"
#include <fits.h>
#include <string>
#include <vector>
#include <map>

#ifdef WIN
#include <windows.h>
#else
#include <dlfcn.h>
#endif /* WIN */

#ifdef HAS_PYTHON

#ifdef DPQT
#pragma push_macro("slots")
#undef slots
#endif

#pragma GCC push_options
#pragma GCC optimize ("O2")

#include <Python.h>
extern "C" {
static void **PyArray_API=NULL;
}
#define NO_IMPORT_ARRAY
#include <numpy/arrayobject.h>

//#include <python/object.h>

#ifdef DPQT
#pragma pop_macro("slots")
#endif

PyObject *main_module, *main_dict;
bool has_numpy = false;
int pythonVersion = 3;
void *library = NULL;

typedef int (*intFunction)(void);
typedef int (*intFunction2)(PyObject *);
typedef long (*longFunction2)(PyObject *);
typedef double (*doubleFunction2)(PyObject *);
typedef int (*intFunction3)(PyObject *, Py_ssize_t, PyObject *);
typedef int (*intFunction4)(PyObject *, const char *, ...);
typedef int (*intFunction5)(PyTypeObject *);
typedef int (*intFunction6)(PyObject *, const char *, PyObject *);
typedef int (*intFunction7)(const char *, PyObject *);
typedef int (*intFunction8)(const char *, PyObject * (*initfunc)(void));
typedef int (*intFunction9)(const char *, PyCompilerFlags *);
typedef char* (*charFunction)(PyObject *);
typedef void (*voidFunction)(void);
typedef void* (*voidFunction1)(PyObject *);
typedef void* (*voidFunction2)(PyObject *, const char *);
typedef Py_ssize_t (*sizetFunction)(PyObject *);
typedef PyObject* (*PyObjectFunction0)(void);
typedef PyObject* (*PyObjectFunction)(const char *);
typedef PyObject* (*PyObjectFunction2)(const char *format, ...);
typedef PyObject* (*PyObjectFunction3)(PyObject *, const char *);
typedef PyObject* (*PyObjectFunction4)(PyObject *);
typedef PyObject* (*PyObjectFunction5)(Py_ssize_t);
typedef PyObject* (*PyObjectFunction6)(PyObject *, PyObject *);
typedef PyObject* (*PyObjectFunction7)(PyObject *, Py_ssize_t);
typedef PyObject* (*PyObjectFunction8)(PyTypeObject *, PyObject *, PyObject *);
typedef PyObject* (*PyObjectFunction9)(struct PyModuleDef*, int);
typedef PyObject* (*PyObjectFunction10)(const char *, PyMethodDef *, const char *, PyObject *, int);
typedef PyObject* (*PyObjectFunction11)(PyObject *, PyObject *, PyObject *);
typedef PyObject* (*PyObjectFunction12)(const char *, int, PyObject *, PyObject *, PyCompilerFlags *);

intFunction dpPy_IsInitialized;
intFunction2 dpPyCallable_Check;
longFunction2 dpPyLong_AsLong;
doubleFunction2 dpPyFloat_AsDouble;
doubleFunction2 dpPyComplex_RealAsDouble;
doubleFunction2 dpPyComplex_ImagAsDouble;
intFunction3 dpPyTuple_SetItem;
intFunction4 dpPyArg_ParseTuple;
intFunction5 dpPyType_Ready;
intFunction6 dpPyModule_AddObject;
intFunction6 dpPyDict_SetItemString;
intFunction7 dpPySys_SetObject;
intFunction8 dpPyImport_AppendInittab;
intFunction9 dpPyRun_SimpleStringFlags;
charFunction dpPyUnicode_AsUTF8;
charFunction dpPyBytes_AsString;
voidFunction dpPy_Initialize;
voidFunction dpPyErr_Print;
voidFunction dpPyErr_Clear;
sizetFunction dpPyTuple_Size;
sizetFunction dpPyList_Size;
PyObjectFunction0 dpPyErr_Occurred;
PyObjectFunction dpPyImport_ImportModule;
PyObjectFunction dpPyImport_AddModule;
PyObjectFunction dpPyUnicode_DecodeFSDefault;
PyObjectFunction2 dpPy_BuildValue;
PyObjectFunction3 dpPyObject_GetAttrString;
PyObjectFunction3 dpPyDict_GetItemString;
voidFunction1 dpPyCObject_AsVoidPtr;
PyObjectFunction0 dpPyEval_GetGlobals;
PyObjectFunction0 dpPyImport_GetModuleDict;
PyObjectFunction0 dpPyDict_New;
voidFunction2 dpPyCapsule_GetPointer;
PyObjectFunction4 dpPyImport_Import;
PyObjectFunction4 dpPyImport_ReloadModule;
PyObjectFunction4 dpPyObject_Bytes;
PyObjectFunction4 dpPyDict_Keys;
PyObjectFunction4 dpPyDict_Values;
PyObjectFunction4 dpPyModule_GetDict;
PyObjectFunction5 dpPyTuple_New;
PyObjectFunction6 dpPyObject_CallObject;
PyObjectFunction7 dpPyTuple_GetItem;
PyObjectFunction7 dpPyList_GetItem;
PyObjectFunction8 dpPyType_GenericNew;
PyObjectFunction9 dpPyModule_Create2;
PyObjectFunction dpPySys_GetObject;
PyObjectFunction11 dpPyEval_CallObjectWithKeywords;
PyObjectFunction12 dpPyRun_StringFlags;

bool loadPythonLibrary(const char *path) {
    if (library) return true;
#ifdef WIN
    library = LoadLibraryA(path);
#else
    library = dlopen(path, RTLD_LAZY|RTLD_GLOBAL);
#endif
    if (!library) {
        std::cout << "no python library found!" << std::endl;
        return false;
    }
#ifdef WIN
    dpPy_IsInitialized = (intFunction)GetProcAddress((HINSTANCE)library, "Py_IsInitialized");
    dpPyCallable_Check = (intFunction2)GetProcAddress((HINSTANCE)library, "PyCallable_Check");
    dpPyTuple_SetItem = (intFunction3)GetProcAddress((HINSTANCE)library, "PyTuple_SetItem");
    dpPyUnicode_AsUTF8 = (charFunction)GetProcAddress((HINSTANCE)library, "PyUnicode_AsUTF8");
    dpPyBytes_AsString = (charFunction)GetProcAddress((HINSTANCE)library, "PyBytes_AsString");
    if (dpPyBytes_AsString == NULL) dpPyBytes_AsString = (charFunction)GetProcAddress((HINSTANCE)library, "PyString_AsString");
    dpPy_Initialize = (voidFunction)GetProcAddress((HINSTANCE)library, "Py_Initialize");
    dpPyErr_Print = (voidFunction)GetProcAddress((HINSTANCE)library, "PyErr_Print");
    dpPyErr_Clear = (voidFunction)GetProcAddress((HINSTANCE)library, "PyErr_Clear");
    dpPyErr_Occurred = (PyObjectFunction0)GetProcAddress((HINSTANCE)library, "PyErr_Occurred");
    dpPyImport_ImportModule = (PyObjectFunction)GetProcAddress((HINSTANCE)library, "PyImport_ImportModule");
    dpPyImport_AddModule = (PyObjectFunction)GetProcAddress((HINSTANCE)library, "PyImport_AddModule");
    dpPyUnicode_DecodeFSDefault = (PyObjectFunction)GetProcAddress((HINSTANCE)library, "PyUnicode_DecodeFSDefault");
    dpPy_BuildValue = (PyObjectFunction2)GetProcAddress((HINSTANCE)library, "Py_BuildValue");
    dpPyObject_GetAttrString = (PyObjectFunction3)GetProcAddress((HINSTANCE)library, "PyObject_GetAttrString");
    dpPyDict_GetItemString = (PyObjectFunction3)GetProcAddress((HINSTANCE)library, "PyDict_GetItemString");
    dpPyCObject_AsVoidPtr = (voidFunction1)GetProcAddress((HINSTANCE)library, "PyCObject_AsVoidPtr");
    dpPyDict_New = (PyObjectFunction0)GetProcAddress((HINSTANCE)library, "PyDict_New");
    dpPyCapsule_GetPointer = (voidFunction2)GetProcAddress((HINSTANCE)library, "PyCapsule_GetPointer");
    dpPyImport_Import = (PyObjectFunction4)GetProcAddress((HINSTANCE)library, "PyImport_Import");
    dpPyImport_ReloadModule = (PyObjectFunction4)GetProcAddress((HINSTANCE)library, "PyImport_ReloadModule");
    dpPyObject_Bytes = (PyObjectFunction4)GetProcAddress((HINSTANCE)library, "PyObject_Str");
    dpPyDict_Keys = (PyObjectFunction4)GetProcAddress((HINSTANCE)library, "PyDict_Keys");
    dpPyDict_Values = (PyObjectFunction4)GetProcAddress((HINSTANCE)library, "PyDict_Values");
    dpPyModule_GetDict = (PyObjectFunction4)GetProcAddress((HINSTANCE)library, "PyModule_GetDict");
    dpPyTuple_New = (PyObjectFunction5)GetProcAddress((HINSTANCE)library, "PyTuple_New");
    dpPyObject_CallObject = (PyObjectFunction6)GetProcAddress((HINSTANCE)library, "PyObject_CallObject");
    dpPyLong_AsLong = (longFunction2)GetProcAddress((HINSTANCE)library, "PyLong_AsLong");
    dpPyFloat_AsDouble = (doubleFunction2)GetProcAddress((HINSTANCE)library, "PyFloat_AsDouble");
    dpPyComplex_RealAsDouble = (doubleFunction2)GetProcAddress((HINSTANCE)library, "PyComplex_RealAsDouble");
    dpPyComplex_ImagAsDouble = (doubleFunction2)GetProcAddress((HINSTANCE)library, "PyComplex_ImagAsDouble");
    dpPyTuple_Size = (sizetFunction)GetProcAddress((HINSTANCE)library, "PyTuple_Size");
    dpPyTuple_GetItem = (PyObjectFunction7)GetProcAddress((HINSTANCE)library, "PyTuple_GetItem");
    dpPyList_Size = (sizetFunction)GetProcAddress((HINSTANCE)library, "PyList_Size");
    dpPyList_GetItem = (PyObjectFunction7)GetProcAddress((HINSTANCE)library, "PyList_GetItem");
    dpPyArg_ParseTuple = (intFunction4)GetProcAddress((HINSTANCE)library, "PyArg_ParseTuple");
    dpPyType_Ready = (intFunction5)GetProcAddress((HINSTANCE)library, "PyType_Ready");
    dpPyType_GenericNew = (PyObjectFunction8)GetProcAddress((HINSTANCE)library, "PyType_GenericNew");
    dpPyModule_Create2 = (PyObjectFunction9)GetProcAddress((HINSTANCE)library, "PyModule_Create2");
    dpPyModule_AddObject = (intFunction6)GetProcAddress((HINSTANCE)library, "PyModule_AddObject");
    dpPyDict_SetItemString = (intFunction6)GetProcAddress((HINSTANCE)library, "PyDict_SetItemString");
    dpPySys_SetObject = (intFunction7)GetProcAddress((HINSTANCE)library, "PySys_SetObject");
    dpPyImport_AppendInittab = (intFunction8)GetProcAddress((HINSTANCE)library, "PyImport_AppendInittab");
    dpPySys_GetObject = (PyObjectFunction)GetProcAddress((HINSTANCE)library, "PySys_GetObject");
    dpPyEval_CallObjectWithKeywords = (PyObjectFunction11)GetProcAddress((HINSTANCE)library, "PyEval_CallObjectWithKeywords");
    dpPyRun_SimpleStringFlags = (intFunction9)GetProcAddress((HINSTANCE)library, "PyRun_SimpleStringFlags");
    dpPyEval_GetGlobals = (PyObjectFunction0)GetProcAddress((HINSTANCE)library, "PyEval_GetGlobals");
    dpPyImport_GetModuleDict = (PyObjectFunction0)GetProcAddress((HINSTANCE)library, "PyImport_GetModuleDict");
    dpPyRun_StringFlags = (PyObjectFunction12)GetProcAddress((HINSTANCE)library, "PyRun_StringFlags");
#else
    dpPy_IsInitialized = (intFunction)dlsym(library, "Py_IsInitialized");
    dpPyCallable_Check = (intFunction2)dlsym(library, "PyCallable_Check");
    dpPyTuple_SetItem = (intFunction3)dlsym(library, "PyTuple_SetItem");
    dpPyUnicode_AsUTF8 = (charFunction)dlsym(library, "PyUnicode_AsUTF8");
    dpPyBytes_AsString = (charFunction)dlsym(library, "PyBytes_AsString");
    if (dpPyBytes_AsString == NULL) dpPyBytes_AsString = (charFunction)dlsym(library, "PyString_AsString"); // Python 2.x
    dpPy_Initialize = (voidFunction)dlsym(library, "Py_Initialize");
    dpPyErr_Print = (voidFunction)dlsym(library, "PyErr_Print");
    dpPyErr_Clear = (voidFunction)dlsym(library, "PyErr_Clear");
    dpPyErr_Occurred = (PyObjectFunction0)dlsym(library, "PyErr_Occurred");
    dpPyImport_ImportModule = (PyObjectFunction)dlsym(library, "PyImport_ImportModule");
    dpPyImport_AddModule = (PyObjectFunction)dlsym(library, "PyImport_AddModule");
    dpPyUnicode_DecodeFSDefault = (PyObjectFunction)dlsym(library, "PyUnicode_DecodeFSDefault");
    dpPy_BuildValue = (PyObjectFunction2)dlsym(library, "Py_BuildValue");
    dpPyObject_GetAttrString = (PyObjectFunction3)dlsym(library, "PyObject_GetAttrString");
    dpPyDict_GetItemString = (PyObjectFunction3)dlsym(library, "PyDict_GetItemString");
    dpPyCObject_AsVoidPtr = (voidFunction1)dlsym(library, "PyCObject_AsVoidPtr");
    dpPyDict_New = (PyObjectFunction0)dlsym(library, "PyDict_New");
    dpPyCapsule_GetPointer = (voidFunction2)dlsym(library, "PyCapsule_GetPointer");
    dpPyImport_Import = (PyObjectFunction4)dlsym(library, "PyImport_Import");
    dpPyImport_ReloadModule = (PyObjectFunction4)dlsym(library, "PyImport_ReloadModule");
    dpPyObject_Bytes = (PyObjectFunction4)dlsym(library, "PyObject_Str");
    dpPyDict_Keys = (PyObjectFunction4)dlsym(library, "PyDict_Keys");
    dpPyDict_Values = (PyObjectFunction4)dlsym(library, "PyDict_Values");
    dpPyModule_GetDict = (PyObjectFunction4)dlsym(library, "PyModule_GetDict");
    dpPyTuple_New = (PyObjectFunction5)dlsym(library, "PyTuple_New");
    dpPyObject_CallObject = (PyObjectFunction6)dlsym(library, "PyObject_CallObject");
    dpPyLong_AsLong = (longFunction2)dlsym(library, "PyLong_AsLong");
    dpPyFloat_AsDouble = (doubleFunction2)dlsym(library, "PyFloat_AsDouble");
    dpPyComplex_RealAsDouble = (doubleFunction2)dlsym(library, "PyComplex_RealAsDouble");
    dpPyComplex_ImagAsDouble = (doubleFunction2)dlsym(library, "PyComplex_ImagAsDouble");
    dpPyTuple_Size = (sizetFunction)dlsym(library, "PyTuple_Size");
    dpPyTuple_GetItem = (PyObjectFunction7)dlsym(library, "PyTuple_GetItem");
    dpPyList_Size = (sizetFunction)dlsym(library, "PyList_Size");
    dpPyList_GetItem = (PyObjectFunction7)dlsym(library, "PyList_GetItem");
    dpPyArg_ParseTuple = (intFunction4)dlsym(library, "PyArg_ParseTuple");
    dpPyType_Ready = (intFunction5)dlsym(library, "PyType_Ready");
    dpPyType_GenericNew = (PyObjectFunction8)dlsym(library, "PyType_GenericNew");
    dpPyModule_Create2 = (PyObjectFunction9)dlsym(library, "PyModule_Create2"); // only exists in Python 3.x
    dpPyModule_AddObject = (intFunction6)dlsym(library, "PyModule_AddObject");
    dpPyDict_SetItemString = (intFunction6)dlsym(library, "PyDict_SetItemString");
    dpPySys_SetObject = (intFunction7)dlsym(library, "PySys_SetObject");
    dpPyImport_AppendInittab = (intFunction8)dlsym(library, "PyImport_AppendInittab");
    dpPySys_GetObject = (PyObjectFunction)dlsym(library, "PySys_GetObject");
    dpPyEval_CallObjectWithKeywords = (PyObjectFunction11)dlsym(library, "PyEval_CallObjectWithKeywords");
    dpPyRun_SimpleStringFlags = (intFunction9)dlsym(library, "PyRun_SimpleStringFlags");
    dpPyEval_GetGlobals = (PyObjectFunction0)dlsym(library, "PyEval_GetGlobals");
    dpPyImport_GetModuleDict = (PyObjectFunction0)dlsym(library, "PyImport_GetModuleDict");
    dpPyRun_StringFlags = (PyObjectFunction12)dlsym(library, "PyRun_StringFlags");
#endif
    if (dpPyModule_Create2 == NULL) pythonVersion = 2;

    std::string missing;

    if (dpPy_IsInitialized == NULL) missing = "Py_IsInitialized";
    if (dpPyCallable_Check == NULL) missing = "PyCallable_Check";
    if (dpPyLong_AsLong == NULL) missing = "PyLong_AsLong";
    if (dpPyFloat_AsDouble == NULL) missing = "PyFloat_AsDouble";
    if (dpPyComplex_RealAsDouble == NULL) missing = "PyComplex_RealAsDouble";
    if (dpPyComplex_ImagAsDouble == NULL) missing = "PyComplex_ImagAsDouble";
    if (dpPyTuple_SetItem == NULL) missing = "PyTuple_SetItem";
    if (dpPyArg_ParseTuple == NULL) missing = "PyArg_ParseTuple";
    if (dpPyType_Ready == NULL) missing = "PyType_Ready";
    if (dpPyModule_AddObject == NULL) missing = "PyModule_AddObject";
    if (dpPySys_SetObject == NULL) missing = "PySys_SetObject";
    if (dpPyImport_AppendInittab == NULL) missing = "PyImport_AppendInittab";
    if (dpPyBytes_AsString == NULL) missing = "PyBytes_AsString";
    if (dpPy_Initialize == NULL) missing = "Py_Initialize";
    if (dpPyErr_Print == NULL) missing = "PyErr_Print";
    if (dpPyErr_Clear == NULL) missing = "PyErr_Clear";
    if (dpPyTuple_Size == NULL) missing = "PyTuple_Size";
    if (dpPyList_Size == NULL) missing = "PyList_Size";
    if (dpPyErr_Occurred == NULL) missing = "PyErr_Occurred";
    if (dpPyImport_ImportModule == NULL) missing = "PyImport_ImportModule";
    if (dpPyImport_AddModule == NULL) missing = "PyImport_AddModule";
    if (dpPy_BuildValue == NULL) missing = "Py_BuildValue";
    if (dpPyObject_GetAttrString == NULL) missing = "PyObject_GetAttrString";
    if (dpPyDict_GetItemString == NULL) missing = "PyDict_GetItemString";
    if (dpPyCapsule_GetPointer == NULL) missing = "PyCapsule_GetPointer";
    if (dpPyImport_Import == NULL) missing = "PyImport_Import";
    if (dpPyObject_Bytes == NULL) missing = "PyObject_Bytes";
    if (dpPyDict_Keys == NULL) missing = "PyDict_Keys";
    if (dpPyDict_Values == NULL) missing = "PyDict_Values";
    if (dpPyModule_GetDict == NULL) missing = "PyModule_GetDict";
    if (dpPyTuple_New == NULL) missing = "PyTuple_New";
    if (dpPyObject_CallObject == NULL) missing = "PyObject_CallObject";
    if (dpPyTuple_GetItem == NULL) missing = "PyTuple_GetItem";
    if (dpPyList_GetItem == NULL) missing = "PyList_GetItem";
    if (dpPyType_GenericNew == NULL) missing = "PyType_GenericNew";
    if (dpPySys_GetObject == NULL) missing = "PySys_GetObject";
    if (dpPyDict_New == NULL) missing = "PyDict_New";
    if (dpPyDict_SetItemString == NULL) missing = "PyDict_SetItemString";
    if (dpPyEval_CallObjectWithKeywords == NULL) missing = "PyEval_CallObjectWithKeywords";
    if (dpPyRun_SimpleStringFlags == NULL) missing = "PyRun_SimpleStringFlags";
    if (dpPyEval_GetGlobals == NULL) missing = "PyEval_GetGlobals";
    if (dpPyImport_GetModuleDict == NULL) missing = "PyImport_GetModuleDict";
    if (dpPyRun_StringFlags == NULL) missing = "PyRun_StringFlags";

    if (pythonVersion == 2) {
        if (dpPyCObject_AsVoidPtr == NULL) missing = "PyCObject_AsVoidPtr";
    } else {
        if (dpPyModule_Create2 == NULL) missing = "PyModule_Create2";
        if (dpPyUnicode_DecodeFSDefault == NULL) missing = "PyUnicode_DecodeFSDefault";
        if (dpPyUnicode_AsUTF8 == NULL) missing = "PyUnicode_AsUTF8";
    }

    if (missing != "") {
        dp_output("Loading python: Could not resolve function %s\n", missing.c_str());
        return false;
    }

    return true;
}

#ifdef DPQT

// code from http://mateusz.loskot.net/post/2011/12/01/python-sys-stdout-redirection-in-cpp/

#include <qtdpuser.h>
#include <QFitsGlobal.h>
#include <functional>
#include <iostream>

namespace emb
{

typedef std::function<void(std::string)> stdout_write_type;

struct Stdout
{
    PyObject_HEAD
    stdout_write_type write;
};

PyObject* Stdout_write(PyObject* self, PyObject* args)
{
    std::size_t written(0);
    Stdout* selfimpl = reinterpret_cast<Stdout*>(self);
    if (selfimpl->write)
    {
        char* data;
        if (!dpPyArg_ParseTuple(args, "s", &data))
            return 0;

        std::string str(data);
        selfimpl->write(str);
        written = str.size();
    }
    return dpPy_BuildValue("l", written);
}

PyObject* Stdout_flush(PyObject* self, PyObject* args)
{
    // no-op
    return dpPy_BuildValue("");
}

PyMethodDef Stdout_methods[] =
{
    {"write", Stdout_write, METH_VARARGS, "sys.stdout.write"},
    {"flush", Stdout_flush, METH_VARARGS, "sys.stdout.write"},
    {0, 0, 0, 0} // sentinel
};

PyTypeObject StdoutType =
{
    PyVarObject_HEAD_INIT(0, 0)
    "emb.StdoutType",     /* tp_name */
    sizeof(Stdout),       /* tp_basicsize */
    0,                    /* tp_itemsize */
    0,                    /* tp_dealloc */
    0,                    /* tp_print */
    0,                    /* tp_getattr */
    0,                    /* tp_setattr */
    0,                    /* tp_reserved */
    0,                    /* tp_repr */
    0,                    /* tp_as_number */
    0,                    /* tp_as_sequence */
    0,                    /* tp_as_mapping */
    0,                    /* tp_hash  */
    0,                    /* tp_call */
    0,                    /* tp_str */
    0,                    /* tp_getattro */
    0,                    /* tp_setattro */
    0,                    /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT,   /* tp_flags */
    "emb.Stdout objects", /* tp_doc */
    0,                    /* tp_traverse */
    0,                    /* tp_clear */
    0,                    /* tp_richcompare */
    0,                    /* tp_weaklistoffset */
    0,                    /* tp_iter */
    0,                    /* tp_iternext */
    Stdout_methods,       /* tp_methods */
    0,                    /* tp_members */
    0,                    /* tp_getset */
    0,                    /* tp_base */
    0,                    /* tp_dict */
    0,                    /* tp_descr_get */
    0,                    /* tp_descr_set */
    0,                    /* tp_dictoffset */
    0,                    /* tp_init */
    0,                    /* tp_alloc */
    0,                    /* tp_new */
};

PyModuleDef embmodule =
{
    PyModuleDef_HEAD_INIT,
    "emb", 0, -1, 0,
};

// Internal state
PyObject* g_stdout;
PyObject* g_stdout_saved;
PyObject* g_stderr;
PyObject* g_stderr_saved;

PyMODINIT_FUNC PyInit_emb(void)
{
    g_stdout = 0;
    g_stdout_saved = 0;

    StdoutType.tp_new = dpPyType_GenericNew;
    if (dpPyType_Ready(&StdoutType) < 0)
        return 0;

    PyObject* m = dpPyModule_Create2(&embmodule, PYTHON_API_VERSION);
    if (m)
    {
        Py_INCREF(&StdoutType);
        dpPyModule_AddObject(m, "Stdout", reinterpret_cast<PyObject*>(&StdoutType));
    }
    return m;
}

void set_stdout(stdout_write_type write)
{
    if (!g_stdout)
    {
        g_stdout_saved = dpPySys_GetObject("stdout"); // borrowed
        g_stdout = StdoutType.tp_new(&StdoutType, 0, 0);
    }

    Stdout* impl = reinterpret_cast<Stdout*>(g_stdout);
    impl->write = write;
    dpPySys_SetObject("stdout", g_stdout);
}

void set_stderr(stdout_write_type write)
{
    if (!g_stderr)
    {
        g_stderr_saved = dpPySys_GetObject("stderr"); // borrowed
        g_stderr = StdoutType.tp_new(&StdoutType, 0, 0);
    }

    Stdout* impl = reinterpret_cast<Stdout*>(g_stderr);
    impl->write = write;
    dpPySys_SetObject("stderr", g_stderr);
}

void reset_stdout()
{
    if (g_stdout_saved)
        dpPySys_SetObject("stdout", g_stdout_saved);

    Py_XDECREF(g_stdout);
    g_stdout = 0;
}

} // namespace emb

#endif


/* Copied from Python's __multiarray_api.h, since we have to replace the calls into the library */

static int dp_import_array(PyObject *numpy) {
  int st;
  PyObject *c_api = NULL;

  c_api = dpPyObject_GetAttrString(numpy, "_ARRAY_API");
  Py_DECREF(numpy);
  if (c_api == NULL) {
//      PyErr_SetString(PyExc_AttributeError, "_ARRAY_API not found");
      return -1;
  }

  if (dpPyCObject_AsVoidPtr != NULL) { // Python 2.x
      std::cout << "seems to be Python 2.x" << std::endl;
      PyArray_API = (void **)dpPyCObject_AsVoidPtr(c_api);
      pythonVersion = 2;
  } else if (dpPyCapsule_GetPointer != NULL) { // Python 3.x
      std::cout << "seems to be Python 3.x" << std::endl;
      PyArray_API = (void **)dpPyCapsule_GetPointer(c_api, NULL);
  } else {
      return -1;
  }

#if PY_VERSION_HEX >= 0x03000000
//  if (!PyCapsule_CheckExact(c_api)) {
//      PyErr_SetString(PyExc_RuntimeError, "_ARRAY_API is not PyCapsule object");
//      Py_DECREF(c_api);
//      return -1;
//  }
//  PyArray_API = (void **)dpPyCapsule_GetPointer(c_api, NULL);
#else
//  if (!PyCObject_Check(c_api)) {
//      PyErr_SetString(PyExc_RuntimeError, "_ARRAY_API is not PyCObject object");
//      Py_DECREF(c_api);
//      return -1;
//  }
//  PyArray_API = (void **)PyCObject_AsVoidPtr(c_api);
#endif
  Py_DECREF(c_api);
  if (PyArray_API == NULL) {
//      PyErr_SetString(PyExc_RuntimeError, "_ARRAY_API is NULL pointer");
      return -1;
  }

  /* Perform runtime check of C API version */
  if (NPY_VERSION != PyArray_GetNDArrayCVersion()) {
//      PyErr_Format(PyExc_RuntimeError, "module compiled against "\
//             "ABI version 0x%x but this version of numpy is 0x%x", \
//             (int) NPY_VERSION, (int) PyArray_GetNDArrayCVersion());
      return -1;
  }
  if (NPY_FEATURE_VERSION > PyArray_GetNDArrayCFeatureVersion()) {
//      PyErr_Format(PyExc_RuntimeError, "module compiled against "\
//             "API version 0x%x but this version of numpy is 0x%x", \
//             (int) NPY_FEATURE_VERSION, (int) PyArray_GetNDArrayCFeatureVersion());
      return -1;
  }

  /*
   * Perform runtime check of endianness and check it matches the one set by
   * the headers (npy_endian.h) as a safeguard
   */
  st = PyArray_GetEndianness();
  if (st == NPY_CPU_UNKNOWN_ENDIAN) {
//      PyErr_Format(PyExc_RuntimeError, "FATAL: module compiled as unknown endian");
      return -1;
  }
#if NPY_BYTE_ORDER == NPY_BIG_ENDIAN
  if (st != NPY_CPU_BIG) {
      PyErr_Format(PyExc_RuntimeError, "FATAL: module compiled as "\
             "big endian, but detected different endianness at runtime");
      return -1;
  }
#elif NPY_BYTE_ORDER == NPY_LITTLE_ENDIAN
  if (st != NPY_CPU_LITTLE) {
//      PyErr_Format(PyExc_RuntimeError, "FATAL: module compiled as "\
//             "little endian, but detected different endianness at runtime");
      return -1;
  }
#endif

  return 0;
}

bool PythonInit() {
    if (library) return true;

    std::string libpath;
#ifdef WIN
    libpath = "\\Programme\\Python3\\python36.dll";
#elif  MACOSX
    libpath = "/Library/Frameworks/Python.framework/Versions/3.6/lib/libpython3.6m.dylib";
#elif LINUX
    libpath = "/usr/lib/x86_64-linux-gnu/libpython3.5m.so";
#else
    libpath = "/usr/lib/libpython2.7.so";
#endif

#ifdef DPQT
    libpath = settings.pythonLibraryPath.toStdString();
#else
    if (getenv("DPUSER_PYTHONLIB") != NULL) libpath = getenv("DPUSER_PYTHONLIB");
#endif

    if (!loadPythonLibrary(libpath.c_str())) return false;


#ifdef DPQT
    if (pythonVersion == 3) { // TODO: enable (by using Py_InitModule) for Python2
        dpPyImport_AppendInittab("emb", emb::PyInit_emb);
    }
#endif

  dpPy_Initialize(); // signal handlers?

#ifdef DPQT
  if (pythonVersion == 3) {
        dpPyImport_ImportModule("emb");
        emb::stdout_write_type write = [] (std::string s) {
            QString out(s.c_str());
            if (out.right(1) == "\n") out.truncate(out.length() - 1);
            dpuserthread.sendText(out);
        };
        emb::set_stdout(write);
        emb::set_stderr(write);
  } else {
      dp_output("Embedded Python: For Python2, output will be directed to Console!");
//      dpuser_widget->enableRedirector();
  }
#endif

  static int argc = 1;
  std::wstring arg0(L"./py/python.exe");
//  static wchar_t* arg0 = (wchar_t*)"./py/python.exe";
  static wchar_t* argv[] = {(wchar_t*)(arg0.c_str())};

//  PySys_SetArgv(argc, argv);

  // check if numpy.core.multiarray is available - python will abort() upon calling import_array otherwise... :-(
  PyObject *n_module;
  n_module = dpPyImport_ImportModule("numpy.core.multiarray");
  if (n_module != NULL) {
  // http://docs.scipy.org/doc/numpy/reference/c-api.array.html#miscellaneous

    if (dp_import_array(n_module) == 0) has_numpy = true;
		std::cout << "we have numpy" << std::endl;
  }
            dpPyErr_Print();

  /* Setup the __main__ module for us to use */
//  main_module = PyImport_ImportModule("__main__");
//  main_dict   = PyModule_GetDict(main_module);
  return true;
}

PyObject *fromDpuserType(const dpuserType &v) {
	switch (v.type) {
        case typeCon: return dpPy_BuildValue("i", v.toInt()); break;
        case typeDbl: return dpPy_BuildValue("d", v.toDouble()); break;
        case typeStr: return dpPy_BuildValue("s", v.svalue->c_str()); break;
		case typeCom: {
			Py_complex c;
			c.real = v.toReal();
			c.imag = v.toImag();
            return dpPy_BuildValue("D", &c);
		}
		break;
        case typeFits: if (has_numpy) {
			npy_intp dimArr[10];
			for (int i = 0; i < v.fvalue->Naxis(0); i++) dimArr[i] = v.fvalue->Naxis(i+1);
			int item_type = 0;
			switch (v.fvalue->getType()) {
                case I1: item_type = NPY_UINT8; break;
                case I2: item_type = NPY_INT16; break;
                case I4: item_type = NPY_INT32; break;
                case I8: item_type = NPY_INT64; break;
                case R4: item_type = NPY_FLOAT32; break;
                case R8: item_type = NPY_FLOAT64; break;
                case C16: item_type = NPY_COMPLEX128; break;
				default: break;
			}
			PyObject *ret = PyArray_SimpleNew(v.fvalue->Naxis(0), dimArr, item_type);
			memcpy(PyArray_DATA(ret), v.fvalue->dataptr, v.fvalue->Nelements() * (abs(v.fvalue->getType()) / 8));
			return ret;
        } else {
            dp_output("Cannot pass array: No numpy available\n");
            return NULL;
        }
		break;
		case typeStrarr: {
		// TODO: Handle String Arrays
		  return NULL;
		}
		break;
		default: return NULL; break;
	}
}

dpuserType toDpuserType(PyObject *pyObj) {
	dpuserType rv;
	std::string obtype = pyObj->ob_type->tp_name;

std::cout << obtype << std::endl;
    if (obtype == "NoneType") {
        return rv;
    } else if(obtype == "str") {
		rv.type = typeStr;
        if (pythonVersion == 3) {
            rv.svalue = CreateString(dpPyUnicode_AsUTF8( pyObj));
        } else {
            PyObject *s = dpPyObject_Bytes(pyObj);
            rv.svalue = CreateString(dpPyBytes_AsString(pyObj));
        }
	} else if(obtype == "int") {
		rv.type = typeCon;
		rv.lvalue = dpPyLong_AsLong( pyObj);
	} else if(obtype == "float") {
		rv.type = typeDbl;
		rv.dvalue = dpPyFloat_AsDouble( pyObj);
	} else if(obtype == "complex") {
	  double re,im;
	  re = dpPyComplex_RealAsDouble( pyObj);
	  im = dpPyComplex_ImagAsDouble( pyObj);
		rv.type = typeCom;
		rv.cvalue = CreateComplex(re, im);
    } else if (obtype == "tuple") {
        rv.type = typeDpArr;
        rv.dparrvalue = CreateDpList();
        for (dpint64 n = 0; n < dpPyTuple_Size(pyObj); n++) {
            dpuserType *element = new dpuserType();
            *element = toDpuserType(dpPyTuple_GetItem(pyObj, n));
            switch (element->type) {
            case typeFits:
                deleteFromListOfFits(element->fvalue);
                break;
            case typeStrarr:
                deleteFromListOfdpStringArrays(element->arrvalue);
                break;
            default:
                break;
            }

            rv.dparrvalue->push_back(element);
        }
    } else if (obtype == "list") {
        rv.type = typeDpArr;
        rv.dparrvalue = CreateDpList();
        for (dpint64 n = 0; n < dpPyList_Size(pyObj); n++) {
            dpuserType *element = new dpuserType();
            *element = toDpuserType(dpPyList_GetItem(pyObj, n));
            switch (element->type) {
            case typeFits:
                deleteFromListOfFits(element->fvalue);
                break;
            case typeStrarr:
                deleteFromListOfdpStringArrays(element->arrvalue);
                break;
            default:
                break;
            }

            rv.dparrvalue->push_back(element);
        }
    } else if (has_numpy) {
        if (obtype == "numpy.ndarray") {
            PyArrayObject* array = PyArray_GETCONTIGUOUS( reinterpret_cast< PyArrayObject *>( pyObj));

            if( array == NULL) return rv;

                int nDim = array->nd;
                int dimArr[3] = { 1, 1, 1 };
            if( nDim > 3) {
            std::cout << "Python array has more than 3 dimensions. Extending last one." << std::endl;
            int lastDim = array->dimensions[3-1];
            for(int i=3; i<nDim; ++i) lastDim *= array->dimensions[ i];
            for(int i=0; i<3-1; ++i)
              dimArr[ i] = array->dimensions[ i];
            dimArr[ 3-1] = lastDim;
            nDim = 3;
            }
          else
            {
              for(int i=0; i<nDim; ++i)
            dimArr[ i] = array->dimensions[ i];
            }
                rv.type = typeFits;
          switch( array->descr->type_num)
            {
            case NPY_UINT8: {  //GDL_BYTE
                    rv.fvalue = CreateFits(dimArr[0], dimArr[1], dimArr[2], I1);
                    unsigned char *dPtr = reinterpret_cast<unsigned char *>( PyArray_DATA(array));
                    for(dpint64 i = 0; i < rv.fvalue->Nelements(); ++i) rv.fvalue->i1data[i] = dPtr[i];
                }
              break;
            case NPY_UINT16:         //GDL_UINT*
            case NPY_INT16: {  //GDL_INT
                    rv.fvalue = CreateFits(dimArr[0], dimArr[1], dimArr[2], I2);
                    short *dPtr = reinterpret_cast<short *>( PyArray_DATA(array));
                    for(dpint64 i = 0; i < rv.fvalue->Nelements(); ++i) rv.fvalue->i2data[i] = dPtr[i];
                }
                    break;
            case NPY_UINT32:         //GDL_ULONG*
            case NPY_INT32: {    //GDL_LONG
                    rv.fvalue = CreateFits(dimArr[0], dimArr[1], dimArr[2], I4);
                    int *dPtr = reinterpret_cast<int *>( PyArray_DATA(array));
                    for(dpint64 i = 0; i < rv.fvalue->Nelements(); ++i) rv.fvalue->i4data[i] = dPtr[i];
                }
                    break;
          case NPY_UINT64:         //GDL_ULONG64*
          case NPY_INT64: {    //GDL_LONG64
                  rv.fvalue = CreateFits(dimArr[0], dimArr[1], dimArr[2], I8);
                  long long *dPtr = reinterpret_cast<long long *>( PyArray_DATA(array));
                  for(dpint64 i = 0; i < rv.fvalue->Nelements(); ++i) rv.fvalue->i8data[i] = dPtr[i];
              }
                  break;
            case NPY_FLOAT32: {  //GDL_FLOAT
                    rv.fvalue = CreateFits(dimArr[0], dimArr[1], dimArr[2], R4);
                    float *dPtr = reinterpret_cast<float *>( PyArray_DATA(array));
                    for(dpint64 i = 0; i < rv.fvalue->Nelements(); ++i) rv.fvalue->r4data[i] = dPtr[i];
                }
                    break;
            case NPY_FLOAT64: { //GDL_DOUBLE
                    rv.fvalue = CreateFits(dimArr[0], dimArr[1], dimArr[2], R8);
                    double *dPtr = reinterpret_cast<double *>( PyArray_DATA(array));
                    for(dpint64 i = 0; i < rv.fvalue->Nelements(); ++i) rv.fvalue->r8data[i] = dPtr[i];
                }
                    break;
            case NPY_COMPLEX64: { //GDL_COMPLEX
                    rv.fvalue = CreateFits(dimArr[0], dimArr[1], dimArr[2], C16);
                    float *dPtr = reinterpret_cast<float *>( PyArray_DATA(array));
                    for(dpint64 i = 0; i < rv.fvalue->Nelements() * 2; ++i) rv.fvalue->r8data[i] = dPtr[i];
                }
                    break;
            case NPY_COMPLEX128: {//GDL_COMPLEXDBL
                    rv.fvalue = CreateFits(dimArr[0], dimArr[1], dimArr[2], C16);
                    double *dPtr = reinterpret_cast<double *>( PyArray_DATA(array));
                    for(dpint64 i = 0; i < rv.fvalue->Nelements(); ++i) rv.fvalue->r8data[i] = dPtr[i];
                }
                    break;

                    // TODO: Handle String Arrays
            default:
              std::cout << "FromPython: Unknown array type." << std::endl;
            }
                if (!PyArray_ISNOTSWAPPED(array)) swapBytes(rv.fvalue->dataptr, rv.fvalue->membits, rv.fvalue->Nelements());
                Py_DECREF(array); // must be decremented
        }
    } else {
        std::cout << "FromPython: Can't convert to dpuserType." << std::endl;
    }

	return rv;
}

dpuserType getPythonVariable(const char *varname) {
    PyObject *a_module, *a_dict, *v;
    dpuserType rv;

    if (!library) {
        dp_output("Getting Python Variable %s: Python has not been initialized.\n", varname);
        return rv;
    }

    a_module = dpPyImport_AddModule("__main__");
    if (a_module == NULL) return rv;
    a_dict = dpPyModule_GetDict(a_module);
    if (a_dict == NULL) return rv;

    v = dpPyDict_GetItemString(a_dict, varname);
    if (v == NULL) {
        dp_output("Getting Python Variable %s: Variable does not exist.\n", varname);
        return rv;
    }
    rv = toDpuserType(v);

    return rv;
}

void simple_python(std::string cmd) {
    PyObject *a_module, *a_dict, *v;

    if (!PythonInit()) return;

    dpPyErr_Clear();

    a_module = dpPyImport_AddModule("__main__");
    if (a_module == NULL)
        return;
    a_dict = dpPyModule_GetDict(a_module);
    v = dpPyRun_StringFlags(cmd.c_str(), Py_file_input, a_dict, a_dict, NULL);

    if (v == NULL) dpPyErr_Print();
}

int call_python(std::string func_or_pro, std::vector<dpuserType> *args, std::vector<std::string> variables, std::map<std::string, std::pair<std::string, dpuserType> > *keyw, bool isFunction, dpuserType &rv) {
    if (!PythonInit()) return -1;
    PyObject *pName, *pModule, *pDict, *pFunc;
    PyObject *newModule;
    PyObject *pArgs, *pKeyw, *pValue;
	int i;
    bool found = false;

    dpPyErr_Clear();

//    PySys_SetPath(L".");
    pName = dpPy_BuildValue("s", func_or_pro.c_str());
//    pName = dpPyUnicode_DecodeFSDefault(func_or_pro.c_str());
    /* Error checking of pName left out */

    // extract module and function names
    std::string moduleName, functionName;
    size_t pointpos = func_or_pro.find_last_of('.');
    if (pointpos < func_or_pro.size()) {
        moduleName = func_or_pro.substr(0, pointpos);
        functionName = func_or_pro.substr(pointpos + 1, func_or_pro.size() - pointpos - 1);
    } else {
        moduleName = "__main__";
        functionName = func_or_pro;
    }
//    std::cout << "from " << moduleName << " import " << functionName << std::endl;
#if 0
    pDict = dpPyImport_GetModuleDict();
    if (pDict) {
        pModule = dpPyDict_GetItemString(pDict, func_or_pro.c_str());
//        pModule = PyDict_GetItemString(main_dict, func_or_pro.c_str());
        if (pModule != NULL) {
            pFunc = dpPyObject_GetAttrString(pModule, func_or_pro.c_str());
            if (pFunc && dpPyCallable_Check(pFunc)) found = true;
        }
    }
    if (found) std::cout << "function " << func_or_pro << " already compiled." << std::endl;
#endif
    if (!found) {
        newModule = dpPyImport_ImportModule(moduleName.c_str());
        if (newModule != NULL) {
            pFunc = dpPyObject_GetAttrString(newModule, functionName.c_str());
            if (pFunc && dpPyCallable_Check(pFunc)) found = true;
        }
    }

    if (!found) {
        pModule = dpPyImport_Import(pName);
        Py_DECREF(pName);

        if (pModule == NULL) {
            if (pythonVersion == 2) {// maybe we are calling a builtin function
                std::string bbb("__builtin__");
                pName = dpPy_BuildValue("s", bbb.c_str());
                pModule = dpPyImport_Import(pName);
                Py_DECREF(pName);
            } else {
                pName = dpPyUnicode_DecodeFSDefault("builtins");
                pModule = dpPyImport_Import(pName);
                Py_DECREF(pName);
            }
        } else {
            pModule = dpPyImport_ReloadModule(pModule);
        }
        if (pModule == NULL) return -1;

        pFunc = dpPyObject_GetAttrString(pModule, func_or_pro.c_str());
        /* pFunc is a new reference */
    }

    if (pFunc && dpPyCallable_Check(pFunc)) {
        pArgs = dpPyTuple_New(args->size());
		for (i = 0; i < args->size(); ++i) {
			pValue = fromDpuserType(args->at(i));
			if (!pValue) {
				Py_DECREF(pArgs);
				Py_DECREF(pModule);
				fprintf(stderr, "Cannot convert argument\n");
				return -1;
			}
                /* pValue reference stolen here: */
            dpPyTuple_SetItem(pArgs, i, pValue);
		}
        if (keyw->size() > 0) {
            pKeyw = dpPyDict_New();
            for (std::map<std::string, std::pair<std::string, dpuserType> >::iterator key=keyw->begin(); key != keyw->end(); ++key) {
                pValue = fromDpuserType(key->second.second);
                if (!pValue) {
                    Py_DECREF(pArgs);
                    Py_DECREF(pKeyw);
                    Py_DECREF(pModule);
                    fprintf(stderr, "Cannot convert argument\n");
                    return -1;
                }
                    /* pValue reference stolen here: */
                dpPyDict_SetItemString(pKeyw, key->first.c_str(), pValue);
            }
            pValue = dpPyEval_CallObjectWithKeywords(pFunc, pArgs, pKeyw);
            Py_DECREF(pKeyw);
        } else {
            pValue = dpPyObject_CallObject(pFunc, pArgs);
        }
		Py_DECREF(pArgs);
		if (pValue != NULL) {
			rv = toDpuserType(pValue);
            if (rv.type == typeUnknown) rv = 0;
//            printf("Result of call: %ld\n", PyLong_AsLong(pValue));
			Py_DECREF(pValue);
		} else {
			Py_DECREF(pFunc);
//			Py_DECREF(pModule);
            dpPyErr_Print();
			fprintf(stderr,"Call failed\n");
			return -1;
		}
	} else {
//		if (PyErr_Occurred())
//		PyErr_Print();
//		fprintf(stderr, "Cannot find function \"%s\"\n", func_or_pro.c_str());
	}
    Py_XDECREF(pFunc);
    Py_XDECREF(newModule);
//  Py_DECREF(pModule);

	return 1;
}

#pragma GCC pop_options

#else /* HAS_PYTHON */

int call_python(std::string func_or_pro, std::vector<dpuserType> *args, std::vector<std::string> variables, std::map<std::string, std::pair<std::string, dpuserType> > *keyw, bool isFunction, dpuserType &rv) {
    return -1;
}

#endif /* HAS_PYTHON */
