package org.edx.nativeapp;

import io.appium.java_client.AppiumDriver;
import io.appium.java_client.NetworkConnectionSetting;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.ios.IOSDriver;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.edx.Config;
import org.edx.basetest.UniqueTestId;
import org.edx.utils.PropertyLoader;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.ElementNotVisibleException;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.Reporter;

/**
 * @author divakarpatil
 * 
 */
public class NativeAppDriver extends RemoteWebDriver {

	protected AppiumDriver appiumDriver;
	private final long maxWaitTime = Long.parseLong(PropertyLoader
			.loadProperty("max.wait.time").get());
	private final long downloadWaitTime = Long.parseLong(PropertyLoader
			.loadProperty("download.wait.time").get());
	private final String testPackageName = PropertyLoader.loadProperty(
			"test.package.name").get();
	private final String deviceOS = PropertyLoader.loadProperty("deviceOS")
			.get();
	private final String appPath = PropertyLoader.loadProperty("appPath").get();

	private final String deviceName = PropertyLoader.loadProperty("deviceName")
			.get();
	private final String appPackage = PropertyLoader.loadProperty("appPackage")
			.get();
	private final String sauceUserName = PropertyLoader.loadProperty(
			"sauceUserName").get();
	private String sauceKey = PropertyLoader.loadProperty("sauceKey").get();

	URL serverAddress;

	DesiredCapabilities desiredCapabilities = new DesiredCapabilities();

	/**
	 * Constructor which sets the required desired capabilities for the Appium
	 * driver
	 * 
	 * @throws Throwable
	 */
	public NativeAppDriver() throws Throwable {
		super();
		if (sauceKey.isEmpty()) {
			serverAddress = new URL("http://0.0.0.0:4723/wd/hub");// appium:
																	// 4723,
																	// 4475
		} else {
			serverAddress = new URL("http://" + sauceUserName + ":" + sauceKey
					+ "@ondemand.saucelabs.com:80/wd/hub");
		}

		try {

			if (isiOS()) {
				desiredCapabilities = Config.iOS.getCapabilities();
				appiumDriver = new IOSDriver(serverAddress, desiredCapabilities);
			} else if (isAndroid()) {
				desiredCapabilities = Config.Android.getCapabilities();
				appiumDriver = new AndroidDriver(serverAddress,
						desiredCapabilities);
				
			}

		} catch (Throwable e) {
			System.out.println(">> Error while initiating Driver " + deviceOS);
			throw e;
		}

	}

	/**
	 * Find element by the given locator after proper wait
	 */
	@Override
	public NativeAppElement findElement(final By locator) {
		WebElement appElement = null;
		try {
			appElement = (new WebDriverWait(appiumDriver, maxWaitTime))
					.until(ExpectedConditions.presenceOfElementLocated(locator));

		} catch (Throwable te) {
			Reporter.log("Unable to find the element by locator: "
					+ locator.toString() + " within " + maxWaitTime + " secs");
			captureScreenshot();
			throw new TimeoutException(te);
		}
		return new NativeAppElement(this, locator, appElement);

	}

	public boolean verifyElementText(By by, String text) {
		boolean result = false;
		String actualText = "";
		int attempt = 0;
		NativeAppElement webElment = findElement(by);
		while (!result && attempt <= 5) {
			try {

				attempt++;
				WebDriverWait wait = (WebDriverWait) new WebDriverWait(
						appiumDriver, maxWaitTime / 5, 500);
				wait.until(ExpectedConditions.presenceOfElementLocated(by));
				wait.until(ExpectedConditions.visibilityOfElementLocated(by));
				// Adding support for textarea
				if (webElment.getTagName().equalsIgnoreCase("input")
						|| webElment.getTagName().equalsIgnoreCase("textarea")) {
					actualText = webElment.getAttribute("value");
				} else {
					actualText = webElment.readInnerText();
				}

			} catch (Exception e) {
				System.err.println("attempt " + attempt + "...");
				if (attempt >= 5) {
					Reporter.log("Unable to get the text by locator "
							+ by.toString());
					captureScreenshot();
					throw new WebDriverException(e);
				}
			}

			result = actualText.equals(text);
		}
		if (result) {
			return true;
		} else {
			Reporter.log("Expected: \"" + text + "\" but found: \""
					+ actualText + "\"");

			captureScreenshot();
			throw new AssertionError("Expected: \"" + text + "\" but found: \""
					+ actualText + "\"" + "\n"
					+ Thread.currentThread().getStackTrace().toString());
		}
	}

	/**
	 * To check the given text contains by the element located by the locator
	 * 
	 * @param by
	 * @param text
	 * @return
	 */
	public boolean verifyElementTextContains(By by, String text) {
		boolean result = false;
		String actualText = "";
		int attempt = 0;
		NativeAppElement webElment = findElement(by);
		while (!result && attempt <= 5) {
			try {
				attempt++;
				WebDriverWait wait = (WebDriverWait) new WebDriverWait(
						appiumDriver, maxWaitTime, 500);
				wait.until(ExpectedConditions.presenceOfElementLocated(by));
				wait.until(ExpectedConditions.visibilityOfElementLocated(by));
				if (webElment.getTagName().equalsIgnoreCase("input")
						|| webElment.getTagName().equalsIgnoreCase("textarea")) {
					actualText = webElment.getAttribute("value");
				} else {
					actualText = webElment.readInnerText();
				}
			} catch (Exception e) {
				System.err.println("attempt " + attempt + "...");
				if (attempt > 5) {
					Reporter.log("Unable to get the text by locator "
							+ by.toString());
					captureScreenshot();
					throw new WebDriverException(e);
				}
			}
			result = actualText.contains(text);
		}
		if (result) {
			return true;
		} else {
			Reporter.log("\"" + text + "\" not found in \"" + actualText + "\"");
			captureScreenshot();
			throw new AssertionError("\"" + text + "\" not found in \""
					+ actualText + "\"" + "\n"
					+ Thread.currentThread().getStackTrace().toString());
		}
	}

	/**
	 * Captures the screenshot
	 */
	public void captureScreenshot() {
		String outputPath = PropertyLoader.loadProperty("output.path").get();

		String screenShotPath = outputPath + "/ScreenShots/";
		String fileName = generateFileName() + ".jpg";
		// Take the screenshot
		File scrFile = ((TakesScreenshot) (this.appiumDriver))
				.getScreenshotAs(OutputType.FILE);
		try {
			FileUtils.copyFile(scrFile, new File(screenShotPath + fileName));
			Reporter.log("<br> Module name: " + getCurrentTestClassName()
					+ "<br>");
			Reporter.log(" Refer to <a href=\"ScreenShots/" + fileName
					+ "\" target = \"_blank\"><img src=\"ScreenShots/"
					+ fileName + "\" width=\"50\" height=\"50\"></a><br>");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Generates the filename for screenshot
	 * 
	 * @return
	 */
	public String generateFileName() {
		return new UniqueTestId().id + "_" + getCurrentTestClassName() + "_"
				+ getCurrentTestMethodName() + "_"
				+ getCurrentTestMethodLineNumber();
	}

	/**
	 * This will return the current test method name it is executing
	 * 
	 * @return String Test case name
	 */
	public String getCurrentTestMethodName() {
		int stackIndex = 0;
		while (stackIndex < Thread.currentThread().getStackTrace().length) {
			if (Thread.currentThread().getStackTrace()[stackIndex]
					.getMethodName().endsWith("Test")
					&& Thread.currentThread().getStackTrace()[stackIndex]
							.getClassName().startsWith(testPackageName)) {
				break;
			}
			stackIndex++;
		}
		return Thread.currentThread().getStackTrace()[stackIndex]
				.getMethodName();
	}

	/**
	 * This will return the current test class name it is executing
	 * 
	 * @return String test class name
	 */
	public String getCurrentTestClassName() {
		int stackIndex = 0;
		while (stackIndex < Thread.currentThread().getStackTrace().length) {
			if (Thread.currentThread().getStackTrace()[stackIndex]
					.getMethodName().endsWith("Test")
					&& Thread.currentThread().getStackTrace()[stackIndex]
							.getClassName().startsWith(testPackageName)) {
				break;
			}
			stackIndex++;
		}
		return Thread.currentThread().getStackTrace()[stackIndex]
				.getClassName();
	}

	/**
	 * This will return the current test method line number it is executing
	 * 
	 * @return int: Test method line number
	 */
	public int getCurrentTestMethodLineNumber() {
		int stackIndex = 0;
		while (stackIndex < Thread.currentThread().getStackTrace().length) {
			if (Thread.currentThread().getStackTrace()[stackIndex]
					.getMethodName().endsWith("Test")
					&& Thread.currentThread().getStackTrace()[stackIndex]
							.getClassName().startsWith(testPackageName)) {
				break;
			}
			stackIndex++;
		}
		return Thread.currentThread().getStackTrace()[stackIndex]
				.getLineNumber();
	}

	/**
	 * AppiumDriver close
	 */
	@Override
	public void close() {
		appiumDriver.quit();
	}

	/**
	 * Find element by ID
	 * 
	 * @param id
	 * @return
	 */
	public NativeAppElement findElementById(String id) {
		return findElement(By.id(id));
	}

	/**
	 * Find element by Name
	 * 
	 * @param name
	 * @return
	 */
	public NativeAppElement findElementByName(String name) {
		return findElement(By.name(name));
	}

	/**
	 * Find element by Xpath
	 * 
	 * @param xpath
	 * @return
	 */
	public NativeAppElement findElementByXpath(String xpath) {
		return findElement(By.xpath(xpath));
	}

	/**
	 * Find element by Class name
	 * 
	 * @param className
	 * @return
	 */
	public NativeAppElement findElementByClassName(String className) {
		return findElement(By.className(className));
	}

	public boolean verifyElementTextByName(String name, String text) {
		return this.verifyElementText(By.name(name), text);
	}

	public boolean verifyElementTextById(String id, String text) {
		return this.verifyElementText(By.id(id), text);
	}

	public boolean verifyElementNotPresntById(String id) {
		boolean found = false;
		try {
			appiumDriver.findElementById(id);
			found = true;
			Reporter.log("Element is present with id " + id);
			throw new AssertionError("Element is present with id " + id);
		} catch (Exception e) {
		}
		return found;
	}

	public boolean verifyElementNotPresntByName(String name) {
		boolean found = false;
		try {
			this.findElementByName(name);
			found = true;
		} catch (Exception e) {
		}
		return found;
	}

	/**
	 * Overridden webDriver find Elements with proper wait.
	 */
	@Override
	public List<WebElement> findElements(By locator) {

		try {
			(new WebDriverWait(appiumDriver, maxWaitTime))
					.until(ExpectedConditions
							.presenceOfAllElementsLocatedBy(locator));
		} catch (ElementNotVisibleException e) {
			Reporter.log("Element not found: " + locator.toString());
			captureScreenshot();
			throw e;
		}
		return appiumDriver.findElements(locator);

	}

	@Override
	public List<WebElement> findElementsById(String using) {
		return findElements(By.id(using));
	}

	public void clickElementWithIndexById(String id, int index) {
		// List<NativeAppElement> elements = findAllElements(By.id(id));
		try {
			findElementsById(id).get(index).click();
			// elements.get(index - 1).click();
		} catch (Exception exp) {
			captureScreenshot();
			Reporter.log("Unable to click element index: " + index
					+ " with id: " + id);
			throw exp;
		}
	}

	public void clickElement(WebElement element) {
		element.click();
	}

	public void clickElement(NativeAppElement element) {
		((JavascriptExecutor) appiumDriver).executeScript(
				"arguments[0].scrollIntoView();", element.getWebElement());
		element.getWebElement().click();
	}

	public void clickElementById(String id) {
		findElementById(id).click();
	}

	public void clickElementByXpath(String xpath) {
		findElementByXpath(xpath).click();
	}

	public void clickElementByName(String name) {
		findElementByName(name).click();
	}

	public void enterTextToElementById(String id, String text) {
		findElementById(id).sendKeys(text);
	}

	public void enterTextToElementByName(String name, String text) {
		findElementByName(name).sendKeys(text);
	}

	public void enterTextToElementByClassname(String className, String text) {
		findElementByClassName(className).sendKeys(text);
	}

	public void verifyElementPresentByName(String name) {
		findElementByName(name);
	}

	public void verifyElementPresentByXpath(String name) {
		findElementByXpath(name);
	}

	public void verifyElementPresentById(String id) {
		findElementById(id);
	}

	public void verifyElementPresentByClassName(String className) {
		findElementByClassName(className);
	}

	public void verifyPresenceOfTextLocatedById(String id, String text) {
		// findElementById(id);
		try {
			WebDriverWait wait = (WebDriverWait) new WebDriverWait(
					appiumDriver, maxWaitTime, 500);
			wait.until(ExpectedConditions.textToBePresentInElementLocated(
					By.id(id), text));
		} catch (Throwable t) {
			Reporter.log("text " + text + " not present in id " + id);
			captureScreenshot();
			throw t;
		}
	}

	public void verifyPresenceOfTextLocatedByName(String name, String text) {
		// findElementByName(name);
		try {
			WebDriverWait wait = (WebDriverWait) new WebDriverWait(
					appiumDriver, maxWaitTime, 500);
			wait.until(ExpectedConditions.textToBePresentInElementLocated(
					By.name(name), text));
		} catch (Throwable t) {
			Reporter.log("text " + text + " not present in name " + name);
			captureScreenshot();
			throw t;
		}
	}

	/**
	 * Accept the alert
	 */
	public void acceptAlert() {
		WebDriverWait wait = (WebDriverWait) new WebDriverWait(appiumDriver,
				maxWaitTime, 500);
		wait.until(ExpectedConditions.alertIsPresent());
		Alert alrt = appiumDriver.switchTo().alert();
		alrt.accept();
	}

	/**
	 * Verify the element is visible
	 * 
	 * @param element
	 */
	public void verifyVisibilityOfElement(WebElement element) {
		try {
			WebDriverWait wait = (WebDriverWait) new WebDriverWait(
					appiumDriver, maxWaitTime, 500);
			wait.until(ExpectedConditions.visibilityOf(element));
		} catch (Throwable t) {
			captureScreenshot();
			throw t;
		}
	}

	/**
	 * Verifies element is visible
	 * 
	 * @param id
	 */
	public void verifyVisibilityOfElementLocatedById(String id) {
		try {
			WebDriverWait wait = (WebDriverWait) new WebDriverWait(
					appiumDriver, maxWaitTime, 500);
			wait.until(ExpectedConditions.visibilityOfElementLocated(By.id(id)));
		} catch (Throwable t) {
			Reporter.log("Elment by Id " + id + " not visible");
			captureScreenshot();
			throw t;
		}
	}

	public void clearInputById(String id) {
		findElementById(id).clear();
	}

	public void clearInputByName(String name) {
		findElementByName(name).clear();
	}

	public void clearInputByClassname(String className) {
		findElementByClassName(className).clear();
	}

	/**
	 * Method returns the appium driver instance
	 * 
	 * @return
	 */
	public WebDriver getDriver() {
		return appiumDriver;
	}

	/**
	 * Switch between data and wifi(specific to android).
	 * 
	 * @param wifi
	 * @param data
	 * @throws InterruptedException
	 */
	public void setNetworkConnection(boolean wifi, boolean data,
			boolean airplane) throws InterruptedException {
		if (deviceOS.equalsIgnoreCase("android")) {
			NetworkConnectionSetting connectionSetting = new NetworkConnectionSetting(
					0);
			connectionSetting.setWifi(wifi);
			connectionSetting.setData(data);
			connectionSetting.setData(airplane);
			((AndroidDriver) appiumDriver)
					.setNetworkConnection(connectionSetting);
			connectionSetting = ((AndroidDriver) appiumDriver)
					.getNetworkConnection();
		} else {
			HashMap<String, Double> coords = new HashMap<String, Double>();
			JavascriptExecutor js = (JavascriptExecutor) appiumDriver;
			Dimension devicediam = appiumDriver.manage().window().getSize();
			int height = devicediam.getHeight();
			// int width = devicediam.getWidth();
			if (deviceName.equals("iPhone 6")) {
				appiumDriver.swipe(100, height, 100, 100, 500);
				Thread.sleep(3 * 1000);
				coords.put("x", new Double("" + 120));
				coords.put("y", new Double("" + 290));
				coords.put("duration", 0.5);
				js.executeScript("mobile: tap", coords);
				Thread.sleep(3 * 1000);
				coords.put("x", new Double("" + 100));
				coords.put("y", new Double("" + 100));
				coords.put("duration", 0.5);
				js.executeScript("mobile: tap", coords);

			} else {// For iPhone 5
				appiumDriver.swipe(100, height, 100, 100, 500);
				Thread.sleep(3 * 1000);
				coords.put("x", new Double("" + 100));
				coords.put("y", new Double("" + 180));
				coords.put("duration", 0.5);
				js.executeScript("mobile: tap", coords);
				Thread.sleep(3 * 1000);
				coords.put("x", new Double("" + 100));
				coords.put("y", new Double("" + 100));
				coords.put("duration", 0.5);
				js.executeScript("mobile: tap", coords);
			}
		}
	}

	/**
	 * Navigating back
	 */
	public void back() {
		appiumDriver.navigate().back();
	}

	/**
	 * Launch the app
	 */
	public void launchApp() {
		appiumDriver.closeApp();
		appiumDriver.launchApp();
		
	}

	/**
	 * Remove app(Uninstall)
	 */
	public void uninstallApp() {
		appiumDriver.removeApp(appPackage);
	}

	/**
	 * Wait till the element is displayed and swipe till the particular time
	 * slot
	 * 
	 * @param id
	 *            - id of the element
	 */
	public void swipe(String id) {
		try {
			if (isAndroid()) {
				new WebDriverWait(appiumDriver, 20).until(ExpectedConditions
						.presenceOfElementLocated(By.id(id)));
				Dimension dimension = appiumDriver.manage().window().getSize();
				int ht = dimension.height;
				int width = dimension.width;
				appiumDriver.swipe((width / 2), (ht / 4), (width / 2),
						(ht / 2), 1000);
			} else {
				new WebDriverWait(appiumDriver, 20).until(ExpectedConditions
						.presenceOfElementLocated(By.id(id)));
				if (deviceName.equalsIgnoreCase("iphone 5")) {
					appiumDriver.swipe((int) 0.1, 557, 211, 206, 500);
				} else if (deviceName.equalsIgnoreCase("iphone 6")) {
					appiumDriver.swipe((int) 0.1, 660, 50, 50, 500);
				}
			}
		} catch (Throwable e) {
			Reporter.log("Element by Id " + id + " not visible");
			captureScreenshot();
			throw e;
		}
	}

	/**
	 * Hide Keyboard (specific for android)
	 */
	public void hideKeyboard() {
		// if (sauceKey.equals("")) {
		appiumDriver.hideKeyboard();
		// }
	}

	/**
	 * verifying that text is present or not
	 * 
	 * @param id
	 *            - id of the element whose text is to be checked for.
	 * @param text
	 *            - text use for comparison.
	 * @return
	 */
	public boolean verifyElementText(String id, String text) {
		try {
			(new WebDriverWait(appiumDriver, 20)).until(ExpectedConditions
					.presenceOfElementLocated(By.id(id)));
		} catch (Exception e) {
			Reporter.log("Element not found: " + e.toString());
			captureScreenshot();
			throw e;
		}
		try {
			if (!(appiumDriver.findElementById(id).getText()).equals(text)) {
				return true;
			} else {
				return false;
			}
		} catch (Exception e) {
			Reporter.log("Element not found: " + e.toString());
			captureScreenshot();
			throw e;
		}
	}

	/**
	 * Inserting wait till the element to be found is present.
	 * 
	 * @param id
	 *            : id of the element.
	 */
	public void insertWait(String id) {
		try {
			new WebDriverWait(appiumDriver, downloadWaitTime)
					.until(ExpectedConditions.invisibilityOfElementLocated(By
							.id(id)));
		} catch (Throwable e) {
			Reporter.log("Element found: " + e.toString());
			captureScreenshot();
			throw e;
		}

	}

	/**
	 * Scroll the list till item found by name
	 * 
	 * @param name
	 *            - name of the element till the scroll is to be performed
	 * @throws Throwable
	 */
	public void scrollList(String name) throws Throwable {
		int i = 0;
		Dimension dimension = appiumDriver.manage().window().getSize();
		int ht = dimension.height;
		int width = dimension.width;
		if (isAndroid()) {
			while (!verifyElementIsDisplayedById(name)) {
				appiumDriver.swipe((width / 2), (ht / 2), (width / 4),
						(ht / 4), 500);
			}
		} else {
			try {
				if (appPath.contains(".ipa")) {
					while (!(verifyElementIsDisplayedById(name))) {
						appiumDriver.swipe((width / 2), (ht / 2), (width / 4),
								(ht / 4), 500);
						i++;
						if (i > 20) {
							throw new Exception();
						}
					}
				}
			} catch (Throwable e) {
				Reporter.log("Element not found by name:" + name);
				throw e;
			}

		}
	}

	public boolean verifyElementIsDisplayedById(String id) {
		boolean found = false;
		try {
			found = appiumDriver.findElementById(id).isDisplayed();
		} catch (Exception e) {

		}
		return found;

	}

	/**
	 * Tap on wifi(only for ios)
	 */
	public void tapOnWifi() {
		System.out.println("Size of iphone 6 is "
				+ appiumDriver.manage().window().getSize());
		if (deviceName.equalsIgnoreCase("iphone 5")) {
			appiumDriver.tap(85, 85, 175, 0);
		} else if (deviceName.equalsIgnoreCase("iphone 6")) {
			appiumDriver.tap(120, 120, 278, 0);
		}
	}

	/**
	 * Verify that whether the text is present or not
	 * 
	 * @param courseWareErrorText
	 * @param text
	 * @return
	 */
	public boolean textIsPresent(String courseWareErrorText, String text) {
		if (courseWareErrorText.equals(text)) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Verify that whether the element is present or not
	 * 
	 * @param id
	 * @return
	 */
	public boolean verifyElementId(String id) {
		boolean found = false;
		try {
			appiumDriver.findElementById(id);
			found = true;
		} catch (Exception e) {

		}
		return found;

	}

	private boolean isAndroid() {
		return deviceOS.equalsIgnoreCase(Config.Android.OS_NAME);
	}

	private boolean isiOS() {
		return deviceOS.equalsIgnoreCase(Config.iOS.OS_NAME);
	}

}