/*******************************************************************************
 * Copyright (c) 2008, 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:
 *    Elliott Baron <ebaron@redhat.com> - initial API and implementation
 *    Alena Laskavaia - javadoc comments and cleanup
 *******************************************************************************/
package org.eclipse.linuxtools.internal.valgrind.core;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;

import org.eclipse.cdt.utils.pty.PTY;
import org.eclipse.core.resources.IProject;
import org.eclipse.linuxtools.tools.launch.core.factory.CdtSpawnerProcessFactory;
import org.eclipse.linuxtools.tools.launch.core.factory.RuntimeProcessFactory;
import org.eclipse.linuxtools.valgrind.core.CommandLineConstants;

/**
 * Helper class to run valgrind
 */
public class ValgrindCommand {
    protected static final String WHICH_CMD = "which"; //$NON-NLS-1$
    protected static final String VALGRIND_CMD = "valgrind"; //$NON-NLS-1$

    protected Process process;
    protected String[] args;

    /**
     * The valgrind executable name
     * @return command line name
     */
    public String getValgrindCommand() {
        return VALGRIND_CMD;
    }

    /**
     * Attempt to local valgrind version
     * @param project - project to get execution context
     * @return version or emptry string if execution failed
     * @throws IOException if failed to create a process
     */
	public String whichVersion(IProject project) throws IOException {
		Process p = RuntimeProcessFactory.getFactory()
				.exec(new String[] { getValgrindCommand(), CommandLineConstants.OPT_VERSION }, project);
		try {
			StringBuilder out = new StringBuilder();
			readIntoBuffer(out, p);
			return out.toString().trim();
		} catch (IOException e) {
			e.printStackTrace(); // TODO fix
		}
		return ""; //$NON-NLS-1$
	}

    /**
     * Execute command
     * @param commandArray - command line arguments, first argument is executable itelv
     * @param env - environment variables
     * @param wd - working directory
     * @param usePty - option to allocate pty or not
     * @param project - project in context of which to launch the process
     * @throws IOException - if cannot execute the command
     */
    public void execute(String[] commandArray, String[] env, File wd, boolean usePty, IProject project) throws IOException {
        args = commandArray;
        try {
            process = startProcess(commandArray, env, wd, usePty, project);
        } catch (IOException e) {
            if (process != null) {
                process.destroy();
            }
            throw e;
        }
    }

    /**
     * Get current process
     * @return process
     */
    public Process getProcess() {
        return process;
    }

    /**
     * Get current valgrind command line, not escaped
     * @return process
     */
    public String getCommandLine() {
    	StringBuilder ret = new StringBuilder();
        for (String arg : args) {
            ret.append(arg).append(" "); //$NON-NLS-1$
        }
        return ret.toString().trim();
    }

    private Process startProcess(String[] commandArray, String[] env, File workDir, boolean usePty, IProject project) throws IOException {
        if (workDir == null) {
            return CdtSpawnerProcessFactory.getFactory().exec(commandArray, env, project);
        }
        if (PTY.isSupported() && usePty) {
            return CdtSpawnerProcessFactory.getFactory().exec(commandArray, env, workDir, new PTY(), project);
        } else {
            return CdtSpawnerProcessFactory.getFactory().exec(commandArray, env, workDir, project);
        }
    }

	private void readIntoBuffer(StringBuilder out, Process p) throws IOException {
		if (p == null) {
			throw new IOException("Null Process object: unabled to read input into buffer"); //$NON-NLS-1$
		}
		// We need to get the inputs before calling waitFor
		try (InputStream err = p.getErrorStream(); InputStream input = p.getInputStream()) {
			boolean success = (p.waitFor() == 0);
			InputStream in;
			if (success) {
				in = input;
			} else {
				in = err;
			}
			int ch;
			while ((ch = in.read()) != -1) {
				out.append((char) ch); // TODO fix reading char by char ??
			}
			if (!success) {
				throw new IOException(out.toString());
			}
		} catch (InterruptedException e) {
			e.printStackTrace(); // TODO fix
		} finally {
			p.getOutputStream().close();
		}
	}
}
