import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.Point;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxProfile;

/**
 * Cool Selenium utility functions
 * @author Luke Bryan
 */
public class SFSeleniumBase {

	public WebDriver browser;
	
	/**
	 * Common profile options to use for automated Firefox.
	 * @return
	 */
	public static FirefoxProfile newFirefoxProfile()
	{
		FirefoxProfile profile = new FirefoxProfile();
		//May rarely get the long script message, why not double*10 the time:
		profile.setPreference("dom.max_script_run_time", 700);
		profile.setPreference("dom.max_chrome_script_run_time", 700);
		
		//Remove the junk - http://www.makeuseof.com/tag/how-to-disable-pocket-integration-and-other-features-in-the-new-firefox/
		profile.setPreference("loop.enabled", false);
		profile.setPreference("browser.pocket.enabled", false);
		
		//Buggy Selenium/Firefox messing up, maybe this may help debugging?:
		profile.setPreference("devtools.chrome.enabled", true);
		//Work in Firefox 43+:
		profile.setPreference("xpinstall.signatures.required", false);
		
		//Maybe this isn't needed?
		profile.setPreference("privacy.donottrackheader.enabled", true);
		return profile;
	}

	/**
	 * Queries for a single element. Like jQuery or querySelector.
	 * @param cssquery
	 * @return
	 */
	public WebElement q( String cssquery )
	{
		try
		{
			return browser.findElement( By.cssSelector( cssquery ) );
		}
		catch( NoSuchElementException e )
		{
			System.out.println( e.toString() );
			throw e;
		}
	}
	
	public WebElement qXpath( String xpathQuery )
	{
		try
		{
			return browser.findElement( By.xpath( xpathQuery ) );
		}
		catch( NoSuchElementException e )
		{
			System.out.println( e.toString() );
			throw e;
		}
	}
	
	public List<WebElement> qAllXpath( String xpathQuery )
	{
		try
		{
			return browser.findElements( By.xpath( xpathQuery ) );
		}
		catch( NoSuchElementException e )
		{
			System.out.println( e.toString() );
			throw e;
		}
	}
	
	public List<WebElement> qAll( String cssquery )
	{
		try
		{
			return browser.findElements( By.cssSelector( cssquery ) );
		}
		catch( NoSuchElementException e )
		{
			System.out.println( e.toString() );
			throw e;
		}
	}
	
	public WebElement qLast( String cssquery )
	{
		List<WebElement> elements = browser.findElements( By.cssSelector( cssquery ) );
		return elements.get( (elements.size() - 1) );
	}
	
	public WebElement qXpathLast( String xpathQuery )
	{
		List<WebElement> elements = browser.findElements( By.xpath( xpathQuery ) );
		return elements.get( (elements.size() - 1) );
	}
	
	public WebElement qFirst( String cssquery )
	{
		List<WebElement> elements = browser.findElements( By.cssSelector( cssquery ) );
		return elements.get( 0 );
	}
	
	public WebElement qXpathFirst( String xpathQuery )
	{
		List<WebElement> elements = browser.findElements( By.xpath( xpathQuery ) );
		return elements.get( 0 );
	}
	
	public void scrollToElement( WebElement element, int offset )
	{
		Point point = element.getLocation();
		int newY = point.y + offset;
		((JavascriptExecutor) this.browser).executeScript( "window.scrollBy( 0, " + newY + " );" );
	}

	public static String getFile( File dir, String file ) throws IOException 
	{
		BufferedReader reader =
			new BufferedReader( new InputStreamReader( new FileInputStream( new File( dir, file ) ), "UTF-8" ));
		
		String line = null;
		String out = "";
		while( (line = reader.readLine()) != null )
		{
			out += line + "\n";
		}
		reader.close();
		return out;
	}
	
	public static String getFile( File inputFile ) throws IOException 
	{
		BufferedReader reader =
			new BufferedReader( new InputStreamReader( new FileInputStream( inputFile ), "UTF-8" ));
		
		String line = null;
		String out = "";
		while( (line = reader.readLine()) != null )
		{
			out += line + "\n";
		}
		reader.close();
		return out;
	}
	
	/**
	 * Waits for an element to exist.
	 * @param selector
	 * @return
	 * @throws Exception
	 */
	public boolean waitFor( String selector ) throws Exception 
	{
		System.out.println( "Waiting for " + selector );
		Thread.sleep( 1000 );
		for( int i = 0; i < (60 * 10); i++ )
		{
			try
			{
				this.q( selector );
				return true;
			}
			catch ( NoSuchElementException e )
			{
				try
				{
					this.q("#pageWrapper >.modal-dialog.sign-out-modal");
					throw new Exception("Logged out message");
				}
				catch( NoSuchElementException ex)
				{
					//okay. wait
					Thread.sleep( 400 );
				}
			}
		}
		throw new Exception( "Not found " + selector );
	}
	
	public boolean waitFor( String selector, int seconds ) throws Exception
	{
		System.out.println( "Waiting for " + selector );
		for( int i = 0; i < seconds; i++ )
		{
			Thread.sleep( 1000 );
			try
			{
				this.q( selector );
				return true;
			}
			catch ( NoSuchElementException e )
			{
				try
				{
					this.q("#pageWrapper >.modal-dialog.sign-out-modal");
					throw new Exception("Logged out message");
				}
				catch( NoSuchElementException ex)
				{
					//okay. wait
				}
			}
		}
		throw new Exception( "Not found " + selector );
	}
	
	/*public boolean waitFor( String selector, String goUrl ) throws Exception
	{
		try {
			this.waitFor( selector );
		} catch (Exception ex)
		{
			this.browser.get( goUrl );
			this.waitFor( selector );
		}
		return true;
	}*/
	
	/**
	 * Waits for an element to exist.
	 * @param selector
	 * @return
	 * @throws Exception
	 */
	public boolean waitFor( String selector, boolean requireVisible ) throws Exception 
	{
		if( !requireVisible )
		{
			return this.waitFor( selector );
		}
		
		System.out.println( "Waiting for " + selector );
		Thread.sleep( 1000 );
		for( int i = 0; i < (60 * 8); i++ )
		{
			try
			{
				WebElement element = this.q( selector );
				
				if( element != null && element.isDisplayed() && element.isEnabled() )
				{
					System.out.println( "Found and visible: " + selector );
					return true;
				}
				else
				{
					throw new NoSuchElementException( "Keep trying" );
				}
			}
			catch ( NoSuchElementException e )
			{
				Thread.sleep( 400 );
			}
		}
		throw new Exception( "Not found " + selector );
	}
	
	/**
	 * Waits for an element to be null.
	 * DON'T USE IF ANY OTHER WAY. IT IS WAITING ON EXECPTION TO RETURN TRUE!!!
	 * @param selector
	 * @return
	 * @throws Exception
	 */
	public boolean waitUntilDisappear( String selector) throws Exception 
	{
		
		System.out.println( "Waiting until " + selector + " disappears");
		Thread.sleep( 1000 );
		for( int i = 0; i < (60 * 8); i++ )
		{
			try
			{
				WebElement element = this.q( selector );
				
				if( element != null )
				{
					System.out.println( "Still visible: " + selector );
					Thread.sleep( 400 );
				}
				else
				{
					throw new NoSuchElementException( "Keep trying" );
				}
			}
			catch ( NoSuchElementException e )
			{
				System.out.println( "Element disappered: " + selector );
				return true;
			}
		}
		throw new Exception( "Element didn't disappear " + selector );
	}
	
	/**
	 * Waits for an xpath element to exist.
	 * @param xpath selector
	 * @return
	 * @throws Exception
	 */
	public boolean waitForXpath( String selector ) throws Exception 
	{
		System.out.println( "Waiting for " + selector );
		Thread.sleep( 1000 );
		for( int i = 0; i < (60 * 8); i++ )
		{
			try 
			{
				this.qXpath( selector );
				System.out.println( "Found: " + selector );
				return true;
			}
			catch ( NoSuchElementException e )
			{
				Thread.sleep( 400 );
			}
		}
		throw new Exception( "Not found " + selector );
	}
	
	/**
	 * Waits for an xpath element to exist for the given number of seconds.
	 * 
	 * Will return true/false instead of throwing an exception so the caller can handle the condition
	 * 
	 * @param xpath selector
	 * @return
	 * @throws Exception
	 */
	public boolean waitForXpath( String selector, int seconds ) throws Exception 
	{
		for( int i = 0; i < seconds; i++ )
		{
			System.out.println( "Waiting for " + selector + " for " + i + " of " + seconds + " seconds" );
			try 
			{
				this.qXpath( selector );
				System.out.println( "Found: " + selector );
				return true;
			}
			catch ( NoSuchElementException e )
			{
				Thread.sleep( 1000 );
			}
		}
		return false;
	}
	
	/**
	 * Waits for an xpath element to exist.
	 * @param xpath selector
	 * @return
	 * @throws Exception
	 */
	public boolean waitForXpath( String selector, boolean requireVisible ) throws Exception 
	{
		if( !requireVisible )
		{
			return this.waitForXpath( selector );
		}
		
		System.out.println( "Waiting for visibility of " + selector );
		Thread.sleep( 1000 );
		for( int i = 0; i < (60 * 8); i++ )
		{
			try 
			{
				WebElement element = this.qXpath( selector );
				
				if( element != null && element.isDisplayed() && element.isEnabled() )
				{
					System.out.println( "Found and visible: " + selector );
					return true;
				}
				else
				{
					throw new NoSuchElementException( "Keep trying" );
				}
			}
			catch ( NoSuchElementException e )
			{
				Thread.sleep( 400 );
			}
		}
		throw new Exception( "Not found " + selector );
	}
	
	public void waitForFile(File mustExist) throws Exception
	{
		Thread.sleep(500);
		for( int i=0; i< (60); i++)
		{
			if( mustExist.exists())
			{
				return;
			}
			else
			{
				Thread.sleep(500);
			}
		}
		throw new Exception("Waited for file, not found: "+mustExist.getAbsolutePath());
	}
	
	public ArrayList<String> screenShotsToUse()
	{
		ArrayList<String> screenshotnums = new ArrayList<String>();
		screenshotnums.add( "4" );
		screenshotnums.add( "3" );
		screenshotnums.add( "2" );
		screenshotnums.add( "1" );
		screenshotnums.add( "0" );
		//reverse order 4,3,2,1,0
		return screenshotnums;
	}
	
	/**
	 * Run a sleep thread for the given number of milliseconds
	 * @throws InterruptedException 
	 */
	public void sleep( int length )
	{
		try
		{
			Thread.sleep( length );
		}
		catch( InterruptedException ex )
		{
			Thread.currentThread().interrupt();
		}
	}
}
