/***************************************************************************
        File                 : PythonScript.cpp
        Project              : SciDAVis
--------------------------------------------------------------------
        Copyright            : (C) 2006 by Knut Franke
        Email (use @ for *)  : knut.franke*gmx.de
        Description          : Execute Python code from within SciDAVis

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

/***************************************************************************
 *                                                                         *
 *  This program is free software; you can redistribute it and/or modify   *
 *  it under the terms of the GNU General Public License as published by   *
 *  the Free Software Foundation; either version 2 of the License, or      *
 *  (at your option) any later version.                                    *
 *                                                                         *
 *  This program is distributed in the hope that it will be useful,        *
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
 *  GNU General Public License for more details.                           *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the Free Software           *
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
 *   Boston, MA  02110-1301  USA                                           *
 *                                                                         *
 ***************************************************************************/
// get rid of a compiler warning
#ifdef _POSIX_C_SOURCE
#undef _POSIX_C_SOURCE
#endif
#include <Python.h>

#include "PythonScript.h"
#include "PythonScripting.h"
#include "ApplicationWindow.h"

#include <QObject>
#include <QVariant>

#include <iostream>
#if PY_MAJOR_VERSION >= 3
#define PYUNICODE_AsUTF8 PyUnicode_AsUTF8
#define PYUNICODE_FromString PyUnicode_FromString
#define PYLong_Check PyLong_Check
#define PYLong_AsLong PyLong_AsLong
#define PYCodeObject_cast
#else
#define PYUNICODE_AsUTF8 PyString_AsString
#define PYUNICODE_FromString PyString_FromString
#define PYLong_Check PyInt_Check
#define PYLong_AsLong PyInt_AsLong
#define PYCodeObject_cast (PyCodeObject *)
#endif
using namespace std;

PythonScript::PythonScript(PythonScripting *env, const QString &code, QObject *context,
                           const QString &name)
    : Script(env, code, context, name)
{
    PyCode = NULL;
    PyGILState_STATE state = PyGILState_Ensure();
    // Old: All scripts share a global namespace, and module top-level has its own nonstandard local
    // namespace
    modLocalDict = PyDict_New();
    // A bit of a hack, but we need either IndexError or len() from __builtins__.
    PyDict_SetItemString(modLocalDict, "__builtins__",
                         PyDict_GetItemString(this->env()->globalDict(), "__builtins__"));
    // New: Each script gets its own copy of the global namespace.
    // it is passed as both globals and locals, so top-level assignments are global to this script.
    modGlobalDict = PyDict_Copy(this->env()->globalDict());
    // To read and write program-wide globals, we provide "globals"
    // e.g. ">>> globals.remote_ctl_server = server"
    PyObject *ret;
    ret = PyRun_String("import __main__\n"
                       "globals = __main__",
                       Py_file_input, modLocalDict, modLocalDict);
    if (ret)
        Py_DECREF(ret);
    else
        PyErr_Print();
    ret = PyRun_String("import __main__\n"
                       "globals = __main__",
                       Py_file_input, modGlobalDict, modGlobalDict);
    if (ret)
        Py_DECREF(ret);
    else
        PyErr_Print();
    // "self" is unique to each script, so they can't all run in the __main__ namespace
    PyGILState_Release(state);
    setQObject(Context, "self");
}

PythonScript::~PythonScript()
{
    Py_DECREF(modLocalDict);
    Py_DECREF(modGlobalDict);
    Py_XDECREF(PyCode);
}

void PythonScript::setContext(QObject *context)
{
    Script::setContext(context);
    setQObject(Context, "self");
}

bool PythonScript::compile(bool for_eval)
{
    hasOldGlobals = Code.contains("\nglobal ") || (0 == Code.indexOf("global "));
    PyObject *topLevelLocal = hasOldGlobals ? modLocalDict : modGlobalDict;

    // Support for the convenient col() and cell() functions.
    // This can't be done anywhere else, because we need access to the local
    // variables self, i and j.
    if (Context->inherits("Table")) {
        PyObject *ret = PyRun_String(
                "def col(c,*arg):\n"
                "\ttry: return self.column(type(c)==str and c or c-1).valueAt(arg[0]-1)\n"
                "\texcept(IndexError): return self.column(type(c)==str and c or c-1).valueAt(i-1)\n"
                "def cell(c,r):\n"
                "\treturn self.cell(c,r)\n"
                "def tablecol(t,c):\n"
                "\treturn self.folder().rootFolder().table(t,True).column(type(c)==str and c or "
                "c-1).valuetAt(i-1)\n"
                "def _meth_table_col_(t,c):\n"
                "\treturn t.column(type(c)==str and c or c-1).valueAt(i-1)\n"
                "self.__class__.col = _meth_table_col_",
                Py_file_input, topLevelLocal, topLevelLocal);
        if (ret)
            Py_DECREF(ret);
        else
            PyErr_Print();
    } else if (Context->inherits("Matrix")) {
        PyObject *ret = PyRun_String("def cell(*arg):\n"
                                     "\ttry: return self.cell(arg[0],arg[1])\n"
                                     "\texcept(IndexError): return self.cell(i,j)\n",
                                     Py_file_input, topLevelLocal, topLevelLocal);
        if (ret)
            Py_DECREF(ret);
        else
            PyErr_Print();
    }
    bool success = false;
    Py_XDECREF(PyCode);
    // Simplest case: Code is a single expression
    PyCode = Py_CompileString(Code.toUtf8().constData(), Name.toUtf8().constData(), Py_eval_input);

    if (PyCode) {
        success = true;
    } else if (for_eval) {
        // Code contains statements (or errors) and we want to get a return
        // value from it.
        // So we wrap the code into a function definition,
        // execute that (as Py_file_input) and store the function object in PyCode.
        // See http://mail.python.org/pipermail/python-bugs-list/2001-June/005534.html
        // and http://mail.python.org/pipermail/python-list/2001-June/087605.html
        // for why there isn't an easier way to do this in Python.
        PyErr_Clear(); // silently ignore errors
        PyObject *key, *value;
#if PY_VERSION_HEX >= 0x02050000
        Py_ssize_t i = 0;
#else
        int i = 0;
#endif
        QString signature = "";
        while (PyDict_Next(topLevelLocal, &i, &key, &value))
            signature.append(PYUNICODE_AsUTF8(key)).append(",");
        signature.truncate(signature.length() - 1);
        QString fdef = "def __doit__(" + signature + "):\n";
        fdef.append(Code);
        fdef.replace('\n', "\n\t");
        PyCode = Py_CompileString(fdef.toUtf8().constData(), Name.toUtf8().constData(),
                                  Py_file_input);
        if (PyCode) {
            PyObject *tmp = PyDict_New();
            Py_XDECREF(PyEval_EvalCode(PYCodeObject_cast PyCode, topLevelLocal, tmp));
            Py_DECREF(PyCode);
            PyCode = PyDict_GetItemString(tmp, "__doit__");
            Py_XINCREF(PyCode);
            Py_DECREF(tmp);
        }
        success = PyCode != NULL;
    } else {
        // Code contains statements (or errors), but we do not need to get
        // a return value.
        PyErr_Clear(); // silently ignore errors
        PyCode = Py_CompileString(Code.toUtf8().constData(), Name.toUtf8().constData(),
                                  Py_file_input);
        success = PyCode != NULL;
    }
    if (!success) {
        compiled = compileErr;
        if (batchMode)
            cerr << env()->errorMsg().toStdString() << endl;
        else
            emit_error(env()->errorMsg(), 0);
    } else
        compiled = isCompiled;
    return success;
}

QVariant PythonScript::eval()
{
    if (!isFunction)
        compiled = notCompiled;
    if (compiled != isCompiled && !compile(true))
        return QVariant();
    PyObject *topLevelGlobal = hasOldGlobals ? env()->globalDict() : modGlobalDict;
    PyObject *topLevelLocal = hasOldGlobals ? modLocalDict : modGlobalDict;
    PyObject *pyret;
    beginStdoutRedirect();
    if (PyCallable_Check(PyCode)) {
        PyObject *empty_tuple = PyTuple_New(0);
        pyret = PyObject_Call(PyCode, empty_tuple, topLevelLocal);
        Py_DECREF(empty_tuple);
    } else
        pyret = PyEval_EvalCode(PYCodeObject_cast PyCode, topLevelGlobal, topLevelLocal);
    endStdoutRedirect();
    if (!pyret) {
        emit_error(env()->errorMsg(), 0);
        return QVariant();
    }

    QVariant qret = QVariant();
    /* None */
    if (pyret == Py_None)
        qret = QVariant("");
    /* numeric types */
    else if (PyFloat_Check(pyret))
        qret = QVariant(PyFloat_AS_DOUBLE(pyret));
    else if (PYLong_Check(pyret))
        qret = QVariant((qlonglong)PYLong_AsLong(pyret));
    else if (PyLong_Check(pyret))
        qret = QVariant((qlonglong)PyLong_AsLongLong(pyret));
    else if (PyNumber_Check(pyret)) {
        PyObject *number = PyNumber_Float(pyret);
        if (number) {
            qret = QVariant(PyFloat_AS_DOUBLE(number));
            Py_DECREF(number);
        }
        /* bool */
    } else if (PyBool_Check(pyret))
        qret = QVariant(pyret == Py_True);
    // could handle advanced types (such as PyList->QList) here if needed
    /* fallback: try to convert to (unicode) string */
    if (!qret.isValid()) {
#if PY_MAJOR_VERSION >= 3
        PyObject *pystring = PyObject_Str(pyret);
#else
        PyObject *pystring = PyObject_Unicode(pyret);
#endif
        if (pystring) {
#if PY_MAJOR_VERSION >= 3
            qret = QVariant(QString(PYUNICODE_AsUTF8(pystring)));
            Py_DECREF(pystring);
#else
            PyObject *asUTF8 = PyUnicode_EncodeUTF8(PyUnicode_AS_UNICODE(pystring),
                                                    PyUnicode_GET_DATA_SIZE(pystring), 0);
            Py_DECREF(pystring);
            if (asUTF8) {
                qret = QVariant(QString::fromUtf8(PyString_AS_STRING(asUTF8)));
                Py_DECREF(asUTF8);
            } else if ((pystring = PyObject_Str(pyret))) {
                qret = QVariant(QString(PyString_AS_STRING(pystring)));
                Py_DECREF(pystring);
            }
#endif
        }
    }

    Py_DECREF(pyret);
    if (PyErr_Occurred()) {
        emit_error(env()->errorMsg(), 0);
        return QVariant();
    } else
        return qret;
}

bool PythonScript::exec()
{
    if (isFunction)
        compiled = notCompiled;
    if (compiled != Script::isCompiled && !compile(false))
        return false;
    PyObject *topLevelGlobal = hasOldGlobals ? env()->globalDict() : modGlobalDict;
    PyObject *topLevelLocal = hasOldGlobals ? modLocalDict : modGlobalDict;
    PyObject *pyret;
    beginStdoutRedirect();
    if (PyCallable_Check(PyCode)) {
        PyObject *empty_tuple = PyTuple_New(0);
        if (!empty_tuple) {
            emit_error(env()->errorMsg(), 0);
            return false;
        }
        pyret = PyObject_Call(PyCode, empty_tuple, topLevelLocal);
        Py_DECREF(empty_tuple);
    } else
        pyret = PyEval_EvalCode(PYCodeObject_cast PyCode, topLevelGlobal, topLevelLocal);
    endStdoutRedirect();
    if (pyret) {
        Py_DECREF(pyret);
        return true;
    }
    if (batchMode)
        cerr << env()->errorMsg().toStdString() << endl;
    else
        emit_error(env()->errorMsg(), 0);

    return false;
}

void PythonScript::beginStdoutRedirect()
{
    if (!batchMode) {
        stdoutSave = PyDict_GetItemString(env()->sysDict(), "stdout");
        Py_XINCREF(stdoutSave);
        stderrSave = PyDict_GetItemString(env()->sysDict(), "stderr");
        Py_XINCREF(stderrSave);
        env()->setQObject(this, "stdout", env()->sysDict());
        env()->setQObject(this, "stderr", env()->sysDict());
    }
}

void PythonScript::endStdoutRedirect()
{
    if (!batchMode && env()) {
        PyDict_SetItemString(env()->sysDict(), "stdout", stdoutSave);
        Py_XDECREF(stdoutSave);
        PyDict_SetItemString(env()->sysDict(), "stderr", stderrSave);
        Py_XDECREF(stderrSave);
    }
}

bool PythonScript::setQObject(QObject *val, const char *name)
{
    if (!PyDict_Contains(modLocalDict, PYUNICODE_FromString(name)))
        compiled = notCompiled;
    return (env()->setQObject(val, name, modLocalDict)
            && env()->setQObject(val, name, modGlobalDict));
}

bool PythonScript::setInt(int val, const char *name)
{
    if (!PyDict_Contains(modLocalDict, PYUNICODE_FromString(name)))
        compiled = notCompiled;
    return (env()->setInt(val, name, modLocalDict) && env()->setInt(val, name, modGlobalDict));
}

bool PythonScript::setDouble(double val, const char *name)
{
    if (!PyDict_Contains(modLocalDict, PYUNICODE_FromString(name)))
        compiled = notCompiled;
    return (env()->setDouble(val, name, modLocalDict)
            && env()->setDouble(val, name, modGlobalDict));
}
