package com.codeborne.selenide;

import org.openqa.selenium.*;
import org.openqa.selenium.WebDriver.TargetLocator;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

import static com.codeborne.selenide.Selenide.Wait;
import static com.codeborne.selenide.WebDriverRunner.getWebDriver;
import static org.openqa.selenium.support.ui.ExpectedConditions.alertIsPresent;
import static org.openqa.selenium.support.ui.ExpectedConditions.frameToBeAvailableAndSwitchToIt;

public class SelenideTargetLocator implements TargetLocator {
  private final TargetLocator delegate;

  SelenideTargetLocator(TargetLocator delegate) {
    this.delegate = delegate;
  }

  @Override
  public WebDriver frame(int index) {
    return Wait().until(frameToBeAvailableAndSwitchToIt(index));
  }

  @Override
  public WebDriver frame(String nameOrId) {
    return Wait().until(frameToBeAvailableAndSwitchToIt(nameOrId));
  }

  @Override
  public WebDriver frame(WebElement frameElement) {
    return waitForFrame(frameElement);
  }

  protected WebDriver waitForFrame(WebElement element) {
    return Wait().until(frameToBeAvailableAndSwitchToIt(element));
  }

  @Override
  public WebDriver parentFrame() {
    return delegate.parentFrame();
  }

  @Override
  public WebDriver defaultContent() {
    return delegate.defaultContent();
  }

  @Override
  public WebElement activeElement() {
    return delegate.activeElement();
  }

  @Override
  public Alert alert() {
    return Wait().until(alertIsPresent());
  }

  /**
   * Switch to the inner frame (last child frame in given sequence)
   */
  public WebDriver innerFrame(String... frames) {
    WebDriver driver = getWebDriver();
    delegate.defaultContent();

    for (String frame : frames) {
      try {
        String selector = String.format("frame#%1$s,frame[name=%1$s],iframe#%1$s,iframe[name=%1$s]", frame);
        waitForFrame(driver.findElement(By.cssSelector(selector)));
      }
      catch (NoSuchElementException | TimeoutException e) {
        throw new NoSuchFrameException("No frame found with id/name = " + frame, e);
      }
    }

    return driver;
  }

  /**
   * Switch to window/tab by index
   * NB! Order of windows/tabs can be different in different browsers, see Selenide tests.
   * @param index index of window (0-based)
   */
  public WebDriver window(int index) {
    WebDriver driver = getWebDriver();
    List<String> windowHandles = new ArrayList<>(driver.getWindowHandles());
    delegate.window(windowHandles.get(index));
    return driver;
  }

  /**
   * Switch to window/tab by name/handle/title
   * @param nameOrHandleOrTitle name or handle or title of window/tab
   */
  @Override
  public WebDriver window(String nameOrHandleOrTitle) {
    try {
      return delegate.window(nameOrHandleOrTitle);
    }
    catch (NoSuchWindowException windowWithNameOrTitleNotFound) {
      return windowExceptHandles(nameOrHandleOrTitle);
    }
  }

  /**
   * Switch to window/tab by name/handle/title except some windows handles
   * @param nameOrHandleOrTitle name or handle or title of window/tab
   * @param exceptHandles window handles that should be ignored
   */
  protected WebDriver windowExceptHandles(String nameOrHandleOrTitle, String... exceptHandles) {
    WebDriver driver = getWebDriver();
    
    Set<String> windowHandles = driver.getWindowHandles();
    windowHandles.removeAll(Arrays.asList(exceptHandles));
    
    for (String windowHandle : windowHandles) {
      driver.switchTo().window(windowHandle);
      if (nameOrHandleOrTitle.equals(driver.getTitle())) {
        return driver;
      }
    }
    throw new NoSuchWindowException("Window with id/name/title not found: " + nameOrHandleOrTitle);
  }
}

@@ -107,16 +107,73 @@
 Locator;
+%0Aimport org.openqa.selenium.support.ui.ExpectedCondition;
 %0A%0Aimport
@@ -1067,109 +1067,8 @@
 ) %7B%0A
-    return waitForFrame(frameElement);%0A  %7D%0A%0A  protected WebDriver waitForFrame(WebElement element) %7B%0A
     
@@ -1119,17 +1119,22 @@
 tchToIt(
-e
+frameE
 lement))
@@ -1865,39 +1865,58 @@
     
-waitForFrame(driver.findElement
+Wait().until(frameToBeAvailableAndSwitchToIt_fixed
 (By.
@@ -2108,16 +2108,16 @@
 %0A    %7D%0A%0A
-
     retu
@@ -2124,32 +2124,604 @@
 rn driver;%0A  %7D%0A%0A
+  private static ExpectedCondition%3CWebDriver%3E frameToBeAvailableAndSwitchToIt_fixed(final By locator) %7B%0A    return new ExpectedCondition%3CWebDriver%3E() %7B%0A      @Override%0A      public WebDriver apply(WebDriver driver) %7B%0A        try %7B%0A          return driver.switchTo().frame(driver.findElement(locator));%0A        %7D catch (NoSuchFrameException e) %7B%0A          return null;%0A        %7D catch (WebDriverException e) %7B%0A          return null;%0A        %7D%0A      %7D%0A%0A      @Override%0A      public String toString() %7B%0A        return %22frame to be available: %22 + locator;%0A      %7D%0A    %7D;%0A  %7D%0A%0A
   /**%0A   * Switc

fix ExpectedCondition.frameToBeAvailableAndSwitchToIt(): it failed if element is not found yet