import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Set;

import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONObject;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.Cookie;
import org.openqa.selenium.ElementNotVisibleException;
import org.openqa.selenium.InvalidElementStateException;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.NoAlertPresentException;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.UnhandledAlertException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxProfile;
import org.openqa.selenium.support.ui.WebDriverWait;

import com.gargoylesoftware.htmlunit.ElementNotFoundException;
import com.google.common.io.Files;

public class iTunesConnectInterface extends SFSeleniumBase {
	

	private static final String LOGINFRAMESEL = "#aid-auth-widget-iFrame";
	private static final String FAILED_SCREENSHOT_SEL = ".screenshotZone .loadingError:not(.ng-hide)";
	private static final String DESCRIPTIONTEXTAREA_SEL = "[ng-show=\"versionInfo.details.value[currentLoc].description.isEditable\"] textarea";
	private final String CREATE_APPWIN_NAMEINPUT = "input[ng-model=\"createAppDetails.newApp.name.value\"]";
	private final String EDIT_APPNAME_SEL = "[ng-model='appInfoDetails.localizedMetadata.value[currentLoc].name.value']";
	
	private final String NEWAPPSTART_BTN = "[ng-bind=\"l10n.interpolate('ITC.apps.manageyourapps.summary.newapp')\"] ";
	private final String NEWVERSIONNUM_INPUT = ".ng-modal:not(.ng-hide) [ng-model=\"tempPageContent.newVersionNumber\"]";
	
	public final static String FIRSTIOSAPP_SIDEBAR = ".pane-layout-sidebar >.ng-scope ul >li:not(.ng-hide) a";
	
	private final String CREATE_APPWIN_CREATEBTN = "button.primary[ng-click=\"saveApp()\"]";
	private final String RELEASENOTES_SEL = "[ng-show=\"versionInfo.details.value[currentLoc].releaseNotes.isEditable\"] textarea";
	
	
	private final String RATING_MODAL_CLOSEBTN = "[ng-click=\"closeRatingModal(true)\"]";
	private final String VERSION_SEL = "input[ng-model=\"versionInfo.version.value\"]";
	
	private final String SUPPORTURL = "http://www.sharefaith.com/category/church-websites.html";
	private final String APPLISTINGURL = "https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa/ra/ng/app";
	
	private final String STARTSUBMIT_BTN = "[ng-click=\"submitForReviewStart()\"]";
	
	private final String SUB_FOR_REVIEW_SEL = "[ng-class=\"{'in-progress':finalizeSubmitForReviewInProgress}\"]";
	
	public final static String PWD = "QaarxOsuB5N7v447D4KT"; //Also needs to be changed in getCurrent.rb
	/*public String ThisUpdateMessage = 
			"We're thrilled to offer our latest app update! Here's what we've changed:\n"+
			"- Improved audio player performance\n" +
			"- Album artwork displays on AppleTV and connected second screens\n\n" +
			"Also added recently: \n" +
			"- A better looking interface with content that's easier to read than ever before\n"+
			"- Choose your own font size  within our articles by long pressing inside of any article\n" +
			"- Improved handling of incoming push notifications\n" +
			"- No more waiting for content changes - use the app while it's working for you in the background\n" +
			"- The ability to connect with us through Facebook more easily\n" +
			"- Various performance improvements to make your experience even better\n\n" +
			"Get the update and connect with us today!";*/

	/*public iTunesConnectInterface()
	{
		this( );//new FirefoxDriver( SFSeleniumBase.newFirefoxProfile() ) );
	}*/
	
	/**
	 * Constructor, aka __init__
	 */
	public iTunesConnectInterface()//WebDriver existingdriver)
	{
		/*browser = existingdriver;
		browser.get("http://itunesconnect.apple.com/");
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}*/
		
		/*try
		{
			this.logIn();
		} catch (Exception e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}*/

	}

	public iTunesConnectInterface( WebDriver existingdriver)
	{
		browser = existingdriver;
		browser.get("http://itunesconnect.apple.com/");
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		try
		{
			this.logIn();
		} catch (Exception e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
	
	private void logIn() throws Exception
	{
		browser.get("https://itunesconnect.apple.com/itc/static/login");
		try
		{
			Thread.sleep(2000);
			this.waitFor( LOGINFRAMESEL, 50 );
			browser.switchTo().frame( this.q(LOGINFRAMESEL) );
			this.waitFor( "#appleId", 50 );
			WebElement elementuser = browser.findElement(By.id("appleId"));
			WebElement elementpass = browser.findElement(By.id("pwd"));
			elementuser.sendKeys("appdev@sharefaith.com");
			elementpass.sendKeys(PWD+"\n");
			Thread.sleep(1000);
			//this.reLoginIfNecessary();
		} catch( Exception e)
		{
			Thread.sleep(10000);
			logIn();
		}
	}
	
	public boolean isAvailableName(String name) throws Exception
	{
		boolean available;
		this.reLoginIfNecessary();
		//Change name of whatever testing app we have
		this.browser.get("https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa/ra/ng/app/944500963");
		this.waitFor(EDIT_APPNAME_SEL, true);
		this.q(EDIT_APPNAME_SEL).clear();
		this.q(EDIT_APPNAME_SEL).sendKeys(name);
		try {
			this.saveTopLevelChanges();
			available = true;
			this.reLoginIfNecessary();
			this.waitFor(EDIT_APPNAME_SEL);
			this.q(EDIT_APPNAME_SEL).clear();
			this.q(EDIT_APPNAME_SEL).sendKeys("Luke TEST app .");
			this.saveTopLevelChanges();
		}
		catch (Exception e)
		{
			available = false;
			//Clear the browser save-me-state so we can continue:
			//Click my-apps back this.q("#headcontent .single[role=menuitem] a:not([target])").click();
			//Thread.sleep(1000);
			//this.q("[role=dialog] .modal-buttons[ng-hide] [ng-click='confirmLeaveModalFunctions.leavePage()']").click();
		}
		return available;
	}

	private void saveTopLevelChanges() throws Exception
	{
		String current = this.browser.getCurrentUrl();
		this.q("[ng-click=\"saveAppInfoDetails()\"]").click();
		try
		{
			this.waitFor("[ng-click=\"saveAppInfoDetails()\"]:not(.in-progress)");
		} catch (UnhandledAlertException uae) {
			try
			{
				browser.switchTo().alert().accept();
				Thread.sleep(10000);
				this.reLoginIfNecessary();
				this.browser.get( current );
			}
			catch( NoAlertPresentException noa) {}
		}
		try
		{
			q(".pagemessage.error:not(.ng-hide)");
			System.err.println("Error msg");
			if (q(".pagemessage.error:not(.ng-hide)").getText()
				.indexOf("unresolved iOS") ==-1)
			{
				throw new Exception("Error message on page.");
			}
		} catch (NoSuchElementException e)
		{
			//pass
		}
	}

	/**
	 * Logs back in if necessary.
	 * @return true if it re-logged-in.
	 */
	public boolean reLoginIfNecessary()
	{
		//return false;
		boolean loggedin = false;
		//WebDriverWait wait = new WebDriverWait(browser, 60);	
		//breaks everything:
		//boolean loggedin = true;//(boolean) ((JavascriptExecutor)browser).executeScript(
		//	"jQuery.ajax({method:'GET', async:false, url:'https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa/ra/apps/manageyourapps/summary'}).status === 200);");

		try
		{
			//Instead, make direct request with cookies that the page would do - 200 status means ok, logged in:
			Set<Cookie> cookies = browser.manage().getCookies();
			StringBuilder cookiestr = new StringBuilder();
			boolean first = true;
			for ( Cookie c : cookies)
			{
				if (c.getDomain().indexOf("apple.com") >= 0)
				{
					if (!first)
					{
						cookiestr.append("; ");
					}
					cookiestr.append( c.getName()+"="+c.getValue() );
					first = false;
				} else {
					System.out.println(c.getName()+"="+c.getValue());
				}
			}
		
			//User-only-request from app listing here:
			//URL myUrl = new URL("https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa/ra/apps/services/universalPurchaseServices");
			//TODO this should be quicker than using the frontpage.
			URL myUrl = new URL("https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa/ra/apps/manageyourapps/summary/v2");
			
			HttpURLConnection urlConn = (HttpURLConnection)myUrl.openConnection();
			urlConn.setConnectTimeout( 60* 1000 );
			urlConn.setRequestProperty("Cookie", cookiestr.toString());
			System.out.println(cookiestr.toString());
			urlConn.setRequestProperty("Accept-Language","en-US,en;q=0.5");
			urlConn.setRequestProperty("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:37.0) Gecko/20100101 Firefox/37.0");
			urlConn.connect();
			int code = urlConn.getResponseCode();
			if (code == 200)
			{
				loggedin = true;
			} else {
				
			}
		} catch (MalformedURLException e) 
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) 
		{
			e.printStackTrace();
			loggedin = false; //Server too busy??
		}
			catch (UnhandledAlertException uae)
		{
			loggedin = false;
		}

		
		if (!loggedin)
		{
			System.err.println("Not-logged-in?");// + browser.getCurrentUrl());
			/*try {
				browser.get("https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa");
				browser.findElement(By.id("accountname"));//to trigger error:
			} catch (UnhandledAlertException aerr)
			{
				System.err.println( "Ouch got to quit-retry...");
				browser.quit();
				browser = null;
				browser = new FirefoxDriver( SFSeleniumBase.newFirefoxProfile() );
				this.reLoginIfNecessary();*/
				/*try
				{
					//Alert on the page happens always when you get logged out, and are looking at something.
					//To test, log out in separate window.
					Alert alert = this.browser.switchTo().alert();
					SendMailTLS.mail("Alert on page??", "The page had a weird message: "+alert.getText()+"\n\nApp should be checked.");
					alert.accept();
					browser.get("https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa");
				} catch (NoAlertPresentException nap)
				{
					System.err.println("Wow Selenium has gone crazy");//this happens a lot, weird. Seems related to broken app-icon that won't save?
					SendMailTLS.mail("Wow Selenium has gone crazy");
				}
			}*/
			try {
				Thread.sleep(20000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			//IF this is login (sometimes the !loggedin triggers in err), log in:
			//Element exists, relogin:
			try 
			{
				this.logIn();
				System.err.println("Re-logged-in!");
			} catch (NoSuchElementException e)
			{
				//pass
			} catch (Exception e)
			{
				e.printStackTrace();
				return false;
			}
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Wait for the uploaded screenshot(s) to be done.
	 * @return
	 * @throws Exception
	 */
	public boolean waitForUploaded() throws Exception
	{
		for (int i=0; i<60*4; i++)
		{ //wait up to 2min? May 500 error and break with spinner.
			try {
				q(".pilltabgroup.disabled");
				Thread.sleep(500);
			} catch (NoSuchElementException e) {
				return true;
			}
		}
		throw new Exception("Upload didn't complete");
	}
	
	public void createNewApp(AppData data) throws Exception
	{
		ProcessBuilder produce = new ProcessBuilder( "fastlane", "produce", "create",
				"-u", "appdev@sharefaith.com",
				"--sku", data.getId(),
				"--app_identifier", data.getId(),
				"--app_version", data.getShortVersion(),
				"--app_name", data.getTitle(),
				"-m", "English",
				"--skip_devcenter"
				);
		produce.environment().put("FASTLANE_PASSWORD", PWD);
		produce.redirectOutput(ProcessBuilder.Redirect.INHERIT); //output the output.
		if (0 != produce.start().waitFor())
		{
			//Note that this DOESNT fail if already exists!
			throw new Exception("Fastlane Produce failed - does the app name already exist??");
		} else {
			//AppserveRecorder.recordBuildCompleted( data.getNumericId() );
		}
		
		this.startNewVersion(data, false);
	}
	
	/* Creates new app */
	@Deprecated
	public void createNewAppSelenium(AppData data) throws Exception
	{
		
		String name = data.getTitle();
		String bundleID = data.getId();
		
		this.reLoginIfNecessary();
		this.browser.get(APPLISTINGURL);
		//Wait for all ajax stuff to load
		
		this.waitAtPage(".left-side .new-button", APPLISTINGURL);
		this.q(".left-side .new-button").click();
		
		this.waitAtPage( NEWAPPSTART_BTN, APPLISTINGURL);
		this.q( NEWAPPSTART_BTN ).click();
		if( this.dismissFakeDialog() ) 
		{
			this.waitFor(".left-side .new-button");
			this.q(".left-side .new-button").click();
			this.waitFor( NEWAPPSTART_BTN);
			this.q( NEWAPPSTART_BTN ).click();
		}
		//Wait for the dialog to load content:
		this.waitFor("[show='modalcontent.showCreateNewAppModal'] [role=dialog] >.modal-dialog >.ng-scope:first-child:not(.loading)");
		this.waitFor(CREATE_APPWIN_NAMEINPUT);//dialog should pop up. fill in:
		this.q(CREATE_APPWIN_NAMEINPUT).sendKeys( name );
		this.q("input[ng-model=\"createAppDetails.newApp.vendorId.value\"]").sendKeys(bundleID);//#SKU
		
		WebElement sel = this.q("select[ng-model=\"createAppDetails.newApp.primaryLanguage.value\"] option[value=\"6\"]");
		if (sel.getText().equals("English"))
		{
			sel.click();
		}
		else
		{
			SendMailTLS.mail("What, no english option on iTunes-connect anymore?");
			throw new Exception("What, no English?");
		}
		
		try {
			this.q("select[ng-model=\"createAppDetails.newApp.bundleId.value\"] option[value=\""+bundleID+"\"]").click();
			//this.q("input[ng-model=\"createAppDetails.versionString.value\"]").sendKeys(data.getShortVersion());

			this.q( "[ng-show=storePlatformIOS] a.checkboxstyle" ).click();
			
			this.waitFor(CREATE_APPWIN_CREATEBTN);//now create new:
			this.q(CREATE_APPWIN_CREATEBTN).click();
		}
		catch (NoSuchElementException e)
		{
			System.out.println( "Trying again in a min." );
			this.q( "button[ng-click='modalcontent.showCreateNewAppModal = false']" ).click();
			Thread.sleep(1000*60);
			try //Maybe user went to correct one started:
			{
				this.q( FIRSTIOSAPP_SIDEBAR );//.click();
			} catch( Exception ex)
			{
				createNewApp(data);
				return;
			}
		}
		
		
		try
		{
			//Necessary?? Should be checked already
			this.q(".pagemessage.error >[ng-repeat=\"error in errorText\"]");
			SendMailTLS.mail("Already an app on iTunes:"+name+"\n" + data.toString() );
			throw new Exception("Already an app on iTunes: "+name);
		}
		catch (NoSuchElementException e)
		{
			//good!
			//this.waitFor("[ng-show=\"versionInfo.details.value[currentLoc].description.isEditable\"] textarea"); 
			// #Once page is loaded, fill in details:
			this.waitFor( FIRSTIOSAPP_SIDEBAR );
			String currentUrl = this.browser.getCurrentUrl();
			if( currentUrl.equals("https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa/ra/ng") )
			{
				throw new Exception("Returned to front screen");
			}
			this.fillInTopLevel(data);

			this.waitAtPage( FIRSTIOSAPP_SIDEBAR, currentUrl);
			
			this.q( FIRSTIOSAPP_SIDEBAR ).click();
			try {
				currentUrl = currentUrl + "/ios/versioninfo";
				this.fillIn(data);
			}
			catch( Exception exc) {
				this.reLoginIfNecessary();
				this.browser.get( currentUrl );
				this.fillIn( data );
			}
			//Since it seems to no longer input the version we want, but defaults 1.0:
			this.waitAtPage( VERSION_SEL, currentUrl );
			this.q( VERSION_SEL ).clear();
			this.q( VERSION_SEL ).sendKeys(data.getShortVersion());

			//String editAppPage = this.browser.getCurrentUrl();
			
			//Clicking manual for now
			this.q("[text=\"Manually release this version\"] .radiostyle").click();
			this.saveChanges();
			
			this.waitAtPage(".pane-layout-sidebar a[href$='/pricing']", currentUrl);
			this.q(".pane-layout-sidebar a[href$='/pricing']").click();
			this.waitAtPage( "[itc-pop-up-menu='tierSelectionID']", currentUrl.replace("/ios/versioninfo","")+"/pricing");
			this.q("[itc-pop-up-menu='tierSelectionID']").click();
			WebElement freeOption = this.q("#tierSelectionID .popupmenuinner tr:first-child td");
			if( freeOption.getText().trim().indexOf( "Free" ) > -1 )
			{
				freeOption.click();
			} else {
				throw new Exception( "Where is free option?" );
			}
			this.q( "[ng-click='savePricingDetails()']" ).click();
			this.waitFor( "[ng-click='savePricingDetails()']:not(.in-progress)" );
			//this.browser.get( editAppPage );
			//this.q( FIRSTIOSAPP_SIDEBAR ).click();//not needed?
		}
	}
	
	/**
	 * When needed, dismiss bogus blank "agreement update" dialog... why?
	 */
	private boolean dismissFakeDialog()
	{
		try
		{
			this.q("[ng-click='closeContractAnnouncementsModal();']").click();
			return true;
		} catch (Exception e) 
		{
			return false;
		}
	}
	
	public void startNewVersion(AppData data, boolean isUpdate) throws IOException, InterruptedException
	{
		File deliverDir = new File(data.getDataDir(), "deliver");
		deliverDir.mkdir();
		File metaDir = new File(data.getDataDir(), "deliver/metadata/");
		metaDir.mkdir();
		File usMetaDir = new File( metaDir, "en-US");
		usMetaDir.mkdir();
		File screenshotDir = new File(data.getDataDir(), "deliver/screenshots/en-US/");
		screenshotDir.mkdirs();
		
		boolean success = false;
		ProcessBuilder deliver;
		
		if( !data.isBinaryOnly())
		{
			for (String num : screenShotsToUse())
			{
				/*if( new File(data.getDataDir(), "images/phonescreen-"+num+".png").exists() )
				{
					Files.copy( new File(data.getDataDir(), "images/phonescreen-"+num+".png"),
							new File(screenshotDir, num+"phonescreen-"+num+".png"));
				}
				if( new File(data.getDataDir(), "images/phonescreen4-"+num+".png").exists() )
				{
					Files.copy( new File(data.getDataDir(), "images/phonescreen4-"+num+".png"),
							new File(screenshotDir, num+"phonescreen4-"+num+".png"));
				}
				if( new File(data.getDataDir(), "images/phonescreen35-"+num+".png").exists() )
				{
					Files.copy( new File(data.getDataDir(), "images/phonescreen35-"+num+".png"),
							new File(screenshotDir, num+"_iphone35.phonescreen35-"+num+".png") );
				}
				if( new File(data.getDataDir(), "images/tabletscreen-"+num+".png").exists() )
				{
					Files.copy( new File(data.getDataDir(), "images/tabletscreen-"+num+".png"),
							new File(screenshotDir, num+"tabletscreen-"+num+".png"));
				}*/
				if( new File(data.getDataDir(), "images/tabletscreen129-"+num+".jpg").exists() )
				{
					Files.copy( new File(data.getDataDir(), "images/tabletscreen129-"+num+".jpg"),
							new File(screenshotDir, num+"tabletscreen-"+num+".jpg"));
				}
				if( new File(data.getDataDir(), "images/phonescreen55-"+num+".jpg").exists() )
				{
					Files.copy( new File(data.getDataDir(), "images/phonescreen55-"+num+".jpg"),
							new File(screenshotDir, num+"phonescreen-"+num+".jpg"));
				}
			}
			if( isUpdate )
			{
				/* Fastlane used to not have overwrite screenshots option.
				 * Seems it needs to have 'use high res' checked with this:
				 */
				ProcessBuilder clearScreenshots = new ProcessBuilder( "ClearScreenshots.rb", 
						data.getId(), data.getShortVersion() );
				clearScreenshots.redirectOutput(ProcessBuilder.Redirect.INHERIT); //output the output.
				if (0 != clearScreenshots.start().waitFor())
				{
					System.err.println("Clear screenshots for update failed - see https://github.com/fastlane/fastlane/issues/1529");
					System.err.println("If you have not set the path, you must run:");
					System.err.println("sudo ln -s ~/Documents/Church_Apps/deployment/ClearScreenshots.rb /usr/local/bin/ClearScreenshots.rb");
					System.err.println("sudo chmod +x /usr/local/bin/ClearScreenshots.rb");
				}
			}
		}
		
		String whatsNew = getFile(data.getDataDir().getParentFile(), "whats_new.txt");
		
		//See https://github.com/fastlane/deliver/blob/master/Deliverfile.md
		Files.write("app_identifier \""+ data.getId() + "\" \nusername \"appdev@sharefaith.com\""+
				"\napp_version \""+data.getShortVersion()+"\" "+
				"\napp_icon \"../images/storeicon-1024x1024.png\" "+
				"\nprice_tier 0"+
				(!isUpdate ? "\napp_rating_config_path \"rating.json\" " : "" )+ //rating must set if first run.
				"\napp_review_information("+
				"\n  first_name: \"Anthony\", "+
				"\n  last_name: \"Kaiserman\", "+
				"\n  email_address: \"appdev@sharefaith.com\", "+
				"\n  phone_number: \"888-317-4018\" "+
				"\n)"+
				"\nautomatic_release "+ (isUpdate ? "true" : "false") + "\n"+
				(isUpdate ? "release_notes({\n  \"default\" => \""+whatsNew+"\"\n})" : "")
				,
				new File(deliverDir, "Deliverfile"), Charset.defaultCharset() );
		if( !isUpdate ) //Not necessary to re-set on update:
		{
			Files.write("{"+
				  "\"CARTOON_FANTASY_VIOLENCE\": 0," +
				  "\"REALISTIC_VIOLENCE\": 0," +
				  "\"PROLONGED_GRAPHIC_SADISTIC_REALISTIC_VIOLENCE\": 0," +
				  "\"PROFANITY_CRUDE_HUMOR\": 0," +
				  "\"MATURE_SUGGESTIVE\": 0," +
				  "\"HORROR\": 0," +
				  "\"MEDICAL_TREATMENT_INFO\": 0," +
				  "\"ALCOHOL_TOBACCO_DRUGS\": 0," +
				  "\"GAMBLING\": 0," +
				  "\"SEXUAL_CONTENT_NUDITY\": 0," +
				  "\"GRAPHIC_SEXUAL_CONTENT_NUDITY\": 0," +
				  
				  "\"UNRESTRICTED_WEB_ACCESS\": 0," +
				  "\"GAMBLING_CONTESTS\": 0" +
				"}",
				new File(deliverDir, "rating.json"), Charset.defaultCharset() );
		}
		String year = String.valueOf( Calendar.getInstance().get(Calendar.YEAR) );
		Files.write("Copyright Sharefaith " + year + ", Content Copyright " + year + ", " + data.getTitle() , new File(metaDir, "copyright.txt"), Charset.defaultCharset());
		Files.write("MZGenre.Lifestyle", new File(metaDir, "primary_category.txt"), Charset.defaultCharset() );
		
		Files.copy(new File(data.getDataDir(), "description.txt"), new File(usMetaDir, "description.txt"));
		
		String keywords = getFile(data.getDataDir(), "keywords.txt");
		if(keywords.length() > 100)
		{
			keywords = keywords.substring(0, 100);
		}
		Files.write(keywords, new File(usMetaDir, "keywords.txt"), Charset.defaultCharset() );
		//Files.copy(new File(data.getDataDir(), "keywords.txt"), new File(usMetaDir, "keywords.txt"));
		
		Files.write("http://www.sharefaith.com/category/church-websites.html", new File(usMetaDir, "support_url.txt"), Charset.defaultCharset());
		Files.write(data.getTitle(), new File(usMetaDir, "name.txt"), Charset.defaultCharset());
		while( !success )
		{
			if( data.isBinOnly() )
			{
				deliver = new ProcessBuilder( "fastlane", "deliver", 
						"run", "--force", "--skip_screenshots", "true" );
			} else {
				deliver = new ProcessBuilder( "fastlane", "deliver", 
					"run", "--force", "--overwrite_screenshots", "true" );
			}
			deliver.environment().put("FASTLANE_PASSWORD", PWD);
			//Note you MUST have the Deliverfile in the working directory!
			deliver.directory( deliverDir );
			deliver.redirectOutput(ProcessBuilder.Redirect.INHERIT); //output the output.
			if (0 != deliver.start().waitFor())
			{
				System.err.println("Fastlane deliver failed.");
				System.err.println("Trying again in about 1min");
				Thread.sleep(1000*70);
			} else {
				success = true;
				AppserveRecorder.recordBuildCompleted( data.getNumericId() );
				//System.out.println("Should delete: " + data.getDataDir().getCanonicalPath() + "/deliver" );
				new ProcessBuilder( "rm", "-rf", data.getDataDir().getCanonicalPath() + "/deliver" ).start();
			}
		}
	}

	/**
	 * Assuming on the page with new-version button, starts new version.
	 * TODO test
	 * @param AppData - from which to grab data-dir, versionstring
	 * @throws Exception
	 */
	@Deprecated
	public void startNewVersionBrowser(AppData app) throws Exception
	{
		String currentPage = this.browser.getCurrentUrl();
		String versionstring = app.getShortVersion();
		this.waitAtPage( FIRSTIOSAPP_SIDEBAR , currentPage);
		if ( this.q( FIRSTIOSAPP_SIDEBAR).getText().trim().contains( app.getShortVersion() ) )
		{
			System.err.println("Already set at "+app.getShortVersion());
			this.q( FIRSTIOSAPP_SIDEBAR ).click();
		} else {
			try //the case of submit rejected:
			{
				this.q(".pagemessage.error:not(.ng-hide) a[href*='/resolutioncenter']");
				this.q("input[itc-field-orig-val='orignalVersionInfo.version.value']").clear();
				this.q("input[itc-field-orig-val='orignalVersionInfo.version.value']").sendKeys(versionstring);
			}
			catch (NoSuchElementException e)//normal:
			{
				waitAtPage( "a.newVersion_link", currentPage );
				try
				{
					q("a.newVersion_link").click();//new-version button when available:
					q("[ng-click=\"openVersionModal(platform.platform)\"]:not(.ng-hide)").click();
					waitAtPage( NEWVERSIONNUM_INPUT, currentPage );
					q( NEWVERSIONNUM_INPUT ).sendKeys(versionstring);
					q(".ng-modal:not(.ng-hide)[show=\"modalsDisplay.newVersion\"] .modal-buttons button.primary").click();
				} catch (ElementNotVisibleException err) //Started but not finished setup, no canAddNewVersion btn
				{
					System.err.println( "Not-visible exception in making new-version " );
				} catch ( NoSuchElementException nfe )
				{
					this.q( FIRSTIOSAPP_SIDEBAR ).click();
					this.waitFor("#localizationSection");
					try
					{
						this.q( "#devRejectID" ).click();
						this.waitAtPage( "[ng-click='devRejectApp()']", currentPage );
						this.q( "[ng-click='devRejectApp()']" ).click();
					}
					catch (Exception noprob)
					{
						//pass
					}
					this.waitAtPage("input[itc-field-orig-val='orignalVersionInfo.version.value']", currentPage);
					this.q("input[itc-field-orig-val='orignalVersionInfo.version.value']").clear();
					this.q("input[itc-field-orig-val='orignalVersionInfo.version.value']").sendKeys(versionstring);
				}
			}
			this.reLoginIfNecessary();
			//currentPage = this.browser.getCurrentUrl();
			waitAtPage( STARTSUBMIT_BTN , currentPage+"/ios/versioninfo");
		}
		//waitFor("textarea.ng-valid.extraTall");
		
		//Data should be updated now, set some metadata:
		//String updateMsg = //file
		BufferedReader br = new BufferedReader(new FileReader( app.getWhatsNewiOS() ));
		StringBuilder updateMsg = new StringBuilder();
        String line = br.readLine();
        while (line != null) {
            updateMsg.append(line);
            updateMsg.append("\n");
            line = br.readLine();
        }
		waitAtPage(RELEASENOTES_SEL, currentPage+"/ios/versioninfo");
		WebElement newVersionText = q(RELEASENOTES_SEL);
		try {
			newVersionText.clear();
			newVersionText.sendKeys( updateMsg );
		}
		catch (StaleElementReferenceException e)
		{
			Thread.sleep(5000);
			newVersionText = q(RELEASENOTES_SEL); //In case of staleelement exception
			try {
				newVersionText.clear();
				newVersionText.sendKeys( updateMsg ); //TODO fails when no release-notes on existing setup.
			}
			catch (ElementNotVisibleException err)
			{
				//pass, resubmission one.
			}
		}
		catch (ElementNotVisibleException e)
		{
			Thread.sleep(10000);
			newVersionText = q(RELEASENOTES_SEL); //In case of staleelement exception
			try {
				newVersionText.clear();
				newVersionText.sendKeys(updateMsg); //TODO fails when no release-notes on existing setup.
			}
			catch (ElementNotVisibleException err)
			{
				//pass, resubmission one.
			}
		} catch (UnhandledAlertException uae)
		{
			this.browser.switchTo().alert().accept();
			//Try again:
			this.waitAtPage( FIRSTIOSAPP_SIDEBAR , currentPage+"/ios/versioninfo");
			startNewVersion( app, true );
			return;
		}
		
		if( !app.isBinaryOnly() )
		{
			try
			{
				//Now it should allow update data:
				replaceData(app); // which saves.
			} catch( Exception uae2)
			{
				this.browser.switchTo().alert().accept();
				//Try again:
				this.waitAtPage( FIRSTIOSAPP_SIDEBAR , currentPage+"/ios/versioninfo");
				startNewVersion( app, true );
				return;
			}
		}
		//Clicking automatic:
		this.q("[text='Automatically release this version'] a").click();
		this.saveChanges();
	}
	
	/**
	 * Save changes for the version screen (not the app-information app-store-information)
	 * @throws Exception
	 */
	public void saveChanges() throws Exception
	{
		String currentUrl = this.browser.getCurrentUrl();
		q( "[ng-click=\"saveVersionDetails()\"]" ).click();
		waitAtPage( "[ng-click=\"saveVersionDetails()\"]:not(.in-progress)", currentUrl );
		try
		{
			q(".pagemessage.error:not(.ng-hide)");
			System.err.println("Error msg");
			if (q(".pagemessage.error:not(.ng-hide)").getText()
				.indexOf("unresolved iOS") !=-1)
			{
				throw new NoSuchElementException("Error message on page.");
				
				//Try once more in the case of temporarily-unable-to-save-changes, it may recover:
				/* Can no longer save in error state iTunesConnect Sep 24 2015
				Thread.sleep(20000);
				q("[ng-click=\"saveVersionDetails()\"]").click();
				waitFor("[ng-click=\"saveVersionDetails()\"]:not(.in-progress)");
				try 
				{
					q(".pagemessage.error:not(.ng-hide)");
					//Fail on non-this-was-rejected error message:
					throw new Exception("Error message on page.");
				} catch (NoSuchElementException nsee)
				{
					//pass
				}*/
			} else if ( q(".pagemessage.error:not(.ng-hide)").getText()
				.indexOf("temporarily unable to save") !=-1 )
			{
				Thread.sleep(1000 * 60);
				this.saveChanges();
			}
		} catch (NoSuchElementException e)
		{
			//pass
		}
	}
	
	/**
	 * Fill in all the toplevel app-information
	 * @param data
	 * @throws Exception
	 */
	@Deprecated
	public void fillInTopLevel(AppData data) throws Exception
	{
		String currentUrl = browser.getCurrentUrl();
		try
		{
			this.waitFor("[ng-model=\"appInfoDetails.primaryCategory.value\"] option[value=\"9\"]");
			WebElement selcategory = this.q("[ng-model=\"appInfoDetails.primaryCategory.value\"] option[value=\"9\"]");
			if( selcategory.getText().trim().equals("Lifestyle"))
			{
				selcategory.click();
			}
			else
			{
				throw new Exception("What, no lifestyle app category?");
			}
			
			this.saveTopLevelChanges();
		} catch (UnhandledAlertException uae)
		{
			try
			{
				browser.switchTo().alert().accept();
			} catch (NoAlertPresentException nope)
			{
				//make up your mind Selenium!
				//pass
			}
			//Retry
			this.reLoginIfNecessary();
			this.browser.get( currentUrl );
			this.fillInTopLevel( data );
		}
	}
	
	/**
	 * Fill in all the data a new app version on itunesConnect needs.
	 * @param data
	 * @throws Exception
	 */
	@Deprecated
	public void fillIn(AppData data) throws Exception
	{
		String url = this.browser.getCurrentUrl();
		
		File dir = data.getDataDir();
		setDescKeywords(dir, url);
		this.q("[ng-show=\"versionInfo.details.value[currentLoc].supportURL.isEditable\"] >input").clear();
		this.q("[ng-show=\"versionInfo.details.value[currentLoc].supportURL.isEditable\"] >input").sendKeys( " " );
		this.q("[ng-show=\"versionInfo.details.value[currentLoc].supportURL.isEditable\"] >input").clear();
		this.q("[ng-show=\"versionInfo.details.value[currentLoc].supportURL.isEditable\"] >input").sendKeys( SUPPORTURL );
		this.q("[ng-show=\"versionInfo.copyright.isEditable\"] >input[type=text]").sendKeys("Sharefaith");
		this.q("[ng-show=\"versionInfo.appReviewInfo.firstName.isEditable\"] >input[type=text]").sendKeys("Chris");
		this.q("[ng-show=\"versionInfo.appReviewInfo.lastName.isEditable\"] >input[type=text]").sendKeys("Ostmo");
		this.q("[ng-show=\"versionInfo.appReviewInfo.phoneNumber.isEditable\"] >input[type=text]").sendKeys("888-317-4018");
		this.q("input[ng-required=\"versionInfo.appReviewInfo.emailAddress.isRequired\"]").sendKeys("appdev@sharefaith.com");
		
		//#Select all the correct ratings:
		this.q("a[ng-show=\"versionInfo.ratings.isEditable\"]").click();
		for (WebElement element : this.browser.findElements(By.cssSelector("[radio-value=\"ITC.apps.ratings.level.NONE\"] a")))
		{
			element.click();
		}
		for (WebElement element : this.browser.findElements(By.cssSelector("[radio-value=\"ITC.apps.ratings.level.NO\"] a")))
		{
			element.click();
		}
		//Close rating dialog:
		this.q(RATING_MODAL_CLOSEBTN).click();
		this.saveChanges();
		if( !data.isBinOnly() )
		{
			uploadScreenshots(dir);
		}
		uploadAppIcon(dir);
		this.saveChanges();
	}
	
	/**
	 * Replaces description, screenshots, app-icon, based on the app folder contents.
	 * @param data
	 * @throws Exception
	 */
	@Deprecated
	public void replaceData(AppData data) throws Exception
	{
		File dir = data.getDataDir();
		setDescKeywords(dir, this.browser.getCurrentUrl());
		uploadScreenshots(dir);
		
		//Re-set the logo:
		((JavascriptExecutor)browser).executeScript(" jQuery('.appversionicon .deleteButton').removeClass('ng-hide') ");//shown, now can click:
		for (WebElement el : this.qAll(".appversionicon .deleteButton"))
		{
			try {
				el.click();
			} catch (ElementNotVisibleException err) {
				System.out.println("A Delbtn not visible - app icon not uploaded?");
			}
		}
		uploadAppIcon(dir);
		
		//TODO set whatever url we want for support
		WebElement supportURLEl = q("[ng-show=\"versionInfo.details.value[currentLoc].supportURL.isEditable\"] >input");
		supportURLEl.clear();
		supportURLEl.sendKeys("http://www.sharefaith.com/category/church-websites.html");
		
		//Manually release: (click <a> after because it is visible)
		//q("[itc-radio=\"versionInfo.releaseOnApproval.value\"][radio-value=false] input ~a").click();
		saveChanges();
	}

	@Deprecated
	private void uploadAppIcon(File dir) throws Exception
	{
		//Make uploadable by bringing into view:
		((JavascriptExecutor)browser).executeScript(" jQuery('[ng-show=\"versionInfo.largeAppIcon.isEditable\"] input[type=file]').css('left','0').show() ");
		Thread.sleep(1000);
		try
		{
			q("[url=\"tempPageContent.appIconDisplayUrl\"] input[type=file]").sendKeys(//upload:
				new File(dir, "images/storeicon-1024x1024.png").getAbsolutePath() );
			try
			{
				this.q(".inputWrapper.iconDrop:not(.ng-hide) >.appversionicon.zone.invalid");//yikes!
				Thread.sleep( 1000*30 );
				throw new ElementNotVisibleException("");
			} catch (NoSuchElementException e) 
			{
				//pass
			}
		} catch (ElementNotVisibleException e)
		{
			// There should be Only ONE, not both [ng-hide=fileInProgress] >.ng-hide .hideOverflow.ios7-style-icon
			this.saveChanges();//reload:
			this.browser.get( this.browser.getCurrentUrl() );
			this.waitFor("textarea");
			System.err.println("Appupload messed up, reloading.");
			//try again:
			uploadAppIcon(dir);
			return;
			//q("[url=\"tempPageContent.appIconDisplayUrl\"] input[type=file]").sendKeys(//upload:
			//	new File(dir, "images/storeicon-1024x1024.png").getAbsolutePath() );
		}
		waitFor("[ng-show=\"fileInProgress\"].ng-hide");
		//wait while q("ng-show="fileInProgress"") visible?
	}

	/**
	 * Waits for description element, sets textarea and keywords from file.
	 * @param dir
	 * @param String url - url to go if it jumps.
	 * @throws IOException
	 */
	private void setDescKeywords(File dir, String url ) throws Exception
	{
		String description = getFile(dir, "description.txt");
		this.waitAtPage( DESCRIPTIONTEXTAREA_SEL, url ); //bubble up exception to re-try
		WebElement descriptionEl = q( DESCRIPTIONTEXTAREA_SEL );
		try
		{
			descriptionEl.clear();
			descriptionEl.sendKeys(description);
		}
		catch (StaleElementReferenceException serf) //weird try again:
		{
			descriptionEl = q( DESCRIPTIONTEXTAREA_SEL );
			descriptionEl.clear();
			descriptionEl.sendKeys(description);
		}
		catch (ElementNotVisibleException enve)
		{
			try {
				Thread.sleep(10000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			try {//Why does descriptionEl give StaleElementReferenceException: Element is no longer attached to the DOM when it's there????
				waitFor( DESCRIPTIONTEXTAREA_SEL, true);
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			descriptionEl = q( DESCRIPTIONTEXTAREA_SEL );
			descriptionEl.clear();
			descriptionEl.sendKeys(description);
		}
		
		String keywords = getFile(dir, "keywords.txt");
		try {
			WebElement keywordsEl = q("[ng-show=\"versionInfo.details.value[currentLoc].keywords.isEditable\"] >input");
			keywordsEl.clear();
			keywordsEl.sendKeys(keywords);
		} catch (ElementNotVisibleException e) {
			//NA can't update? can happen when updating app.
		}
	}
	
	
	/**
	 * Clears, uploads phone, tablet screenshots.
	 * @param dir
	 * @throws Exception
	 */
	public void uploadScreenshots(File dir) throws Exception
	{
		//TODO make this detect right:
		boolean uploadErr = false; //Check with each tab section because .loadError only there for the selected one.
		
		ArrayList<String> screenshotnums = screenShotsToUse();
		
		String url = this.browser.getCurrentUrl();
		
		boolean success = false;
		while( !success )
		{
			clearCurrentScreenshotsSection();
			try
			{
				for (String num : screenshotnums)
				{
					if( uploadIfExists(new File(dir, "images/phonescreen-"+num+".png") ) )
					{
						this.waitForUploaded();
					}
				}
			
				if (this.qAll( FAILED_SCREENSHOT_SEL ).size() < 1)
				{
					success = true;
				} else {
					Thread.sleep(1000*60);
					this.forceReloadSubmissionPage( url );
				}
			} catch (Exception e) //todo more specific
			{
				this.forceReloadSubmissionPage( url );
			}
		}
		this.saveChanges();
		
		success = false;
		while( !success )
		{
			this.waitFor(".pilltabgroup li[role=menuitem][value=ipad]");
			this.q(".pilltabgroup li[role=menuitem][value=ipad]").click();
			//Wait for switch animation or it seems clear-screenshots will think something's there
			Thread.sleep(1000);
			clearCurrentScreenshotsSection();
			Thread.sleep(300);
			try
			{
				for (String num : screenshotnums) {
					uploadIfExists(new File(dir, "images/tabletscreen-"+num+".png"));
					this.waitForUploaded();
				}
				if (this.qAll( FAILED_SCREENSHOT_SEL ).size() < 1)
				{
					success = true;
				} else {
					Thread.sleep(1000*60);
					this.forceReloadSubmissionPage( url );
				}
			}
			catch (Exception e) //todo more specific
			{
				this.forceReloadSubmissionPage( url );
			}
		}
		this.saveChanges();
		
		success = false;
		while( !success )
		{
			this.waitFor(".pilltabgroup li[role=menuitem][value=iphone4]");
			this.q(".pilltabgroup li[role=menuitem][value=iphone4]").click();
			Thread.sleep(1000);
			clearCurrentScreenshotsSection();
			try
			{
				for (String num : screenshotnums)
				{
					uploadIfExists(new File(dir, "images/phonescreen4-"+num+".png"));
					waitForUploaded();
				}
				if (this.qAll( FAILED_SCREENSHOT_SEL ).size() < 1)
				{
					this.saveChanges();
					success = true;
				} else {
					Thread.sleep(1000*60);
					this.forceReloadSubmissionPage( url );
				}
			} catch (Exception e) //todo more specific
			{
				this.forceReloadSubmissionPage( url );
			}
		}
		
		success = false;
		while( !success )
		{
			this.waitFor(".pilltabgroup li[role=menuitem][value=iphone35]");
			this.q(".pilltabgroup li[role=menuitem][value=iphone35]").click();
			Thread.sleep(1000);
			if( this.browser.findElements(By.className("imageHolder")).size() > 1 )
			{
				success = true;
				//Do not clear out when there are >1, custom was resized, clipped, added.
			}
			 else 
			{
				clearCurrentScreenshotsSection();
				try
				{
					for( String num : screenshotnums )
					{
						uploadIfExists( new File( dir, "images/phonescreen35-" + num + ".png" ) );
					}
				
					waitForUploaded();
					if (this.qAll( FAILED_SCREENSHOT_SEL ).size() < 1)
					{
						success = true;
					} else {
						uploadErr = true;
						this.forceReloadSubmissionPage( url );
						uploadIfExists(new File(dir, "images/launch-640x960.png"));
					}
				} catch (Exception e)
				{
					this.forceReloadSubmissionPage( url );
				}
				if (uploadErr)
				{
					SendMailTLS.mail("Check Images Please", "Looks like this page may be having upload difficulty:\n" + this.browser.getCurrentUrl() );
				}
			}
		}
	}
	
	/**
	 * Gets the overall view of all apps.
	 * TODO also make a checker of build statuses and broken builds - https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa/ra/apps/1123082164/buildHistory?platform=ios
	 * @return
	 * @throws Exception
	 */
	public JSONObject getAllAppsRequest() throws Exception
	{
		JSONObject itcData = null;
		Set<Cookie> cookies;
		
		//Make direct request with cookies that the page would do - 200 status means ok, logged in:
		try {
			//Buggy Selenium, didn't do this before...
			cookies = this.browser.manage().getCookies();
		} catch( Exception e ){
			Thread.sleep(10000);
			cookies = this.browser.manage().getCookies();
		}
		StringBuilder cookiestr = new StringBuilder();
		boolean first = true;
		for ( Cookie c : cookies)
		{
			if (c.getDomain().indexOf("apple.com") >= 0)
			{
				if (!first)
				{
					cookiestr.append("; ");
				}
				cookiestr.append( c.getName()+"="+c.getValue() );
				first = false;
			} else {
				System.out.println(c.getName()+"="+c.getValue());
			}
		}
	
		//User-only-request from app listing here:
		//URL myUrl = new URL("https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa/ra/apps/services/universalPurchaseServices");
		//TODO this should be quicker than using the frontpage.
		URL myUrl = new URL("https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa/ra/apps/manageyourapps/summary/v2");
		
		HttpURLConnection urlConn = (HttpURLConnection)myUrl.openConnection();
		urlConn.setConnectTimeout( 60* 1000 );
		urlConn.setRequestProperty("Cookie", cookiestr.toString());
		System.out.println(cookiestr.toString());
		urlConn.setRequestProperty("Accept-Language","en-US,en;q=0.5");
		urlConn.setRequestProperty("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:37.0) Gecko/20100101 Firefox/37.0");
		urlConn.connect();
		int code = urlConn.getResponseCode();
		if (code == 200)
		{
			BufferedReader br = new BufferedReader(new InputStreamReader((urlConn.getInputStream())));
			StringBuilder sb = new StringBuilder();
			String output;
			while ((output = br.readLine()) != null)
			{
				sb.append(output);
			}
			//Now we have the output (the json at that url)
			// Hint:
			//  python -m json.tool ./v2.json
			//  is good way to prettyprint these.
			itcData = new JSONObject( sb.toString() );

		} else {
			System.err.println("Not logged in?");
		}
		return itcData;
		
	}
	
	private void forceReloadSubmissionPage( String url )
	{
		if( url == null) url = browser.getCurrentUrl();
		System.err.println( "Have to refresh on " + url );
		//Reload, try again:
		((JavascriptExecutor)browser).executeScript(" jQuery(window).off('beforeunload') ");//Ignore need-save-change that will never finish.
		this.reLoginIfNecessary();
		browser.get( url );
		try
		{
			this.waitFor( FIRSTIOSAPP_SIDEBAR );
			this.q( FIRSTIOSAPP_SIDEBAR ).click();
		} catch (Exception e)
		{
			e.printStackTrace();
			this.forceReloadSubmissionPage( url );
		}
	}

	public void publishBuild(String build, String appUrl) throws Exception
	{
		Thread.sleep(4000);
		long start = System.currentTimeMillis();
		long duration;
		boolean showing = false;
		while (!showing)
		{
			this.waitAtPage( FIRSTIOSAPP_SIDEBAR, appUrl);
			this.q( FIRSTIOSAPP_SIDEBAR ).click();
			try {
				q("a[ng-click=\"removeBuild()\"]").click();
			} catch (NoSuchElementException e) {
				//pass
			} catch (ElementNotVisibleException e) {
				//pass
			}
			
			try
			{
				q("[ng-click=\"showBuildPicker()\"]").click();
				showing = true; //and continue...
				duration = System.currentTimeMillis() - start;
				System.out.println("To show up took "+String.valueOf(duration/1000)+" seconds.");
				
				//Bad! reload if disabled-processing: (since apparently it won't check with ajax here.
				try
				{
					browser.findElement(By.xpath( "//td[contains(..,'" + build + "')]//a[@class='radiostyle disabled']" ));
					throw new Exception( "disabled-processing" );
				} catch (Exception e) {
					this.q(".right-buttons [ng-click='closeBuildModal(false)']").click();
					//pass
				}
			} catch (Exception e) {
				throw new Exception("No build yet");
				//NA. Wait awhile.
				/*Thread.sleep((long) (1000*15 + Math.random()*60*1000));
				this.waitAtPage( FIRSTIOSAPP_SIDEBAR, appUrl );
				this.q( FIRSTIOSAPP_SIDEBAR ).click();
				//q(".modal-dialog [ng-click=\"closeBuildModal(false)\"]").click();
				waitFor("textarea");//ajax loaded.*/
			}
		}
		
		
		//Xpath (Test with $x("//td[contains(.,'1.0.4.1')]") in Chrome/Firefox console.)
		//The <a> link in row that contains the target build version:
		showing = false;
		if (!showing) // or while
		{
			try
			{
				//q("[ng-click=\"showBuildPicker()\"]").click();
				Thread.sleep(1500);
				//Why does it not, when it's clearly there?
				browser.findElement(By.xpath("//td[contains(..,'"+build+"')]//a[contains(@class,'radiostyle')]")).click();
				showing = true;
			} catch (Exception e)
			{
				//e.printStackTrace();
				//System.out.println("Waiting for build");
				//Thread.sleep(9000);
				//q(".modal-dialog [ng-click=\"closeBuildModal(false)\"]").click();
			}
		}
		
		q("[show=\"modalsDisplay.buildsModal\"] button.primary").click();
		saveChanges();
		Thread.sleep(2000);//for fade
		this.waitAtPage( STARTSUBMIT_BTN , appUrl+"/ios/versioninfo" );
		q( STARTSUBMIT_BTN ).click();
		
		waitFor("[name=\"submitforreview\"] [radio-value=false] a");
		this.waitFor(SUB_FOR_REVIEW_SEL);
		Thread.sleep(3000);
		int radios = 0;
		while( radios < 3 ) //must be 3 checked on final submit.
		{
			List<WebElement> nos = browser.findElements(By.cssSelector("[name=\"submitforreview\"] [radio-value=false] a"));
			radios = nos.size();
			for (WebElement no : nos)
			{
				try
				{
					no.click();
				} catch (ElementNotVisibleException e) {
					//System.err.println("No no-radio?");
				}
			}
		}
		this.waitFor(SUB_FOR_REVIEW_SEL);
		this.q(SUB_FOR_REVIEW_SEL).click();
		Thread.sleep(2000);
		try
		{
			this.q("[ng-show=\"tempPageContent.submittingForReview\"] [name=submitforreview] .pagemessage.error:not(.ng-hide)");
			//visible error. try again:
			System.out.println("Waiting a couple min. before trying submit again.");
			Thread.sleep(1000*120);
			this.q(SUB_FOR_REVIEW_SEL).click();
		
		} catch (Exception e)
		{
			//pass
		}
	}
	
	/**
	 * Clears current visible screenshots-section
	 * @throws Exception when it can't click the delete-all button, none uploaded.
	 */
	private void clearCurrentScreenshotsSection() throws Exception {
		if( browser.findElements(By.className("imageHolder")).size() > 0 ){
			waitFor("[ng-click=\"deleteAllMedia($event, false)\"]:not(.disabled)");
			q(      "[ng-click=\"deleteAllMedia($event, false)\"]" ).click();
		}
	}
	
	/**
	 * Upload a preview-image screenshot, if file exists.
	 * @param path
	 * @throws InterruptedException 
	 */
	private boolean uploadIfExists(File path) throws InterruptedException, InvalidElementStateException
	{
		if (path.exists() && path.isFile())
		{
			q("input#mainDropTrayFileSelect").sendKeys(path.getAbsolutePath());
			Thread.sleep(300);
			return true;
		}
		return false;
	}
	
	/**
	 * Go to url (if necessary) and wait for element.
	 * @param selector
	 * @param url
	 * @throws Exception 
	 */
	public void waitAtPage( String selector, String url ) throws Exception
	{
		try
		{
			if( ! this.browser.getCurrentUrl().equals(url) )
			{
				System.out.println("\nGoing back to "+url+" to look for "+selector+"\n");
				this.reLoginIfNecessary();
				this.browser.get(url);
			}
			Thread.sleep( 1000 );
			for( int i = 0; i < (60 * 2); i++ )
			{
				try
				{
					this.q( selector );
					return;// true;
				}
				catch ( NoSuchElementException e )
				{
					try
					{
						if( ! this.browser.getCurrentUrl().equals(url) )
						{
							//Log in try again.
							this.waitAtPage(selector, url);
						}
					}
					catch( NoSuchElementException ex)
					{
						//okay. wait
						Thread.sleep( 400 );
					}
				}
			}
			//Try once more
			this.reLoginIfNecessary();
			this.browser.get(url);
			for( int i = 0; i < (60 * 2); i++ )
			{
				try
				{
					this.q( selector );
					return;// true;
				}
				catch ( NoSuchElementException e )
				{
					try
					{
						if( ! this.browser.getCurrentUrl().equals(url) )
						{
							//Log in try again.
							this.waitAtPage(selector, url);
						}
					}
					catch( NoSuchElementException ex)
					{
						//okay. wait
						Thread.sleep( 400 );
					}
				}
			}
		} catch ( UnhandledAlertException uae )
		{
			try{
				browser.switchTo().alert().accept();
			} catch (NoAlertPresentException nope)
			{
				//make up your mind Selenium!
				//pass
			}
			//Try again:
			waitAtPage( selector, url );
			return;
		}
		Thread.sleep(1000*60);
		System.err.println("Trying again waitAtPage ");
		waitAtPage( selector, url );
		//throw new Exception( "Not found " + selector );
	}
	
	/**
	 * Search for app with id, goes to its page.
	 * @param id
	 * @return the /app/number url it's on
	 * @throws Exception 
	 */
	public String findApp(String id) throws Exception
	{
		//In case logged out:
		//Waitatpage should handle that - this.reLoginIfNecessary();
		browser.get(APPLISTINGURL);
		
		//try
		//{

			//todo can be stuck here if blank page for some reason.
			waitAtPage("li.app", APPLISTINGURL);
			q("input.search-input").clear();
			q("input.search-input").sendKeys(id+"\n");
			
			Thread.sleep(3000);
			int found = browser.findElements(By.cssSelector("#searchTextResults li.app")).size();
			if (1 == found) {
				String url = q("#searchTextResults li.app .app-icon a").getAttribute("href");
				//q("#searchTextResults li.app .app-icon a").click();
				this.browser.get(url);
				waitAtPage("textarea", url);
				waitAtPage(".sectioncontentwrapper", url);
				return url;
			} else {
				throw new Exception("No app? '"+id+"'");
			}
		/*} catch (Exception e)
		{
			e.printStackTrace();
			//re-log in and try agin?
		}*/
	}
}
