require 'ffi'
require 'thread'
require 'rubypython/interpreter'

module RubyPython
  # This module will hold the loaded RubyPython interpreter.
  module Python #:nodoc: all
    @lock = Mutex.new

    def self.synchronize(&block)
      @lock.synchronize(&block)
    end
  end
end

class RubyPython::Interpreter
  # Infects the provided module with the Python FFI. Once a single module
  # has been infected, the #infect! method is removed from
  # RubyPython::Interpreter.
  def infect!(mod)
    self.class.class_eval do
      undef :infect!
    end

    mod.extend ::FFI::Library
    # FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_GLOBAL
    mod.ffi_lib_flags :lazy, :global
    mod.ffi_lib self.library

    # This class is a little bit of a hack to extract the address of
    # global structs. If someone knows a better way please let me know.
    mod.module_eval do
      self.const_set :DummyStruct, Class.new(::FFI::Struct)
      self::DummyStruct.layout :dummy_var, :int

      self.const_set(:PY_FILE_INPUT, 257)
      self.const_set(:PY_EVAL_INPUT, 258)
      self.const_set(:METH_VARARGS, 0x0001)

      # Function methods & constants
      attach_function :PyCFunction_New, [:pointer, :pointer], :pointer
      callback :PyCFunction, [:pointer, :pointer], :pointer

      attach_function :PyRun_String, [:string, :int, :pointer, :pointer], :pointer
      attach_function :PyRun_SimpleString, [:string], :pointer
      attach_function :Py_CompileString, [:string, :string, :int], :pointer
      attach_function :PyEval_EvalCode, [:pointer, :pointer, :pointer], :pointer
      attach_function :PyErr_SetString, [:pointer, :string], :void

      # Python interpreter startup and shutdown
      attach_function :Py_IsInitialized, [], :int
      attach_function :Py_Initialize, [], :void
      attach_function :Py_Finalize, [], :void

      # Module methods
      attach_function :PyImport_ImportModule, [:string], :pointer

      # Object Methods
      attach_function :PyObject_HasAttrString, [:pointer, :string], :int
      attach_function :PyObject_GetAttrString, [:pointer, :string], :pointer
      attach_function :PyObject_SetAttrString, [:pointer, :string, :pointer], :int
      attach_function :PyObject_Dir, [:pointer], :pointer

      attach_function :PyObject_Compare, [:pointer, :pointer], :int

      attach_function :PyObject_Call, [:pointer, :pointer, :pointer], :pointer
      attach_function :PyObject_CallObject, [:pointer, :pointer], :pointer
      attach_function :PyCallable_Check, [:pointer], :int

      ### Python To Ruby Conversion
      # String Methods
      attach_function :PyString_AsString, [:pointer], :string
      attach_function :PyString_FromString, [:string], :pointer
      attach_function :PyString_AsStringAndSize, [:pointer, :pointer, :pointer], :int
      attach_function :PyString_FromStringAndSize, [:buffer_in, :ssize_t], :pointer

      # List Methods
      attach_function :PyList_GetItem, [:pointer, :int], :pointer
      attach_function :PyList_Size, [:pointer], :int
      attach_function :PyList_New, [:int], :pointer
      attach_function :PyList_SetItem, [:pointer, :int, :pointer], :void

      # Integer Methods
      attach_function :PyInt_AsLong, [:pointer], :long
      attach_function :PyInt_FromLong, [:long], :pointer

      attach_function :PyLong_AsLong, [:pointer], :long
      attach_function :PyLong_FromLongLong, [:long_long], :pointer

      # Float Methods
      attach_function :PyFloat_AsDouble, [:pointer], :double
      attach_function :PyFloat_FromDouble, [:double], :pointer

      # Tuple Methods
      attach_function :PySequence_List, [:pointer], :pointer
      attach_function :PyList_AsTuple, [:pointer], :pointer
      attach_function :PyTuple_Pack, [:int, :varargs], :pointer

      # Dict/Hash Methods
      attach_function :PyDict_Next, [:pointer, :pointer, :pointer, :pointer], :int
      attach_function :PyDict_New, [], :pointer
      attach_function :PyDict_SetItem, [:pointer, :pointer, :pointer], :int
      attach_function :PyDict_Contains, [:pointer, :pointer], :int
      attach_function :PyDict_GetItem, [:pointer, :pointer], :pointer

      # Error Methods
      attach_variable :PyExc_Exception, self::DummyStruct.by_ref
      attach_variable :PyExc_StopIteration, self::DummyStruct.by_ref
      attach_function :PyErr_SetNone, [:pointer], :void
      attach_function :PyErr_Fetch, [:pointer, :pointer, :pointer], :void
      attach_function :PyErr_Occurred, [], :pointer
      attach_function :PyErr_Clear, [], :void

      # Reference Counting
      attach_function :Py_IncRef, [:pointer], :void
      attach_function :Py_DecRef, [:pointer], :void

      # Type Objects
      # attach_variable :PyBaseObject_Type, self::DummyStruct.by_value # built-in 'object' 
      # attach_variable :PyBaseString_Type, self::DummyStruct.by_value
      # attach_variable :PyBool_Type, self::DummyStruct.by_value
      # attach_variable :PyBuffer_Type, self::DummyStruct.by_value
      # attach_variable :PyByteArrayIter_Type, self::DummyStruct.by_value
      # attach_variable :PyByteArray_Type, self::DummyStruct.by_value
      attach_variable :PyCFunction_Type, self::DummyStruct.by_value
      # attach_variable :PyCObject_Type, self::DummyStruct.by_value
      # attach_variable :PyCallIter_Type, self::DummyStruct.by_value
      # attach_variable :PyCapsule_Type, self::DummyStruct.by_value
      # attach_variable :PyCell_Type, self::DummyStruct.by_value
      # attach_variable :PyClassMethod_Type, self::DummyStruct.by_value
      attach_variable :PyClass_Type, self::DummyStruct.by_value
      # attach_variable :PyCode_Type, self::DummyStruct.by_value
      # attach_variable :PyComplex_Type, self::DummyStruct.by_value
      # attach_variable :PyDictItems_Type, self::DummyStruct.by_value
      # attach_variable :PyDictIterItem_Type, self::DummyStruct.by_value
      # attach_variable :PyDictIterKey_Type, self::DummyStruct.by_value
      # attach_variable :PyDictIterValue_Type, self::DummyStruct.by_value
      # attach_variable :PyDictKeys_Type, self::DummyStruct.by_value
      # attach_variable :PyDictProxy_Type, self::DummyStruct.by_value
      # attach_variable :PyDictValues_Type, self::DummyStruct.by_value
      attach_variable :PyDict_Type, self::DummyStruct.by_value
      # attach_variable :PyEllipsis_Type, self::DummyStruct.by_value
      # attach_variable :PyEnum_Type, self::DummyStruct.by_value
      # attach_variable :PyFile_Type, self::DummyStruct.by_value
      attach_variable :PyFloat_Type, self::DummyStruct.by_value
      # attach_variable :PyFrame_Type, self::DummyStruct.by_value
      # attach_variable :PyFrozenSet_Type, self::DummyStruct.by_value
      attach_variable :PyFunction_Type, self::DummyStruct.by_value
      # attach_variable :PyGen_Type, self::DummyStruct.by_value
      # attach_variable :PyGetSetDescr_Type, self::DummyStruct.by_value
      # attach_variable :PyInstance_Type, self::DummyStruct.by_value
      attach_variable :PyInt_Type, self::DummyStruct.by_value
      attach_variable :PyList_Type, self::DummyStruct.by_value
      attach_variable :PyLong_Type, self::DummyStruct.by_value
      # attach_variable :PyMemberDescr_Type, self::DummyStruct.by_value
      # attach_variable :PyMemoryView_Type, self::DummyStruct.by_value
      attach_variable :PyMethod_Type, self::DummyStruct.by_value
      # attach_variable :PyModule_Type, self::DummyStruct.by_value
      # attach_variable :PyNullImporter_Type, self::DummyStruct.by_value
      # attach_variable :PyProperty_Type, self::DummyStruct.by_value
      # attach_variable :PyRange_Type, self::DummyStruct.by_value
      # attach_variable :PyReversed_Type, self::DummyStruct.by_value
      # attach_variable :PySTEntry_Type, self::DummyStruct.by_value
      # attach_variable :PySeqIter_Type, self::DummyStruct.by_value
      # attach_variable :PySet_Type, self::DummyStruct.by_value
      # attach_variable :PySlice_Type, self::DummyStruct.by_value
      # attach_variable :PyStaticMethod_Type, self::DummyStruct.by_value
      attach_variable :PyString_Type, self::DummyStruct.by_value
      # attach_variable :PySuper_Type, self::DummyStruct.by_value # built-in 'super' 
      # attach_variable :PyTraceBack_Type, self::DummyStruct.by_value
      attach_variable :PyTuple_Type, self::DummyStruct.by_value
      attach_variable :PyType_Type, self::DummyStruct.by_value
      # attach_variable :PyUnicode_Type, self::DummyStruct.by_value
      # attach_variable :PyWrapperDescr_Type, self::DummyStruct.by_value

      attach_variable :Py_TrueStruct, :_Py_TrueStruct, self::DummyStruct.by_value
      attach_variable :Py_ZeroStruct, :_Py_ZeroStruct, self::DummyStruct.by_value
      attach_variable :Py_NoneStruct, :_Py_NoneStruct, self::DummyStruct.by_value

      # This is an implementation of the basic structure of a Python PyObject
      # struct. The C struct is actually much larger, but since we only access
      # the first two data members via FFI and always deal with struct
      # pointers there is no need to mess around with the rest of the object.
      self.const_set :PyObjectStruct, Class.new(::FFI::Struct)
      self::PyObjectStruct.layout :ob_refcnt, :ssize_t,
                                  :ob_type, :pointer

      # This struct is used when defining Python methods.
      self.const_set :PyMethodDef, Class.new(::FFI::Struct)
      self::PyMethodDef.layout :ml_name, :pointer,
                               :ml_meth, :PyCFunction,
                               :ml_flags, :int,
                               :ml_doc, :pointer
    end

  end

  private :infect!
end
