##
# Renderer for HTML with callbacks

class Borges::HtmlRenderer < Borges::HtmlBuilder

  attr_accessor :action_url, :callbacks, :rendering_context

  ##
  # Creates a callback that sends +message+ to +object+ with zero
  # arguments.

  def action_callback(message, object)
    proc do object.send message end
  end

  ##
  # Creates a callback that sets +attr+ on +object+.

  def setter_callback(attr, object)
    proc do |val| object.send "#{attr}=", val end
  end

  ##
  # Creates a link with +anchor_text+ that executes a block when
  # clicked.

  def anchor(anchor_text, &block)
    open_anchor(&block)
    text(anchor_text)
    close
  end

  ##
  # Creates a link that calls +sym+ on +obj+ when clicked.  +sym+
  # is used as the title for the link.

  def anchor_on(sym, obj)
    element_id(sym)
    anchor(label_for(sym)) do obj.send(sym) end
  end

  ##
  # Creates a <select> menu of two choices with +labels+ (default
  # to Yes and No).  If +bool+ is true, the first item is the
  # default.

  def boolean_menu(bool, labels = [:Yes, :No], &block)
    select([true, false].zip(labels), bool, &block)
  end

  ##
  # Creates a menu that picks the default value by calling +sym+
  # on +obj+.

  def boolean_menu_on(sym, obj)
    element_id(sym)
    boolean_menu(obj.send(sym), &setter_callback(sym, obj))
  end

  ##
  # Creates a checkbox.  The checkbox will be initially checked
  # if +checked+ is set to true.

  def checkbox(checked = false, &block)
    @attributes[:checked] if checked

    update_key = value_input(:checkbox, 'true') do |v|
      block.call(v != 'false')
    end
      
    input(:hidden, update_key, 'false') if checked
  end

  ##
  # Creates a default action that gets called when the form is
  # submitted.

  def default_action(&block)
    input(:hidden, @callbacks.register_action_callback(&block))
  end

  ##
  # Creates a default action that sends +message+ to +object+.
  # +message+ is sent with zero arguments.

  def default_action_on(message, object)
    callback = action_callback message, object
    input :hidden, @callbacks.register_action_callback(&callback)
  end

  ##
  # Creates a link to download +object+ with the text of +text+.
  # +mime_type+ will default to text/plain.
  #
  # #to_s will be sent to +object+, and the resulting string will
  # be returned to the user.
  #
  # WARNING! A download link created by this method will remain
  # live until Borges is shut down.  The space used will not be
  # reclaimed.  If you are creating many download links, you may
  # wish to serve them from outside Borges.

  def download_link(object, text, mime_type = nil)
    url_anchor url_for_document(object, mime_type), text
  end

  ##
  # Creates a file upload field.
  #--
  # TODO: Document how the file is passed to the block

  def file_upload(&block)
    input(:file, @callbacks.register_callback(&block))
  end

  ##
  # Create a form.

  def form(&block)
    open_form
    block.call
    close
  end
  
  ##
  # Creates a link with image from img_url as content. The link 
  # executes +block+ when executed. Image has alt text of +alt+. 
  
  def image_anchor(img_url, alt='', &block)
    open_anchor(&block)
    image(img_url, alt)
    close
  end

  def initialize(rendering_context)
    @rendering_context = rendering_context

    super(rendering_context.document)

    @action_url = rendering_context.action_url
    @callbacks = rendering_context.callbacks
  end

  ##
  # Turn +sym+ into a capitalized label.

  def label_for(sym)
    label = sym.to_s
    label.gsub!('_', ' ')
    label.gsub!(/\w+/) do |w|
      w.capitalize! unless %w(of in at a or to by).include? w
      w
    end

    return label
  end

  def open_anchor(&block)
    @attributes[:href] = url_for(&block)
    open_tag(:a)
  end

  def open_form
    @attributes[:method] = 'POST'
    @attributes[:action] = @action_url
    open_tag(:form)
  end

  def open_select
    @attributes[:name] = @callbacks.register_dispatch_callback
    open_tag(:select)
  end

  ##
  # Creates an option for a <select>.  +label+ sets the label for
  # the select. The option will be selected if +selected+ is
  # true.

  def option(label, selected = false, &callback)
    @attributes[:selected] if selected
    @attributes[:value] = @callbacks.register_callback(&callback)
    
    open_tag(:option)
    text(label)
    close
  end

  ##
  # Creates a radio button that is part of radio group +group+.
  # The radio button will be checked if +checked+ is true.
  #
  # +group+ must come from a previous #radio_group:
  #
  #   group = r.radio_group
  #   r.radio_button(group) do ... end

  def radio_button(group, checked = false, &block)
    @attributes[:checked] if checked
    
    input(:radio, group, @callbacks.register_callback(&block))
  end

  ##
  # Creates a default action for a radio group.

  def radio_group
    return @callbacks.register_dispatch_callback
  end

  ##
  # Creates a <select> from +list+.
  #
  # Calls #each on +list+ and if +list+ yields a single item,
  # then #to_s will be called on the item for use as the label.
  # If +list+ yields two items, then the second item will be used
  # as a label.
  #
  # If +selected+ matches an item in +list+, that item will be
  # selected.
  #
  # +callback+ will be passed the user-selected item.

  def select(list, selected = nil, &callback)
    open_select
  
    list.each do |item, label|
      option(label || item, item == selected) do
        callback.call item
      end
    end

    close
  end

  ##
  # Creates a new document containing +css+, and returns a URL
  # for the document.

  def style(css)
    style_link(url_for_document(css, 'text/css'))
  end

  ##
  # Creates a submit button with a label of +label+, or 'Submit'
  # if not given.

  def submit_button(label = 'Submit', &block)
    @attributes[:value] = label

    if block then
      input :submit, @callbacks.register_action_callback(&block)
    else
      input :submit
    end
  end

  ##
  # Creates a submit button with a label of +sym+ that calls
  # +sym+ on +obj+ when clicked.

  def submit_button_on(sym, obj)
    element_id(sym)
    submit_button(label_for(sym)) do
      obj.send(sym)
    end
  end

  ##
  # Creates a textarea with +value+ as its contents.

  def text_area(value = "", &block)
    callback = nil

    if value.nil? then
      callback = proc do |v|
        block.call(v == '' ? nil : v)
      end
    else
      callback = block
    end

    @attributes[:name] = @callbacks.register_callback(&callback)
    open_tag :textarea
    encode_text value
    close
  end

  ##
  # Creates a <textarea> by using getter and setter methods for 
  # method +sym+ on object +obj+.
  
  def text_area_on(sym, obj)
    element_id sym
    text_area obj.send(sym), &setter_callback(sym, obj)
  end

  ##
  # Creates a text input containing +value+.

  def text_input(value = "", &callback)
    value_input(:text, value, &callback)
  end

  ##
  # Creates a text input that gets its value by calling +sym+ to
  # +obj+, then calls +sym+= to +obj+ when submitted.

  def text_input_on(sym, obj)
    element_id(sym)
    text_input(obj.send(sym), &setter_callback(sym, obj))
  end

  def url_for(&block)
    return "#{@action_url}?#{@callbacks.register_action_callback(&block)}"
  end

  def url_for_document(obj, mime_type = nil)
    return Borges::Session.current_session.application.url_for_request_handler(
      Borges::DocumentHandler.new(obj, mime_type))
  end

  def value_input(input_type, value, &block)
    callback = block

    if value.kind_of? Integer then
      callback = proc do |v|
        block.call(v.to_i)
      end

    elsif value.kind_of? Float then
      callback = proc do |v|
        block.call(v == value ? anObject : v.to_f)
      end

    elsif value.nil? then
      value = ''

      callback = proc do |v|
        block.call(v == '' ? nil : v)
      end

    end

    update_key = @callbacks.register_callback(&callback)
    input(input_type, update_key, value)
    return update_key
  end

  ##
  # Creates a password input containing +value+. It is however
  # not a good idea to output the current password to html, since
  # the user can retrieve it by looking at the source. 

  def password_input(value = "", &callback)
    value_input(:password, value, &callback)
  end

  ##
  # Creates a password input that gets its value by calling +sym+ to
  # +obj+, then calls +sym+= to +obj+ when submitted.

  def password_input_on(sym, obj)
    value = "********"
    element_id(sym)
    current_password = obj.send sym
    value = "" if current_password.nil? or current_password.empty?
    password_input value, &setter_callback(sym, obj)
  end
    

=begin

  def anchorWithAction_do(actionBlock, linkBlock)
    self.openAnchorWithAction(actionBlock)
    linkBlock.call
    self.close
  end

  def anchorWithAction_form(actionBlock, aForm)
    self.anchorWithAction_do(actionBlock,
      proc do self.imageWithForm(aForm) end)
  end

  def anchorWithDocument_mimeType_text(anObject, mimeType, aString)
    self.openAnchorWithDocument_mimeType(anObject, mimeType)
    self.text(aString)
    self.close
  end

  def anchorWithDocument_text(anObject, aString)
    self.anchorWithDocument_mimeType_text(anObject, nil, aString)
  end

  def booleanMenuWithValue_callback(aBoolean, callbackBlock)
    self.booleanMenuWithValue_callback_labels(aBoolean, callbackBlock,
      [:Yes, :No])
  end

  def checkboxOn_of(aSymbol, anObject)
    self.cssId(aSymbol)
    self.checkboxWithValue_callback(anObject.perform(aSymbol),
      self.callbackForSelector_of(aSymbol, anObject))
  end

  def hiddenInputWithValue_callback(anObject, callbackBlock)
    self.valueInputOfType_value_callback('hidden',
      anObject, callbackBlock)
  end

  def imageMapWithAction_form(aBlock, aForm)
    point = ValueHolder.new

    pointKey = @callbacks.register_callback do |ptString|
      point.contents(self.parseImageMap(ptString))
    end

    actionKey = @callbacks.register_action_callback do
      aBlock.call(point.contents)
    end

    self.attributeAt_put('href', "#{@action_url}?#{actionKey}&#{pointKey}=")

    self.tag_do('a', proc do
      self.attributeAt_put('border', 0)
      self.attributeAt_put('ismap', true)
      self.imageWithForm(aForm)
    end)
  end

  def imageWithForm(aForm)
    self.image_width_height(self.urlForDocument(aForm), aForm.width, aForm.height)
  end

  def labelledRowForCheckboxOn_of(aSymbol, anObject)
    self.tableRowWithLabel_column(self.labelForSelector(aSymbol),
      proc do self.checkboxOn_of(aSymbol, anObject) end)
  end

  def labelledRowForList_on_of(aCollection, aSymbol, anObject)
    self.tableRowWithLabel_column(self.labelForSelector(aSymbol),
      proc do self.selectFromList_selected_callback(aCollection,
        anObject.perform(aSymbol),
        self.callbackForSelector_of(aSymbol, anObject))
      end)
  end

  def labelledRowForTextAreaOn_of(aSymbol, anObject)
    self.tableRowWithLabel_column(self.labelForSelector(aSymbol),
      proc do self.textAreaOn_of(aSymbol, anObject) end)
  end

  def labelledRowForTextInputOn_of(aSymbol, anObject)
    self.labelledRowForTextInputOn_of_size(aSymbol, anObject, nil)
  end

  def labelledRowForTextInputOn_of_size(aSymbol, anObject, sizeIntegerOrNil)
    self.tableRowWithLabel_column(self.labelForSelector(aSymbol),
      proc do
        unless sizeIntegerOrNil.nil? then
          self.attributeAt_put(:size, sizeIntegerOrNil)
        end
        self.textInputOn_of(aSymbol, anObject)
      end)
  end

  def linkWithScript(jsString)
    self.scriptWithUrl(self.urlForDocument_mimeType(jsString, 'text/javascript'))
  end

  def openAnchorWithDocument_mimeType(anObject, mimeType)
    self.attributeAt_put('href',
      self.urlForDocument_mimeType(anObject, mimeType))
    self.openTag('a')
  end

  def parseImageMap(aString)
    return nil unless '?*,*'.match(aString)

    s = aString.readStream
    s.upTo(??)
    x = s.upTo(?,)
    y = s.upToEnd
    return [x.to_i, y.to_i]
  end

  def selectFromList_selected_callback(aCollection, selectedObject, callbackBlock)
    self.selectFromList_selected_callback_labels(aCollection,
      selectedObject, callbackBlock, proc do |i| i.to_s end)
  end

  def selectInputOn_of_list(selectedSymbol, anObject, aCollection)
    self.selectFromList_selected_callback(aCollection,
      anObject.perform(selectedSymbol),
      self.callbackForSelector_of(selectedSymbol, anObject))
  end

  def textInputWithCallback(callbackBlock)
    textInputWithValue_callback('', callbackBlock)
  end

=end

end

