/*
# Licensed Materials - Property of IBM
# Copyright IBM Corp. 2015  
 */
package com.ibm.streamsx.topology.internal.streams;

import static com.ibm.streamsx.topology.internal.streams.InvokeSc.trace;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;

import com.google.gson.JsonObject;
import com.ibm.streams.operator.logging.TraceLevel;
import com.ibm.streamsx.topology.jobconfig.JobConfig;
import com.ibm.streamsx.topology.jobconfig.SubmissionParameter;

public class InvokeStandalone {

    private final File bundle;
    // Keep bundle when finished.
    private final boolean keepBundle;
    private final Map<String,String> envVars = new HashMap<>();

    public InvokeStandalone(File bundle, boolean keepBundle) {
        super();
        this.bundle = bundle;
        this.keepBundle = keepBundle;
    }
    
    public void addEnvironmentVariable(String key, String value) {
        envVars.put(key, value);
    }

    public Future<Integer> invoke(JsonObject deploy)
            throws Exception, InterruptedException {
        String si = System.getProperty("java.home");
        File jvm = new File(si, "bin/java");
        
        JobConfig jc = JobConfigOverlay.fromFullOverlay(deploy);

        List<String> commands = new ArrayList<>();
        commands.add(jvm.getAbsolutePath());
        commands.add("-jar");
        commands.add(bundle.getAbsolutePath());
        Level traceLevel = jc.getTracing();
        if (traceLevel != null) {
            commands.add("-t");

            // -t, --trace-level=INT Trace level: 0 - OFF, 1 - ERROR, 2 - WARN,
            // 3 - INFO, 4 - DEBUG, 5 - TRACE.
            
            int tli = traceLevel.intValue();
            String tls;
            if (tli == Level.OFF.intValue())
                tls = "0";
            else if (tli == Level.ALL.intValue())
                tls = "5";
            else if (tli >= TraceLevel.ERROR.intValue())
                tls = "1";
            else if (tli >= TraceLevel.WARN.intValue())
                tls = "2";
            else if (tli >= TraceLevel.INFO.intValue())
                tls = "3";
            else if (tli >= TraceLevel.DEBUG.intValue())
                tls = "4";
            else
                tls = "5";
            commands.add(tls);
        }
        if (jc.hasSubmissionParameters()) {
            for(SubmissionParameter param : jc.getSubmissionParameters()) {
                // note: this execution path does correctly
                // handle / preserve the semantics of escaped \t and \n.
                // e.g., "\\n" is NOT treated as a newline 
                // rather it's the two char '\','n'
                commands.add(param.getName()+"="+param.getValue());
            }
        }

        trace.info("Invoking standalone application");
        trace.info(Util.concatenate(commands));

        ProcessBuilder pb = new ProcessBuilder(commands);
        pb.inheritIO();

        for (Entry<String,String> ev : envVars.entrySet()) {
            trace.fine("Setting environment variable for standalone: " + ev.getKey() + "=" + ev.getValue());
            pb.environment().put(ev.getKey(), ev.getValue());
        }

        Process standaloneProcess = pb.start();

        return new ProcessFuture(standaloneProcess, keepBundle ? null : bundle);
    }

    private static class ProcessFuture implements Future<Integer> {

        private final Process process;
        // Set if bundle to be deleted.
        private File bundle;
        private int rc;
        private boolean isDone;
        private boolean isCancelled;

        ProcessFuture(Process process, File bundle) {
            this.process = process;
            this.bundle = bundle;
            if (bundle != null)
                bundle.deleteOnExit();
        }
        
        private void deleteBundle() {
            if (bundle != null) {
                bundle.delete();
                bundle = null;
            }
        }

        @Override
        public synchronized boolean cancel(boolean mayInterruptIfRunning) {
            if (isDone())
                return false;
            if (!mayInterruptIfRunning)
                return false;

            try {
                process.destroy();
            } finally {
                deleteBundle();
            }
            isCancelled = true;
            notifyAll();
            return true;
        }

        @Override
        public synchronized boolean isCancelled() {
            return isCancelled;
        }

        @Override
        public synchronized boolean isDone() {
            boolean done =  isDone || isCancelled;
            if (done)
                deleteBundle();
            return done;
        }

        @Override
        public synchronized Integer get() throws InterruptedException,
                ExecutionException {
            if (isDone())
                return rc;
            try {
                rc = process.waitFor();
            } finally {
                deleteBundle();
            }
            trace.info("Standalone application completed: return code=" + rc);
            notifyAll();
            return rc;
        }

        @Override
        public synchronized Integer get(long timeout, TimeUnit unit)
                throws InterruptedException, ExecutionException,
                TimeoutException {
            if (!isDone()) {
                wait(unit.toMillis(timeout));
            }
            if (isCancelled())
                throw new CancellationException();
            if (!isDone())
                throw new TimeoutException();
            return rc;
        }
    }
}

@@ -5335,32 +5335,72 @@
 ;%0A            %7D%0A
+            %0A            isDone = true;%0A
             trac

Set isDone in InvokeStandalone after waiting for process to finish