require 'watir/exceptions'

module Watir

class TimeKeeper
  attr_reader :sleep_time
  def initialize 
    @sleep_time = 0.0
  end
  def sleep seconds
    @sleep_time += Kernel.sleep seconds    
  end
  def now
    Time.now
  end
end

class Waiter
  # This is an interface to a TimeKeeper which proxies
  # calls to "sleep" and "Time.now".
  # Useful for unit testing Waiter.
  attr_accessor :timer

  # How long to wait between each iteration through the wait_until
  # loop. In seconds.
  attr_accessor :polling_interval

  # Timeout for wait_until.
  attr_accessor :timeout
  
  @@default_polling_interval = 0.5
  @@default_timeout = 60.0

  def initialize(timeout=@@default_timeout,
                 polling_interval=@@default_polling_interval)
    @timeout = timeout
    @polling_interval = polling_interval
    @timer = TimeKeeper.new
  end

  # Execute the provided block until either (1) it returns true, or
  # (2) the timeout (in seconds) has been reached. If the timeout is reached,
  # a TimeOutException will be raised. The block will always
  # execute at least once.  
  # 
  # waiter = Waiter.new(5)
  # waiter.wait_until {puts 'hello'}
  # 
  # This code will print out "hello" for five seconds, and then raise a 
  # Watir::TimeOutException.
  def wait_until # block
    start_time = now
    until yield do
      if (duration = now - start_time) > @timeout
        raise Watir::Exception::TimeOutException.new(duration, @timeout),
          "Timed out after #{duration} seconds."
      end
      sleep @polling_interval
    end
  end  

  # Execute the provided block until either (1) it returns true, or
  # (2) the timeout (in seconds) has been reached. If the timeout is reached,
  # a TimeOutException will be raised. The block will always
  # execute at least once.  
  # 
  # Waiter.wait_until(5) {puts 'hello'}
  # 
  # This code will print out "hello" for five seconds, and then raise a 
  # Watir::TimeOutException.  

  # IDEA: wait_until: remove defaults from Waiter.wait_until
  def self.wait_until(timeout=@@default_timeout,
                      polling_interval=@@default_polling_interval)
    waiter = new(timeout, polling_interval)
    waiter.wait_until { yield }
  end
     
  private
  def sleep seconds
    @timer.sleep seconds
  end
  def now
    @timer.now
  end
end  
    
end # module