/* 
 * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
 *
 * 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; version 2 of the
 * License.
 * 
 * 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 St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */


#ifndef _GRT_PYTHON_CONTEXT_H_
#define _GRT_PYTHON_CONTEXT_H_


#if defined(_WIN32)
# include <Python/Python.h>
#else
# include <Python.h>
#endif

// Undef junk #defined by Python headers
#undef tolower
#undef toupper

#include "grtpp.h"
#include "grtpp_notifications.h"

#if (PY_MAJOR_VERSION == 2) && (PY_MINOR_VERSION < 5)
// stuff that doesn't exist in 2.4
typedef int Py_ssize_t;

typedef int (*lenfunc)(PyObject *);
typedef PyObject *(*ssizeargfunc)(PyObject*, Py_ssize_t);
typedef PyObject *(*ssizeargfunc)(PyObject*, Py_ssize_t);
#define ssizeobjargproc intobjargproc

#endif

namespace grt {
  extern const std::string LanguagePython;
  
  class AutoPyObject
  {
    PyObject *object;
  public: 
    AutoPyObject()
      : object(0)
    {
    }

    AutoPyObject(const AutoPyObject &other)
    : object(other.object)
    {
      Py_XINCREF(object);
    }
    
    AutoPyObject(PyObject *py, bool retain=true)
    : object(py)
    {
      if (retain)
      {
        Py_XINCREF(object);
      }
    }
    
    ~AutoPyObject()
    {
      Py_XDECREF(object);
    }

    AutoPyObject &operator= (PyObject *other)
    {
      Py_XINCREF(other);
      Py_XDECREF(object);
      object= other;

      return *this;
    }
    
    operator bool()
    {
      return object != 0;
    }
    
    operator PyObject*()
    {
      return object;
    }
    
    PyObject* operator->()
    {
      return object;
    }
  };
  
  class MYSQLGRT_PUBLIC PythonContext : public GRTObserver
  {
  public:
    PythonContext(GRT *grt, const std::string &module_path);
    ~PythonContext();

    static PythonContext *get();
    static PythonContext *get_and_check();

    static PyObject *internal_cobject_from_value(const ValueRef &value);
    static ValueRef value_from_internal_cobject(PyObject *value);

    static void set_wrap_pyobject_func(PyObject *(*func)(PyObject *, PyObject*));
    static void set_unwrap_pyobject_func(PyObject *(*func)(PyObject *, PyObject*));

    void add_module_path(const std::string &path, bool prepend = false);
    bool import_module(const std::string &name);
    
    PyObject *from_grt(const ValueRef &value);
    grt::ValueRef from_pyobject(PyObject *object);
    grt::ValueRef from_pyobject(PyObject *object, const grt::TypeSpec &expected_type);
    bool pystring_to_string(PyObject *str, std::string &ret_string, bool convert = false);

    int run_file(const std::string &file, bool interactive);
    int run_buffer(const std::string &buffer, std::string *line_buffer=0);
    
    int call_grt_function(const std::string &module, const std::string &function,
                          const BaseListRef &args);
    
    PyObject *eval_string(const std::string &expression);
    
    PyObject *get_grt_module();
    
    PyObject *get_global(const std::string &value);
    bool set_global(const std::string &name, PyObject *value);
        
    int refresh();
        
    GRT *get_grt() const { return _grt; }
    
    bool set_cwd(const std::string &path);
    std::string get_cwd() const { return _cwd; }

    boost::function<std::string ()> stdin_readline_slot;

    static void set_user_interrupted(const grt::user_cancelled &exc);
    static void set_db_access_denied(const grt::db_access_denied &exc);
    static void set_db_login_error(const grt::db_login_error &exc);
    static void set_db_not_conected(const grt::db_not_connected &exc);
    static void set_db_error(const grt::db_error &exc);
    static void set_python_error(const grt::type_error &exc, const std::string &location="");
    static void set_python_error(const grt::bad_item &exc, const std::string &location="");
    static void set_python_error(const std::exception &exc, const std::string &location="");
    
    static void log_python_error(const char *message);
    
    PyObject *user_interrupted_error() { return _grt_user_interrupt_error; }
    PyObject *db_access_denied_error() { return _grt_db_access_denied_error; }
    PyObject *db_login_error() { return _grt_db_login_error; }
    PyObject *db_error() { return _grt_db_error; }
    PyObject *db_not_connected() { return _grt_db_not_connected; }

    void set_grt_observer_callable(PyObject *obj);
    
  protected:
    GRT *_grt;    
    std::string _cwd;
    AutoPyObject _grt_module;
    AutoPyObject _grt_classes_module;
    AutoPyObject _grt_modules_module;

    AutoPyObject _grt_module_class;
    AutoPyObject _grt_function_class;

    AutoPyObject _grt_list_class;
    AutoPyObject _grt_dict_class;
    AutoPyObject _grt_object_class;
    AutoPyObject _grt_method_class;
    
    AutoPyObject _grt_user_interrupt_error;
    AutoPyObject _grt_db_access_denied_error;
    AutoPyObject _grt_db_login_error;
    AutoPyObject _grt_db_error;
    AutoPyObject _grt_db_not_connected;

    AutoPyObject _grt_notification_observer;
    
    std::map<std::string, AutoPyObject> _grt_class_wrappers;

  private:
    ValueRef simple_type_from_pyobject(PyObject *object, const grt::SimpleTypeSpec &type);
    
    void register_grt_module();
    void register_grt_functions();
    void redirect_python_output();

    void init_grt_module_type();
    void init_grt_list_type();
    void init_grt_dict_type();
    void init_grt_object_type();

    void run_post_init_script();
    
    virtual void handle_grt_notification(const std::string &name, ObjectRef sender, DictRef info);
    virtual void handle_notification(const std::string &name, void *sender, base::NotificationInfo &info);
    
    PyThreadState*  _main_thread_state;

  };
  
  // Must be placed when Python code will be called
  struct WillEnterPython
  {
    PyGILState_STATE state;
    
    WillEnterPython()
    : state(PyGILState_Ensure())
    {
     // PyEval_AcquireLock();
    }

    ~WillEnterPython()
    {
      //PyEval_ReleaseLock();
      PyGILState_Release(state);
    }
  };
  
  
  // Must be placed when non-python code will be called from a Python handler/callback
  struct WillLeavePython
  {
    PyThreadState *save;
    
    WillLeavePython()
    : save(PyEval_SaveThread())
    {
    }
    
    ~WillLeavePython()
    {
      PyEval_RestoreThread(save);
    }
  };
};

#endif
