# frozen_string_literal: true

require 'gir_ffi/property_not_found_error'

GObject.load_class :Object

module GObject
  # Overrides for GObject, GObject's generic base class.
  class Object
    setup_method 'new'
    if !GLib.check_version(2, 54, 0)
      setup_method 'newv'

      def initialize_with_automatic_gtype(properties = {})
        names = []
        values = []
        properties.each do |name, value|
          name = name.to_s
          gvalue = gvalue_for_property(name)
          gvalue.set_value value

          names << name
          values << gvalue
        end
        initialize_without_automatic_gtype(self.class.gtype, names, values)
      end
    else
      def initialize_with_automatic_gtype(properties = {})
        gparameters = properties.map do |name, value|
          name = name.to_s
          property_param_spec(name)
          GObject::Parameter.new.tap do |gparam|
            gparam.name = name
            gparam.value = value
          end
        end
        initialize_without_automatic_gtype(self.class.gtype, gparameters)
      end
    end

    alias_method :initialize_without_automatic_gtype, :initialize
    alias_method :initialize, :initialize_with_automatic_gtype
    alias_method :base_initialize, :initialize

    private :base_initialize

    remove_method :ref

    def ref
      Lib.g_object_ref self
      self
    end

    def store_pointer(ptr)
      super
      ObjectSpace.define_finalizer self, self.class.make_finalizer(ptr)
    end

    def self.make_finalizer(ptr)
      proc do
        rc = GObject::Object::Struct.new(ptr)[:ref_count]
        if rc.zero?
          warn "not unreffing #{name}:#{ptr} (#{rc})"
        else
          GObject::Lib.g_object_unref ptr
        end
      end
    end

    # TODO: Generate accessor methods from GIR at class definition time
    def method_missing(method, *args)
      getter_name = "get_#{method}"
      return send(getter_name, *args) if respond_to?(getter_name)
      if method.to_s =~ /(.*)=$/
        setter_name = "set_#{Regexp.last_match[1]}"
        return send(setter_name, *args) if respond_to?(setter_name)
      end
      super
    end

    def respond_to_missing?(*)
      false
    end

    def signal_connect(event, data = nil, &block)
      GObject.signal_connect(self, event, data, &block)
    end

    def signal_connect_after(event, data = nil, &block)
      GObject.signal_connect_after(self, event, data, &block)
    end

    setup_instance_method 'get_property'
    setup_instance_method 'set_property'

    def get_property_extended(property_name)
      value = get_property(property_name)
      type_info = get_property_type property_name
      property_value_post_conversion value, type_info
    end

    def get_property_with_override(property_name)
      gvalue = gvalue_for_property property_name
      get_property_without_override property_name, gvalue
      gvalue.get_value
    end

    def set_property_extended(property_name, value)
      type_info = get_property_type property_name
      adjusted_value = property_value_pre_conversion(value, type_info)
      set_property property_name, adjusted_value
    end

    def set_property_with_override(property_name, value)
      gvalue = gvalue_for_property(property_name)
      gvalue.set_value value
      set_property_without_override property_name, gvalue
    end

    alias_method :get_property_without_override, :get_property
    alias_method :get_property, :get_property_with_override

    alias_method :set_property_without_override, :set_property
    alias_method :set_property, :set_property_with_override

    setup_instance_method 'is_floating'
    alias_method :floating?, :is_floating

    private

    def get_property_type(property_name)
      prop = self.class.find_property(property_name)
      prop.property_type
    end

    def gvalue_for_property(property_name)
      gtype = property_gtype property_name
      GObject::Value.for_gtype gtype
    end

    def property_gtype(property_name)
      pspec = property_param_spec(property_name)
      pspec.value_type
    end

    def property_param_spec(property_name)
      object_class.find_property property_name or
        raise GirFFI::PropertyNotFoundError.new(property_name, self.class)
    end

    # TODO: Move to ITypeInfo and unify with ArgHelper.cast_from_pointer
    def property_value_post_conversion(val, type_info)
      case type_info.flattened_tag
      when :ghash
        GLib::HashTable.from type_info.element_type, val
      when :glist
        GLib::List.from type_info.element_type, val
      when :callback
        GirFFI::Builder.build_class(type_info.interface).wrap val
      else
        val
      end
    end

    # TODO: Move to ITypeInfo and unify with ArgHelper.cast_from_pointer
    def property_value_pre_conversion(val, type_info)
      case type_info.flattened_tag
      when :ghash
        GLib::HashTable.from type_info.element_type, val
      when :glist
        GLib::List.from type_info.element_type, val
      when :strv
        GLib::Strv.from val
      when :callback
        GirFFI::Builder.build_class(type_info.interface).from val
      else
        val
      end
    end
  end
end
