require 'pycall/pyobject_wrapper'

module PyCall
  module PyTypeObjectWrapper
    include PyObjectWrapper

    def self.extend_object(cls)
      unless cls.kind_of? Class
        raise TypeError, "PyTypeObjectWrapper cannot extend non-class objects"
      end
      pyptr = cls.instance_variable_get(:@__pyptr__)
      unless pyptr.kind_of? PyTypePtr
        raise TypeError, "@__pyptr__ should have PyCall::PyTypePtr object"
      end
      super
      cls.include PyObjectWrapper
    end

    def inherited(subclass)
      subclass.instance_variable_set(:@__pyptr__, __pyptr__)
    end

    def new(*args)
      wrap_pyptr(LibPython::Helpers.call_object(__pyptr__, *args))
    end

    def wrap_pyptr(pyptr)
      return pyptr if pyptr.kind_of? self
      pyptr = pyptr.__pyptr__ if pyptr.kind_of? PyObjectWrapper
      unless pyptr.kind_of? PyPtr
        raise TypeError, "unexpected argument type #{pyptr.class} (expected PyCall::PyPtr)"
      end
      unless pyptr.kind_of? __pyptr__
        raise TypeError, "unexpected argument Python type #{pyptr.__ob_type__.__tp_name__} (expected #{__pyptr__.__tp_name__})"
      end
      allocate.tap do |obj|
        obj.instance_variable_set(:@__pyptr__, pyptr)
      end
    end

    def ===(other)
      case other
      when PyObjectWrapper
        __pyptr__ === other.__pyptr__
      when PyPtr
        __pyptr__ === other
      else
        super
      end
    end

    private

    def register_python_type_mapping
      PyCall::Conversion.register_python_type_mapping(__pyptr__, self)
    end
  end

  module_function

  class WrapperClassCache < WrapperObjectCache
    def initialize
      types = [LibPython::API::PyType_Type]
      types << LibPython::API::PyClass_Type if defined? LibPython::API::PyClass_Type
      super(*types)
    end

    def check_wrapper_object(wrapper_object)
      unless wrapper_object.kind_of?(Class) && wrapper_object.kind_of?(PyTypeObjectWrapper)
        raise TypeError, "unexpected type #{wrapper_object.class} (expected Class extended by PyTypeObjectWrapper)"
      end
    end

    def self.instance
      @instance ||= self.new
    end
  end

  private_constant :WrapperClassCache

  def wrap_class(pytypeptr)
    check_isclass(pytypeptr)
    WrapperClassCache.instance.lookup(pytypeptr) do
      Class.new do |cls|
        cls.instance_variable_set(:@__pyptr__, pytypeptr)
        cls.extend PyTypeObjectWrapper
      end
    end
  end
end
