# frozen_string_literal: true

require "gir_ffi/builders/registered_type_builder"
require "gir_ffi/builders/with_layout"
require "gir_ffi/builders/property_builder"
require "gir_ffi/object_base"
require "gir_ffi/struct"

module GirFFI
  module Builders
    # Implements the creation of a class representing a GObject Object.
    class ObjectBuilder < RegisteredTypeBuilder
      include WithLayout

      # Dummy builder for the ObjectBase class
      class ObjectBaseBuilder
        def build_class
          ObjectBase
        end

        def class_struct_class
          GObject::TypeClass
        end

        def ancestor_infos
          []
        end
      end

      def find_signal(signal_name)
        seek_in_ancestor_infos { |info| info.find_signal signal_name }
      end

      def find_property(property_name)
        seek_in_ancestor_infos { |info| info.find_property property_name }
      end

      def class_struct_class
        @class_struct_class ||=
          begin
            parent_struct = parent_builder.class_struct_class
            if class_struct_info
              StructBuilder.new(class_struct_info,
                                superclass: parent_struct).build_class
            else
              parent_struct
            end
          end
      end

      def ancestor_infos
        @ancestor_infos ||= [info] + info.interfaces + parent_ancestor_infos
      end

      def eligible_properties
        info.properties.reject do |pinfo|
          info.find_instance_method("get_#{pinfo.name}")
        end
      end

      private

      def setup_class
        setup_layout
        setup_constants
        stub_methods
        setup_property_accessors
        setup_vfunc_invokers
        setup_interfaces
        setup_initializer
      end

      def class_struct_info
        @class_struct_info ||= info.class_struct
      end

      # FIXME: Private method only used in subclass
      def layout_superclass
        GirFFI::Struct
      end

      def parent_info
        info.parent
      end

      def superclass
        @superclass ||= parent_builder.build_class
      end

      def parent_builder
        @parent_builder ||= if parent_info
                              Builders::TypeBuilder.builder_for(parent_info)
                            else
                              ObjectBaseBuilder.new
                            end
      end

      def parent_ancestor_infos
        @parent_ancestor_infos ||= parent_builder.ancestor_infos
      end

      def setup_property_accessors
        eligible_properties.each do |prop|
          PropertyBuilder.new(prop).build
        end
      end

      # TODO: Guard agains accidental invocation of undefined vfuncs.
      # TODO: Create object responsible for creating these invokers
      def setup_vfunc_invokers
        info.vfuncs.each do |vfinfo|
          define_vfunc_invoker vfinfo.name, vfinfo.invoker_name if vfinfo.has_invoker?
        end
      end

      def define_vfunc_invoker(vfunc_name, invoker_name)
        return if vfunc_name == invoker_name

        klass.class_eval <<-DEF, __FILE__, __LINE__ + 1
          def #{vfunc_name} *args, &block   # def foo *args, &block
            #{invoker_name}(*args, &block)  #   foo_invoker *args, &block
          end                               # end
        DEF
      end

      def setup_initializer
        return if info.find_method "new"

        if info.abstract?
          define_abstract_initializer
        else
          define_default_initializer
        end
      end

      def define_abstract_initializer
        klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
          def initialize(*)
            raise NoMethodError
          end
        RUBY
      end

      def define_default_initializer
        klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
          def initialize(properties = {})
            base_initialize(properties)
          end
        RUBY
      end

      def setup_interfaces
        interfaces.each do |iface|
          klass.send :include, iface
        end
      end

      def interfaces
        info.interfaces.map do |ifinfo|
          GirFFI::Builder.build_class ifinfo
        end
      end

      def seek_in_ancestor_infos
        ancestor_infos.each do |info|
          item = yield info
          return item if item
        end
        nil
      end
    end
  end
end
