package in.ramachandr.automation.core;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.Select;
import org.testng.Assert;
import org.openqa.selenium.Keys;

public abstract class AbstractPage implements IPageInteractions {	
	@Override
	public boolean scrollElementIntoView(WebElement w)  throws Exception{
		boolean result = false;
		try{
			jse.executeScript("arguments[0].scrollIntoView()", w);
			result = true;
		}catch(Exception e){
			result = false;
		}
		Assert.assertTrue(result, "Scroll " + w.toString() + " into view");
		return result;
	}

	@Override
	public boolean clickElement(WebElement w) throws Exception{
		boolean result = false;
		try{
			w.click();
			result = true;
		} catch(Exception e){
			e.printStackTrace();
			result = false;
		}
		Assert.assertTrue(result, "Click " + w.toString());
		return result;
	}
	
	@Override
	public boolean clearText(WebElement w) throws Exception{
		boolean result = false;
		try{
			w.clear();
			result = true;
		} catch(Exception e){
			e.printStackTrace();
			result = false;
		}
		Assert.assertTrue(result, "Clear " + w.toString());
		return result;
	}
	
	@Override
	public boolean checkTextContains(WebElement w, String...args) throws Exception{
		String actual = this.getText(w);
		actual = actual.trim().replaceAll("\n", "").replaceAll("\r", "");
		boolean result = false;
		for(String s: args){
			result = actual.contains(s);
			if(!result) {
				Assert.assertTrue(result, actual + " contains " + s);
				break;
			}
		}
		return result;
	}

	@Override
	public boolean checkElementExists(WebElement w) throws Exception{
		Assert.assertTrue(w.isDisplayed(), w.toString() + " is visible");
		return w.isDisplayed();
	}

	@Override
	public boolean checkElementDoesNotExist(WebElement w) throws Exception{
		Assert.assertTrue(w == null, w.toString() + " exists");
		return w == null;
	}

	@Override
	public boolean checkCssClass(WebElement w, String... args) throws Exception{
		String d = args[0];
		String d2 = args[1];
		boolean result = w.getCssValue(d).contains(d2);
		Assert.assertTrue(result, w.toString() + "'s CSS contains " + d2);
		return result;
	}

	@Override
	public boolean checkAttributeValue(WebElement w, String... args) throws Exception{
		String d = args[0];
		String d2 = args[1];
		String actual = this.getAttribute(w, d);
		boolean result = (0 == actual.compareTo(d2));
		Assert.assertTrue(result, w.toString() + " has attribute " + d2);
		return result;
	}

	@Override
	public boolean checkAttributeValueContains(WebElement w, String... args) throws Exception{
		String d = args[0];
		String d2 = args[1];
		String actual = this.getAttribute(w, d);
		boolean result = actual.contains(d2);
		Assert.assertTrue(result, w.toString() + " contains attribute " + d2);
		return result;
	}

	@Override
	public boolean checkAttributeValueDoesNotContain(WebElement w, String... args) throws Exception{
		boolean result = !checkAttributeValueContains(w, args);
		String d2 = args[1];
		Assert.assertTrue(result, w.toString() + " does not have attribute " + d2);
		return result;
	}

	@Override
	public boolean checkAttributeValueDoesNotExist(WebElement w, String... args) throws Exception{
		String d = args[0];
		boolean result = (this.getAttribute(w, d) == null);
		Assert.assertTrue(result, w.toString() + " does not have attribute " + d + " with value = " + this.getAttribute(w, d));
		return result;
	}

	@Override
	public boolean clickAllElements(List<WebElement> wList) throws Exception{
		boolean clicked = false;
		for (Integer i = 0; i < wList.size(); i++) {
			WebElement w = wList.get(i);
			scrollElementIntoView(w);
			this.clickElement(w);
			clicked = true;
		}
		return clicked;
	}

	@Override
	public boolean clickLink(WebElement w) throws Exception{
		return this.clickElement(w);
	}

	@Override
	public boolean checkLinkExists(WebElement w) throws Exception{
		Assert.assertTrue(w != null, w.toString() + " link exists");
		return w != null;
	}

	@Override
	public boolean clickDropdownOptionAndIndex(WebElement w, String... args) throws Exception{
		boolean result = false;
		String d = args[0];
		try{
			Select s = new Select(w);
			s.selectByIndex(Integer.parseInt(d) - 1);
			result = true;
		}catch(Exception e){
			e.printStackTrace();
			result = false;
		}
		Assert.assertTrue(result, w.toString() + " - select drop down item with index = " + d);
		return result;
	}

	@Override
	public boolean putText(WebElement w, String... args) throws Exception{
		boolean result = false;
		String d = args[0];
		boolean includesReturn = false;
		if(d.indexOf("\\n") > -1 || d.indexOf("\\r") > -1){
			d = d.replace("\\n", "");
			d = d.replace("\\r", "");
			includesReturn = true;
		}
		try{
			w.clear();
			w.sendKeys(d);
			if(includesReturn){
				w.sendKeys(Keys.RETURN);
			}
			result = true;
		}catch(Exception e){
			e.printStackTrace();
			result = false;
		}
		Assert.assertTrue(result, w.toString() + " insert text " + d);
		return result;
	}

	@Override
	public boolean checkExactText(WebElement w, String... args) throws Exception{
		boolean result = false;
		String d = args[0];
		try{
			String actual = this.getText(w);
			result = (0 == actual.compareTo(d));
		}catch(Exception e){
			e.printStackTrace();
			result = false;
		}
		Assert.assertTrue(result, w.toString() + " exactly matches " + d);
		return result;
	}

	@Override
	public boolean checkExactValueOfJavascriptKeyPath(String... args) throws Exception{
		String d = args[0]; // key path = utag_data.visitorLoginStatus|anonymous
		String d2 = args[1];
		String actualVal = (String) jse.executeScript("return " + d);
		boolean result = false;
		try{
			result = actualVal != null && actualVal.compareTo(d2) == 0;
		}catch(Exception e){
			e.printStackTrace();
			result = false;
		}
		Assert.assertTrue(result, "Exact value of keypath " + d + " matches " + d2);
		return result;
	}

	@Override
	public boolean checkCssClassDoesNotContain(WebElement w, String... args) throws Exception{
		String d = args[0];
		String d2 = args[1];
		boolean result = false;
		try{
			result = !w.getCssValue(d).contains(d2);
		}catch(Exception e){
			e.printStackTrace();
			result = false;
		}
		Assert.assertTrue(result, w.toString() + "'s CSS does not contains " + d2);
		return result;
	}
	
	@Override
	public boolean openPage() throws Exception{
		driver.manage().window().maximize();
		driver.get(baseUrl + url);
		return true;
	}
	
	@Override
	public String getText(WebElement w) {
		String returnValue = "";
		try {
			if (null != w.getAttribute("value")) {
				returnValue += w.getAttribute("value");
			} else if (null != w.getText()) {
				returnValue += w.getText();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return returnValue;
	}
	
	@Override
	public String getAttribute(WebElement w, String attrName) throws Exception {
		return w.getAttribute(attrName);
	}
	
	@Override
	public void clearBrowserCookies() throws Exception{
		driver.manage().deleteAllCookies();
	}
	/*
	 * Code specific to AbstractPage classes
	 * 
	 */
	protected String url;
	protected WebDriver driver;
	protected JavascriptExecutor jse;
	protected String baseUrl;
	protected Map<String, WebElement> nameElementMap;
	protected List<WebElement> FOUND_FIELDS;
	protected AbstractPage(String url){
		this.url = url;
		nameElementMap = new HashMap<String, WebElement>();
	}
	
	public String forUrl(){
		return url;
	}
	
	/**
	 * receive web driver from TestExecutionController or a previous page
	 * @param driver
	 */
	public void receiveWebDriver(WebDriver driver){
		this.driver = driver;
		jse = (JavascriptExecutor) this.driver;
	}
	
	/**
	 * Tranfer the execution thread to the next page <p>
	 * @param p
	 */
	public void transferControlTo(AbstractPage p){
		p.receiveWebDriver(driver);
	}
	
	protected void setBaseUrl(String url){
		baseUrl = url;
	}
	
	/**
	 * Map the WebElements from the page to their class names
	 * 
	 * E.g, an input WebElement with name = user_id would
	 * have the following mapping user_id => <WebElement> 
	 * 
	 * @param p
	 * @throws Exception
	 */
	protected void map(AbstractPage p) throws Exception{
		if(nameElementMap.size() == 0){
			Field[] fields = p.getClass().getDeclaredFields();
			for(Field f: fields){
				String s = f.getName();
				Object obj = f.get(p);
				Assert.assertNotNull(obj);
				if(f.getType() == WebElement.class){
					nameElementMap.put(s, (WebElement) obj);
				}
			}
		}
	};
	
	/**
	 * Get element from page by fieldname 
	 * 
	 * @param fieldName
	 * @return
	 */
	public WebElement getWebElementByFieldName(String fieldName){
		return nameElementMap.get(fieldName);
	}
	
	/**
	 * get all elements from page matching the fieldNamePattern
	 * 
	 * @param fnPattern
	 * @return
	 */
	public List<WebElement> getWebElementsByFieldNamePattern(String fieldNamePattern){
		Set<String> keys = nameElementMap.keySet()
				.stream()
				.filter(s -> s.matches(fieldNamePattern))
				.collect(Collectors.toSet());
		List<WebElement> elems = !keys.isEmpty()? new ArrayList<WebElement>() :null;
		for(String s: keys){
			elems.add(nameElementMap.get(s));
		}
		return elems;
	}
	
	public void setFoundElements(List<WebElement> f){
		FOUND_FIELDS = f;
	}
}