require 'watir-webdriver/atoms'

module PageObject
  module Platforms
    #
    # Selenium implementation of the common functionality found across all elements
    #
    module SeleniumWebDriver
      module Element
        include Watir::Atoms

        #
        # return true if an element is visible
        #
        def visible?
          element.displayed?
        end

        #
        # return true if an element exists
        #
        def exists?
          not element.nil?
        end

        #
        # flash the element by temporarily changing the background color
        #
        def flash
          original_color = attribute('backgroundColor')
          the_bridge = bridge
          10.times do |n|
            color = (n % 2 == 0) ? 'red' : original_color
            the_bridge.executeScript("arguments[0].style.backgroundColor = '#{color}'", element)
          end
        end
        
        #
        # Get the text for the element
        #
        # @return [String]
        #
        def text
          element.text
        end

        #
        # Get the html for the element
        #
        # @return [String]
        #
        def html
          script = "return (%s).apply(null, arguments)" % ATOMS.fetch(:getOuterHtml)
          bridge.executeScript(script, element).strip
        end

        #
        # Get the value of this element
        #
        # @return [String]
        #
        def value
          element.attribute('value')
        end

        #
        # compare this element to another to determine if they are equal
        #
        def ==(other)
          element == other.element
        end

        #
        # Get the tag name of this element
        #
        # @return [String]
        #
        def tag_name
          element.tag_name
        end

        #
        # Get the value of a the given attribute of the element.  Will
        # return the current value, even if this has been modified
        # after the page has been loaded. More exactly, this method
        # will  return the value of the given attribute, unless that
        # attribute is not present, in which case the value of the
        # property with the same name is returned. If neither value is
        # set, nil is returned. The "style" attribute is converted as
        # best can be to a text representation with a trailing
        # semi-colon. The following are deemed to be "boolean"
        # attributes, and will return either "true" or "false":
        #
        # async, autofocus, autoplay, checked, compact, complete,
        # controls, declare, defaultchecked, defaultselected, defer,
        # disabled, draggable, ended, formnovalidate, hidden, indeterminate,
        # iscontenteditable, ismap, itemscope, loop, multiple, muted,
        # nohref, noresize, noshade, novalidate, nowrap, open, paused,
        # pubdate, readonly, required, reversed, scoped, seamless, seeking,
        # selected, spellcheck, truespeed, willvalidate
        #
        # Finally, the following commonly mis-capitalized
        # attribute/property names are evaluated as expected:
        #
        # class, readonly
        #
        # @param [String]
        #   attribute name
        # @return [String,nil]
        #   attribute value
        #
        def attribute(attribute_name)
          element.attribute attribute_name
        end

        #
        # Fire the provided event on the current element
        #
        def fire_event(event_name)
          event_name = event_name.to_s.sub(/^on/, '').downcase
          script = "return (%s).apply(null, arguments)" % ATOMS.fetch(:fireEvent)
          bridge.executeScript(script, element, event_name)
        end

        #
        # hover over the element
        #
        def hover
          mouse = Selenium::WebDriver::Mouse.new(bridge)
          mouse.move_to(element)
        end

        #
        # hover over the element
        #
        def double_click
          mouse = Selenium::WebDriver::Mouse.new(bridge)
          mouse.double_click(element)
        end

        #
        # find the parent element
        #
        def parent
          script = "return (%s).apply(null, arguments)" % ATOMS.fetch(:getParentElement)
          parent = bridge.executeScript(script, element)
          type = element.attribute(:type).to_s.downcase if parent.tag_name.to_sym == :input
          cls = ::PageObject::Elements.element_class_for(parent.tag_name, type)
          cls.new(parent, :platform => :selenium_webdriver)
        end

        #
        # Set the focus to the current element
        #
        def focus
          bridge.executeScript("return arguments[0].focus()", element)
        end

        #
        # Click this element
        #
        def right_click
          element.context_click
        end

        #
        # Waits until the element is present
        #
        # @param [Integer] (defaults to: 5) seconds to wait before timing out
        #
        def when_present(timeout=::PageObject.default_element_wait)
          wait = Object::Selenium::WebDriver::Wait.new({:timeout => timeout, :message => "Element not present in #{timeout} seconds"})
          wait.until do
            self.exists?
          end
          self
        end

        #
        # Waits until the element is not present
        #
        # @param [Integer] (defaults to: 5) seconds to wait before
        # timing out
        #
        def when_not_present(timeout=::PageObject.default_element_wait)
          wait = Object::Selenium::WebDriver::Wait.new({:timeout => timeout, :message => "Element still present in #{timeout} seconds"})
          wait.until do
            not_present = false
            begin
              not_present = false if element and element.displayed?
            rescue Selenium::WebDriver::Error::ObsoleteElementError
              not_present = true
            end
            not_present
          end
        end

        #
        # Waits until the element is visible
        #
        # @param [Integer] (defaults to: 5) seconds to wait before timing out
        #
        def when_visible(timeout=::PageObject.default_element_wait)
          wait = Object::Selenium::WebDriver::Wait.new({:timeout => timeout, :message => "Element not visible in #{timeout} seconds"})
          wait.until do
            self.visible?
          end
          self
        end

        #
        # Waits until the element is not visible
        #
        # @param [Integer] (defaults to: 5) seconds to wait before timing out
        #
        def when_not_visible(timeout=::PageObject.default_element_wait)
          wait = Object::Selenium::WebDriver::Wait.new({:timeout => timeout, :message => "Element still visible in #{timeout} seconds"})
          wait.until do
            not self.visible?
          end
          self
        end

        #
        # Waits until the block returns true
        #
        # @param [Integer] (defaults to: 5) seconds to wait before timing out
        # @param [String] the message to display if the event timeouts
        # @param the block to execute when the event occurrs
        #
        def wait_until(timeout=::PageObject.default_element_wait, message=nil, &block)
          wait = Object::Selenium::WebDriver::Wait.new({:timeout => timeout, :message => message})
          wait.until &block
        end
        
        #
        # Send keystrokes to this element
        #
        # @param [String, Symbol, Array]
        #
        # Examples:
        #
        #     element.send_keys "foo"                     #=> value: 'foo'
        #     element.send_keys "tet", :arrow_left, "s"   #=> value: 'test'
        #     element.send_keys [:control, 'a'], :space   #=> value: ' '
        #
        # @see Selenium::WebDriver::Keys::KEYS
        #
        def send_keys(*args)
          element.send_keys(*args)
        end
         
        #
        # clear the contents of the element
        #
        def clear
          element.clear
        end

        #
        # get the id of the element
        #
        def id
          attribute(:id)
        end

        private

        def bridge
          bridge = element.instance_variable_get(:@bridge)
        end
      end
    end
  end
end