/* 
 * Copyright (c) 2009, 2014, 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_
#include "base/python_utils.h"

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

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

  class AutoPyObject
  {
  private:
    PyObject *object;
    bool autorelease;
  public: 
    AutoPyObject()
      : object(0), autorelease(false)
    {
    }

    // Assigning another auto object always makes this one ref-counting as they share
    // now the same object. Same for the assignment operator.
    AutoPyObject(const AutoPyObject &other)
      : object(other.object), autorelease(true)
    {
      Py_XINCREF(object);
    }
    
    AutoPyObject(PyObject *py, bool retain = true)
    : object(py)
    {
      autorelease = retain;
      if (autorelease)
      {
        // Leave the braces in place, even though this is a one liner. They will silence LLVM.
        Py_XINCREF(object);
      }
    }
    
    ~AutoPyObject()
    {
      if (autorelease)
      {
        Py_XDECREF(object);
      }
    }

    AutoPyObject &operator= (PyObject *other)
    {
      if (object == other) // Ignore assignments of the same object.
        return *this;

      // Auto release only if we actually have increased its ref count.
      // Always make this auto object auto-releasing after that as we get an object that might
      // be shared by another instance.
      if (autorelease)
      {
        Py_XDECREF(object);
      }

      object = other;
      autorelease = true;
      Py_XINCREF(object);

      return *this;
    }
    
    operator bool()
    {
      return object != 0;
    }
    
    operator PyObject*()
    {
      return object;
    }
    
    PyObject* operator->()
    {
      return object;
    }
  };
  
  // Helper class to allow cleaning up the python context after its instance vars are finished.
  class PythonContextHelper
  {
  private:
    PyThreadState*  _main_thread_state;

  protected:
    PythonContextHelper(const std::string &module_path);
    virtual ~PythonContextHelper();
  };

  class MYSQLGRT_PUBLIC PythonContext : private PythonContextHelper, public GRTObserver
  {
  public:
    PythonContext(GRT *grt, const std::string &module_path);
    virtual ~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);
    
  };

  class python_error : public std::runtime_error
  {
  public:
    python_error(const std::string &what) : std::runtime_error(what) {}
  };
};

#endif
