require "webrat/core/save_and_open_page"
require "webrat/selenium/selenium_rc_server"
require "webrat/selenium/application_server_factory"
require "webrat/selenium/application_servers/base"

begin
  require "selenium/client"
rescue LoadError => e
  e.message << " (You may need to install the selenium-webdriver gem)"
  raise e
end

module Webrat
  class TimeoutError < WebratError
  end

  class SeleniumResponse
    attr_reader :body
    attr_reader :session

    def initialize(session, body)
      @session = session
      @body = body
    end

    def selenium
      session.selenium
    end
  end

  class SeleniumSession
    include Webrat::SaveAndOpenPage
    include Webrat::Selenium::SilenceStream

    def initialize(*args) # :nodoc:
    end

    def simulate
    end

    def automate
      yield
    end

    def visit(url)
      selenium.open(url)
    end

    webrat_deprecate :visits, :visit

    def fill_in(field_identifier, options)
      locator = "webrat=#{field_identifier}"
      selenium.wait_for_element locator, :timeout_in_seconds => 5
      selenium.type(locator, "#{options[:with]}")
    end

    webrat_deprecate :fills_in, :fill_in

    def response
      SeleniumResponse.new(self, response_body)
    end

    def response_body #:nodoc:
      selenium.get_html_source
    end

    def current_url
      selenium.location
    end

    def click_button(button_text_or_regexp = nil, options = {})
      if button_text_or_regexp.is_a?(Hash) && options == {}
        pattern, options = nil, button_text_or_regexp
      elsif button_text_or_regexp
        pattern = adjust_if_regexp(button_text_or_regexp)
      end
      pattern ||= '*'
      locator = "button=#{pattern}"

      selenium.wait_for_element locator, :timeout_in_seconds => 5
      selenium.click locator
    end

    webrat_deprecate :clicks_button, :click_button

    def click_link(link_text_or_regexp, options = {})
      if link_text_or_regexp.is_a?(Regexp)
        pattern = "evalregex:#{link_text_or_regexp.inspect}"
      else
        pattern = link_text_or_regexp.to_s
      end

      locator = "webratlink=#{pattern}"
      selenium.wait_for_element locator, :timeout_in_seconds => 5
      selenium.click locator
    end

    webrat_deprecate :clicks_link, :click_link

    def click_link_within(selector, link_text, options = {})
      locator = "webratlinkwithin=#{selector}|#{link_text}"
      selenium.wait_for_element locator, :timeout_in_seconds => 5
      selenium.click locator
    end

    webrat_deprecate :clicks_link_within, :click_link_within

    def select(option_text, options = {})
      id_or_name_or_label = options[:from]

      if id_or_name_or_label
        select_locator = "webrat=#{id_or_name_or_label}"
      else
        select_locator = "webratselectwithoption=#{option_text}"
      end

      selenium.wait_for_element select_locator, :timeout_in_seconds => 5
      selenium.select(select_locator, option_text)
    end

    webrat_deprecate :selects, :select

    def choose(label_text)
      locator = "webrat=#{label_text}"
      selenium.wait_for_element locator, :timeout_in_seconds => 5
      selenium.click locator
    end

    webrat_deprecate :chooses, :choose

    def check(label_text)
      locator = "webrat=#{label_text}"
      selenium.wait_for_element locator, :timeout_in_seconds => 5
      selenium.click locator
    end
    alias_method :uncheck, :check

    webrat_deprecate :checks, :check

    def fire_event(field_identifier, event)
      locator = "webrat=#{Regexp.escape(field_identifier)}"
      selenium.fire_event(locator, "#{event}")
    end

    def key_down(field_identifier, key_code)
      locator = "webrat=#{Regexp.escape(field_identifier)}"
      selenium.key_down(locator, key_code)
    end

    def key_up(field_identifier, key_code)
      locator = "webrat=#{Regexp.escape(field_identifier)}"
      selenium.key_up(locator, key_code)
    end

    def wait_for(params={})
      timeout = params[:timeout] || 5
      message = params[:message] || "Timeout exceeded"

      begin_time = Time.now

      while (Time.now - begin_time) < timeout
        value = nil

        begin
          value = yield
        rescue Exception => e
          unless is_ignorable_wait_for_exception?(e)
            raise e
          end
        end

        return value if value

        sleep 0.25
      end

      error_message = "#{message} (after #{timeout} sec)"
      
      if $browser && Webrat.configuration.selenium_verbose_output
        error_message += <<-EOS


HTML of the page was:

#{selenium.get_html_source}"
EOS
      end

      raise Webrat::TimeoutError.new(error_message)
      true
    end

    def selenium
      return $browser if $browser
      setup
      $browser
    end

    webrat_deprecate :browser, :selenium


    def save_and_open_screengrab
      return unless File.exist?(Webrat.configuration.saved_pages_dir)

      filename = "#{Webrat.configuration.saved_pages_dir}/webrat-#{Time.now.to_i}.png"

      if $browser.chrome_backend?
        $browser.capture_entire_page_screenshot(filename, '')
      else
        $browser.capture_screenshot(filename)
      end
      open_in_browser(filename)

    end

    protected
    def is_ignorable_wait_for_exception?(exception) #:nodoc:
      if defined?(::Spec::Expectations::ExpectationNotMetError)
        return true if exception.class == ::Spec::Expectations::ExpectationNotMetError
      end
      return true if [::Selenium::CommandError, Webrat::WebratError].include?(exception.class)
      return false
    end

    def setup #:nodoc:
      Webrat::Selenium::SeleniumRCServer.boot
      Webrat::Selenium::ApplicationServerFactory.app_server_instance.boot

      create_browser
      $browser.start

      extend_selenium
      define_location_strategies
      $browser.window_maximize
    end


    def create_browser
      $browser = ::Selenium::Client::Driver.new(
        Webrat.configuration.selenium_server_address || "localhost",
        Webrat.configuration.selenium_server_port,
        Webrat.configuration.selenium_browser_key,
        "http://#{Webrat.configuration.application_address}:#{Webrat.configuration.application_port_for_selenium}"
      )
      $browser.set_speed(0) unless Webrat.configuration.selenium_server_address

      at_exit do
        silence_stream(STDOUT) do
          $browser.stop
        end
      end
    end

    def adjust_if_regexp(text_or_regexp) #:nodoc:
      if text_or_regexp.is_a?(Regexp)
        "evalregex:#{text_or_regexp.inspect}"
      else
        "evalregex:/#{text_or_regexp}/"
      end
    end

    def extend_selenium #:nodoc:
      extensions_file = File.join(File.dirname(__FILE__), "selenium_extensions.js")
      extenions_js = File.read(extensions_file)
      selenium.get_eval(extenions_js)
    end

    def define_location_strategies #:nodoc:
      Dir[File.join(File.dirname(__FILE__), "location_strategy_javascript", "*.js")].sort.each do |file|
        strategy_js = File.read(file)
        strategy_name = File.basename(file, '.js')
        selenium.add_location_strategy(strategy_name, strategy_js)
      end
    end
  end
end
