include ApplicationHelper

# add a hover_intent method to element, so that you can do find(selector).hover_intent
module Capybara
  module Node
    class Element

      def attempt_hover
        begin
          hover
        rescue => e
        end
      end
      def hover_intent
        hover
        sleep 0.3
        attempt_hover
        sleep 0.3
        attempt_hover
      end

      def help_bubble
        hover_intent
      end
    end
  end
end



# holds a single test's session name's, mapped to pooled session names
$capybara_session_mapper = {}

# called in before (or after) test, to make sure each test run has it's own map of session names
def reset_session_mapper
  $capybara_session_mapper.clear

  Capybara.session_name = :default
end

# manages the mapped session name
def mapped_session_name(session_name)
  return :default if session_name == :default # special treatment for the built-in session
  $capybara_session_mapper[session_name] ||= 'session_' + $capybara_session_mapper.length.to_s
end

# in place of ever using Capybara.session_name directly,
# this utility is used to handle the mapping of session names in a way across all tests runs
def in_client(name)
  session_name = name.class == JamRuby::User ? name.id : name

  Capybara.session_name = mapped_session_name(session_name)

  yield
end


def cookie_jar
  Capybara.current_session.driver.browser.current_session.instance_variable_get(:@rack_mock_session).cookie_jar
end


#see also ruby/spec/support/utilities.rb
JAMKAZAM_TESTING_BUCKET = 'jamkazam-testing' #at least, this is the name given in jam-ruby
def wipe_s3_test_bucket
  s3 = AWS::S3.new(:access_key_id => Rails.application.config.aws_access_key_id,
                   :secret_access_key => Rails.application.config.aws_secret_access_key)
  test_bucket = s3.buckets[JAMKAZAM_TESTING_BUCKET]
  if test_bucket.name == JAMKAZAM_TESTING_BUCKET
    test_bucket.objects.each do |obj|
      obj.delete
    end
  end
end

def authorize_google_user(youtube_client, user, google_password)
  youtube_client.wait_for_callback(2112) do |access_token|
    #puts("Authorizing with token #{access_token}")
    user_auth_hash = {
      :provider => "google_login",
      :uid => user.email,
      :token => access_token,
      :token_expiration => nil,
      :secret => ""
    }
    authorization = user.user_authorizations.build(user_auth_hash)
    authorization.save
  end

  url = youtube_client.get_login_url(user.email)
  #puts("Login URL: #{url}")
  visit url
  sleep(1)

  # (the username is filled in with a hint in URL):
  # fill_in "Usernm", with: user.email
  find('#next').trigger(:click)
  sleep(5)

  # Fill in password
  fill_in "Passwd", with: google_password
  find('#signIn').trigger(:click)

  # Wait for submit to enable and then click it:
  sleep(5)
  save_screenshot("about_to_submit.png")
  find('#submit_approve_access').trigger(:click)
  sleep(5)

  youtube_client
end

def sign_in(user)
  visit signin_path
  within('#landing-inner form.signin-form') do
    fill_in "Email",    with: user.email
    fill_in "Password", with: user.password
    click_button "SIGN IN"
  end
  # Sign in when not using Capybara as well.
  cookie_jar[:remember_token] = user.remember_token
end

def set_cookie(k, v)
  case Capybara.current_session.driver
    when Capybara::Poltergeist::Driver
      page.driver.set_cookie(k,v)
    when Capybara::RackTest::Driver
      headers = {}
      Rack::Utils.set_cookie_header!(headers,k,v)
      cookie_string = headers['Set-Cookie']
      Capybara.current_session.driver.browser.set_cookie(cookie_string)
    when Capybara::Selenium::Driver
      page.driver.browser.manage.add_cookie(:name=>k, :value=>v)
    else
      raise "no cookie-setter implemented for driver #{Capybara.current_session.driver.class.name}"
  end
end

def sign_in_poltergeist(user, options = {})
  validate = options[:validate]
  validate = true if validate.nil?

  if user.password.nil? && !options[:password]
    raise "user has no password.  Use a user newly created so that it's password is still present"
  end
  uri = URI.parse(current_url)

  # in tests, we often have an issue where an old signin screen is unloading
  # as this one is loading.
  # so one way to fix this is to go to a different page in this case, and then come back to /signin

  if uri.path == signin_path
    visit '/'
    should_be_at_root
  end

  visit signin_path
  page.should have_selector('#landing-inner form.signin-form')

  within('#landing-inner form.signin-form') do
    fill_in "Email Address:",    with: user.email
    fill_in "Password:", with: options[:password] || user.password
    click_button "SIGN IN"
  end

  page.should have_no_selector('h1', text: 'sign in or register')

  wait_until_curtain_gone

  # presence of this means websocket gateway is not working
  page.should have_no_selector('.no-websocket-connection') if validate
end

# skip the typical login form, which redirects to /client (slow due to extra login step).
# So this just sets the cookie, and puts you where you want to be
def fast_signin(user, url)
  visit '/'
  set_login_cookie(user)
  visit url
end

#skip the 'hunt' for Sign Out, and redirect after.  Just empty cookie, and go to '/'
def fast_signout
  create_cookie("remember_token", "")
  visit '/'
end

def set_login_cookie(user)
  create_cookie("remember_token", user.remember_token)
end

def sign_out
  delete_cookie("remember_token")
end

def switch_user(user, url)
  sign_out_poltergeist(validate:true)
  fast_signin(user, url)
end
def sign_out_poltergeist(options = {})
  sign_out
  open_user_dropdown
  click_link 'Sign Out'
  if options[:validate]
    visit "/"
    find('#jamclass-link')
  end
end

def open_user_dropdown
  find('.userinfo').hover()
end


def go_to_root
  visit '/'
  should_be_at_root
end

def should_be_at_root(options={signed_in:nil})

  signed_in = !get_me_the_cookie('remember_token').nil?
  if signed_in
    find('h2', text: 'jamtracks')
  else
    find('a.join-today', text: 'JOIN TODAY, PLAY FREE!')
  end
end

def should_be_at_signin
  find('h1', text: 'sign in or register')
end

def should_be_at_logged_out_client
  find('#profile a.signin', text: 'Sign Up')
  find('.musicians.not-logged-in')
end

def leave_music_session_sleep_delay
  # add a buffer to ensure WSG has enough time to expire
  sleep_dur = (Rails.application.config.websocket_gateway_connect_time_stale_browser +
      Rails.application.config.websocket_gateway_connect_time_expire_browser) * 1.4
  sleep sleep_dur
end


def wait_for_ajax(wait=Capybara.default_max_wait_time)
  wait = wait * 10 #(because we sleep .1)

  counter = 0
  while page.execute_script("$.active").to_i > 0
    counter += 1
    sleep(0.1)
    raise "AJAX request took longer than #{wait} seconds." if counter >= wait
  end
end

# waits until the user object has been requested, which comes after the 'curtain' is lifted
# and after a call to /api/user/:id for the current user is called initially
def wait_until_user(wait=Capybara.default_max_wait_time)
  wait = wait * 10 #(because we sleep .1)

  counter = 0
  # while page.execute_script("$('.curtain').is(:visible)") == "true"
  #   counter += 1
  #   sleep(0.1)
  #   raise "Waiting for user to populate took longer than #{wait} seconds." if counter >= wait
  # end
end

def wait_until_curtain_gone
  page.should have_no_selector('.curtain')
end

def wait_to_see_my_track
  within('div.session-mytracks') {first('div.session-track.track')}
end

def repeat_for(duration=Capybara.default_max_wait_time)
  finish_time = Time.now + duration.seconds
  loop do
    yield
    sleep 1 # by default this will execute the block every 1 second
    break if (Time.now > finish_time)
  end
end

def determine_test_name(metadata, test_name_buffer = '')
  description = metadata[:description_args]
  if description.kind_of?(Array)
    description = description[0]
  end
  if metadata.has_key? :example_group
    return determine_test_name(metadata[:example_group], "#{description} #{test_name_buffer}")
  else
    return "#{description} #{test_name_buffer}"
  end
end

def get_description
  description = example.metadata[:description_args]
  if description.kind_of?(Array)
    description = description[0]
  end
  return description
end

# will select the value from a easydropdown'ed select element
def jk_select(text, select)
  # the approach here is to find the hidden select element, and work way back up to the elements that need to be interacted with
  find(select, :visible => false).find(:xpath, 'ancestor::div[contains(@class, "dropdown easydropdown") and not(contains(@class, "disabled"))]').trigger(:click)
  find(select, :visible => false).find(:xpath, 'ancestor::div[contains(@class, "dropdown-wrapper") and contains(@class, "easydropdown-wrapper") and contains(@class, "open") ]').find('li', text: text).trigger(:click)

  # works, but is 'cheating' because of visible = false
  #select(genre, :from => 'genres', :visible => false)
end

# takes, or creates, a unique session description which is returned for subsequent calls to join_session to use
# in finding this session)
def create_session(options={})
  creator = options[:creator] || FactoryGirl.create(:user)
  unique_session_name = options[:name] || "create_join_session #{SecureRandom.urlsafe_base64}"
  unique_session_desc = options[:description] || "create_join_session #{SecureRandom.urlsafe_base64}"
  genre = options[:genre] || 'Alternative Rock'
  musician_access = options[:musician_access].nil? ? true : options[:musician_access]
  approval_required = options[:approval_required].nil? ? true : options[:approval_required]
  fan_access = options[:fan_access].nil? ? true : options[:fan_access]
  fan_chat = options[:fan_chat].nil? ? false : options[:fan_chat]

  # create session in one client
  in_client(creator) do
    page.driver.resize(1500, 800) # makes sure all the elements are visible
    emulate_client
    fast_signin(creator, "/client#/createSession")
    expect(page).to have_selector('h1',    text: 'create session')

    within('#create-session-form') do

      # step 1
      find('li[create-type="immediately"] ins').trigger(:click)
      find('.btn-next').trigger(:click)

      # step 2
      jk_select(genre, '#create-session-form select[name="genres"]')
      fill_in('session-name', :with => unique_session_name)
      fill_in('session-description', :with => unique_session_desc)
      find('.btn-next').trigger(:click)

      # step 3
      find('.btn-next').trigger(:click)

      # step 4
      musician_access_value = "Musicians may join by approval"
      if !musician_access && !approval_required
        musician_access_value = "Only RSVP musicians may join"
      elsif musician_access && approval_required
        musician_access_value = "Musicians may join at will"
      end
      jk_select(musician_access_value, '#session-musician-access')

      fan_access_value = "Fans may listen, chat with each other"
      if !fan_access && !fan_chat
        fan_access_value = 'Fans may not listen to session'
      elsif fan_access && fan_chat
        fan_access_value = 'Fans may listen, chat with the band' #XXX this option is currently disabled/not available
      end
      jk_select(fan_access_value, '#session-fans-access')

      find('#divSessionPolicy ins').trigger(:click)
      find('.btn-next').trigger(:click)

      # step 5
      find('.btn-next').trigger(:click)
    end

    # verify that the in-session page is showing
    expect(page).to have_selector('h2',    text: 'my live tracks')
    find('#session-screen .session-my-tracks .session-track.my-track')
  end

  return creator, unique_session_desc, genre

end

def schedule_session(options = {})
  creator = options[:creator] || FactoryGirl.create(:user)
  unique_session_name = options[:name] || "schedule_session #{SecureRandom.urlsafe_base64}"
  unique_session_desc = options[:description] || "schedule_session #{SecureRandom.urlsafe_base64}"
  genre = options[:genre] || 'Alternative Rock'
  musician_access = options[:musician_access].nil? ? true : options[:musician_access]
  approval_required = options[:approval_required].nil? ? true : options[:approval_required]
  fan_access = options[:fan_access].nil? ? true : options[:fan_access]
  fan_chat = options[:fan_chat].nil? ? false : options[:fan_chat]
  musician_access_value = 'Musicians may join by approval'
  fan_permission_value = 'Fans may listen, chat with each other'
  rsvp = options[:rsvp]
  immediate = options[:immediate]
  quickstart = options[:quickstart]

  if musician_access && !approval_required
    musician_access_value = 'Musicians may join at will'
  elsif !musician_access && !approval_required
    musician_access_value = 'Only RSVP musicians may join'
  end

  if fan_access && fan_chat
    fan_permission_value = 'Fans may listen, chat with the band'
  elsif !fan_access && !fan_chat
    fan_permission_value = 'Fans may not listen to session'
  end

  # schedule a session in one client
  in_client(creator) do
    page.driver.resize(1500, 800) # makes sure all the elements are visible
    emulate_client
    fast_signin(creator, "/client#/createSession")
    expect(page).to have_selector('h1',    text: 'sessions')

    within('#create-session-form') do
      if rsvp
        find('li[create-type="rsvp"] ins').trigger(:click)
      elsif immediate
        find('li[create-type="immediately"] ins').trigger(:click)
      elsif quickstart
        find('li[create-type="quick-start"] ins').trigger(:click)
      else
        find('li[create-type="schedule-future"] ins').trigger(:click)
      end

      find('.btn-next').trigger(:click)

      unless quickstart
        jk_select(genre, '#create-session-form select[name="genres"]')
        fill_in('session-name', :with => unique_session_name)
        fill_in('session-description', :with => unique_session_desc)
        find('.btn-next').trigger(:click)

        find('.btn-next').trigger(:click)

        find('div#divSessionPolicy ins').trigger(:click)
        jk_select(musician_access_value, '#session-musician-access')
        jk_select(fan_permission_value, '#session-fans-access')
        find('.btn-next').trigger(:click)
      end

      if immediate
        find('.btn-next', text: 'START SESSION').trigger(:click)
      elsif !quickstart
        find('.btn-next', text: 'PUBLISH SESSION').trigger(:click)
      end
    end

    # find('h2', text: 'create session') unless quickstart || immediate

    sleep 1 # to get rid of this, we need to verify that the URL is /client#/home.. otherwise intermittent fails
  end

  return creator, unique_session_name, genre
end

# this code assumes that there are no music sessions in the database. it should fail on the
# find('.join-link') call if > 1 session exists because capybara will complain of multiple matches
def join_session(joiner, options)
  description = options[:description]

  in_client(joiner) do
    page.driver.resize(1500, 800) # makes sure all the elements are visible
    emulate_client
    fast_signin(joiner, "/client#/findSession")

    # verify the session description is seen by second client
    expect(page).to have_text(description)
    find('.join-link').trigger(:click)
    unless options[:no_verify]
      find('#btn-accept-terms').trigger(:click)
      expect(page).to have_selector('h2',    text: 'my live tracks')
      find('#session-screen .session-my-tracks .session-track.my-track')
    end
  end
end

def request_to_join_session(joiner, options)
  join_session(joiner, options.merge(no_verify: true))
  find('#btn-alert-ok').trigger(:click)
  # page.should have_no_selector('h1', text: 'Alert')
end

def emulate_client
  page.driver.headers = { 'User-Agent' => ' JamKazam ' }
end

def create_join_session(creator, joiners=[], options={})
  options[:creator] = creator
  creator, unique_session_desc = create_session(options)

  # find session in second client
  [*joiners].each do |joiner|
    join_session(joiner, description: unique_session_desc)
  end

  return creator, unique_session_desc
end

def formal_leave_by user
  in_client(user) do
    find('.session-leave.leave').trigger(:click)
    #find('#btn-accept-leave-session').trigger(:click)
    expect(page).to have_selector('h2',    text: 'feed')
  end
end

def verify_feed_shows_users *users
  users = [*users]
  visit "/client#/feed"
  find('.feed-details a.details').trigger(:click)
  within 'div.music-session-history-entry' do
    users.each do |user|
      # confirm user avatar exists
      find("a.avatar-tiny[user-id=\"#{user.id}\"][hoveraction=\"musician\"] img")

      # confirm user name exists
      find("a.musician-name[user-id=\"#{user.id}\"][hoveraction=\"musician\"]", text: user.name)
    end
  end
end

def start_recording_with(creator, joiners=[], genre=nil)

  create_join_session(creator, joiners, {genre: genre})
  in_client(creator) do

    find('.session-record').trigger(:click)

    # gotta wait for popup
    sleep 1

    page.within_window(->{ page.title == 'JamKazam | Recording Controls' }) do
      find('#recording-status', text: 'Start Recording')
      find('a.control').trigger(:click)
      find('#recording-status', text: 'Stop Recording')
    end
  end

  joiners.each do |joiner|
    # gotta wait for popup
    sleep 1

    in_client(joiner) do
      find('#notification').should have_content 'started a recording'
      find('.session-record[data-is-recording="true"]')
    end
  end
end

def stop_recording
  find('.session-record[data-is-recording="true"]')
  # gotta wait for popup
  sleep 1

  page.within_window(->{ page.title == 'JamKazam | Recording Controls' }) do
    find('#recording-status', text: 'Stop Recording')
    find('a.control').trigger(:click)
  end

end

def assert_recording_finished
  find('.session-record[data-is-recording="false"]', visible: false)
  should have_selector('h1', text: 'recording finished')
end

def check_recording_finished_for(users=[])
  users.each do |user|
    in_client(user) do
      assert_recording_finished
    end
  end
end

def claim_recording(name, description)
  find('#recording-finished-dialog h1')
  fill_in "claim-recording-name",    with: name
  fill_in "claim-recording-description",    with: description
  jk_select('Alternative Rock', '#recording-finished-dialog .genre-selector select')
  find('#keep-session-recording').trigger(:click)
  page.should have_no_selector('h1',  text: 'recording finished')
end

def set_session_access access_type
  case access_type
    when :only_rsvp, :private
      access_level = "Only RSVP musicians may join"
    when :by_approval
      access_level = "Musicians may join by approval"
    when :at_will, :public, :open
      access_level = "Musicians may join at will"
    else
      access_level = "Musicians may join at will"
  end

  find('.session-settings').trigger(:click)
  within('#session-settings-dialog') do
    jk_select(access_level, '#session-settings-dialog #session-settings-musician-access')
    find('#session-settings-dialog-submit').trigger(:click)
  end
  # verify it's dismissed
  page.should have_no_selector('h1', text: 'update session settings')
end

def get_options(selector)
  find(selector, :visible => false).all('option', :visible => false).collect(&:text).uniq
end

def selected_genres(selector='#session-settings-genre')
  page.evaluate_script("JK.GenreSelectorHelper.getSelectedGenres('#{selector}')")
end

def random_genre
  ['African',
   'Ambient',
   'Asian',
   'Blues',
   'Classical',
   'Country',
   'Electronic',
   'Folk',
   'Hip Hop',
   'Jazz',
   'Latin',
   'Metal',
   'Pop',
   'R&B',
   'Reggae',
   'Religious',
   'Alternative Rock',
   'Ska',
   'Other'].sample
end

def change_session_genre   #randomly just change it
  here = 'select.genre-list'
  #wait_for_ajax
  find('.session-settings').trigger(:click)
  find('#session-settings-dialog') # ensure the dialog is visible
      within('#session-settings-dialog') do
        wait_for_ajax
        @new_genre = get_options(here).-(["Select Genre", "Unspecified"]).-(selected_genres).sample.to_s
        jk_select(@new_genre, '#session-settings-dialog select[name="genres"]')
        wait_for_ajax
        find('#session-settings-dialog-submit').trigger(:click)
      end
  return @new_genre
end

def get_session_genre
  here = 'select.genre-list'
  find('.session-settings').trigger(:click)
  wait_for_ajax
  @current_genres = selected_genres
  find('#session-settings-dialog-submit').trigger(:click)
  return @current_genres.join(" ")
end

def find_session_contains?(text)
    visit "/client#/findSession"
    wait_for_ajax
    within('#find-session-form') do
      expect(page).to have_text(text)
    end
end

def assert_all_tracks_seen(users=[])
  users.each do |user|
    in_client(user) do
      users.reject {|u| u==user}.each do |other|
        find(".session-track", text: other.name)
        #puts user.name + " is able to see " + other.name + "\'s track"
      end
    end
  end
end

def view_profile_of user
  id = user.kind_of?(JamRuby::User) ? user.id : user
  # assume some user signed in already
  visit "/client#/profile/#{id}"
  wait_until_curtain_gone
end

def view_band_profile_of band
  id = band.kind_of?(JamRuby::Band) ? band.id :
      band.kind_of?(JamRuby::BandMusician) ? band.bands.first.id : band
  visit "/client#/bandProfile/#{id}"
  wait_until_curtain_gone
end

def sidebar_search_for string, category
  visit "/client#/home"
  find('#search-input')
  fill_in "search", with: string
  sleep 1
  page.execute_script("JK.Sidebar.searchForInput()")
  wait_for_ajax
  jk_select(category, "search_text_type")
  wait_for_ajax
end

def expand_sidebar header_name
  #search, friends, chat, notifications
  panel_id = "panel#{header_name.to_s.capitalize}"
  within("div[layout-id='#{panel_id}']") { find('div.panel-header').trigger(:click) }
end

def show_user_menu
  page.execute_script("$('ul.shortcuts').show()")
  #page.execute_script("JK.UserDropdown.menuHoverIn()")
end

# wait for the easydropdown version of the specified select element to become visible
def wait_for_easydropdown(select)
  find(select, :visible => false).find(:xpath, 'ancestor::div[contains(@class, "dropdown easydropdown")]')
end

# defaults to enter key (13)
def send_key(selector, keycode = 13)
  keypress_script = "var e = $.Event('keyup', { keyCode: #{keycode} }); jQuery('#{selector}').trigger(e);"
  page.driver.execute_script(keypress_script)
end

def send_key_sequence(selector, text)
  text.each_char do |char|
    keycode = char.ord - 32
    keypress_script = "var e = $.Event('keydown', { keyCode: #{keycode} }); jQuery('#{selector}').trigger(e);"
    page.driver.execute_script(keypress_script)
  end

end

def send_keydown(selector, keycode = 13)
  keypress_script = "var e = $.Event('keydown', { keyCode: #{keycode} }); jQuery('#{selector}').trigger(e);"
  page.driver.execute_script(keypress_script)
end


def special_characters
  ["?", "[", "]", "/", "\\", "=", "<", ">", ":", ";", ",", "'", "\"", "&", "$", "#", "*", "(", ")", "|", "~", "`", "!", "{", "}"]
end

def garbage length
  output = ''
  length.times { output << special_characters.sample }
  output.slice(0, length)
end

def nav_profile_history(user)
  visit Nav.profile(user)
  find('#history-link').trigger(:click)
end