/*
 * Copyright (c) 2002-2017 "Neo Technology,"
 * Network Engine for Objects in Lund AB [http://neotechnology.com]
 *
 * This file is part of Neo4j.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.neo4j.driver.v1.util.cc;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;

import org.neo4j.driver.v1.util.ProcessEnvConfigurator;

import static java.lang.System.lineSeparator;
import static java.util.Arrays.asList;

public class CommandLineUtil
{
    public static boolean boltKitAvailable()
    {
        try
        {
            executeCommand( "neoctrl-cluster", "--help" );
            return true;
        }
        catch ( CommandLineException e )
        {
            return false;
        }
    }

    public static String executeCommand( List<String> commands )
    {
        try
        {
            ProcessBuilder processBuilder = new ProcessBuilder().command( commands );
            ProcessEnvConfigurator.configure( processBuilder );
            return executeAndGetStdOut( processBuilder );
        }
        catch ( IOException | CommandLineException e )
        {
            throw new CommandLineException( "Error running command " + commands, e );
        }
        catch ( InterruptedException e )
        {
            Thread.currentThread().interrupt();
            throw new CommandLineException( "Interrupted while waiting for command " +
                                            commands, e );
        }
    }

    public static String executeCommand( String... command )
    {
        return executeCommand( asList( command ) );
    }

    private static String executeAndGetStdOut( ProcessBuilder processBuilder )
            throws IOException, InterruptedException
    {
        Process process = processBuilder.start();
        int exitCode = process.waitFor();
        String stdOut = asString( process.getInputStream() );
        String stdErr = asString( process.getErrorStream() );
        if ( exitCode != 0 )
        {
            throw new CommandLineException( "Non-zero exit code\nSTDOUT:\n" + stdOut + "\nSTDERR:\n" + stdErr );
        }
        return stdOut;
    }

    private static String asString( InputStream input )
    {
        StringBuilder result = new StringBuilder();
        try ( BufferedReader reader = new BufferedReader( new InputStreamReader( input ) ) )
        {
            String line;
            while ( (line = reader.readLine()) != null )
            {
                result.append( line ).append( lineSeparator() );
            }
        }
        catch ( IOException e )
        {
            throw new CommandLineException( "Unable to read from stream", e );
        }
        return result.toString();
    }
}

@@ -891,182 +891,595 @@
 st;%0A
-%0Aimport org.neo4j.driver.v1.util.ProcessEnvConfigurator;%0A%0Aimport static java.lang.System.lineSeparator;%0Aimport static java.util.Arrays.asList;%0A%0Apublic class CommandLineUtil%0A%7B
+import java.util.concurrent.Callable;%0Aimport java.util.concurrent.ExecutorService;%0Aimport java.util.concurrent.Executors;%0Aimport java.util.concurrent.Future;%0A%0Aimport org.neo4j.driver.v1.util.DaemonThreadFactory;%0Aimport org.neo4j.driver.v1.util.ProcessEnvConfigurator;%0A%0Aimport static java.lang.System.lineSeparator;%0Aimport static java.util.Arrays.asList;%0Aimport static java.util.concurrent.TimeUnit.MINUTES;%0A%0Apublic class CommandLineUtil%0A%7B%0A    private static final ExecutorService executor = Executors.newCachedThreadPool(%0A            new DaemonThreadFactory( %22command-line-thread-%22 ) );%0A
 %0A   
@@ -2397,52 +2397,8 @@
  %22 +
-%0A                                           
  com
@@ -2752,39 +2752,69 @@
     
-int exitCode = process.waitFor(
+Future%3CString%3E stdOutFuture = read( process.getInputStream() 
 );%0A 
@@ -2824,32 +2824,42 @@
     
+Future%3C
 String
+%3E
  std
-Out = asString
+ErrFuture = read
 ( pr
@@ -2859,37 +2859,37 @@
 ead( process.get
-Input
+Error
 Stream() );%0A    
@@ -2896,58 +2896,128 @@
     
-String stdErr = asString( process.getErrorStream()
+int exitCode = process.waitFor();%0A        String stdOut = get( stdOutFuture );%0A        String stdErr = get( stdErrFuture
  );%0A
@@ -3231,17 +3231,324 @@
 tic 
-String as
+Future%3CString%3E read( final InputStream input )%0A    %7B%0A        return executor.submit( new Callable%3CString%3E()%0A        %7B%0A            @Override%0A            public String call() throws Exception%0A            %7B%0A                return readToString( input );%0A            %7D%0A        %7D );%0A    %7D%0A%0A    private static String readTo
 Stri
@@ -4087,11 +4087,246 @@
 );%0A    %7D
+%0A%0A    private static %3CT%3E T get( Future%3CT%3E future )%0A    %7B%0A        try%0A        %7B%0A            return future.get( 10, MINUTES );%0A        %7D%0A        catch ( Exception e )%0A        %7B%0A            throw new RuntimeException( e );%0A        %7D%0A    %7D
 %0A%7D%0A

Read stdout and stderr in separate threads