require 'druid/accessors'
require 'druid/assist'
require 'druid/page_factory'
require 'druid/core_ext/string'
require 'druid/element_locators'
require 'druid/page_populator'

# require 'watir-webdriver/extensions/alerts'
#
# Module that when included adds functionality to a page object.  This module
# will add numerous class and instance methods that you use to define and
# interact with web pages.
#
# If we have a login page with a username and password textfield and a login
# button we might define our page like the one below.  We can then interact with
# the object using the generated methods.
#
# @example Login page example
#   class LoginPage
#     include Druid
#
#     text_field(:username, :id => 'user')
#     text_field(:password, :id => 'pass')
#     button(:login, :value => 'Login')
#   end
#
#   ...
#
#   browser = Watir::Browser.new :firefox
#   login_page = LoginPage.new(browser)
#   login_page.username = 'tim'
#   login_page.password = 'sheng'
#   login_page.login
#
# @see Druid::Accessors to see what class level methods are added to this module at runtime.
#
module Druid
  include Assist
  include ElementLocators
  include PagePopulator
  # include Watir::AlertHelper
  # @return [Watir::Browser] the drvier passed to the constructor
  attr_reader :driver
  #
  # Construct a new druid. Upon initialization of the page it will call a method named
  # initialize_page if it exists
  #
  # @param [Watir::Browser] the driver to use
  # @param [bool] open the page if page_url is set
  #
  def initialize(driver, visit=false)
    if driver.is_a? Watir::Browser
      @driver ||= driver
      initialize_page if respond_to?(:initialize_page)
      goto if visit && respond_to?(:goto)
    else
      raise ArgumentError, "expect Watir::Browser"
    end
  end

  # @private
  def self.included(cls)
    cls.extend Druid::Accessors
  end

  #
  # navigate to the provided url
  #
  # @param [String] the full url to navigate to
  #
  def navigate_to url
    driver.goto url
  end

  #
  # get the current page url
  #
  def current_url
    driver.url
  end

  #
  # Returns the text of the current page
  #
  def text
    driver.text
  end

  #
  # Returns the html of the current page
  #
  def html
    driver.html
  end

  #
  # Returns the title of the current page
  #
  def title
    driver.title
  end

  #
  # Refresh current page
  #
  def refresh
    driver.refresh
  end

  #
  # Go back to the previous page
  #
  def back
    driver.back
  end

  #
  # Go forward to the next page
  #
  def forward
    driver.forward
  end
  #
  # Wait until the block returns true or times out
  #
  # @example
  #   @page.wait_until(5, 'Success not found on page') do
  #     @page.text.include? 'Success'
  #   end
  #
  # @param [Numeric] the amount of time to wait for the block to return true
  # @param [String] the message to include with the error if we exceed the timeout duration
  # @param block the block to execute. It should return true when successful.
  #
  def wait_until(timeout = 30, message = nil, &block)
    driver.wait_until(timeout, message, &block)
  end

  #
  # Override the normal alert popup so it does not occurr.
  #
  # @example
  #   message = @page.alert do
  #     @page.button_that_causes_alert
  #   end
  #
  # @param block a block that has the call that will cause the alert to display
  # @return [String] the message that was contained in the alert
  #
  def alert(&block)
    # switch_to_frame(frame)
    yield
    value = nil
    if driver.alert.exists?
      value = driver.alert.text
      driver.alert.ok
    end
    # switch_to_default_content(frame)
    value
  end

  #
  # Override the normal confirm popup so it does not occurr
  #
  # @example
  #   message = @popup.confirm(true) do
  #     @page.button_that_causes_confirm
  #   end
  #
  # @param [boolean] what response you want to return back from the confirm popup
  # @param block a block that has the call that will cause the confirm to display
  # @return [String] the message that was contained in the confirm
  #
  def confirm(response, &block)
    yield
    value = nil
    if driver.alert.exists?
      value = driver.alert.text
      response ? driver.alert.ok : driver.alert.close
    end
    value
  end

  #
  # Override the normal prompt popup so it does not occurr
  #
  # @example
  #   message = @popup.prompt("Some Value") do
  #     @page.button_that_causes_prompt
  #   end
  #
  # @param [String] the value will be setted in the prompt field
  # @param block a block that has the call that will cause the prompt to display
  # @return [String] the message that was contained in the prompt
  #
  def prompt(answer, &block)
    yield
    value = nil
    if driver.alert.exists?
      value = driver.alert.text
      driver.alert.set answer
      driver.alert.ok
    end
    value
  end

  #
  # Attach to a running window. You can locate the window using either
  # the window's title or url or index, If it failes to connect to a window it will
  # pause for 1 second and try again.
  #
  # @example
  #   @page.attach_to_window(:title => "other window's title")
  #
  # @param [Hash] either :title or :url or index of the other window. The url does not need to
  # be the entire url - it can just be the page name like index.html
  #
  def attach_to_window(identifier, &block)
    if identifier.keys.first == :url
      win_id = {identifier.keys.first => /#{Regexp.escape(identifier.values.first)}/}
    else
      win_id = {identifier.keys.first => identifier.values.first}
    end
    begin
      driver.window(win_id).use &block
    rescue
      sleep 1
      driver.window(win_id).use &block
    end
  end

  #
  # Override the normal showModalDialog call is it opens a window instead of a dialog.
  # You will need to attach to the new window in order to continue.
  #
  # @example
  #   @page.modal_dialog do
  #     @page.action_that_spawns_the_modal
  #   end
  #
  # @param block a block that contains the call that will cause the modal dialog.
  #
  def modal_dialog(&block)
    script =
    %Q{
      window.showModalDialog = function(sURL, vArguments, sFeatures) {
        window.dialogArguments = vArguments;
        modalWin = window.open(sURL, 'modal', sFeatures);
        return modalWin;
      }
    }
    driver.execute_script script
    yield if block_given?
  end

  #
  # Clear the cookies from the browser
  #
  def clear_cookies
    driver.clear_cookies
  end

  #
  # Save the current screenshot to the provided path. File is saved as a png file.

  def save_screenshot(file_name)
    driver.screenshot.save(file_name)
  end

  #
  # Identify an element as existing within a frame or iframe. A frame parameter is
  # passed to the block and must be passed to the other calls to Druid.
  # You can nest calls to in_frame by passing the frame to the next level.
  #
  # @example
  #  @page.in_frame(:id => 'frame_id') do |frame|
  #    @page.text_field_element(:id=> 'fname', :frame => frame)
  #  end
  #
  # @param [Hash] identifier how we find the frame. The valid keys are:
  #    * :id
  #    * :index
  #    * :name
  # @param block that contains the calls to elements that exist inside the frame.
  #
  def in_frame(identifier, frame=nil, &block)
    frame = [] if frame.nil?
    frame << {frame: identifier}
    block.call(frame)
  end

  #
  # Identify an element as existing within a frame or iframe. A frame parameter is
  # passed to the block and must be passed to the other calls to Druid.
  # You can nest calls to in_frame by passing the frame to the next level.
  #
  # @example
  #  @page.in_iframe(:id => 'frame_id') do |frame|
  #    @page.text_field_element(:id=> 'fname', :frame => frame)
  #  end
  #
  # @param [Hash] identifier how we find the frame. The valid keys are:
  #    * :id
  #    * :index
  #    * :name
  # @param block that contains the calls to elements that exist inside the frame.
  #
  def in_iframe(identifier, frame=nil, &block)
    frame = [] if frame.nil?
    frame << {iframe: identifier}
    block.call(frame)
  end

  # def switch_to_frame(frame_identifiers)
  #   unless frame_identifiers.nil?
  #     frame_identifiers.each do |frame|
  #       frame_id = frame.values.first
  #       value = frame_id.values.first
  #       driver.wd.switch_to.frame(value)
  #     end
  #   end
  # end

  def call_block(&block)
    block.arity == 1 ? block.call(self) : self.instance_eval(&block)
  end

end