# =XMPP4R - XMPP Library for Ruby
# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
# Website::http://xmpp4r.github.io

module Jabber
  ##
  # This class manages a list of callbacks.
  #
  # ==Callbacks management and priority
  #
  # Callbacks are managed by the class CallbackList. When they are added, a
  # priority (just a number or anything Comparable with other priorities) is
  # specified. The biggest the priority is, the earliest the callback will be
  # considered.
  #
  # Callbacks are processed for a given set of objects as long as they return
  # false. If you want to stop processing, you must return true. Example :
  #  cbl = CallbackList.new
  #  c1 = false
  #  c2 = false
  #  c3 = false
  #  cbl.add(10) { c1 = true; 1 }
  #  cbl.add(5) { c2 = true; true }
  #  cbl.add(0) { c3 = true }
  #  cbl.process('aa')
  #  puts "#{c1} #{c2} #{c3}"
  # This example would display "true true false" as callbacks processing was
  # stopped after the second callback returned true.
  #
  # In XMPP4R, callbacks' priorities are quite normalized since we want to be
  # able to "cascade" callbacks in a clean way. Here are values your code should
  # take into account :
  #
  # >= 200::  logging & debugging callbacks. Those callbacks should not consume
  #           elements.
  # 100-199:: Where Helpers register their callbacks. The normal value is 100,
  #           and Helpers shouldn't register something else unless there's a
  #           very good reason to.
  # < 100::   all those numbers are normally available for your application.
  #           That's enough, don't you think ?
  class CallbackList
    include Enumerable

    # Create a new list of callbacks
    def initialize
      @list = []
    end

    ##
    # Add a callback to the list
    #
    # List will be sorted afterwards
    #
    # prio:: [Integer] the callback's priority, the higher, the sooner.
    # ref:: [String] the callback's reference
    # block:: [Block] a block to execute
    # return:: [Jabber::CallbackList] The list, for chaining
    def add(prio = 0, ref = nil, proc = nil, &block)
      block = proc if proc
      @list.push(Callback.new(prio, ref, block))
      @list.sort! { |a, b| b.priority <=> a.priority }
      self
    end

    ##
    # Delete a callback by reference
    # ref:: [String] the reference of the callback to delete
    # return:: [CallBackList] The list, for chaining
    def delete(ref)
      @list.delete_if { |item| item.ref == ref }
      self
    end

    def each(&block)
      @list.each(&block)
    end

    ##
    # Number of elements in the list
    # return:: [Integer] The number of elements
    def length
      @list.length
    end

    ##
    # Process an element through all my callbacks. returns e.consumed?
    # e:: [Object] The elements to pass to the callback. You can pass
    # several, but of course, you block must know how to handle them.
    # return:: [Boolean] true if the element has been consumed
    def process(*e)
      # If somebody adds a new callback the list will get modified
      # and sorted(!) while still iterating through it. So we use a
      # local copy of @list. Any freshly added callback will receive
      # the next stanzas, not the current.
      list = @list.dup

      # process through callbacks
      list.each do |item|
        return true if item.block.call(*e) == true
      end
      false
    end
  end

  # This class is used to store callbacks inside CallbackList. See the
  # CallbackList class for more detailed explanations.
  class Callback

    # The Callback's priority
    attr_reader :priority

    # The Callback's reference, using for deleting purposes
    attr_reader :ref

    # The Callback's block to execute
    attr_reader :block

    ##
    # Create a new callback
    # priority:: [Integer] the callback's priority. The higher, the sooner it
    # will be executed
    # ref:: [String] The callback's reference
    def initialize(priority = 0, ref = nil, block = Proc.new {})
      @priority = priority
      @ref = ref
      @block = block
    end

    def to_s
      "#<#{[self.class, priority, ref].compact * " "}>"
    end
  end
end
