content: /*
   * Copyright (c) 2014 3 Round Stones Inc., Some Rights Reserved
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   *
   */
  package org.callimachusproject.webdriver.helpers;
  
  import static org.junit.Assert.assertTrue;
  
  import java.io.File;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.concurrent.TimeUnit;
  
  import org.callimachusproject.engine.model.TermFactory;
  import org.openqa.selenium.Alert;
  import org.openqa.selenium.By;
  import org.openqa.selenium.ElementNotVisibleException;
  import org.openqa.selenium.Keys;
  import org.openqa.selenium.NoAlertPresentException;
  import org.openqa.selenium.NoSuchFrameException;
  import org.openqa.selenium.StaleElementReferenceException;
  import org.openqa.selenium.WebDriver;
  import org.openqa.selenium.WebDriverException;
  import org.openqa.selenium.WebElement;
  import org.openqa.selenium.interactions.Actions;
  import org.openqa.selenium.interactions.MoveTargetOutOfBoundsException;
  import org.openqa.selenium.remote.RemoteWebDriver;
  import org.openqa.selenium.support.ui.ExpectedCondition;
  import org.openqa.selenium.support.ui.ExpectedConditions;
  import org.openqa.selenium.support.ui.Wait;
  import org.openqa.selenium.support.ui.WebDriverWait;
  
  public class WebBrowserDriver {
  	public static final int IMPLICITLY_WAIT = 60;
  	public RemoteWebDriver driver;
  
  	public WebBrowserDriver(RemoteWebDriver driver) {
  		this.driver = driver;
  	}
  
  	public RemoteWebDriver getRemoteWebDriver() {
  		return driver;
  	}
  
  	public String toString() {
  		return driver.toString();
  	}
  
  	public void quit() {
  		driver.quit();
  	}
  
  	public void navigateTo(String ref) {
  		String url = TermFactory.newInstance(driver.getCurrentUrl()).resolve(ref);
  		driver.navigate().to(url);
  	    waitForScript();
  	}
  
  	public void navigateBack() {
  		driver.navigate().back();
  		waitForScript();
  	}
  	
  	public void reload() {
  		driver.navigate().refresh();
  	}
  
  	public String getCurrentUrl() {
  		return driver.getCurrentUrl();
  	}
  
  	public void innerHTML(By locator, String html) {
  		String script = "arguments[0].innerHTML=arguments[1]";
  		driver.executeScript(script, driver.findElement(locator));
  	}
  
  	public void appendHTML(By locator, String html) {
  		String script = "arguments[0].innerHTML+=arguments[1]";
  		driver.executeScript(script, driver.findElement(locator), html);
  	}
  
  	public void setFieldValue(By locator, String value) {
  		String script = "arguments[0].value=arguments[1]";
  		driver.executeScript(script, driver.findElement(locator), value);
  	}
  
  	public void setStyle(By locator, String prop, String value) {
  		String script = "arguments[0].style[arguments[1]]=arguments[2]";
  		driver.executeScript(script, driver.findElement(locator), prop, value);
  	}
  
  	public void focusInTopWindow() {
  		driver.switchTo().window(driver.getWindowHandle());
  		waitForScript();
  	}
  
  	public void focusInFrame(String... frameNames) {
  		driver.switchTo().window(driver.getWindowHandle());
  		for (final String frameName : frameNames) {
  			if (frameName != null) {
  				driver.switchTo().frame(driver.findElement(By.name(frameName)));
  			}
  		}
  		waitForScript();
  	}
  
  	public void focusInFrameIndex(int... frames) {
  		driver.switchTo().window(driver.getWindowHandle());
  		for (final int frame : frames) {
  			new WebDriverWait(driver, 60)
  					.until(new ExpectedCondition<WebDriver>() {
  						public WebDriver apply(WebDriver driver) {
  							try {
  								return driver.switchTo().frame(frame);
  							} catch (NoSuchFrameException e) {
  								return null;
  							}
  						}
  
  						public String toString() {
  							return "frame index " + frame + " to be present";
  						}
  					});
  		}
  		waitForScript();
  	}
  
  	public void focusInModalFrame(String... frameNames) {
  		if (frameNames.length < 2) {
  			driver.switchTo().window(driver.getWindowHandle());
  		} else {
  			String[] parent = new String[frameNames.length - 1];
  			System.arraycopy(frameNames, 0, parent, 0, parent.length);
  			focusInFrame(parent);
  		}
  		waitUntilModalOpen();
  		focusInFrame(frameNames);
  	}
  
  	public void waitForFrameToClose(final String frameName) {
  		driver.switchTo().window(driver.getWindowHandle());
  		if (frameName != null) {
  			try {
  				driver.manage().timeouts().implicitlyWait(0, TimeUnit.SECONDS);
  				new WebDriverWait(driver, 60)
  						.until(new ExpectedCondition<Boolean>() {
  							public Boolean apply(WebDriver driver) {
  								return driver.findElements(By.name(frameName))
  										.isEmpty();
  							}
  
  							public String toString() {
  								return "frame " + frameName + " to be absent";
  							}
  						});
  			} finally {
  				driver.manage().timeouts().implicitlyWait(IMPLICITLY_WAIT, TimeUnit.SECONDS);
  			}
  		}
  		waitForScript();
  	}
  
  	public void click(By locator) {
  		waitForScript();
  		WebElement element = driver.findElement(locator);
  		try {
  			new Actions(driver).moveToElement(element).build().perform();
  			element.click();
  		} catch (MoveTargetOutOfBoundsException e) {
  			// firefox can't scroll to reveal element
  			driver.executeScript("arguments[0].click()", element);
  		} catch (ElementNotVisibleException e) {
  			// firefox can't scroll to reveal element
  			driver.executeScript("arguments[0].click()", element);
  		} catch (WebDriverException e) {
  			// chrome redrew the screen and lost the element
  			driver.executeScript("arguments[0].click()", element);
  		}
  	}
  
  	public void submit(By locator) {
  		waitForScript();
  		driver.findElement(locator).submit();
  	}
  
  	public void type(By locator, String text) {
  		waitForScript();
  		WebElement element = driver.findElement(locator);
  		element.clear();
  		sendKeys(element, text);
  		// tab to the next element to fire onchange event
  		sendKeys(element, Keys.TAB);
  	}
  
  	public void confirm(String msg) {
  		new WebDriverWait(driver, 120).ignoring(NoAlertPresentException.class)
  				.until(ExpectedConditions.alertIsPresent());
  		Alert alert = driver.switchTo().alert();
  		assertTrue(alert.getText().contains(msg));
  	    alert.accept();
  	    waitForScript();
  	}
  
  	public void clickHiddenLink(final String cssSelector) {
  		Wait<WebDriver> wait = new WebDriverWait(driver, 120);
  		Boolean present = wait.until(new ExpectedCondition<Boolean>() {
  			public Boolean apply(WebDriver wd) {
  				String js = "return document.querySelector(arguments[0]) && true || false";
  				try {
  					return (Boolean) driver.executeScript(js, cssSelector);
  				} catch (WebDriverException e) {
  					return null;
  				}
  			}
  
  			public String toString() {
  				return "hidden " + cssSelector + " to load";
  			}
  		});
  		assertTrue(present);
  		driver.executeScript("document.querySelector(arguments[0]).click()",
  				cssSelector);
  	}
  	
  	public void select(By locator, String value) {
  		List<WebElement> optionList = driver.findElement(locator).findElements(By.tagName("option"));
  		for (WebElement option : optionList) {
  			if (value.equalsIgnoreCase(option.getText())){
  				option.click();
  				break;
  			}
  		}
  	}
  
  	public void sendFileName(By locator, File file) {
  		driver.findElement(locator).sendKeys(file.getAbsolutePath());
  	}
  
  	public void sendKeys(CharSequence... keys) {
  		sendKeys(driver.switchTo().activeElement(), keys);
  	}
  
  	public void sendKeys(By locator, CharSequence... keys) {
  		sendKeys(driver.findElement(locator), keys);
  	}
  
  	private void sendKeys(WebElement element, CharSequence... keys) {
  		StringBuilder sb = new StringBuilder();
  		List<CharSequence> list = new ArrayList<CharSequence>(keys.length * 2);
  		for (CharSequence key : keys) {
  			if (key instanceof String) {
  				for (char chr : ((String) key).toCharArray()) {
  					switch (chr) {
  					case '-':
  						sendKeys(element, list, sb);
  						list.add(Keys.SUBTRACT);
  						break;
  					case '!':
  						sendKeys(element, list, sb);
  						list.add(Keys.chord(Keys.SHIFT, "1"));
  						break;
  					case '@':
  						sendKeys(element, list, sb);
  						list.add(Keys.chord(Keys.SHIFT, "2"));
  						break;
  					case '#':
  						sendKeys(element, list, sb);
  						list.add(Keys.chord(Keys.SHIFT, "3"));
  						break;
  					case '$':
  						sendKeys(element, list, sb);
  						list.add(Keys.chord(Keys.SHIFT, "4"));
  						break;
  					case '%':
  						sendKeys(element, list, sb);
  						list.add(Keys.chord(Keys.SHIFT, "5"));
  						break;
  					case '^':
  						sendKeys(element, list, sb);
  						list.add(Keys.chord(Keys.SHIFT, "6"));
  						break;
  					case '&':
  						sendKeys(element, list, sb);
  						list.add(Keys.chord(Keys.SHIFT, "7"));
  						break;
  					case '*':
  						sendKeys(element, list, sb);
  						list.add(Keys.chord(Keys.SHIFT, "8"));
  						break;
  					case '(':
  						sendKeys(element, list, sb);
  						list.add(Keys.chord(Keys.SHIFT, "9"));
  						break;
  					case ')':
  						sendKeys(element, list, sb);
  						list.add(Keys.chord(Keys.SHIFT, "0"));
  						break;
  					case '{':
  					case '}':
  					case '<':
  					case '>':
  					case '[':
  					case ']':
  					case '"':
  					case '\'':
  					case '\n':
  						sendKeys(element, list, sb);
  						sb.append(chr);
  						sendKeys(element, list, sb);
  						break;
  					default:
  						sb.append(chr);
  					}
  				}
  			} else {
  				list.add(key);
  			}
  		}
  		sendKeys(element, list, sb);
  	}
  
  	private void sendKeys(WebElement element, List<CharSequence> keys,
  			StringBuilder words) {
  		if (words.length() > 0) {
  			keys.add(words.toString());
  			words.setLength(0);
  		}
  		if (!keys.isEmpty()) {
  			element.sendKeys(keys.toArray(new CharSequence[keys.size()]));
  			keys.clear();
  		}
  	}
  
  	public void waitUntilElementPresent(final By locator) {
  		waitForScript();
  		new WebDriverWait(driver, 10).until(ExpectedConditions
  				.presenceOfElementLocated(locator));
  	}
  
  	public void waitUntilTextPresent(final By locator, final String needle) {
  		waitForScript();
  		try {
  			driver.manage().timeouts().implicitlyWait(0, TimeUnit.SECONDS);
  			Wait<WebDriver> wait = new WebDriverWait(driver, 60);
  			Boolean present = wait.until(new ExpectedCondition<Boolean>() {
  				public Boolean apply(WebDriver driver) {
  					for (WebElement element : driver.findElements(locator)) {
  						try {
  							if (element.getText().contains(needle)) {
  								return true;
  							}
  						} catch (StaleElementReferenceException e) {
  							continue;
  						}
  					}
  					return null;
  				}
  
  				public String toString() {
  					return "text " + needle + " to be present in " + locator;
  				}
  			});
  			assertTrue(present);
  		} finally {
  			driver.manage().timeouts().implicitlyWait(IMPLICITLY_WAIT, TimeUnit.SECONDS);
  		}
  	}
  
  	public void waitForScript() {
  		try {
  			driver.manage().timeouts().implicitlyWait(0, TimeUnit.SECONDS);
  			Wait<WebDriver> wait = new WebDriverWait(driver, 240);
  			assertTrue(wait.until(new ExpectedCondition<Boolean>() {
  				public Boolean apply(WebDriver wd) {
  					return wd.findElements(By.cssSelector(".loading")).isEmpty();
  				}
  
  				public String toString() {
  					return "script to be ready";
  				}
  			}));
  		} finally {
  			driver.manage().timeouts().implicitlyWait(IMPLICITLY_WAIT, TimeUnit.SECONDS);
  		}
  	
  	}
  
  	public void waitUntilModalOpen() {
  		Wait<WebDriver> wait = new WebDriverWait(driver, 240);
  		assertTrue(wait.until(new ExpectedCondition<Boolean>() {
  			public Boolean apply(WebDriver wd) {
  				List<WebElement> modals = wd.findElements(By.cssSelector(".modal.fade.in"));
  				if (modals.isEmpty())
  					return false;
  				for (WebElement modal : modals) {
  					if (!modal.getCssValue("opacity").equals("1"))
  						return false;
  				}
  				return true;
  			}
  
  			public String toString() {
  				return "modal to open";
  			}
  		}));
  	}
  
  	public int getPositionTop(By locator) {
  		return driver.findElement(locator).getLocation().getY();
  	}
  
  	public int getPositionBottom(By locator) {
  		WebElement element = driver.findElement(locator);
  		return element.getLocation().getY() + element.getSize().getHeight();
  	}
  
  	public String getText(By locator) {
  		try {
  			return driver.findElement(locator).getText();
  		} catch (StaleElementReferenceException e) {
  			return null;
  		}
  	}
  
  	public int getElementCount(By locator) {
  		return driver.findElements(locator).size();
  	}
  
  	public List<String> getTextOfElements(By locator) {
  		List<WebElement> elements = driver.findElements(locator);
  		List<String> texts = new ArrayList<String>(elements.size());
  		for (WebElement element : elements) {
  			try {
  				texts.add(element.getText());
  			} catch (StaleElementReferenceException e) {
  				continue;
  			}
  		}
  		return texts;
  	}
  
  }
  