/* 
 * © 2009 Sun Microsystems, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
 
#include "stdafx.h"

#include "python_context.h"
#include "grtpp.h"
#include "grtpp_util.h"

#ifdef ENABLE_PYTHON_MODULES

#include "python_grtobject.h"
#include "python_grtlist.h"
#include "python_grtdict.h"

using namespace grt;

const std::string grt::LanguagePython= "python";


// used to identify a proper GRT context object as a PyCObject
static const char *GRTTypeSignature= "GRTCONTEXT";
// used to identify a GRT value as a PyCObject
static const char *GRTValueSignature= "GRTVALUE";


static std::string flatten_class_name(std::string name)
{
  std::string::size_type p;
  while ((p= name.find('.')) != std::string::npos)
    name[p]= '_';
  return name;
}


PythonContext::PythonContext(GRT *grt)
: _grt(grt)
{
  PyObject *main;
  static const char *argv[2]= { "/dev/null" , NULL };
  
  Py_InitializeEx(0); // skips signal handler init
    
  PySys_SetArgv(1, (char**)argv);
  
  PyEval_InitThreads();
  
  _grt_list_class= 0;
  _grt_dict_class= 0;
  _grt_object_class= 0;
  _grt_method_class= 0;
  
  register_grt_module();
  
  main= PyImport_AddModule("__main__");
  PyDict_SetItemString(PyModule_GetDict(main),
                       "grt", PyImport_ImportModule("grt"));
  
  // make sys.stdout and sys.stderr send stuff to GRT  
  PySys_SetObject((char*)"stdout", get_grt_module());
  PySys_SetObject((char*)"stderr", get_grt_module());
  
  release();
}


PythonContext::~PythonContext()
{
//  Py_Finalize();
}


void PythonContext::acquire()
{
  PyEval_AcquireLock();
}


void PythonContext::release()
{
  PyEval_ReleaseLock();
}



void PythonContext::add_module_path(const std::string &modpath)
{
  // add the path to the search path so that it can be imported
  PyObject *path_list;
  PyObject *path= PyString_FromString(modpath.c_str());
  
  path_list= PySys_GetObject("path");
  Py_ssize_t i;
  
  // check if the path is already in it
  for (i= PyList_Size(path_list)-1; i >= 0; --i)
  {
    if (PyObject_Compare(PyList_GetItem(path_list, i), path) == 0)
      break;
  }
  
  if (i < 0) // not found
    PyList_Append(path_list, path);
  
  Py_DECREF(path);
}


void PythonContext::set_python_error(const grt::type_error &exc)
{
  PyErr_SetString(PyExc_TypeError, exc.what());
}


void PythonContext::set_python_error(const grt::bad_item &exc)
{
  PyErr_SetString(PyExc_IndexError, exc.what());
}


void PythonContext::set_python_error(const std::exception &exc)
{
  PyErr_SetString(PyExc_SystemError, exc.what());
}



/** Gets the PythonContext from the Python interpreter.
 */
PythonContext *PythonContext::get()
{
  PyObject *ctx;
  PyObject *module;
  PyObject *dict;

  module= PyDict_GetItemString(PyImport_GetModuleDict(), "grt");
  if (!module)
    throw std::runtime_error("GRT module not found in Python runtime");
  
  dict= PyModule_GetDict(module);
  if (!dict)
    throw std::runtime_error("GRT module is invalid in Python runtime");
  
  ctx= PyDict_GetItemString(dict, "__GRT__");
  if (!ctx)
    throw std::runtime_error("GRT context not found in Python runtime");

  if (PyCObject_GetDesc(ctx) == &GRTTypeSignature)
    return static_cast<PythonContext*>(PyCObject_AsVoidPtr(ctx));

  throw std::runtime_error("Invalid GRT context in Python runtime");
}


PythonContext *PythonContext::get_and_check()
{
  try
  {
    return PythonContext::get();
  }
  catch (std::exception &exc)
  {
    PyErr_SetString(PyExc_SystemError, strfmt("Could not get GRT context: %s", exc.what()).c_str());
  }
  return NULL;
}


static PyObject *grt_send_output(PyObject *self, PyObject *args)
{
  PythonContext *ctx;
  const char *text= "";
    
  if (!(ctx= PythonContext::get_and_check()))
    return NULL;
  
  if (!PyArg_ParseTuple(args, "z", &text))
    return NULL;

  //g_message("%s", text);
  ctx->get_grt()->send_output(text);
  
  Py_INCREF(Py_None);
  return Py_None;
}


static PyObject *grt_send_warning(PyObject *self, PyObject *args)
{
  PythonContext *ctx;
  const char *text= "";
  if (!(ctx= PythonContext::get_and_check()))
    return NULL;
  
  if (!PyArg_ParseTuple(args, "z", &text))
    return NULL;
  
  ctx->get_grt()->send_warning(text);
  
  Py_INCREF(Py_None);
  return Py_None;
}

static PyObject *grt_send_info(PyObject *self, PyObject *args)
{
  PythonContext *ctx;
  const char *text= "";
  if (!(ctx= PythonContext::get_and_check()))
    return NULL;
  
  if (!PyArg_ParseTuple(args, "z", &text))
    return NULL;
  
  ctx->get_grt()->send_info(text);
  
  Py_INCREF(Py_None);
  return Py_None;
}

static PyObject *grt_send_error(PyObject *self, PyObject *args)
{
  PythonContext *ctx;
  const char *text= "";
  if (!(ctx= PythonContext::get_and_check()))
    return NULL;
  
  if (!PyArg_ParseTuple(args, "z", &text))
    return NULL;
  
  ctx->get_grt()->send_error(text);
  
  Py_INCREF(Py_None);
  return Py_None;
}

static PyObject *grt_get_by_path(PyObject *self, PyObject *args)
{
  PythonContext *ctx;
  const char *path= "";
  PyObject *object;
  grt::ValueRef value;
  
  if (!(ctx= PythonContext::get_and_check()))
    return NULL;
  
  if (!PyArg_ParseTuple(args, "s|O", &path, &object))
    return NULL;

  if (object)
  {
    try
    {
      value= ctx->from_pyobject(object);
    }
    catch (grt::type_error &exc)
    {
      PyErr_SetString(PyExc_TypeError, exc.what());
      return NULL;
    }
    catch (std::exception &exc)
    {
      PythonContext::set_python_error(exc);
      return NULL;
    }
  }
  else
    value= ctx->get_grt()->root();
  
  if (!path)
    path= "";

  try
  {
    value= get_value_by_path(value, path);
  }
  catch (std::exception &exc)
  {
    PythonContext::set_python_error(exc);
    return NULL;
  }

  return ctx->from_grt(value);
}


/** Register grt related functionality as a module

 Stuff made available in the module include:
 <li>__GRT__ variable holding a CObject pointer back to PythonContext
 <li>GRTList, GRTDict and GRTObject types
 <li>send_output, send_error, send_info etc
 <li>etc
 */
static PyMethodDef GrtModuleMethods[] = {
{"send_output",  grt_send_output, METH_VARARGS,
"Write a string in the GRT shell."},
{"write",  grt_send_output, METH_VARARGS,
"Write a string in the GRT shell (alias to send_output)."},
{"send_error",  grt_send_error, METH_VARARGS,
"Write an error message to the GRT shell."},
{"send_warning",  grt_send_warning, METH_VARARGS,
"Write a warning message to the GRT shell."},
{"send_info",  grt_send_info, METH_VARARGS,
"Write a info message to the GRT shell."},

{"get", grt_get_by_path, METH_VARARGS,
"Gets a value from a GRT dict or object (or from the global tree) by path."},

{NULL, NULL, 0, NULL}        /* Sentinel */
};


void PythonContext::register_grt_module()
{
  PyObject *module = Py_InitModule("grt", GrtModuleMethods);
  if (module == NULL)
    throw std::runtime_error("Error initializing GRT module in Python support");
  
  _grt_module= module;
  
  // add the context ptr
  PyObject* context_object= PyCObject_FromVoidPtrAndDesc(this, &GRTTypeSignature, NULL);
  if (context_object != NULL)
    PyModule_AddObject(module, "__GRT__", context_object);
  
  PyModule_AddStringConstant(module, "INT", (char*)type_to_str(IntegerType).c_str());
  PyModule_AddStringConstant(module, "DOUBLE", (char*)type_to_str(DoubleType).c_str());
  PyModule_AddStringConstant(module, "STRING", (char*)type_to_str(StringType).c_str());
  PyModule_AddStringConstant(module, "LIST", (char*)type_to_str(ListType).c_str());
  PyModule_AddStringConstant(module, "DICT", (char*)type_to_str(DictType).c_str());
  PyModule_AddStringConstant(module, "OBJECT", (char*)type_to_str(ObjectType).c_str());
  
  init_grt_module_type();
  init_grt_list_type();
  init_grt_dict_type();
  init_grt_object_type();
  
  _grt_modules_module = Py_InitModule("grt.modules", NULL);
  if (_grt_modules_module == NULL)
    throw std::runtime_error("Error initializing grt.modules module in Python support");
  PyModule_AddObject(_grt_module, "modules", _grt_modules_module);
  
  _grt_classes_module = Py_InitModule("grt.classes", NULL);
  if (_grt_classes_module == NULL)
    throw std::runtime_error("Error initializing grt.classes module in Python support");
  PyModule_AddObject(_grt_module, "classes", _grt_classes_module);
  
  PyModule_AddObject(_grt_classes_module, "grt", _grt_module);
}




PyObject *PythonContext::get_grt_module()
{
  return _grt_module;
}


PyObject *PythonContext::eval_string(const std::string &expression)
{
//  LockPython lock(this);
  
  PyObject *mainmod= PyImport_AddModule("__main__");
  if (!mainmod)
  {
    PyErr_Clear();
    return NULL;
  }
  PyObject *globals= PyModule_GetDict(mainmod);
  if (globals)
  {
    PyObject *result= PyRun_String(expression.c_str(), Py_eval_input, globals, globals);
    PyErr_Clear();
    return result;
  }
  PyErr_Clear();
  return NULL;
}


static void release_value(void *value, void *desc)
{
  internal::Value *v= reinterpret_cast<internal::Value*>(value);
  
  v->release();
}

/** Wraps a grt value in a PyCObject.
 
 PyCObjects are used internally to initialize a grt.List/Dict or Object from an existing grt value.
 */
PyObject *PythonContext::internal_cobject_from_value(const ValueRef &value)
{
  internal::Value *v= value.valueptr();
  v->retain();
  return PyCObject_FromVoidPtrAndDesc(v, &GRTValueSignature, release_value);
}


ValueRef PythonContext::value_from_internal_cobject(PyObject *value)
{
  if (PyCObject_GetDesc(value) == &GRTValueSignature)
    return ValueRef(reinterpret_cast<internal::Value*>(PyCObject_AsVoidPtr(value)));
  
  throw std::runtime_error("attempt to extract GRT value from invalid Python object");
}


/** Convert a GRT value to a Python object/value. 
 *
 * For objects, it will also wrap in the appropriate object subclass from grt.classes
 */
PyObject *PythonContext::from_grt(const ValueRef &value)
{
  if (value.is_valid())
  {
    switch (value.type())
    {
      case IntegerType:
        return PyLong_FromLong(*IntegerRef::cast_from(value));
        
      case DoubleType:
        return PyFloat_FromDouble(*DoubleRef::cast_from(value));
        
      case StringType:
        return PyString_FromString(StringRef::cast_from(value).c_str());

      case ListType:
      {
        PyObject *content= PythonContext::internal_cobject_from_value(value);
        PyObject *r= PyObject_Call(_grt_list_class, Py_BuildValue("(ssO)", "", "", content), NULL);
        Py_XDECREF(content);
        return r;
      }
      case DictType:
      {
        PyObject *content= PythonContext::internal_cobject_from_value(value);
        PyObject *r= PyObject_Call(_grt_dict_class, Py_BuildValue("(ssO)", "", "", content), NULL);
        Py_XDECREF(content);
        return r;
      }
      case ObjectType:
      {
        std::string class_name= grt::ObjectRef::cast_from(value).class_name();
        PyObject *content= PythonContext::internal_cobject_from_value(value);
        PyObject *theclass= _grt_class_wrappers[class_name];
        PyObject *r= PyObject_Call(theclass ? theclass : (PyObject*)_grt_object_class, Py_BuildValue("(sO)", "", content), NULL);
        Py_XDECREF(content);
        
        return r;
      }
      default:
        return NULL;
    }
  }
  Py_INCREF(Py_None);
  return Py_None;
}


ValueRef PythonContext::from_pyobject(PyObject *object)
{
  if (!object || object == Py_None)
    return ValueRef();

  if (PyInt_Check(object))
    return IntegerRef(PyInt_AsLong(object));

  if (PyFloat_Check(object))
    return DoubleRef(PyFloat_AsDouble(object));
  
  if (PyString_Check(object))
    return StringRef(PyString_AsString(object));
  
  if (PyList_Check(object))
  {
    grt::BaseListRef list(_grt);
  
    for (Py_ssize_t c= PyList_Size(object), i= 0; i < c; i++)
    {
      PyObject *item= PyList_GetItem(object, i);
      list.ginsert(from_pyobject(item));
    }
    return list;
  }
  else if (PyObject_IsInstance(object, _grt_list_class))
    return *((PyGRTListObject*)object)->list;
  
  if (PyDict_Check(object))
  {
    grt::DictRef dict(_grt);
    PyObject *key, *value;
    Py_ssize_t pos = 0;
    
    while (PyDict_Next(object, &pos, &key, &value))
    {
      dict.set(PyString_AsString(key), from_pyobject(value));
    }
    
    return dict;
  }
  else if (PyObject_IsInstance(object, _grt_dict_class))
    return *((PyGRTDictObject*)object)->dict;
  
  if (PyObject_IsInstance(object, _grt_object_class))
    return *((PyGRTObjectObject*)object)->object;

  return ValueRef();
}


ValueRef PythonContext::simple_type_from_pyobject(PyObject *object, const grt::SimpleTypeSpec &type)
{
  switch (type.type)
  {
    case IntegerType:
    {
      if (PyFloat_Check(object))
        return IntegerRef((long)PyFloat_AsDouble(object));
      else
        PyErr_Clear();
      if (!PyInt_Check(object))
        throw grt::type_error("expected integer type");
      return IntegerRef(PyInt_AsLong(object));
    }
    case DoubleType:
    {
      if (PyInt_Check(object))
        return DoubleRef(PyInt_AsLong(object));
      else
        PyErr_Clear();
      if (!PyFloat_Check(object))
        throw grt::type_error("expected double type");
      return DoubleRef(PyFloat_AsDouble(object));
    }
    case StringType:
    {
      if (!PyString_Check(object))
        throw grt::type_error("expected string type");
      return StringRef(PyString_AsString(object));
    }
    case ObjectType:
    {
      if (!PyObject_IsInstance(object, _grt_object_class))
        throw grt::type_error("expected GRT object");

      grt::ObjectRef grtobject(*((PyGRTObjectObject*)object)->object);

      if (!type.object_class.empty() && !grtobject->is_instance(type.object_class))
        throw grt::type_error(strfmt("expected GRT object of class %s", type.object_class.c_str()));

      return grtobject;
    }
    default:
      return ValueRef();
  }
}


ValueRef PythonContext::from_pyobject(PyObject *object, const grt::TypeSpec &expected_type)
{
  if (object == Py_None)
    return ValueRef();

  switch (expected_type.base.type)
  {
    case ObjectType:
    case IntegerType:
    case DoubleType:
    case StringType:
      return simple_type_from_pyobject(object, expected_type.base);
      
    case ListType:
    {
      if (PyList_Check(object))
      {
        grt::BaseListRef list(_grt);
        
        for (Py_ssize_t c= PyList_Size(object), i= 0; i < c; i++)
        {
          PyObject *item= PyList_GetItem(object, i);
          switch (expected_type.content.type)
          {
            case ObjectType:
            case IntegerType:
            case DoubleType:
            case StringType:
              list.ginsert(simple_type_from_pyobject(item, expected_type.content));
              break;
            case AnyType:
              list.ginsert(from_pyobject(item));
              break;
            default:
              g_warning("invalid type spec requested");
              break;
          }
        }
        return list;
      }
      else if (PyObject_IsInstance(object, _grt_list_class))
      {
        return *((PyGRTListObject*)object)->list;
      }
      else
        throw grt::type_error("expected list");
    }
    case DictType:
    {
      if (PyDict_Check(object))
      {
        grt::DictRef dict(_grt);
        PyObject *key, *value;
        Py_ssize_t pos = 0;
        
        while (PyDict_Next(object, &pos, &key, &value))
        {
          switch (expected_type.content.type)
          {
            case ObjectType:
            case IntegerType:
            case DoubleType:
            case StringType:
              dict.set(PyString_AsString(key), simple_type_from_pyobject(value, expected_type.content));
              break;
            case AnyType:
              dict.set(PyString_AsString(key), from_pyobject(value));
              break;
            default:
              g_warning("invalid type spec requested");
              break;
          }
        }
        
        return dict;
      }
      else if (PyObject_IsInstance(object, _grt_dict_class))
      {
        return *((PyGRTDictObject*)object)->dict;
      }
      else
        throw grt::type_error("expected dict");
    }
    default:
      return from_pyobject(object);
  }
  
  return ValueRef();
}


int PythonContext::run_file(const std::string &file, bool interactive)
{
  FILE *f= fopen(file.c_str(), "r");
  if (!f)
    return -1;
  
  if (PyRun_SimpleFile(f, file.c_str()) < 0)
  {
    fclose(f);
    PyErr_Print();
    return -1;
  }
  fclose(f);
  
  return 0;
}


// template of a python function to create a wrapper class for a GRT class
static const char *create_class_template=
"class %s(grt.Object):\n"
"  def __init__(self, classname = None, wrapobj = None):\n"
"    grt.Object.__init__(self, classname, wrapobj)";

static const char *create_class_template_sub=
"class %s(%s):\n"
"  def __init__(self, classname = '%s', wrapobj = None):\n"
"    %s.__init__(self, classname, wrapobj)";



/** Create a Python subclass of our grt.Object class, that will wrap around the given GRT class
 */
static void create_class_wrapper(grt::MetaClass *meta, PyObject *locals)
{
  std::string script;
  grt::MetaClass *parent;

  if ((parent= meta->parent()) && parent->parent() != 0)
  {
    std::string parname= flatten_class_name(parent->name());
    script= strfmt(create_class_template_sub, 
                   flatten_class_name(meta->name()).c_str(), parname.c_str(), 
                   meta->name().c_str(),
                   parname.c_str());
  }
  else
  {
    script= strfmt(create_class_template, 
                   flatten_class_name(meta->name()).c_str());
  }

  if (!PyRun_String(script.c_str(), Py_single_input, locals, locals))
    PyErr_Print();
}


/** Refresh Python environment with GRT information.
 */
int PythonContext::refresh()
{
  LockPython lock(this);
  
  PyModule_AddObject(get_grt_module(), "root", from_grt(_grt->root()));
  
  PyObject *classes_dict= PyModule_GetDict(_grt_classes_module);

  Py_INCREF(classes_dict);

  // Generate Python class hierarchy to wrap GRT classes
  const std::list<grt::MetaClass*> &classes(_grt->get_metaclasses());
  for (std::list<grt::MetaClass*>::const_iterator iter= classes.begin(); iter != classes.end(); ++iter)
  {
    create_class_wrapper(*iter, classes_dict);

    _grt_class_wrappers[(*iter)->name()]= PyDict_GetItemString(classes_dict, flatten_class_name((*iter)->name()).c_str());
  }

  Py_DECREF(classes_dict);
  
  // Generate module wrappers
  const std::vector<grt::Module*> &modules(_grt->get_modules());
  for (std::vector<grt::Module*>::const_iterator iter= modules.begin(); iter != modules.end(); ++iter)
  {
    PyObject *r= PyObject_Call(_grt_module_class, Py_BuildValue("(s)", (*iter)->name().c_str()), NULL);

    if (!r)
      PyErr_Print();
    else
      if (PyModule_AddObject(_grt_modules_module, (char*)(*iter)->name().c_str(), r) < 0)
        PyErr_Print();
  }

  return 0;
}

#endif // ENABLE_PYTHON_MODULES
