# 
# element.rb
# vienna
# 
# Created by Adam Beynon.
# Copyright 2010 Adam Beynon.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
  
# Represents a DOM element in the browser.
# 
# ## Implementation Details
# 
# Native Elements are not extended due to cross browser issues. Instead, 
# instances of this class will have an instance property '__element__' which
# is the native javascript element. Extensions to this class should access
# the element in this way for modification etc. In future, this class will
# cache some information on the element, such as class name etc in an aim to
# speed up performance by reducing hits to the DOM.
class Element
  
  # @group Managing Elements in the Document
  
  # Creates a new element of the specified type.
  # 
  # @param [Symbol, String] type the tag name for the +Element+ to have
  # @param [Hash] options a set of options given to {#set}
  # @return [Element] returns the new element
  def initialize(type, options = {})
    `if (!#{options}) { #{options} = vnH()}`
    # puts "creating new element #{type}"
    `#{self}.__element__ = document.createElement(#{type.to_s});`
    set options
  end
  
  # Return an instance with the passed native element as the instance's own
  # element. This is used to return the body element, for instance
  # 
  #   Element.from_native(..some native element pointer..)
  #   # => element
  # 
  # @note The given element MUST be a Javascript element, not an Opal one.
  # 
  # @param [Native Element] element
  # @return [Element]
  # 
  def self.from_native(native_element)
    #`console.log("loogking up for "  + #{native_element});`
    `if(!#{native_element}) return #{nil};`
    element = allocate
    `#{element}.__element__ = #{native_element};`
    element
  end
  
  # Return the body element for the document. The body element is an instance
  # of this class, with some singleton methods defined, such as inspect so
  # that it gets some nicer inspect properties.
  # 
  # @return [Element] body element
  # 
  def self.body
    return @body_element if @body_element
    
    @body_element = from_native(`document.body`)
    # def @body_element.inspect
      # "#<Element body>"
    # end
    
    @body_element
  end
  
  # Find the DOM selector in the given context. Context should always be 
  # given. 
  # 
  #     Element.find_in_context('.first', Element.body)
  # 
  # @param [String|Synbol] selector
  # @param [Element] context
  # @return [Array]
  # 
  def self.find_in_context(selector, context)
    selector = `'#' + #{selector.to_s}` if selector.is_a? Symbol
    elements = `Sizzle(#{selector}, #{context}.__element__);`
    # if elements.length == 1
      # `return #{Element}.$from_native(#{elements}[0]);`
    # else
      # `console.log(#{elements});`
      # raise "need to handle find_in_context array"
    # end
    
    elements.map do |e|
      from_native e
    end
  end
  
  def find(selector)
    self.class.find_in_context selector, self
  end
  
  # Returns the tag name of the Element as a symbol, in lower case.
  # 
  # @example HTML
  #     <div id="my_div"></div>
  # 
  # @example Ruby
  #     Document[:my_div].tag
  #     # => :div
  # 
  # @return [Symbol] tag name of the element
  def tag
    @tag ||= `#{self}.Y(#{self}.__element__.tagName.toLowerCase())`
  end
  
  # Sets the innerHTML of the receiver.
  #
  # @example HTML
  #     <div id="foo"></foo>
  # 
  # @example Ruby
  #     Document[:foo].html = "<div></div>"
  # 
  # @example Result
  #     <div id="foo">
  #       <div></div>
  #     </div>
  # 
  # @param [String] html the html string to set
  # @return [Elements] returns the receiver
  def html=(html)
    `#{self}.__element__.innerHTML = #{html};`
    self
  end
  
  # Returns the text content of the receiver.
  # 
  # @example HTML
  #     <div id="foo">bar</div>
  # 
  # @example Ruby
  #     Document[:foo].text   # => "bar"
  # 
  # @return [String] the receivers text content
  def text
    `var e = #{self}.__element__;
    return e.innerText == null ? e.textContent : e.innerText;`
  end
  
  # Set the inner text content of the receiver.
  # 
  # @example HTML
  #     <div id="foo"></div>
  # 
  # @example Ruby
  #     Document[:foo].text = "bar"
  # 
  # @example Result
  #     <div id="foo">bar</div>
  # 
  # @param [String] text the text content to set
  # @return [Element] returns the receiver
  def text=(text)
    `var e = #{self}.__element__;
    if (e.textContent !== undefined) {
      e.textContent = #{text}.toString();
    }
    else {
      e.innerText = #{text}.toString();
    }`
    self
  end
  
  alias_method :content=, :text=
  
  # Set the id of the receiver
  # 
  # @example HTML
  #     <div class="foo"></div>
  # 
  # @example Ruby
  #     Document['.foo'].first.id = "bar"
  # 
  # @example Result
  #     <div class="foo" id="bar"></div>
  # 
  # @param [String, Symbol] id the id to set on the element
  # @return [Element] returns the receiver
  def id=(id)
    `#{self}.__element__.id = #{id.to_s};`
    self
  end
  
  # Returns the id of the receiver as a +Symbol+, or +nil+ if no id is defined.
  # 
  # @example HTML
  #     <div id="foo" class="bar"><div>
  #     <div class="baz"</div>
  # 
  # @example Ruby
  #     Document['.bar'].first.id     # => :foo
  #     Document['.baz'].first.id     # => nil
  # 
  # @return [Symbol, nil] returns the id of the receiver
  def id
    `return #{self}.__element__.id || #{nil};`
  end
  
  # Returns +true+ if the receiver is the body element, +false+ otherwise
  # 
  # @example Testing Elements
  #     Document[:foo].body?    # => false
  #     Document.body.body?     # => true
  # 
  # @return [true, false] whether the receiver is the body element
  def body?
    false
  end
  
  def inspect
    description = ["#<Element #{tag}"]
    description << " class_name='#{class_name}'" unless class_name == ""
    description << " id='#{id}'" unless id == ""
    description << ">"
    description.join ""
  end
    
  # Set some options on the element
  # 
  # @example
  #   element.set :class_name => "adam", :id => "beynon"
  # 
  # ## Valid keys:
  # 
  # - class / class_name
  # - id
  # - text /content
  # - html
  # 
  # @param [Hash] options to set
  # @return [Element] returns the receiver
  def set(options)
    options.each do |key, value|
      __send__ "#{key}=", value
    end
  end
  
  # =========================================
  # = Inserting/appending/removing children =
  # =========================================
  
  # Insert the given element in location :bottom
  # 
  # @param [Element] element the element to insert
  # @return [Element] returns the receiver
  def <<(element)
    append element
  end
  
  # Insert the given +element+ at the bottom of the receiver
  # 
  # @param [Element] element the element to append
  # @return [Element] returns the receiver
  def append(element)
    `#{self}.__element__.appendChild(#{element}.__element__);`
    self
  end
  
  # Insert the given +element+ before the receiver
  # 
  # @param [Element] element the element to insert
  # @return [Element] returns the receiver
  def before(element)
    `var parent = #{self}.__element__.parentNode;
    if (parent) {
      parent.insertBefore(#{element}.__element__, #{self}.__element__);
    }`
    self
  end
  
  # Insert the given +element+ after the receiver, but before the next sibling
  # 
  # @param [Element] element the element to insert
  # @return [Element] returns the receiver
  def after(element)
    `var parent = #{self}.__element__.parentNode;
    if (parent) {
      parent.insertBefore(#{element}.__element__, #{self}.__element__.nextSibling);
    }`
    self
  end
  
  
  
  # Removes the receiver from the DOM, and then returns it.
  # 
  # @example HTML
  #   !!!plain
  #   <div id="foo"></div>
  #   <div id="bar"></div>
  # 
  # @example Ruby
  #   Document[:foo].dispose
  # 
  # @example Result
  #   !!!plain
  #   <div id="bar"></div>
  # 
  # @return [Element] returns the receiver.
  def remove
    `var e = #{self}.__element__;
    if (e.parentNode) {
      e.parentNode.removeChild(e);
    }`
    self
  end
  
  # Removes all children from the receiver and then removes the receiver itself
  # from the DOM tree
  # 
  # @return [nil] returns nil
  def destroy
    # FIXME: we do not do this properly.
    remove
  end
  
  
  # Returns `true` if the receiver is empty (contains only whitespace), `false`
  # otherwise.
  # 
  # @example HTML
  #   !!!plain
  #   <div id="foo"></div>
  #   <div id="bar"><p></p></div>
  #   <div id="baz">   </baz>
  # 
  # @example Ruby
  #   Document[:foo].empty?
  #   # => true
  #   Document[:bar].empty?
  #   # => false
  #   Document[:baz].empty?
  #   # => true
  # 
  # @return [Boolean]
  def empty?
    `return /^\s*$/.test(#{self}.__element__.innerHTML) ? #{true} : #{false};`
  end
  
  # Removes all child elements from the receiver, and then returns self.
  # 
  # @example HTML
  #   !!!plain
  #   <div id="foo">
  #     <span class="bar"></span>
  #     <div class="baz"></div>
  #   </div>
  # 
  # @example Ruby
  #   Document[:foo].empty
  #   # => #<Element div, id='foo'>
  # 
  # @example Result
  #   !!!plain
  #   <div id="foo"></div>
  # 
  # @return [Element] returns receiver 
  def clear
    `var e = #{self}.__element__;
    for (var children = e.childNodes, i = children.length; i > 0;) {
      var child = children[--i];
      if (child.parentNode) {
        child.parentNode.removeChild(child);
      }
    }`
    self
  end
  
  # Return the graphics context of the receiver, which will be an instance of
  # [CanvasContext] or a subclass (for VML browsers). Everytime this method is
  # called, the native 'getContext' method is applied for complaince with its
  # side effects (clearing context etc)
  # 
  # @return [CanvasContext] the canvas context for the receiver
  def context
    CanvasContext.new self
  end

  
  # ========================
  # = Parent, children etc =
  # ========================
  
  
  # Get the parent of the receiver
  # 
  # @return [Element] the parent
  def parent(selector=nil)
    Document.traverse self, 'parentNode', nil, false
  end
  
  def parents(selector=nil)
    Document.traverse self, 'parentNode', nil, true
  end
  
  def next(selector=nil)
    Document.traverse self, 'nextSibling', nil, false
  end
  
  def prev(selector=nil)
    Document.traverse self, 'previousSibling', nil, false
  end
  
  def first(selector=nil)
    Document.traverse self, 'firstChild', nil, false
  end
  
  def last(selector=nil)
    Document.traverse self, 'lastChild', nil, false
  end
  
  
  # Append the element. If string passed, then add as html content
  # 
  def <<(elem)
    append elem
    self
  end
  
  # Returns the offset of the element, taking into account the parents, scroll
  # values, etc etc. needs improving, not 100% for all browsers
  # 
  # @return [Hash] :left/:top => Number
  # 
  def element_offset
    left = 0
    top = 0
    
    `var element = #{self}.__element__;
    var parent = element;
    while (parent) {
      #{left} += parent.offsetLeft;
      #{top} += parent.offsetTop;
      parent = parent.offsetParent;
    }
    `
    Point.new left, top
  end
  
  # All valid html tags. These are looped over so each element instance has
  # an instance method of the same name to construct an element of that type
  # with some given options
  VALID_HTML_TAGS = [
    :html, :head, :title, :base, :meta, :link, :style, :script, :body, :div,
    :dl, :dt, :dd, :span, :pre
  ]
  
  VALID_HTML_TAGS.each do |tag_name|
    define_method(tag_name) do |options|
      # options are the options to se
      e = Element.new tag_name, options
      # now add to self
      self << e
      # return new element (for chaining)
      e
    end
  end
end

require 'browser/element/attributes'
require 'browser/element/css'
require 'browser/element/form'