module PageObject
  module Elements
    #
    # Contains functionality that is common across all elements.
    #
    # @see PageObject::Platforms::WatirWebDriver::Element for the Watir version of all common methods
    # @see PageObject::Platforms::SeleniumWebDriver::Element for the Selenium version of all common methods
    #
    class Element
      include Object::PageObject::NestedElements
      
      attr_reader :element

      def initialize(element, platform)
        @element = element
        include_platform_for platform
      end

      #
      # click the element
      #
      def click
        @element.click
      end

      #
      # double click the element
      #
      def double_click
        @element.double_click
      end

      #
      # return true if the element is enabled
      #
      def enabled?
        @element.enabled?
      end
      
      #
      # get the value of the given CSS property
      #
      def style(property)
        @element.style property
      end
      
      def inspect
        @element.inspect
      end

      # @private
      def self.watir_identifier_for identifier
        if should_build_watir_xpath(identifier)
          how = :xpath
          what = build_xpath_for(identifier)
          return how => what
        end
        all_identities = {}
        identifier.each do |key, value|
          each = {key => value}
          ident = identifier_for each, watir_finders, watir_mapping
          all_identities[ident.keys.first] = ident.values.first
        end
        all_identities
      end

      # @private
      def self.selenium_identifier_for identifier
        if identifier.length == 1
          identifier = identifier_for identifier, selenium_finders, selenium_mapping
          return identifier.keys.first, identifier.values.first
        elsif identifier.length > 1
          how = :xpath
          what = build_xpath_for identifier
          return how, what
        end
      end
      
      # @private
      # delegate calls to driver element
      def method_missing(*args, &block)
        m = args.shift
        puts "*** DEPRECATION WARNING"
        puts "*** You are calling a method named #{m}."
        puts "*** This method does not exist in page-object so it is being passed to the driver."
        puts "*** This feature will be removed in the near future."
        puts "*** Please change your code to call the correct page-object method."
        puts "*** If you are using functionality that does not exist in page-object please request it be added."
        begin
          element.send m, *args, &block
        rescue Exception => e
          raise
        end
      end

      protected

      def self.should_build_watir_xpath identifier
        ['table', 'span', 'div', 'td', 'li', 'ul', 'ol', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p'].include? identifier[:tag_name] and identifier[:name]
      end

      def self.build_xpath_for identifier
        tag_locator = identifier.delete(:tag_name)
        idx = identifier.delete(:index)
        if tag_locator == 'input' and identifier[:type] == 'submit'
          identifier.delete(:type)
          btn_ident = identifier.clone
          if btn_ident[:value]
            btn_ident[:text] = btn_ident[:value]
            btn_ident.delete(:value)
          end
          xpath = ".//button"
          xpath << "[#{attribute_expression(btn_ident)}]" unless btn_ident.empty?
          xpath << "[#{idx+1}]" if idx
          identifier[:type] = %w[button reset submit image]
          xpath << " | .//input"
        else
          xpath = ".//#{tag_locator}"
        end
        xpath << "[#{attribute_expression(identifier)}]" unless identifier.empty?
        xpath << "[#{idx+1}]" if idx
        xpath
      end

      def self.attribute_expression(identifier)
        identifier.map do |key, value|
          if value.kind_of?(Array)
            "(" + value.map { |v| equal_pair(key, v) }.join(" or ") + ")"
          else
            equal_pair(key, value)
          end
        end.join(" and ")
      end

      def self.equal_pair(key, value)
        if key == :label
          "@id=//label[normalize-space()=#{xpath_string(value)}]/@for"
        else
          "#{lhs_for(key)}=#{xpath_string(value)}"
        end
      end

      def self.lhs_for(key)
        case key
          when :text, 'text'
            'normalize-space()'
          when :href
            'normalize-space(@href)'
          else
            "@#{key.to_s.gsub("_", "-")}"
        end
      end

      def self.xpath_string(value)
        if value.include? "'"
          parts = value.split("'", -1).map { |part| "'#{part}'" }
          string = parts.join(%{,"'",})
          "concat(#{string})"
        else
          "'#{value}'"
        end
      end

      def self.identifier_for identifier, find_by, find_by_mapping
        how, what = identifier.keys.first, identifier.values.first
        return how => what if find_by.include? how
        return find_by_mapping[how] => what if find_by_mapping[how]
        return nil => what
      end

      def self.watir_finders
        [:class, :id, :index, :name, :xpath]
      end

      def self.watir_mapping
        {}
      end

      def self.selenium_finders
        [:class, :id, :index, :name, :xpath]
      end

      def self.selenium_mapping
        {}
      end

      def include_platform_for platform
        if platform[:platform] == :watir_webdriver
          require 'page-object/platforms/watir_webdriver/element'
          require 'page-object/platforms/watir_webdriver/page_object'
          self.class.send :include,  ::PageObject::Platforms::WatirWebDriver::Element
          @platform = ::PageObject::Platforms::WatirWebDriver::PageObject.new(@element)
        elsif platform[:platform] == :selenium_webdriver
          require 'page-object/platforms/selenium_webdriver/element'
          require 'page-object/platforms/selenium_webdriver/page_object'
          self.class.send :include, ::PageObject::Platforms::SeleniumWebDriver::Element
          @platform = ::PageObject::Platforms::SeleniumWebDriver::PageObject.new(@element)
        else
          raise ArgumentError, "expect platform to be :watir_webdriver or :selenium_webdriver"
        end
      end
    end
  end
end