#!/usr/bin/env groovy

import com.urbancode.commons.util.processes.Processes
import com.urbancode.shell.Shell;

final String lineSep = System.getProperty('line.separator')
final String osName = System.getProperty('os.name').toLowerCase(Locale.US)
final String pathSep = System.getProperty('path.separator')
final boolean windows = (osName =~ /windows/)
final boolean vms = (osName =~ /vms/)
final boolean os9 = (osName =~ /mac/ && !osName.endsWith('x'))
final boolean unix = (pathSep == ':' && !vms && !os9)
final File PLUGIN_HOME = new File(System.getenv().get("PLUGIN_HOME"))
final Processes processes = new Processes()

def String getArch() {
    String result
    String arch = System.getProperty("os.arch").toLowerCase(Locale.US)

    if (arch.indexOf("amd64") > -1 || arch.indexOf("x64") > -1 || arch.indexOf("x86_64") > -1) {
        result = "x64"
    }
    else if (arch.indexOf("x86") > -1 || arch.indexOf("386") > -1 || arch.indexOf("486") > -1 ||
             arch.indexOf("586") > -1 || arch.indexOf("686") > -1 || arch.indexOf("pentium") > -1) {
        result = "x86"
    }
    else if (arch.indexOf("ia64") > -1 || arch.indexOf("itanium") > -1 || arch.indexOf("ia-64") > -1) {
        result = "ia64"
    }
    else if (arch.indexOf("ppc") > -1 || arch.indexOf("powerpc") > -1) {
        result = "ppc"
    }
    else if (arch.indexOf("sparc") > -1) {
        result = "sparc"
    }
    else if (arch.indexOf("parisc") > -1 || arch.indexOf("pa_risc") > -1 || arch.indexOf("pa-risc") > -1) {
        result = "parisc"
    }
    else if (arch.indexOf("alpha") > -1) {
        result = "alpha"
    }
    else if (arch.indexOf("mips") > -1) {
        result = "mips"
    }
    else if (arch.indexOf("arm") > -1) {
        result = "arm"
    }
    else {
        result = "unknown"
    }
    return result
}

def int execute(workDir, daemon, vms, processes, commandLine, outputFile) {
    def shell = new Shell(commandLine as String[]);
    shell.workingDirectory = workDir
    shell.daemon = daemon
    shell.outputFile = outputFile

    println(commandLine.join(' '))
    if (daemon) {
        def message = "running as daemon: output "
        message += (outputFile ? "redirected to ${outputFile.path}" : 'discarded')
        println(message)
    }
    else {
        println('-------------------------------')
        println("command output: ")
    }

    def proc = null
    try {
        if ( vms ) {
            proc = Runtime.runtime.exec(commandLine as String[])
        }
        else {
            shell.execute()
            proc = shell.process
        }

        if (!daemon) {
            def hook = {
                proc.destroy();
            }
            Runtime.getRuntime().addShutdownHook(hook as Thread);
            
            // Trigger early load of this class. AIX Java 5 has a bug the produces a 
            // LinkageError if this class is loaded normally. Most likely, it is an
            // issue with concurrent loading of the class in different threads.
            Class.forName("com.urbancode.commons.util.IO");
            
            // handle process IO
            proc.outputStream.close()           // close process stdin
            def outFuture = processes.redirectOutput(proc, System.out);
            def errFuture = processes.redirectError(proc, System.err);
            outFuture.await()
            errFuture.await()
            proc.waitFor()

            Runtime.getRuntime().removeShutdownHook(hook as Thread);

            // print results
            println('===============================')
            println("command exit code: ${proc.exitValue()}")
            println("")
        
            return proc.exitValue()
        }
        else {
            return 0
        }
    }
    finally {
        if (!daemon && proc) {
            proc.destroy()
        }
    }
}

if (windows) {
    def arch = getArch()
    def libraryPath = new File(PLUGIN_HOME, "lib/native/${arch}/WinAPI.dll")
    System.setProperty("com.urbancode.winapi.WinAPI.dllPath", libraryPath.absolutePath)
}

// TODO replace this with groovy 1.6 String.denormalize() method call
/**
 * Return a String with lines (separated by LF, CR/LF, or CR) terminated by the platform specific line separator
 * @param value the string to denormalize
 * @return the denormalized string
 */
def denormalizeLines = { String value ->
    return value.replaceAll(/\n|\r\n|\r/, java.util.regex.Matcher.quoteReplacement(lineSep))
}

final def workDir = new File('.').canonicalFile
final def props = new Properties();
final def inputPropsFile = new File(args[0]);
final def inputPropsStream = null;
try {
    inputPropsStream = new FileInputStream(inputPropsFile);
    props.load(inputPropsStream);
}
catch (IOException e) {
    throw new RuntimeException(e);
}

final def directoryOffset = props['directoryOffset']
final def interpreter = props['shellInterpreter']
final def xargs = props['xargs']
final def runOncePerArg = Boolean.valueOf(props['runOncePerArg'])
final def scriptBody = props['scriptBody']
final def runAsDaemon = Boolean.valueOf(props['runAsDaemon'])

def outputFilePath = props['outputFile']

final def verbose = false

def exitCode = 0

//
// Validation
//

if (directoryOffset) {
    workDir = new File(workDir, directoryOffset).canonicalFile
}

if (workDir.isFile()) {
    throw new IllegalArgumentException("Working directory ${workDir} is a file!")
}

if (!scriptBody) {
    throw new IllegalArgumentException("Shell Script is required")
}

boolean daemon = vms ? false : runAsDaemon

//
// Determine OS specific interpreter and script extension if needed
//

def scriptExt = null
if (!interpreter) {

    if (windows) {
        scriptExt = '.bat'
    }
    else if (vms) {
        scriptExt = '.com'

        // Work dir needs to be a genuine VMS style path for inclusion in DCL
        def path = workDir.path.tokenize('/\\')

        def buf = path.first()+':'    // device
        def directories = path.tail() // everything after first element
        if (directories) {
            buf += '['+directories.join('.')+']'
        }

        scriptBody = "\$ SET DEFAULT ${buf}\n\$ ${scriptBody}"
    }
    else if (unix) {
        // unix doesn't care about extension, but does about #!
        if (!scriptBody.startsWith('#!')) {
            // defaultShell not on all agents right now
            //interpreter = defaultShell ?: '/bin/sh'
            interpreter = '/bin/sh'
        }
    }
    else {
        // uknown platform and unknown interpreter, use defaultShell as interpreter if available
        interpreter = defaultShell
    }
}


//
// Construct output file (if needed)
//

outputFilePath = outputFilePath?.trim()
File outputFile = null
if (outputFilePath) {
    outputFile = new File(outputFilePath)
    if (!outputFile.isAbsolute()) {
        outputFile = new File(workDir, outputFile.getPath()).absoluteFile
    }
}

//
// Create workDir and scriptFile
//

// ensure work-dir exists
workDir.mkdirs()

def xargArray = xargs.split(',')
println("executing script for all values in ${xargs}")

/*
ImpersonationToken impersonationToken = null
String user = user?.resolvedStr?.trim()
if (user != null) {
    String password = password?.resolvedStr?.trim();
    impersonationToken = new ImpersonationToken(user, password)
}
*/

// write script content (for groovy, filename must be legal java classname chars)
final def scriptFile = File.createTempFile("shell_command_", scriptExt?:'.tmp')
try {
    scriptFile.text = denormalizeLines(scriptBody) // write out with platform specific line endings

    int xargIndex = 0
    int commandLineLength = 0
    int limit = runOncePerArg ? 0 : 8190
    boolean limitReached = false
    def commandLine = []
    def xargExitCode = 0
    
    xargArray.each() { xarg ->
        // limit reached if adding the next xarg will put us over the limit
        limitReached = commandLineLength + 1 + xarg.length() > limit
        if (limitReached && commandLineLength) {
            // print out command info
            println('')
            println('-------------------------------')
            println("executing script for value: ${xarg}")
            
            xargExitCode = execute(workDir, daemon, vms, processes, commandLine, outputFile)
            if (!exitCode && xargExitCode) {
                exitCode = xargExitCode
            }
            
            commandLine = []
        }
        
        //
        // Build Command Line
        //
        if (commandLine.isEmpty()) {
            if (interpreter) {
                if (windows) {
                    commandLine.add('cmd')
                    commandLine.add('/C')
                    commandLine.add(interpreter) // tokenize?
                }
                else {
                    commandLine.addAll(interpreter.tokenize())
                }
            }
            else {
                if (unix) {
                    // fix unix execute bit
                    def chmod = ['chmod', '+x', scriptFile.absolutePath].execute()
                    chmod.outputStream.close()
                    chmod.consumeProcessOutput(System.out, System.err)
                    chmod.waitFor() // TODO check exit code?
                }
            }
            commandLine.add(scriptFile.absolutePath)
            commandLineLength = commandLine.join(' ').length()
        }
        commandLine.add(xarg)
        commandLineLength += xarg.length() + 1
    }
    xargExitCode = execute(workDir, daemon, vms, processes, commandLine, outputFile)
    if (!exitCode && xargExitCode) {
        exitCode = xargExitCode
    }
}
finally {
    if (!daemon) {
        scriptFile.delete()
    }
}

if (!daemon) {
    System.exit(exitCode)
}
