# Copyright (C) 2013-2023  Ruby-GNOME Project Team
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

module Gdk
  class Loader < GObjectIntrospection::Loader
    def load
      self.version = "3.0"
      super("Gdk")
    end

    private
    def event_class
      @event_class ||= @base_module.const_get(:Event)
    end

    def pre_load(repository, namespace)
      setup_pending_constants
      setup_pending_rectangle_functions
      define_keyval_module
      define_selection_module
      define_event_singleton_methods_module
      define_event_motion_methods_module
      define_window_methods_module
      define_cairo_context_methods_module
      define_cairo_surface_methods_module
      require_pre_libraries
    end

    def define_keyval_module
      @keyval_module = define_methods_module(:Keyval)
    end

    def post_keyval_module
      post_methods_module(@keyval_module)
    end

    def define_selection_module
      @selection_module = Module.new
      @base_module.const_set(:Selection, @selection_module)
    end

    def define_event_singleton_methods_module
      @event_singleton_methods_module =
        define_methods_module(:EventSingletonMethods)
    end

    def apply_event_singleton_methods
      event_class = @base_module.const_get(:Event)
      apply_methods_module(@event_singleton_methods_module,
                           event_class.singleton_class)
    end

    def define_event_motion_methods_module
      @event_motion_methods_module =
        define_methods_module(:EventMotionMethods)
    end

    def apply_event_motion_methods
      event_motion_class = @base_module.const_get(:EventMotion)
      apply_methods_module(@event_motion_methods_module,
                           event_motion_class)
    end

    def define_window_methods_module
      @window_methods_module = define_methods_module(:WindowMethods)
    end

    def apply_window_methods
      window_class = @base_module.const_get(:Window)
      apply_methods_module(@window_methods_module, window_class)
    end

    def define_cairo_context_methods_module
      @cairo_context_methods_module =
        define_methods_module(:CairoContextMethods)
    end

    def apply_cairo_context_methods
      apply_methods_module(@cairo_context_methods_module, Cairo::Context)
    end

    def define_cairo_surface_methods_module
      @cairo_surface_methods_module =
        define_methods_module(:CairoSurfaceMethods)
    end

    def apply_cairo_surface_methods
      apply_methods_module(@cairo_surface_methods_module, Cairo::Surface)
    end

    def require_pre_libraries
      require "gdk3/event-readers"
    end

    def post_load(repository, namespace)
      apply_pending_constants
      apply_pending_rectangle_functions
      post_keyval_module
      apply_event_singleton_methods
      apply_event_motion_methods
      apply_window_methods
      apply_cairo_context_methods
      apply_cairo_surface_methods
      require_post_libraries
      convert_event_classes
      define_selection_constants
    end

    def setup_pending_constants
      @pending_constants = []
    end

    def apply_pending_constants
      @pending_constants.each do |info|
        case info.name
        when /\AEVENT_/
          event_class.const_set($POSTMATCH, info.value)
        end
      end
    end

    def setup_pending_rectangle_functions
      @pending_rectangle_get_type_function = nil
      @pending_rectangle_functions = []
    end

    def apply_pending_rectangle_functions
      get_type_info = @pending_rectangle_get_type_function
      if get_type_info
        rectangle_class = load_cairo_rectangle_int
        gtype = get_type_info.invoke([])
        self.class.register_boxed_class_converter(gtype) do |rectangle|
          rectangle_class
        end
      else
        rectangle_class = @base_module.const_get(:Rectangle)
      end

      @pending_rectangle_functions.each do |info|
        name = rubyish_method_name(info, :prefix => "rectangle_")
        define_method(info, rectangle_class, name)
      end
    end

    def load_cairo_rectangle_int
      info = find_cairo_rectangle_int_info
      klass = self.class.define_class(info.gtype,
                                      "Rectangle",
                                      @base_module,
                                      :size => info.size)
      load_fields(info, klass)
      load_methods(info, klass)
      klass
    end

    def find_cairo_rectangle_int_info
      repository = GObjectIntrospection::Repository.default
      repository.each("cairo") do |info|
        if info.name == "RectangleInt"
          return info
        end
      end
      nil
    end

    def require_post_libraries
      require "gdk3/atom"
      require "gdk3/color"
      require "gdk3/cursor"
      require "gdk3/event"
      require "gdk3/rectangle"
      require "gdk3/rgba"
      require "gdk3/screen"
      require "gdk3/window"
      require "gdk3/window-attr"

      require "gdk3/cairo"

      require "gdk3/deprecated"
    end

    def convert_event_classes
      event_map = {
        EventType::EXPOSE              => EventExpose,
        EventType::MOTION_NOTIFY       => EventMotion,
        EventType::BUTTON_PRESS        => EventButton,
        EventType::BUTTON2_PRESS       => EventButton,
        EventType::BUTTON3_PRESS       => EventButton,
        EventType::BUTTON_RELEASE      => EventButton,
        EventType::KEY_PRESS           => EventKey,
        EventType::KEY_RELEASE         => EventKey,
        EventType::ENTER_NOTIFY        => EventCrossing,
        EventType::LEAVE_NOTIFY        => EventCrossing,
        EventType::FOCUS_CHANGE        => EventFocus,
        EventType::CONFIGURE           => EventConfigure,
        EventType::PROPERTY_NOTIFY     => EventProperty,
        EventType::SELECTION_CLEAR     => EventSelection,
        EventType::SELECTION_REQUEST   => EventSelection,
        EventType::SELECTION_NOTIFY    => EventSelection,
        EventType::PROXIMITY_IN        => EventProximity,
        EventType::PROXIMITY_OUT       => EventProximity,
        EventType::DRAG_ENTER          => EventDND,
        EventType::DRAG_LEAVE          => EventDND,
        EventType::DRAG_MOTION         => EventDND,
        EventType::DRAG_STATUS         => EventDND,
        EventType::DROP_START          => EventDND,
        EventType::DROP_FINISHED       => EventDND,
        EventType::VISIBILITY_NOTIFY   => EventVisibility,
        EventType::SCROLL              => EventScroll,
        EventType::WINDOW_STATE        => EventWindowState,
        EventType::SETTING             => EventSetting,
        EventType::OWNER_CHANGE        => EventOwnerChange,
        EventType::GRAB_BROKEN         => EventGrabBroken,
        EventType::DAMAGE              => EventExpose,
        EventType::TOUCH_BEGIN         => EventTouch,
        EventType::TOUCH_UPDATE        => EventTouch,
        EventType::TOUCH_END           => EventTouch,
        EventType::TOUCH_CANCEL        => EventTouch,
      }
      self.class.register_boxed_class_converter(Event.gtype) do |event|
        event_map[event.type] || Event
      end
    end

    def define_selection_constants
      selections = {
        "PRIMARY"         => "PRIMARY",
        "SECONDARY"       => "SECONDARY",
        "CLIPBOARD"       => "CLIPBOARD",
        "TARGET_BITMAP"   => "BITMAP",
        "TARGET_COLORMAP" => "COLORMAP",
        "TARGET_DRAWABLE" => "DRAWABLE",
        "TARGET_PIXMAP"   => "PIXMAP",
        "TARGET_STRING"   => "STRING",
        "TYPE_ATOM"       => "ATOM",
        "TYPE_BITMAP"     => "BITMAP",
        "TYPE_COLORMAP"   => "COLORMAP",
        "TYPE_DRAWABLE"   => "DRAWABLE",
        "TYPE_INTEGER"    => "INTEGER",
        "TYPE_PIXMAP"     => "PIXMAP",
        "TYPE_WINDOW"     => "WINDOW",
        "TYPE_STRING"     => "STRING",
      }
      selections.each do |key, value|
        @selection_module.const_set(key, Gdk::Atom.intern(value))
      end
    end

    def load_function_info(info)
      name = info.name
      case name
      when "rectangle_get_type"
        @pending_rectangle_get_type_function = info
      when /\Arectangle_/
        @pending_rectangle_functions << info
      when "init", /_get_type\z/
        # ignore
      when /\Apixbuf_/
        target_class = nil
        case $POSTMATCH
        when "get_from_window"
          target_class = @window_methods_module
        when "get_from_surface"
          target_class = @cairo_surface_methods_module
        end
        if target_class
          define_method(info, target_class, "to_pixbuf")
        else
          super
        end
      when /\Aevent_/
        name = $POSTMATCH
        case name
        when "request_motions"
          define_method(info, @event_motion_methods_module, "request")
        else
          # ignore because moved to Gdk::Event
        end
      when /\Aevents_/
        method_name = rubyish_method_name(info, :prefix => "events_")
        define_module_function(info,
                               @event_singleton_methods_module,
                               method_name)
      when /\Acairo_/
        name = $POSTMATCH
        case name
        when "create"
          define_method(info, @window_methods_module, "create_cairo_context")
        when "set_source_color"
          define_method(info,
                        @cairo_context_methods_module,
                        "set_source_gdk_color")
        when "set_source_rgba"
          define_method(info,
                        @cairo_context_methods_module,
                        "set_source_gdk_rgba")
        when "rectangle"
          define_method(info,
                        @cairo_context_methods_module,
                        "gdk_rectangle")
        when "region_create_from_surface"
          # TODO
        when "surface_create_from_pixbuf"
          # TODO
        else
          define_method(info, @cairo_context_methods_module, name)
        end
      when /\Akeyval_/
        name = rubyish_method_name(info, :prefix => "keyval_")
        case name
        when "name"
          name = "to_name"
        end
        define_module_function(info, @keyval_module, name)
      else
        super
      end
    end

    def load_struct_info(info)
      return if info.gtype_struct?

      case info.name
      when /\AEvent/
        name = rubyish_class_name(info)
        klass = Class.new(event_class)
        @base_module.const_set(name, klass)
        load_fields(info, klass)
        load_methods(info, klass)
      else
        define_struct(info)
      end
    end

    def define_enum(info)
      case info.name
      when "EventType"
        self.class.register_constant_rename_map("2BUTTON_PRESS",
                                                "BUTTON2_PRESS")
        self.class.register_constant_rename_map("3BUTTON_PRESS",
                                                "BUTTON3_PRESS")
        super
      else
        super
      end
    end

    def load_constant_info(info)
      case info.name
      when /\AEVENT_/
        @pending_constants << info
      when /\AKEY_/
        @keyval_module.const_set(info.name, info.value)
      else
        super
      end
    end

    def load_field(info, i, field_info, klass)
      return super unless klass.name.start_with?("Gdk::Event")

      field_name = field_info.name
      if klass.name == "Gdk::Event"
        super if field_name == "type"
        return
      end

      case field_name
      when "window", "direction"
        super(info, i, field_info, klass, :readable => false)
      when "axes"
        klass.__send__(:prepend, EventAxisReader)
      when "button"
        klass.__send__(:prepend, EventButtonReader)
      when "state"
        case klass.name
        when "Gdk::EventProperty", "Gdk::EventVisibility"
          super
        else
          klass.__send__(:prepend, EventStateReader)
        end
      when "x"
        klass.__send__(:prepend, EventCoordsReader)
      when "x_root"
        klass.__send__(:prepend, EventRootCoordsReader)
      else
        unless klass.method_defined?(field_name)
          super
        end
      end
    end

    def may_be_cached?(constructor_info, klass)
      case klass.name
      when "Gdk::Cursor"
        case constructor_info.name
        when "new_from_name", "new_for_display"
          true
          # false
        else
          false
        end
      else
        false
      end
    end
  end
end
