require 'monkeybars/exceptions'
module Monkeybars
  module EventHandlerRegistrationAndDispatchMixin
    def self.included(target)
      target.extend ClassMethods
    end

    module ClassMethods
      @@event_handler_procs ||= {}
      def event_handler_procs
        @@event_handler_procs[self] ||= Hash.new {|hash, key| hash[key] = []}
      end

      @@handlers ||= {}
      def handlers
        @@handlers[self] ||= []
      end

      # Declares which components you want events to be generated for.  add_listener
      # takes a hash of the form :type => type, :components => [components for events]
      # All AWT and Swing listener types are supported.  See Monkeybars::Handlers for
      # the full list.
      #
      # The array of components should be strings or symbols with the exact naming of the 
      # component in the Java class declared in the view.  As an example, if you have a JFrame 
      # with a text area named infoTextField that you wanted to receive key events for, perhaps
      # to filter certain key input or to enable an auto-completion feature you could use:
      #
      #   add_listener :type => :key, :components => [:infoTextField]
      #
      # To handle the event you would then need to implement a method named
      # <component>_<event> which in this case would be info_text_field_key_pressed,
      # info_text_field_key_released or info_text_field_key_typed.
      #
      # If you have a single component you can omit the array and pass a single string or symbol.
      # 
      #   add_listener :type => :key, :components => :infoTextField
      #
      # You will run into errors if your component is a nested name, for example
      # 
      #   add_listener :type => :document, :components => "infoTextField.document"
      #   
      # because when the event is generated and a handler is attempted to be located, 
      # the name infoTextField.document doesn't map well to a method.  To resolve this, 
      # the component name can be a hash, the key being the component name and the value 
      # being the desired callback name.
      # 
      #   add_listener :type => :document, :components => {"infoTextField.document" => "info_text_field"}
      #
      # This will cause the info_text_field_action_performed method to be called when
      # the action performed event is generated by infoTextField.document.
      # 
      # If you want to add a listener to the view itself (JFrame, JDialog, etc.)
      # then you can use :java_window as the component
      # 
      #   add_listener :type => :window, :components => [:java_window]
      #
      # If it is not possible to declare a method, or it is desirable to do so dynamically
      # (even from outside the class), you can use the define_handler method.
      #
      # If you wish to override the default event handling behavior, override handle_event
      def add_listener details
        handlers << details
      end

      # define_handler takes a component/event name and a block to be called when that
      # event is generated for that component.  This can be used in place of a method
      # declaration for that component/event pair.
      #
      # So, if you have declared:
      #
      #   add_listener :type => :action, :components => [:ok_button]
      #
      # you could implement the handler using:
      #
      #   define_handler(:ok_button_action_performed) do |event|
      #     # handle the event here
      #   end
      #   
      # Note that handlers defined using this method will create implicit listener
      # registrations the same as a declared method would.
      #   
      # define_handler also accepts multiple event names
      #
      #   define_handler(:ok_button_action_performed, :cancel_button_action_performed) do
      #     # handle event(s) here
      #   end
      def define_handler *actions, &block
        actions.each {|action| event_handler_procs[action.to_sym] << block}
      end
    end

    # This method should be called from the initialize method of the class using
    # this mixin to set up the needed instance variables and both declared
    # and implicit handlers
    def setup_implicit_and_explicit_event_handlers # :nodoc:
      @__event_handler_view_target = if self.class.ancestors.member?(Monkeybars::Controller)
        @__view
      else
        self
      end
      @__registered_handlers = Hash.new{|h,k| h[k] = []}
      @__event_handler_procs = Hash.new{|h,k| h[k] = []}

      unless self.class.handlers.empty?
        if @__event_handler_view_target.nil?
          raise "A view must be declared in order to add event listeners"
        end

        self.class.handlers.each do |handler|            
          handler[:components].each do |component|
            if component.kind_of? Array
              component = component.first
            end
            begin
              resolved_component = @__event_handler_view_target.instance_eval(component.to_s, __FILE__, __LINE__)
            rescue NoMethodError => e
              raise InvalidHandlerError, "Could not find component: #{component} on view #{@__event_handler_view_target}\nOriginal exception: #{e.message}"
            end
            
            add_handler_for handler[:type], handler[:components], resolved_component
          end
        end
      end

      monkeybars_base_class = self.class.ancestors.find {|klass| /^Monkeybars::(Controller|View)$/ =~ klass.name }
      (methods.grep(/_/) - (EventHandlerRegistrationAndDispatchMixin.instance_methods + monkeybars_base_class.instance_methods)).each {|method| add_implicit_handler_for_method(method) }

      self.class.event_handler_procs.each {|method, proc| add_implicit_handler_for_method(method)}
    end

    # Instance-level version of Monkeybars::Controller.define_handler.  It follows the same
    # syntax as the class-level version but applies the callback block as a listener to events
    # generated by this instance of the controller class' view.  This callback is
    # useful when the application has nested controllers and event handling needs to be different
    # for each instance of a controller class.
    #
    # define_handler can accept either a single event or a list of events to apply the block to:
    # 
    # define_handler :ok_button_action_performed { puts "action performed on 'ok button'" }
    # 
    # define_handler :ok_button_action_performed, :cancel_button_action_performed { puts "action performed on a button" }
    #
    # If you are defining a handler that requires aliasing, define handler can also be passed a hash of method => component mappings
    # mixed in with the methods to apply the handler to.
    # 
    # define_handler :text_field_insert_update => "text_field.document" { puts "you typed something" }
    # 
    # define_handler :text_field_insert_update => "text_field.document", :text_field_remove_update => "text_field.document" { puts "you typed or deleted something" }
    #
    # These mappings can also be mixed in with regular methods.  It is suggested that you put
    # all of your hash items at the end of the argument list so they are wrapped up into an
    # implicit Hash object although this is not strictly necessary.
    # 
    # define_handler :ok_button_action_performed, :text_field_insert_update => "text_field.document" { puts "you did ... something" }
    #    
    def define_handler(*actions, &block)
      # define_handler :foo_action_performed => :foo_document_action_performed,  { handle event here }
      actions.each do |action|
        if action.kind_of? Hash
          # handle a hash with multiple mappings, e.g.
          # define_handler :text_field_insert_update => "text_field.document", :text_field2_insert_update => "text_field2.document { ... handler code here ... }
          action.each do |method, component|
            @__event_handler_procs[method.to_sym] << block
            add_implicit_handler_for_method(method, component)
          end
        else
          @__event_handler_procs[action.to_sym] << block
          add_implicit_handler_for_method(action)
        end
      end
    end

    # Specific handlers get precedence over general handlers, that is button_mouse_released
    # gets called before mouse_released.
    def handle_event(component_name, event_name, event) #:nodoc:
      return if event.nil?

      callbacks = get_callbacks("#{component_name}_#{event_name}".to_sym)
      if callbacks.empty?
        callbacks = get_callbacks(event_name.to_sym)
      end

      callbacks.each{ |proc| 0 == proc.arity ? proc.call : proc.call(event) }
    end

  private
    def get_callbacks(method)
      callbacks = []
      begin
        callbacks << method(method)
      rescue NameError; end
      callbacks + self.class.event_handler_procs[method] + @__event_handler_procs[method]
    end

    def add_implicit_handler_for_method(method, component_to_alias = nil)
      component_match = nil
      
      Monkeybars::Handlers::ALL_EVENT_NAMES.each do |event|
        component_match = Regexp.new("(.*)_(#{event})(!?)").match(method.to_s)
        break unless component_match.nil?
      end

      return if component_match.nil?

      auto_invoke_update_view = false
      auto_invoke_update_view = true if '!' == component_match[3]
      component_name, event_name = component_match[1], component_match[2]
      
      begin
        if component_to_alias.nil?
          component = @__event_handler_view_target.instance_eval(component_name)
        else
          component = @__event_handler_view_target.instance_eval(component_to_alias)
        end
      rescue NameError => e
      rescue Monkeybars::UndefinedComponentError => e
        # swallow, handler style methods for controls that don't exist is allowed
      else
        component.methods.each do |method|
          listener_match = /add(.*)Listener/.match(method)
          next if listener_match.nil?
          if Monkeybars::Handlers::EVENT_NAMES_BY_TYPE[listener_match[1]].member? event_name
            if component_to_alias.nil?
              add_handler_for listener_match[1], component_name, component, auto_invoke_update_view
            else
              add_handler_for listener_match[1], {component_to_alias => component_name}, component, auto_invoke_update_view
            end
          end
        end
      end
    end

    def add_handler_for(handler_type, components, java_component, auto_invoke_update_view = false)
      components = ["global"] if components.nil?
      components = [components] unless components.kind_of? Array
      components.each do |component|
        # handle aliases :components => {"text_area.document" => "text_area"}
        if component.kind_of? Hash
          component_name = component.values[0]
          component_field = component.keys[0]
        else
          component_name = component
          component_field = component
        end
        unless @__registered_handlers[java_component].member? handler_type.underscore
          handler = "Monkeybars::#{handler_type.camelize}Handler".constantize.new(self, component_name.to_s, auto_invoke_update_view)
          @__event_handler_view_target.add_handler(handler, component_field)
          @__registered_handlers[java_component] << handler_type.underscore
        end
      end
    end
  end
end