/**
* Licensed Materials - Property of IBM
* 5748-XX8
* (C) Copyright IBM Corp. 2013, 2014 All Rights Reserved
* US Government Users Restricted Rights - Use, duplication or
* disclosure restricted by GSA ADP Schedule Contract with
* IBM Corp.
**/

import com.urbancode.air.AirPluginTool;
import com.urbancode.shell.Shell;
import com.urbancode.air.CommandHelper;
import com.ibm.rational.air.plugin.android.ADBCommandHelper;
import com.ibm.rational.air.plugin.android.EmulatorHelper;

/**
* The step for starting an emulator. This step is primarily implemented here 
* to reduce the JAR file dependencies to this step. The JAR file
* dependencies come from the use of Shell, which is required to
* start the emulator as a daemon process. The emulator is started as a daemon 
* process because the process is a long running process that runs even when 
* the step is complete.
* See http://developer.android.com/tools/devices/emulator.html#starting.
*
* Before you attempt to start the emulator, the maximum number of running 
* emulators on the target is determined.
*
* Unfortunately, wait-for-device cannot be used to determine the emulator
* startup state because the command blocks returns until the 'device' state is
* returned. This state is returned when the device might not be fully
* booted. Instead, the step checks the logcat file to determine the emulator
* state by using a polling interval.
* See
* http://developer.android.com/tools/help/adb.html#commandsummary.
*
* During startup, a server socket is started to receive the console port 
* from the emulator, which makes up the end of the serial number.
**/

def apTool = new AirPluginTool(this.args[0], this.args[1]);
final def props = apTool.getStepProperties();

def pathToSDK = props['pathToSDK']
def name = props['name']
boolean loadSnapshot = Boolean.parseBoolean(props['loadSnapshot'])
def additionalArgs = props['additionalArgs']
def snapshotName = props['snapshotName']
def outputFilePath = props['outputFile']
int maxEmulators = Integer.parseInt(props['maxEmulators']?:"4")
int concurrentRetries = Integer.parseInt(props['concurrentRetries']?:"15")
int startupRetries = Integer.parseInt(props['startupRetries']?:"15")

def adbHelper = new ADBCommandHelper(pathToSDK, apTool.isWindows);

// From shell
def interpreter = props['shellInterpreter']
def osName = System.getProperty("os.name").toLowerCase(Locale.US)
def isVMS = (osName =~ /(?i)vms/).find()
def isMacOS9 = (osName =~ /(?i)mac/ && !osName.endsWith('x')).find()
def isUnix = (System.getProperty("path.separator") == ":" && !isVMS && !isMacOS9)
File PLUGIN_HOME = new File(System.getenv().get("PLUGIN_HOME"))

def shellExtension

if(name == null || name.trim().length() == 0) {
    println "Error: The specified emulator name is incorrect: ${name}."
    System.exit(1)
}

/**
Check whether the emulator maximum has been reached. If it has, attempt to 
start the emulator after an interval. Fail if the emulator still 
cannot be started.
**/
if(!adbHelper.canStartEmulator(maxEmulators)) {
    println "The maximum number of emulators (" + maxEmulators + 
        ") has been reached. Retrying.";
    boolean canStart = false;
    for(int i = 0; i < concurrentRetries; i++) {
        Thread.sleep(20000);
        if(adbHelper.canStartEmulator(maxEmulators)) {
            canStart = true;
            break;
        }
    }
    if(!canStart) {
        println "Error: The emulator cannot be started because too many " +
            "emulators are running concurrently.";
        System.exit(-1);
    }
}

// From shell.groovy
// Determine the architecture of the computer to load the native support.
if(apTool.isWindows) {
    def arch = System.getProperty("os.arch").toLowerCase(Locale.US)
    def archPath
    
    if(arch.contains("amd64") || arch.contains("x64") || arch.contains("x86_64")) {
        archPath = "x64"
    } else if (arch.contains("x86") || arch.contains("386") || arch.contains("486") ||
        arch.contains("586") || arch.contains("686") || arch.contains("pentium")) {
        archPath = "x86"
    } else {
        System.out.println("Error: Unknown architecture.");
        return -1;
    }
    
    def libraryPath = new File(PLUGIN_HOME, "lib/native/${archPath}/WinAPI.dll")
    System.setProperty("com.urbancode.winapi.WinAPI.dllPath", libraryPath.absolutePath)
    
    shellExtension = ".bat"
} else if(isUnix) {
    interpreter = "/bin/sh"
} else {
    // Unknown so throw an error.
    System.out.println("Error: Unsupported operating system.");
    return -1;
}

def arguments
def workDir = new File('.').getCanonicalFile();

// Check whether the additional arguments are a file or space-separated list
def inFile = new File(additionalArgs);
if(!inFile.absolute) {    
    inFile = new File(workDir, additionalArgs);
}

if(inFile.file) {
    arguments = inFile.getText();
    System.out.println("Reading from: " + inFile.getCanonicalFile());
} else {
    arguments = additionalArgs;
}

// Set up the server for finding the console port.
ServerSocket server = null;
try {
    server = new ServerSocket();
    server.bind(new InetSocketAddress("localhost", 0));
} catch (IOException e) {
    println "Error: An error occurred while setting up the server for " +
        "finding the console port: ${e.message}";
    System.exit(-1);
}
def emulatorHelper = new EmulatorHelper(pathToSDK, apTool.isWindows);

// Build the command to start the emulator as a daemon.
StringBuilder command = new StringBuilder(emulatorHelper.getEmulatorPath());
command.append(" -report-console");
command.append(" tcp:" + server.getLocalPort());
command.append(" @");
command.append(name);
// Disable the welcome messages
command.append(" -prop");
command.append(" ro.test_harness=true");

/**
If you specify to load from a snapshot, include
the name of the snapshot to load. Otherwise, even if there is a
snapshot that is associated with the emulator, the snapshot is not loaded.
**/
if(loadSnapshot) {
    command.append(" -snapshot " + snapshotName);
} else {
    command.append(" -no-snapshot");
}

if(arguments) {
    arguments.eachLine { arg ->
        if(arg?.trim()) {
            command.append(" ");
            command.append(arg.trim());            
        }
    }
}

println "The command to run is: " + command.toString();

// Write the command to a script file.
def scriptFile = File.createTempFile("start_emulator", shellExtension?:".tmp")
scriptFile.deleteOnExit()
println "Adding the command to the script file: " + scriptFile.path
scriptFile.text = command.toString().denormalize();

// Output to a file, if necessary
outputFilePath = outputFilePath?.trim()
File outputFile = null;
if(outputFilePath) {
    outputFile = new File(outputFilePath)
    if(!outputFile.isAbsolute()) {
        outputFile = new File(workDir, outputFile.getPath()).absoluteFile
    }
}

// Output the command to the script file.
try {
    def cmdLine = []
    if(interpreter) {
        if(apTool.isWindows) {
            cmdLine << "cmd" << "/C" << interpreter << scriptFile.absolutePath
        } else {
            cmdLine.addAll(interpreter.tokenize())
            cmdLine.add(scriptFile.absolutePath)
        }
    } else {
        if(isUnix) {
            def chmod = ["chmod", "+x", scriptFile.absolutePath].execute()
            chmod.outputStream.close()
            chmod.consumeProcessOutput(System.out, System.err)
            chmod.waitFor()
        }
        cmdLine << scriptFile.absolutePath
    }
    
    println "The script command line command is: " + cmdLine.toString()
    
    def shell = new Shell(cmdLine as String[])
    shell.workingDirectory = workDir
    shell.daemon = (boolean)true
    shell.outputFile = outputFile
    
    def msg = "Starting this emulator: " + name
    msg += (outputFile ? " with output written to " + outputFile.path + "." : ".")
    println msg
    
    shell.execute()
    println "The request to start the emulator ran."

    // Set a maximum timeout of 5 minutes for the socket to wait for a process to connect.
    server.setSoTimeout(300000);    
    // Obtain the console port. Start the emulator first because the server blocks the process.
    Socket socket = server.accept();
    // Set a maximum timeout of 5 minutes for the socket to block the process.
    socket.setSoTimeout(300000);
    BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    def console = input.readLine();
    socket.close();
    server.close();
    
    String serialNumber = "emulator-" + console;
    println "The emulator with the following serial number is started: " + serialNumber;
    // Wait for emulator to start.
    adbHelper = new ADBCommandHelper(pathToSDK, apTool.isWindows, "serial", serialNumber);
    adbHelper.waitForEmulator(startupRetries);
    
    apTool.setOutputProperty("serialNumber", serialNumber);
    apTool.storeOutputProperties();
} catch (SocketTimeoutException e) {
    println "Error: The emulator could not be communicated with during emulator startup."
    e.printStackTrace();
    System.exit(-1)
} catch (Exception e) {
    println "Error: An error occurred while starting the emulator: ${e.message}"
    e.printStackTrace();
    System.exit(-1)
}

println "The emulator is started."
System.exit(0)