# 
# css.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.
#


class Element
  
  # @group Styling of the Element
  
  # Set the given style property +name+ to the given +value+ for +element+. 
  # Name should/can be ruby case, or css-property or camelcase. Every property
  # will be converted to the correct format within this method.
  # 
  # @param [Element] element the element to set the style on
  # @param [Symbol, String] name the style property name to set
  # @param [String, Number] value the value to set
  # @return [Element] returns +element+
  def self.css(element, name, value = false)
    # FIXME: should really check if comment or text node, and then skip if so.

    # make sure we are dealing with a string
    name = name.to_s
    # get the actual style property (IE uses the element itself)
    style = `#{element}.__element__.style || #{element}.__element__`
    # convert to camelCaseStyle
    name = `#{name}.replace(/[_-]\D/g, function(res) {
      return res.charAt(1).toUpperCase();
    });`
    
    if value == false
      # no value, so retrieve (nil is a good value: set to :none)
      `return #{style}[#{name}] || "";`
    else
      # set style property
      `return #{style}[#{name}] = #{value};`
    end
  end
  
  # 
  # @param {Hash} styles
  # 
  def css(styles = nil)
    case styles
    when nil
      @style ||= StyleDeclaration.new self
    when Hash
      styles.each { |style, value| Element.css self, style, value }
    when String, Symbol
      Element.css self, styles
    end
  end
  
  alias_method :style, :css
  
  # Checks whether the receiver has the passed in class_name
  # 
  # @example Checking for classes
  #   # Assuming the following HTML
  #   # <div id="some_id" class="first second"></div>
  #   
  #   elem = Document['some_id']
  #   
  #   elem.has_class? 'first'
  #   # => true
  # 
  #   elem.has_class? 'third'
  #   # => false
  # 
  # @param [String, Symbol] class_name the class_name to check
  # @return [true, false] if the element has the given class_name
  def has_class?(class_name)
    self.class_name.__contains__ class_name.to_s, " "
  end
  
  # Adds the given class_name to the receivers' classes, unless the class 
  # already have the class
  # 
  # @param [String, Symbol] class_name the class_name to add
  # @return [Element] returns the receiver
  def add_class(class_name)
    self.class_name += " #{class_name}" unless has_class? class_name
    self
  end
  
  # Adds each of the given class_names using {#add_class}.
  # 
  # @param [Array<String, Symbol>] class_names the list of class_names
  # @return [Element] returns the receiver
  def add_classes(*class_names)
    class_names.each do |class_name|
      add_class class_name
    end
    self
  end
  
  # Remove the given class_name from the element, if it has the class_name
  # 
  # @param [String, Symbol] class_name the class_name to remove
  # @return [Element] returns the receiver
  def remove_class(class_name)
    class_name = class_name.to_s
    `#{self}.__element__.className = #{self.class_name}.replace(new RegExp('(^|\\s)' + #{class_name} + '(?:\\s|$)'), '$1');`
    self
  end
  
  # Adds the given class_name to the receiver if it does not already have the 
  # class_name, otherwise removes it.
  # 
  # @param [String, Symbol] class_name the class_name to toggle
  # @return [Element] returns the receiver
  def toggle_class(class_name)
    class_name = class_name.to_s
    has_class?(class_name) ? remove_class(class_name) : add_class(class_name)
    self
  end
  
  # As we overwrite #class, this allows us to still access its functionality
  alias_method :__class__, :class
  
  # Set the class name. Here we do not append, just rewrite the entire class
  # name.
  # 
  # @param [String] class_name the class to set
  # @return [Elements] returns the receiver
  def class_name=(class_name)
    `#{self}.__element__.className = #{class_name}.toString();`
    self
  end
  
  alias_method `#{self}.Y('class=')`, :class_name=
  
  # Returns the CSS class name for the receiver. See {#__class__} for default
  # access.
  # 
  # @return [String] returns class name
  def class
    `return #{self}.__element__.className || "";`
  end
  
  alias_method :class_name, :class
  
  # set class names from hash
  def set_class_names(class_names)
    current = self.class_name.split ' '
    
    class_names.each do |name, flag|
      if current.include? name
        unless flag
          current.delete name 
        end
      else
        if flag
          current << name
        end
      end
    end
    
    self.class_name = current.join(" ")
  end
  
  # Returns `true` if the receiver is visible, or `false` otherwise.
  # 
  # @example HTML
  #   !!!plain
  #   <div id="foo" style="display: none;"></div>
  #   <div id="bar"></div>
  # 
  # @example Ruby
  #   Document[:foo].visible?
  #   # => false
  #   Document[:bar].visible?
  #   # => true
  # 
  # @return [Boolean]
  def visible?
    Element.css(self, 'display') != "none"
  end
  
  # Returns `true` if the receiver is hidden, `false` otherwise.
  # 
  # @example HTML
  #   !!!plain
  #   <div id="foo" style="display: none;"></div>
  #   <div id="bar"></div>
  # 
  # @example Ruby
  #   Document[:foo].hidden?
  #   # => true
  #   Document[:bar].hidden?
  #   # => false
  # 
  # @return [Boolean]
  def hidden?
    Element.css(self, 'display') == "none"
  end
  
  # Hides the receiver in the DOM using the `display = none;` property. Returns
  # the receiver for chaining.
  # 
  # @example HTML
  #   !!!plain
  #   <div id='foo'></div>
  # 
  # @example Ruby
  #   Document[:foo].hide
  #   # => #<Element div, id='foo'> (and is now hidden)
  # 
  # @return [Element] returns the receiver
  def hide
    Element.css self, :display, 'none'
    self
  end
  
  # Shows the receiver in the DOM using the `display = ''` property. Returns the
  # receiver for chaining.
  # 
  # @example HTML
  #   !!!plain
  #   <div id='foo' style='display:none'></div>
  # 
  # @example Ruby
  #   Document[:foo].show
  #   # => #<Element div, id='foo'> (and is now visible)
  # 
  # @return [Element] returns the receiver
  def show
    Element.css self, :display, ''
    self
  end
  
  # Toggles the visible state of the receiver by checking its current 
  # {#visible?} state. If currently visible, the element will become hidden, or
  # if currently hidden, the element will become visible.
  # 
  # @return [Element] returns the receiver
  def toggle
    visible? ? hide : show
    self
  end
  
  # Set the opacity of the receiver
  # 
  # @return [Element] returns the receiver
  def opacity=(opacity)
    raise "not implemented"
  end
  
  # A hash like interface for setting and retreiving CSS properties for the
  # dom element it represents.
  class StyleDeclaration
    
    # Initialize witht he given {Element} instance.
    # 
    # @param [Element] element
    def initialize(element)
      `#{self}.__element__ = #{element}.__element__;`
      `#{self}.__style__ = #{element}.__element__.style || #{element}.__element__;`
    end
    
    def [](style_name)
      Element.css self, style_name
    end
    
    def []=(style_name, value)
      Element.css self, style_name, value
    end
  end
end