import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.FileUtils;

import os.path.Pathutil;

public class Xcoder implements Runnable {
	
	/* Don't underestimate how long it takes on slow hard drive */
	private static final String LISTWAITSEC = "120";

	private AppData app;
	
	
	public static String SikuliXcode = Pathutil.expanduser("~/Desktop/xcodeauto.sikuli").getAbsolutePath();
	
	public Xcoder(AppData app)
	{
		this.app = app;
	}
	
	/**
	 * Sets the pbxproj file within the xcode project to the proper settings for signing.
	 * @throws IOException
	 */
	private void setProjectDistributionMode() throws IOException
	{
		//Make sure ios-distribution is setting for all of the project:
		File xcodepbx = new File(app.getDataDir().getParentFile(), "Sharefaith ChurchApp/ios/Sharefaith ChurchApp.xcodeproj/project.pbxproj");
		File xcodetmp = new File(app.getDataDir().getParentFile(), "Sharefaith ChurchApp/ios/Sharefaith ChurchApp.xcodeproj/project.pbxtmp");
		BufferedReader bufferedReader = new BufferedReader ( new FileReader ( xcodepbx ) );
		BufferedWriter bufferedWriter = new BufferedWriter ( new FileWriter ( xcodetmp ) );
		String line = null;
		// Read the file line by line
		while ( ( line = bufferedReader.readLine () ) != null )
		{
			if( line.indexOf("[sdk=iphoneos*]") > -1 )
			{
				//This "iPhone Developer" is ok, need DEVELOPMENT_TEAM too apparently:
				bufferedWriter.write(line);
				bufferedWriter.newLine();
				bufferedWriter.write("				DEVELOPMENT_TEAM = DAZH82FKH9;");
				bufferedWriter.newLine();
			} else {
				//Replace only for the main CODE_SIGN_IDENTITY
				bufferedWriter.write(line.replaceAll( "\"iPhone Developer([^\\\"]*)\"", "\"iPhone Distribution\"" ));
				bufferedWriter.newLine();
			}
		}
		bufferedWriter.close();
		bufferedReader.close();
		//overwrite change:
		FileUtils.copyFile(xcodetmp, xcodepbx);
		xcodetmp.delete();	
	}
	
	public static void incrementBuildNumber( AppData app ) throws IOException, InterruptedException
	{
		File infoplist = new File(app.getDataDir().getParentFile(), "Sharefaith ChurchApp/ios/Sharefaith ChurchApp/Sharefaith ChurchApp-Info.plist");
		File tmp = new File(app.getDataDir().getParentFile(), "Sharefaith ChurchApp/ios/Sharefaith ChurchApp/TMPSharefaith ChurchApp-Info.plist");
		BufferedReader bufferedReader = new BufferedReader ( new FileReader ( infoplist ) );
		BufferedWriter bufferedWriter = new BufferedWriter ( new FileWriter ( tmp ) );
		String line = null;
		
		Pattern versionNum = Pattern.compile("<string>(\\d+)\\.(\\d+)\\.(\\d+)</string>");

		ProcessBuilder checkVer = new ProcessBuilder( "getCurrent.rb", app.getId() );
		try
		{
			Process checker = checkVer.start();
			if (0 != checker.waitFor())
			{
				System.err.println("Checkversion failed");
			}
			BufferedReader reader = new BufferedReader(new InputStreamReader(checker.getInputStream()));
			StringBuilder builder = new StringBuilder();
			String tmpline = null;
			while ( (tmpline = reader.readLine()) != null) {
			   builder.append(tmpline);
			   builder.append(System.getProperty("line.separator"));
			}
			String result = builder.toString();
			System.out.println( "Looks like current is " + result );
			
			// Read the file line by line replacing the version
			while ( ( line = bufferedReader.readLine () ) != null )
			{
				Matcher m = versionNum.matcher( line );
				if (m.find())
				{
					Matcher currentOniTunes = versionNum.matcher( result );
					//System.out.println( " +1 on "+ result );
					currentOniTunes.find();
					bufferedWriter.write( "<string>" + currentOniTunes.group(1) + "." + currentOniTunes.group(2) + "." +
						String.valueOf( 1 + Integer.valueOf(currentOniTunes.group(3)) ) + "</string>" );
				} else {
					bufferedWriter.write(line);
				}
				bufferedWriter.newLine();
			}
			bufferedWriter.close();
			bufferedReader.close();
			//overwrite change:
			FileUtils.copyFile(tmp, infoplist);
			tmp.delete();

		}
		catch( IOException err )
		{
			System.err.println("Checkversion failed");
			System.err.println("If you have not set the path, you must run:");
			System.err.println("sudo ln -s ~/Documents/Church_Apps/deployment/getCurrent.rb /usr/local/bin/getCurrent.rb");
			System.err.println("sudo chmod +x /usr/local/bin/getCurrent.rb");
		}
	}
	
	/**
	 * Compile it, do stuff with the super heavy Xcode and Sikuli runner.
	 * @param app
	 * @throws Exception
	 */
	@Deprecated
	private void compileit() throws Exception
	{
		setProjectDistributionMode();
		File xcodeproj = new File(app.getDataDir().getParentFile(), "Sharefaith ChurchApp/ios/Sharefaith ChurchApp.xcodeproj");
		//The equivalent of Python subprocess - [command, args] usage.
		ProcessBuilder xcoderun = new ProcessBuilder("open", xcodeproj.getAbsolutePath());
		xcoderun.redirectOutput(ProcessBuilder.Redirect.INHERIT);//output the output.
		if (0 != xcoderun.start().waitFor()) {
			SendMailTLS.mail("Xcode Automation Sikuli Failed.");
			throw new Exception("Xcode fail");
		}
		//Show debug (-d 3): run sikuli Jython from Sikulix IDE:
		ProcessBuilder automate = new ProcessBuilder(SikuliRunner.Sikuli, "-d 3 -r", SikuliXcode);
		automate.redirectOutput(ProcessBuilder.Redirect.INHERIT);//output the output.
		if (0 != automate.start().waitFor()) {
			throw new Exception("automate fail");
		}
		
		File desktop = Pathutil.expanduser("~/Desktop/");
		for ( Path folder : Files.newDirectoryStream( desktop.toPath() ) ) 
		{
			File archive;
			if( folder.toFile().getName().startsWith( "Sharefaith ChurchApp ") )
			{
				archive = new File( folder.toFile(), "Sharefaith ChurchApp.ipa" );
				//Modified recently, last minute?
				if( archive.exists() && archive.lastModified() + 60*1000 > System.currentTimeMillis())
				{
					Files.move( archive.toPath(),
					    new File(app.getDataDir().getParentFile(), "Sharefaith ChurchApp.ipa").toPath() );
				}
			}
		}
		
		FileUtils.copyDirectory(getLatestDsymDir(), new File(app.getDataDir().getParentFile(), "Sharefaith ChurchApp.app.dSYM"));
		
	}
	
	/**
	 * Compile it, upload.
	 * @param app
	 * @throws Exception
	 */
	private void compileCmdLine( ) throws Exception
	{
		//Unnecessary with fastlane gym it seems, it finds the provisioning profile.
		/*String adHocProfileName = null;
		String profileName = null;
		
		for ( Path archive : Files.newDirectoryStream(app.getDataDir().getParentFile().toPath()) )
		{
			String name = archive.getFileName().toString();
			if (name.endsWith(".mobileprovision"))
			{
				String tmpProfileName = name.replace('_',' ')
						.replace(".mobileprovision", "")
						.replace("comsharefaithchurchapp", "com.sharefaith.churchapp.");
				if( name.indexOf("_AH_") > -1)
				{
					adHocProfileName = tmpProfileName;
				} else {
					profileName = tmpProfileName;
				}
			}
		}*/
		
		setProjectDistributionMode();
		File xcodeproj = new File(app.getDataDir().getParentFile(), "Sharefaith ChurchApp/ios/Sharefaith ChurchApp.xcworkspace");
		
		//For some reason you need to open xcode on the project before compiling?
		/*ProcessBuilder xcoderun = new ProcessBuilder("open", xcodeproj.getAbsolutePath());
		//xcoderun.redirectOutput(ProcessBuilder.Redirect.INHERIT);//output the output.
		if (0 != xcoderun.start().waitFor()) {
			throw new Exception("Xcode fail");
		}
		Thread.sleep(5000); //Maybe wait for whatever xcuserstate files Xcode writes that build needs.
		Just rename instead...*/
		File podUserData = new File(app.getDataDir().getParentFile(), "Sharefaith ChurchApp/ios/Pods/Pods.xcodeproj/xcuserdata/");
		for( File statedir : podUserData.listFiles() )
		{
			if( statedir.getName().indexOf( System.getProperty("user.name") ) != 0 )
			{
				//It should be username.dirtype
				Files.move( statedir.toPath(), new File( podUserData, statedir.getName().replace("Sharefaith1", System.getProperty("user.name") ) ).toPath() );
		
			}
		}
		File xcodeUserData = new File(app.getDataDir().getParentFile(), "Sharefaith ChurchApp/ios/Sharefaith Churchapp.xcodeproj/xcuserdata/");
		for( File statedir : xcodeUserData.listFiles() )
		{
			if( statedir.getName().indexOf( System.getProperty("user.name") ) != 0 )
			{
				//It should be username.dirtype
				Files.move( statedir.toPath(), new File( xcodeUserData, statedir.getName().replace("Sharefaith1", System.getProperty("user.name") ) ).toPath() );
			}
		}
		
		//The equivalent of Python subprocess module - [command, args] usage.
		/*ProcessBuilder compile = new ProcessBuilder("xcodebuild", "-scheme", "Sharefaith ChurchApp", "archive");
		compile.directory(xcodeproj.getParentFile());
		compile.redirectOutput(ProcessBuilder.Redirect.INHERIT);//output the output.
		System.out.println("Compiling, this will take a long time...");
		AppserveRecorder.recordBuildStarted(this.app.getNumericId());
		if (0 != compile.start().waitFor())
		{
			System.err.println("Xcodebuild archive failed... TRY-Again...");
			//throw new Exception("Xcodebuild archive failed.");
			//Try again
			Thread.sleep(30000); //Maybe wait for whatever xcuserstate files Xcode writes that build needs.
			compileCmdLine();
			return;
		}
		
		*/
		

		System.out.println("Archiving...");
		new File( app.getDataDir().getParentFile().getAbsolutePath() + "/AdHoc.ipa" ).delete();//(if existed)
		/*String xcArchive = getLatestXCArchive().getAbsolutePath();
		ProcessBuilder archiveAH = new ProcessBuilder("xcodebuild", "-exportArchive", "-exportFormat", "ipa",
			"-archivePath", xcArchive,
			"-exportPath", app.getDataDir().getParentFile().getAbsolutePath() + "/AdHoc",//.ipa
			"-exportProvisioningProfile", adHocProfileName);
		*/
		ProcessBuilder archiveAH = new ProcessBuilder( "fastlane", "gym",
			//"--use_legacy_build_api", "true",
			"--scheme", "Sharefaith ChurchApp",
			"--clean", "false",
			//"--include_symbols", "true", "--include_bitcode", "true",
			"--export_method", "ad-hoc", "--output_name", "AdHoc",//.ipa
			"--output_directory", "../../" );
		archiveAH.redirectOutput(ProcessBuilder.Redirect.INHERIT);//output the output.
		archiveAH.directory( xcodeproj.getParentFile() );
		archiveAH.environment().put("FASTLANE_XCODE_LIST_TIMEOUT", LISTWAITSEC);

		//Documented in "fastlane actions gym" command, these should prevent overwriting latest build with multiple:
		archiveAH.environment().put("GYM_DERIVED_DATA_PATH", "derivedData");
		archiveAH.environment().put("GYM_ARCHIVE_PATH", "myarchive");
		
		boolean firstTime = true;
		while (0 != archiveAH.start().waitFor()) // Y on earth can it npe on this line?
		{
			if( !firstTime )
			{
				//Something wrong? Seems first compile of app needs open xcode.
				//For some reason you need to open xcode on the project before compiling?
				ProcessBuilder xcoderun = new ProcessBuilder("open", xcodeproj.getAbsolutePath());
				//xcoderun.redirectOutput(ProcessBuilder.Redirect.INHERIT);//output the output.
				if (0 != xcoderun.start().waitFor()) {
					throw new Exception("Xcode fail");
				}
				Thread.sleep(5000); //Maybe wait for whatever xcuserstate files Xcode writes that build needs.
			}
			Thread.sleep( 30*1000 );
			//Try again... maybe needs awhile for Xcode to open, set scheme.
			archiveAH = new ProcessBuilder( "fastlane", "gym",
				//"--use_legacy_build_api", "true",
				"--scheme", "Sharefaith ChurchApp",
				"--clean", "false",
				//"--include_symbols", "true", "--include_bitcode", "true",
				"--export_method", "ad-hoc", "--output_name", "AdHoc",//.ipa
				"--output_directory", "../../" );
			archiveAH.redirectOutput(ProcessBuilder.Redirect.INHERIT);//output the output.
			archiveAH.directory( xcodeproj.getParentFile() );
			archiveAH.environment().put("FASTLANE_XCODE_LIST_TIMEOUT", LISTWAITSEC);

			//Documented in "fastlane actions gym" command, these should prevent overwriting latest build with multiple:
			archiveAH.environment().put("GYM_DERIVED_DATA_PATH", "derivedData");
			archiveAH.environment().put("GYM_ARCHIVE_PATH", "myarchive");
			
			/*if (0 != archiveAH.start().waitFor())
			{
				throw new Exception("Adhoc export fail");
			}*/
			firstTime = false;
		}
		System.out.println("Successfully exported ad-hoc");
		
		ProcessBuilder archive = new ProcessBuilder("fastlane", "gym", "--output_name", "Production",//.ipa
				"--scheme", "Sharefaith ChurchApp",
				"--clean", "false",
				//"--use_legacy_build_api", "true", no help?
				"--skip_build_archive",
				"--output_directory", "../../" );
		archive.directory( xcodeproj.getParentFile() );
		archive.environment().put("FASTLANE_XCODE_LIST_TIMEOUT", LISTWAITSEC);
		//Documented in "fastlane actions gym" command, these should prevent overwriting latest build with multiple:
		archive.environment().put("GYM_DERIVED_DATA_PATH", "derivedData");
		archive.environment().put("GYM_ARCHIVE_PATH", "myarchive");
		archive.redirectOutput(ProcessBuilder.Redirect.INHERIT);//output the output.
		while (0 != archive.start().waitFor())
		{
			//Retry for it keeps timing out.
			 archive = new ProcessBuilder("fastlane", "gym", "--output_name", "Production",//.ipa
					"--scheme", "Sharefaith ChurchApp",
					"--clean", "false",
					//"--use_legacy_build_api", "true", no help?
					"--skip_build_archive",
					"--output_directory", "../../" );
			archive.directory( xcodeproj.getParentFile() );
			archive.environment().put("FASTLANE_XCODE_LIST_TIMEOUT", LISTWAITSEC);
			//Documented in "fastlane actions gym" command, these should prevent overwriting latest build with multiple:
			archive.environment().put("GYM_DERIVED_DATA_PATH", "derivedData");
			archive.environment().put("GYM_ARCHIVE_PATH", "myarchive");
			archive.redirectOutput(ProcessBuilder.Redirect.INHERIT);//output the output.
			
			/*if (0 != archive.start().waitFor())
			{
				throw new Exception("Production export fail");
			}*/
		}
		System.out.println("Successfully exported for production");
		
		boolean success = false;
		while( !success )
		{
			System.out.println("Uploading Production.ipa...");
			//If fixed as described on http://stackoverflow.com/a/28484962 it should be linked correctly:
			ProcessBuilder upload = new ProcessBuilder("/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Versions/Current/Support/altool",
					"--upload-app", "-f", app.getDataDir().getParentFile().getAbsolutePath() + "/Production.ipa",
					"-u", "appdev@sharefaith.com",
					"-p", iTunesConnectInterface.PWD );
			int returnval = upload.start().waitFor();
			if (0 != returnval )
			{
				System.out.println("Returned " + String.valueOf( returnval ));
				Thread.sleep(70*1000);
			} else {
				success = true;
			}
		}
		System.out.println("Uploaded.");
		AppserveRecorder.recordReadyUploaded( this.app.getNumericId() );
		FileUtils.deleteDirectory( new File(xcodeproj.getParentFile(), "myarchive.xcarchive") );
		FileUtils.deleteDirectory( new File(xcodeproj.getParentFile(), "derivedData") );
		
		/*
		File desktop = Pathutil.expanduser("~/Desktop/");
		for ( Path folder : Files.newDirectoryStream( desktop.toPath() ) ) 
		{
			File archive;
			if( folder.toFile().getName().startsWith( "Sharefaith ChurchApp ") )
			{
				archive = new File( folder.toFile(), "Sharefaith ChurchApp.ipa" );
				//Modified recently, last minute?
				if( archive.exists() && archive.lastModified() + 60*1000 > System.currentTimeMillis())
				{
					Files.move( archive.toPath(),
					    new File(app.getDataDir().getParentFile(), "Sharefaith ChurchApp.ipa").toPath() );
				}
			}
		}
		FileUtils.copyDirectory(getLatestDsymDir(), new File(app.getDataDir().getParentFile(), "Sharefaith ChurchApp.app.dSYM"));
		
		*/
		
		//If closed too soon may not get schemes set correctly, that's why this is at end.
		ProcessBuilder closeXcode = new ProcessBuilder("osascript", "-e", "tell app \"Xcode\" to close (every window)");
		if (0 != closeXcode.start().waitFor())
		{
			System.err.println("Xcode close failed.");
		}
	}
	
	public static File getLatestDsymDir() throws Exception
	{
		//Return the latest dsym, which is a folder under latest-archive:
		File output = new File( getLatestXCArchive(), "dSYMs/Sharefaith ChurchApp.app.dSYM");
		if (output.exists() && output.isDirectory()) // success
		{
			return output;
		} else {
			throw new Exception("no dir?");
		}
	}
	
	/**
	 * Returns latest archived directory.
	 * No longer needed as archive intermediates should be in the numbered directory.
	 * @return
	 * @throws IOException
	 */
	@Deprecated
	private static File getLatestXCArchive() throws IOException
	{
		long latestMtime = 0;
		Path latestDir = null;
		
		//Yikes this can fail just-after midnight.
		String date = (new SimpleDateFormat("yyyy-MM-dd")).format(new Date());
		File archives = Pathutil.expanduser("~/Library/Developer/Xcode/Archives/"+date);
		for ( Path archive : Files.newDirectoryStream(archives.toPath()))
		{
			if (archive.toFile().lastModified() > latestMtime && !archive.getFileName().toString().equals(".DS_Store"))
			{
				latestMtime = archive.toFile().lastModified();
				latestDir = archive;
			}
		}
		return latestDir.toFile();
	}

	@Override
	public void run()
	{
		try {
			compileCmdLine();
		} catch (Exception e) {
			SendMailTLS.mail("Sikuli Xcoder Failed:\n"+e.getLocalizedMessage());
			e.printStackTrace();
		}
	}
	
}
