/*
 * python.c
 *
 * See the README file for copyright information and how to reach the author.
 *
 */

#include "python.h"

cDbTable* Python::globalEventsDb = 0;
int Python::globalNamingMode = 0;
const char* Python::globalTmplExpression = "";

//***************************************************************************
// Static Interface Methods (Table events)
//***************************************************************************

PyObject* Python::eventTitle(PyObject* /*self*/, PyObject* /*args*/)
{
   if (!globalEventsDb)
      return Py_BuildValue("s", "");

   return Py_BuildValue("s", globalEventsDb->getStrValue("TITLE"));
}

PyObject* Python::eventShortText(PyObject* /*self*/, PyObject* /*args*/)
{
   if (!globalEventsDb)
      return Py_BuildValue("s", "");

   return Py_BuildValue("s", globalEventsDb->getStrValue("SHORTTEXT"));
}

PyObject* Python::eventStartTime(PyObject* /*self*/, PyObject* /*args*/)
{
   if (!globalEventsDb)
      return Py_BuildValue("i", 0);

   return Py_BuildValue("i", globalEventsDb->getIntValue("STARTTIME"));
}

PyObject* Python::eventYear(PyObject* /*self*/, PyObject* /*args*/)
{
   if (!globalEventsDb)
      return Py_BuildValue("s", "");

   return Py_BuildValue("s", globalEventsDb->getStrValue("YEAR"));
}

PyObject* Python::eventCategory(PyObject* /*self*/, PyObject* /*args*/)
{
   if (!globalEventsDb)
      return Py_BuildValue("s", "");

   return Py_BuildValue("s", globalEventsDb->getStrValue("CATEGORY"));
}

PyObject* Python::episodeName(PyObject* /*self*/, PyObject* /*args*/)
{
   if (!globalEventsDb)
      return Py_BuildValue("s", "");

   return Py_BuildValue("s", globalEventsDb->getStrValue("EPISODENAME"));
}

PyObject* Python::episodeShortName(PyObject* /*self*/, PyObject* /*args*/)
{
   if (!globalEventsDb)
      return Py_BuildValue("s", "");

   return Py_BuildValue("s", globalEventsDb->getStrValue("EPISODESHORTNAME"));
}

PyObject* Python::episodePartName(PyObject* /*self*/, PyObject* /*args*/)
{
   if (!globalEventsDb)
      return Py_BuildValue("s", "");

   return Py_BuildValue("s", globalEventsDb->getStrValue("EPISODEPARTNAME"));
}

PyObject* Python::extracol1(PyObject* /*self*/, PyObject* /*args*/)
{
   if (!globalEventsDb)
      return Py_BuildValue("s", "");

   return Py_BuildValue("s", globalEventsDb->getStrValue("EPISODEEXTRACOL1"));
}

PyObject* Python::extracol2(PyObject* /*self*/, PyObject* /*args*/)
{
   if (!globalEventsDb)
      return Py_BuildValue("s", "");

   return Py_BuildValue("s", globalEventsDb->getStrValue("EPISODEEXTRACOL2"));
}

PyObject* Python::extracol3(PyObject* /*self*/, PyObject* /*args*/)
{
   if (!globalEventsDb)
      return Py_BuildValue("s", "");

   return Py_BuildValue("s", globalEventsDb->getStrValue("EPISODEEXTRACOL3"));
}

PyObject* Python::episodeSeason(PyObject* /*self*/, PyObject* /*args*/)
{
   if (!globalEventsDb)
      return Py_BuildValue("i", na);

   return Py_BuildValue("i", globalEventsDb->getIntValue("EPISODESEASON"));
}

PyObject* Python::episodePart(PyObject* /*self*/, PyObject* /*args*/)
{
   if (!globalEventsDb)
      return Py_BuildValue("i", na);

   return Py_BuildValue("i", globalEventsDb->getIntValue("EPISODEPART"));
}

PyObject* Python::episodeNumber(PyObject* /*self*/, PyObject* /*args*/)
{
   if (!globalEventsDb)
      return Py_BuildValue("i", na);

   return Py_BuildValue("i", globalEventsDb->getIntValue("EPISODENUMBER"));
}

PyObject* Python::namingMode(PyObject* /*self*/, PyObject* /*args*/)
{
   return Py_BuildValue("i", globalNamingMode);
}

PyObject* Python::tmplExpression(PyObject* /*self*/, PyObject* /*args*/)
{
   return Py_BuildValue("s", globalTmplExpression);
}

//***************************************************************************
// The Methods
//***************************************************************************

PyMethodDef Python::eventMethods[] =
{
   {  "title",           Python::eventTitle,       METH_VARARGS,  "Return the events ..."   },
   {  "shorttext",       Python::eventShortText,   METH_VARARGS,  "Return the events ..."   },
   {  "starttime",       Python::eventStartTime,   METH_VARARGS,  "Return the events ..."   },
   {  "year",            Python::eventYear,        METH_VARARGS,  "Return the events ..."   },
   {  "category",        Python::eventCategory,    METH_VARARGS,  "Return the events ..."   },

   {  "episodname",      Python::episodeName,      METH_VARARGS,  "Return the events ..."   },
   {  "shortname",       Python::episodeShortName, METH_VARARGS,  "Return the events ..."   },
   {  "partname",        Python::episodePartName,  METH_VARARGS,  "Return the events ..."   },

   {  "season",          Python::episodeSeason,    METH_VARARGS,  "Return the events ..."   },
   {  "part",            Python::episodePart,      METH_VARARGS,  "Return the events ..."   },
   {  "number",          Python::episodeNumber,    METH_VARARGS,  "Return the events ..."   },

   {  "extracol1",       Python::extracol1,        METH_VARARGS,  "Return the events ..."   },
   {  "extracol2",       Python::extracol2,        METH_VARARGS,  "Return the events ..."   },
   {  "extracol3",       Python::extracol3,        METH_VARARGS,  "Return the events ..."   },

   {  "namingmode",      Python::namingMode,       METH_VARARGS,  "Return the ..."          },
   {  "tmplExpression",  Python::tmplExpression,   METH_VARARGS,  "Return the ..."          },

   {            0,                       0,                   0,                        0   }
};

#if PY_MAJOR_VERSION >= 3

PyModuleDef Python::moduledef =
{
   PyModuleDef_HEAD_INIT,    // m_base
   "event",                  // m_name     - name of module
   0,                        // m_doc      - module documentation, may be NULL
   -1,                       // m_size     - size of per-interpreter state of the module, or -1 if the module keeps state in global variables.
   eventMethods,             // m_methods
   0,                        // m_slots    - array of slot definitions for multi-phase initialization, terminated by a {0, NULL} entry.
                             //              when using single-phase initialization, m_slots must be NULL.
   0,                        // traverseproc m_traverse - traversal function to call during GC traversal of the module object, or NULL if not needed.
   0,                        // inquiry m_clear         - clear function to call during GC clearing of the module object, or NULL if not needed.
   0                         // freefunc m_free         - function to call during deallocation of the module object or NULL if not needed.
};

#endif

//***************************************************************************
// Object
//***************************************************************************

Python::Python(const char* aFile, const char* aFunction)
{
   pFunc = 0;
   pModule = 0;
   result = 0;
   file = strdup(aFile);
   function = strdup(aFunction);
}

Python::~Python()
{
   exit();

   free(result);
   free(file);
   free(function);
}

//***************************************************************************
// Init / Exit
//***************************************************************************

int Python::init(const char* modulePath)
{
   PyObject* pName;

   tell(0, "Initialize python script '%s/%s.py'", modulePath, file);

   // register event methods

#if PY_MAJOR_VERSION >= 3
   PyImport_AppendInittab("event", &PyInitEvent);
   Py_Initialize();                         // initialize the Python interpreter
   pName = PyUnicode_FromString(file);
#else
   Py_Initialize();                         // initialize the Python interpreter
   Py_InitModule("event", eventMethods);
   pName = PyString_FromString(file);
#endif

   // add search path for Python modules

   if (modulePath)
   {
      char* p;
      asprintf(&p, "sys.path.append(\"%s\")", modulePath);
      PyRun_SimpleString("import sys");
      PyRun_SimpleString(p);
      free(p);
   }

   // import the module

   pModule = PyImport_Import(pName);
   Py_DECREF(pName);

   if (!pModule)
   {
      showError();
      tell(0, "Failed to load '%s.py'", file);

      return fail;
   }

   pFunc = PyObject_GetAttrString(pModule, function);

   // pFunc is a new reference

   if (!pFunc || !PyCallable_Check(pFunc))
   {
      if (PyErr_Occurred())
         showError();

      tell(0, "Cannot find function '%s'", function);

      return fail;
   }

   return success;
}

int Python::exit()
{
   if (pFunc)
      Py_XDECREF(pFunc);

   if (pModule)
      Py_DECREF(pModule);

   Py_Finalize();

   return success;
}

//***************************************************************************
// Execute
//***************************************************************************

int Python::execute(cDbTable* eventsDb, int namingmode, const char* tmplExpression)
{
   PyObject* pValue;

   free(result);
   result = 0;

   globalEventsDb = eventsDb;
   globalNamingMode = namingmode;
   globalTmplExpression = tmplExpression;

   pValue = PyObject_CallObject(pFunc, 0);

   if (!pValue)
   {
      showError();
      tell(0, "Python: Call of function '%s()' failed", function);

      return fail;
   }

#if PY_MAJOR_VERSION >= 3
   // PyObject* strExc = PyObject_Repr(pValue);
   // PyObject* pyStr = PyUnicode_AsEncodedString(strExc, "utf-8", "replace");
   PyObject* pyStr = PyUnicode_AsEncodedString(pValue, "utf-8", "replace");
   result = strdup(PyBytes_AsString(pyStr));
   // Py_XDECREF(strExc);

#else
   result = strdup(PyString_AsString(pValue));
#endif

   tell(3, "Result of call: %s", result);

   Py_DECREF(pValue);

   return success;
}


//***************************************************************************
// Dup Py String
//***************************************************************************

char* dupPyString(PyObject* pyObj)
{
   char* s;
   PyObject* pyString;

   if (!pyObj || !(pyString=PyObject_Str(pyObj)))
   {
      s = strdup("unknown error");
      return s;
   }

#if PY_MAJOR_VERSION >= 3
   PyObject* pyUni = PyObject_Repr(pyString);    // Now a unicode object
   PyObject* pyStr = PyUnicode_AsEncodedString(pyUni, "utf-8", "Error ~");
   s = strdup(PyBytes_AsString(pyStr));
   Py_XDECREF(pyUni);
   Py_XDECREF(pyStr);
#else
   s = strdup(PyString_AsString(pyString));
#endif

   Py_DECREF(pyString);

   return s;
}

//***************************************************************************
// PY String From String
//***************************************************************************

PyObject* pyStringFromString(const char* s)
{
#if PY_MAJOR_VERSION >= 3
   return PyUnicode_FromString(s);
#else
   return PyString_FromString(s);
#endif
}

//***************************************************************************
// Show Error
//***************************************************************************

void Python::showError()
{
   char* error;
   PyObject *ptype = 0, *pError = 0, *ptraceback = 0;
   PyObject *moduleName, *pythModule;

   PyErr_Fetch(&ptype, &pError, &ptraceback);

   error = dupPyString(pError);
   moduleName = pyStringFromString("traceback");

   tell(0, "Python error was '%s'", error);

   pythModule = PyImport_Import(moduleName);
   Py_DECREF(moduleName);

   // traceback

   if (pythModule)
   {
      PyObject* pyFunc = PyObject_GetAttrString(pythModule, "format_exception");

      if (pyFunc && PyCallable_Check(pyFunc))
      {
         PyObject *pythVal, *pystr;
         char* s;

         pythVal = PyObject_CallFunctionObjArgs(pyFunc, ptype, pError, ptraceback, 0);
         if (pythVal)
         {
            s = dupPyString(pystr = PyObject_Str(pythVal));
            tell(0, "   %s", s);

            free(s);
            Py_DECREF(pystr);
            Py_DECREF(pythVal);
         }
      }
   }

   free(error);
   Py_DECREF(pError);
   Py_DECREF(ptype);
   Py_XDECREF(ptraceback);
}