/*******************************************************************************
 * Copyright (c) 2009, 2018 Red Hat, Inc.
 * 
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Red Hat - initial API and implementation
 *******************************************************************************/
package org.eclipse.linuxtools.rpm.core.utils;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.SequenceInputStream;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.linuxtools.tools.launch.core.factory.RuntimeProcessFactory;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.FrameworkUtil;
/**
 * Utilities for calling system executables.
 */
public class Utils {

    /**
     * Runs the given command and parameters.
     *
     * @param command
     *            The command with all parameters.
     * @return Stream containing the combined content of stderr and stdout.
     * @throws IOException
     *             If IOException occurs.
     */
	public static BufferedProcessInputStream runCommandToInputStream(String... command) throws IOException {
        return runCommandToInputStream(null, command);
    }

    /**
     * Runs the given command and parameters.
     * @param project rpm project
     *
     * @param command
     *            The command with all parameters.
     * @return Stream containing the combined content of stderr and stdout.
     * @throws IOException
     *             If IOException occurs.
     * @since 2.1
     */
	private static BufferedProcessInputStream runCommandToInputStream(IProject project, String... command)
			throws IOException {
        Process p = RuntimeProcessFactory.getFactory().exec(command, project);
        return new BufferedProcessInputStream(p);
    }

    /**
     * Runs the given command and parameters. Note that the command's process can
     * be terminated by interrupting the thread this method runs in, but only after
     * the process' execution has begun.
     *
     * @param outStream
     *            The stream to write the output to.
     * @param project
     *               The project which is executing this command.
     * @param command
     *            The command with all parameters.
     * @return An IStatus indicating the result of the command.
     * @throws IOException If an IOException occurs.
     * @since 1.1
     */
	public static IStatus runCommand(final OutputStream outStream, IProject project, String... command)
			throws IOException {
        return watchProcess(outStream, RuntimeProcessFactory.getFactory().exec(command, project));
    }

    /**
     * Watches the streams of the given running process. Note that the process can
     * be terminated at any time by interrupting the thread this method runs in.
     *
     * @param outStream
     *            The stream to write the output to.
     * @param child
     *            The process to watch the output of.
     * @return An IStatus indicating the result of the command.
     * @since 2.2
     */
    public static IStatus watchProcess(final OutputStream outStream, Process child) {
		final BufferedInputStream in = new BufferedInputStream(
				new SequenceInputStream(child.getInputStream(), child.getErrorStream()));

        Thread readinJob = new Thread(() -> {
		    try {
		        int i;
		        while ((i = in.read()) != -1) {
		            outStream.write(i);
		        }
		        outStream.flush();
		        outStream.close();
		        in.close();
		    } catch (IOException e) {}
		    return;
		});
        readinJob.start();

        boolean canceled = false;
        try {
            // Catch interrupt attempts made before the wait
            if (Thread.currentThread().isInterrupted()) {
                throw new InterruptedException();
            }
            child.waitFor();
        } catch (InterruptedException e) {
            child.destroy();
            canceled = true;
        }

        while (readinJob.isAlive()) {
            try {
                readinJob.join();
            } catch (InterruptedException e) {}
        }

        if (canceled) {
            return Status.CANCEL_STATUS;
        }
		if (child.exitValue() != 0) {
			return new Status(IStatus.ERROR, FrameworkUtil.getBundle(Utils.class).getSymbolicName(),
					NLS.bind(Messages.Utils_NON_ZERO_RETURN_CODE, child.exitValue()), null);
		}
        return Status.OK_STATUS;
    }

    /**
     * Run a command and return its output.
     * @param command The command to execute.
     * @return The output of the executed command.
     * @throws IOException If an I/O exception occurred.
     */
	public static String runCommandToString(String... command) throws IOException {
        return runCommandToString(null, command);
    }

    /**
     * Run a command and return its output.
     * @param project rpm Project
     * @param command The command to execute.
     * @return The output of the executed command.
     * @throws IOException If an I/O exception occurred.
     * @since 2.1
     */
	public static String runCommandToString(IProject project, String... command) throws IOException {
        BufferedInputStream in = runCommandToInputStream(project, command);
        return inputStreamToString(in);
    }
    /**
     * Reads the content of the given InputStream and returns its textual
     * representation.
     *
     * @param stream
     *            The InputStream to read.
     * @return Textual content of the stream.
     * @throws IOException If an IOException occurs.
     */
	private static String inputStreamToString(InputStream stream) throws IOException {
        StringBuilder retStr = new StringBuilder();
        int c;
        while ((c = stream.read()) != -1) {
            retStr.append((char) c);
        }
        stream.close();
        return retStr.toString();
    }
}
