// src/python_cpu_policy.cc - Copyright 2005, 2006, University
//               of Padova, dept. of Pure and Applied
//               Mathematics
//
// This file is part of SGPEMv2.
//
// This 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.
//
// SGPEMv2 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 SGPEMv2; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

#include "config.h"

#include "python_cpu_policy.hh"

#include <limits>
#include <unistd.h>
#include <iostream>

using namespace sgpem;
using namespace std;

// Static member data
Glib::StaticRecMutex PythonCPUPolicy::_mtx = GLIBMM_STATIC_REC_MUTEX_INIT;


// WARNING :  this class needs extensive and above all
// *strong* exception checking / handling!

PythonCPUPolicy::PythonCPUPolicy(const char* name) throw(MalformedPolicyException)
    : _upolicy_dict(NULL), _adapter(NULL), _name(name), _description()
{
  PyObject* pLoadmeStr = PyString_FromString(name);
  PyObject* pUserCPUPolicyModule = PyImport_Import(pLoadmeStr);
  Py_DECREF(pLoadmeStr);

  if (pUserCPUPolicyModule == NULL)
    throw MalformedPolicyException(get_exception_information());

  // Dictionary with defined ``symbols'' for .pyc file
  _upolicy_dict = PyModule_GetDict(pUserCPUPolicyModule);
  assert(_upolicy_dict);

  // Loads ScriptAdapter module and get its dictionary
  pLoadmeStr = PyString_FromString("ScriptAdapter");
  PyObject* pScriptAdapterModule = PyImport_Import(pLoadmeStr);
  Py_DECREF(pLoadmeStr);
  assert(pScriptAdapterModule);
  PyObject* pAdapterDict = PyModule_GetDict(pScriptAdapterModule);
  assert(pAdapterDict);

  // Now takes the user-defined policy class from pUserCPUPolicyDict
  PyObject* pCPUPolicyClass = PyDict_GetItemString(_upolicy_dict, name);
  if (pCPUPolicyClass == NULL)
    throw MalformedPolicyException(Glib::ustring(_("Cannot find a class named ")) + 
                                   name + _(" into the corresponding .py file."));

  // Retrieve a description for the policy using the __doc__ attribute
  PyObject* pDescriptionString = PyObject_GetAttrString(pCPUPolicyClass, "__doc__");
  if(pDescriptionString != Py_None)
  	_description = PyString_AsString(pDescriptionString);
  Py_DECREF(pDescriptionString);

  // Creates a new object of type ScriptAdapter :
  // takes init function from ScriptAdapter class
  PyObject* pAdapterClass = PyDict_GetItemString(pAdapterDict, "ScriptAdapter");
  PyObject* pAdapterCtorParam = PyTuple_New(1);
  Py_INCREF(pCPUPolicyClass); // PyTuple_SetItem steals a reference
  PyTuple_SetItem(pAdapterCtorParam, 0, pCPUPolicyClass);
  _adapter = PyInstance_New(pAdapterClass, pAdapterCtorParam, NULL);
  Py_DECREF(pAdapterCtorParam);

  Py_DECREF(pScriptAdapterModule);

  if (_adapter == NULL)
    throw MalformedPolicyException(get_exception_information());

  // And now, who's your daddy, huh?
}


PythonCPUPolicy::~PythonCPUPolicy()
{
  if (_adapter)      Py_DECREF(_adapter);

  // We keep this alive until dtor time, because
  // the user may have defined some static global-space
  // variables and they make use of them.
  if (_upolicy_dict) Py_DECREF(_upolicy_dict);
}

void
PythonCPUPolicy::activate() throw(UserInterruptException, MalformedPolicyException)
{
  Glib::RecMutex::Lock lock(_mtx);;
  set_callback_policy(const_cast<PythonCPUPolicy*>(this));

  // TODO write the rest, taking away code from constructor

  configure();
  
  set_callback_policy(NULL);
}


void
PythonCPUPolicy::deactivate()
{
  Glib::RecMutex::Lock lock(_mtx);;
  set_callback_policy(const_cast<PythonCPUPolicy*>(this));

  // TODO See if some code has to be moved here from the
  // destructor, AFTER having written activate()

  _parameters.clear();

  set_callback_policy(NULL);
}



void
PythonCPUPolicy::configure() throw(UserInterruptException, MalformedPolicyException)
{
  Glib::RecMutex::Lock lock(_mtx);;
  set_callback_policy(const_cast<PythonCPUPolicy*>(this));

  PyObject* retval = PyObject_CallMethod(_adapter, "async_configure", NULL);
  Py_DECREF(retval);

  wait_unlock();

  set_callback_policy(NULL);
}


void
PythonCPUPolicy::sort_queue() const throw(UserInterruptException, MalformedPolicyException)
{  
  Glib::RecMutex::Lock lock(_mtx);;
  set_callback_policy(const_cast<PythonCPUPolicy*>(this));

  PyObject* retval = PyObject_CallMethod(_adapter, "async_sort_queue", NULL);

  // Do minimal debugging
  if (!retval) PyErr_Print();
  else Py_DECREF(retval);

  wait_unlock();

  set_callback_policy(NULL);
}


Glib::ustring
PythonCPUPolicy::get_description() const
{
  return _description;
}


Glib::ustring
PythonCPUPolicy::get_name() const
{
  return _name;
}

bool
PythonCPUPolicy::is_pre_emptive() const throw(UserInterruptException, MalformedPolicyException)
{
  Glib::RecMutex::Lock lock(_mtx);;
  set_callback_policy(const_cast<PythonCPUPolicy*>(this));

  PyObject* retval = PyObject_CallMethod(_adapter, "async_is_preemptive", NULL);
  Py_DECREF(retval);

  wait_unlock();

  // Parse return value stored in global Python object
  retval = PyObject_CallMethod(_adapter, "get_return_value", NULL);
  assert(retval);
  bool ret = PyObject_IsTrue(retval);
  Py_DECREF(retval);

  set_callback_policy(NULL);
  return ret;
}


int
PythonCPUPolicy::get_time_slice() const throw(UserInterruptException, MalformedPolicyException)
{
  Glib::RecMutex::Lock lock(_mtx);;
  set_callback_policy(const_cast<PythonCPUPolicy*>(this));

  PyObject* retval = PyObject_CallMethod(_adapter, "async_get_time_slice", NULL);

  // Do minimal debugging
  if (!retval) PyErr_Print();
  else Py_DECREF(retval);

  wait_unlock();

  // Parse return value stored in global Python object
  retval = PyObject_CallMethod(_adapter, "get_return_value", NULL);
  assert(retval);
  long tmp = PyInt_AsLong(retval);
  Py_DECREF(retval);

  set_callback_policy(NULL);
  return tmp < 0 ? numeric_limits<int>::max() : static_cast<int>(tmp);
}


void
PythonCPUPolicy::wait_unlock() const throw(UserInterruptException, MalformedPolicyException)
{
  // Polling time
  static const int wait_for = 150000;

  PyThreadState* _save;
  int i = 0; // We give the sort_queue() three seconds max time, then...
  // we shot it stone dead! Bang.

  bool still_locked;
  do
  {
    Py_UNBLOCK_THREADS;
    usleep(wait_for); // hack'a'ton! magggggiccc nummmbeeerrrrrs!!
    Py_BLOCK_THREADS;

    PyObject* retval = PyObject_CallMethod(_adapter, "mutex_test_lock", NULL);
    assert(retval);
    still_locked = PyObject_IsTrue(retval);
    Py_DECREF(retval);
    
    if (i++ > (1000000 / wait_for) * 5) /* waits for ~5 seconds */
    {
      PyThreadState_Clear(_save);
      // As the API documentation says, the caller of PyEval_RestoreThread
      // should NOT possess the interpreter lock
      Py_UNBLOCK_THREADS;
      PyEval_RestoreThread(_save);

      if(PyErr_Occurred() != NULL)
        abort();
    
      throw UserInterruptException(_("User-defined policy is "
                                     "taking too long to terminate."));
    }
  }
  while (still_locked);

  // check if there were unhandled exception in the user-defined code
  
  PyObject* pException = PyObject_CallMethod(_adapter, "get_last_exception", NULL);
  
  if(pException != NULL)
  {
    if(pException != Py_None)
    {
      PyObject* pExceptStr = PyObject_Str(pException);

      string msg = _("unhandled exception in user policy: ");
      msg += PyString_AsString(pExceptStr);

      Py_DECREF(pExceptStr);
      Py_DECREF(pException);

      throw MalformedPolicyException(msg);
    }
    else
      Py_DECREF(pException);
  }

  // What we should really do here:
  /* do {
     enable python threads
     wait for some time...
     disable python threads
     ...check if user asked for interruption, reading a
        syncronized variable...
     ...if he has, break
     ...else:
           if the global lock is set:
       stay in this loop
    else:
       all's went okay, can exit loop
  } */

}

string
PythonCPUPolicy::get_exception_information()
{
  if (PyErr_Occurred() == NULL)
    return _("no error");

  PyObject* pType = NULL;
  PyObject* pValue = NULL;
  PyObject* pTraceback = NULL;

  PyErr_Fetch(&pType, &pValue, &pTraceback);

  string msg;

  if (pValue != NULL)
  {
    PyObject* pValueStr = PyObject_Str(pValue);

    msg = PyString_AsString(pValueStr);

    Py_DECREF(pValueStr);
  }
  else
    msg = string(_("no available information for this error"));

  if (pType != NULL) Py_DECREF(pType);
  if (pValue != NULL) Py_DECREF(pValue);
  if (pTraceback != NULL) Py_DECREF(pTraceback);

  PyErr_Clear();
  
  return msg;
}

