##
# Exception to be raised if the application writer makes a boo-boo.

class Borges::CallbackNotFound < RuntimeError; end

##
# Borges::CallbackStore holds callbacks for processing.
# Callbacks are called in HTML order.  Only one ActionCallback
# is called.

class Borges::CallbackStore

  ##
  # Sets +action_callback+ as the ActionCallback to run when form
  # processing is finished.

  def action(&action_callback)
    @action = action_callback
  end

  ##
  # Run a +callback+, passing it +obj+.  Raises
  # Borges::CallbackNotFound if the application writer made a
  # boo-boo, like not specifying a radio_group for radio_buttons.

  def evaluate(key, input)
    if @callbacks[key].nil? then
      raise Borges::CallbackNotFound,
            "No callback registered for key #{key.inspect}" 
    else
      @callbacks[key].evaluate input, self
    end
  end

  ##
  # Creates a new Borges::CallbackStore

  def initialize
    @callbacks = []
  end

  ##
  # Processes the form fields from +req+, calling all appropriate
  # callbacks, then returns the result of an action callback, if
  # any.

  def process_request(req)
    @action = nil

    sort_request(req.fields).each do |key, val|
      evaluate key, val
    end

    unless @action.nil? then
      return @action.call
    else
      return nil
    end
  end

  ##
  # Registers a Borges::ActionCallback that will run +block+.

  def register_action_callback(&block)
    store Borges::ActionCallback.new(&block)
  end

  ##
  # Registers a Borges::ValueCallback that will run +block+.
  # ValueCallbacks may take an argument.

  def register_callback(&block)
    store Borges::ValueCallback.new(&block)
  end

  ##
  # Registers a Borges::DispatchCallback that will act as a
  # placeholder for a ValueCallback.

  def register_dispatch_callback
    store Borges::DispatchCallback.new
  end

  ##
  # Sorts the +fields+ of the request to be processed.
  # +fields+ must be an Enumerable that yields two values.

  def sort_request(fields)
    sorted = []

    unless fields.nil? then
      fields.each do |k, v|
        unless k.to_i.nil? then
          sorted << [k.to_i, value_for_field(v)]
        end
      end
    end

    sorted.sort_by do |k, v| k end
  end

  ##
  # Stores +callback+ in the CallbackStore, and returns its index.

  def store(callback)
    @callbacks << callback
    @callbacks.length - 1
  end

  ##
  # Converts +field+ into an appropriate String representation.

  def value_for_field(field)
    if field.nil? then
      ''
    elsif field.respond_to?(:first) && field.respond_to?('empty?'.intern) then
      field.empty? ? '' : field.first
    else
      field
    end
  end

end # class Borges::CallbackStore

