/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.dolphinscheduler.common.shell;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.util.Map;

/**
 * shell command executor.
 *
 * <code>ShellExecutor</code> should be used in cases where the output
 * of the command needs no explicit parsing and where the command, working
 * directory and the environment remains unchanged. The output of the command
 * is stored as-is and is expected to be small.
 */
public class ShellExecutor extends AbstractShell {

    private String[] command;
    private StringBuffer output;

    public ShellExecutor(String... execString) {
        this(execString, null);
    }

    public ShellExecutor(String[] execString, File dir) {
        this(execString, dir, null);
    }

    public ShellExecutor(String[] execString, File dir,
                         Map<String, String> env) {
        this(execString, dir, env, 0L);
    }

    /**
     * Create a new instance of the ShellExecutor to execute a command.
     *
     * @param execString The command to execute with arguments
     * @param dir If not-null, specifies the directory which should be set
     *            as the current working directory for the command.
     *            If null, the current working directory is not modified.
     * @param env If not-null, environment of the command will include the
     *            key-value pairs specified in the map. If null, the current
     *            environment is not modified.
     * @param timeout Specifies the time in milliseconds, after which the
     *                command will be killed and the status marked as timedout.
     *                If 0, the command will not be timed out.
     */
    public ShellExecutor(String[] execString, File dir,
                         Map<String, String> env, long timeout) {
        command = execString.clone();
        if (dir != null) {
            setWorkingDirectory(dir);
        }
        if (env != null) {
            setEnvironment(env);
        }
        timeOutInterval = timeout;
    }

    /**
     * Static method to execute a shell command.
     * Covers most of the simple cases without requiring the user to implement
     * the <code>AbstractShell</code> interface.
     * @param cmd shell command to execute.
     * @return the output of the executed command.
     * @throws IOException errors
     */
    public static String execCommand(String... cmd) throws IOException {
        return execCommand(null, cmd, 0L);
    }

    /**
     * Static method to execute a shell command.
     * Covers most of the simple cases without requiring the user to implement
     * the <code>AbstractShell</code> interface.
     * @param env the map of environment key=value
     * @param cmd shell command to execute.
     * @param timeout time in milliseconds after which script should be marked timeout
     * @return the output of the executed command.
     * @throws IOException errors
     */
    public static String execCommand(Map<String, String> env, String[] cmd,
                                     long timeout) throws IOException {
        ShellExecutor exec = new ShellExecutor(cmd, null, env,
                timeout);
        exec.execute();
        return exec.getOutput();
    }

    /**
     * Static method to execute a shell command.
     * Covers most of the simple cases without requiring the user to implement
     * the <code>AbstractShell</code> interface.
     * @param env the map of environment key=value
     * @param cmd shell command to execute.
     * @return the output of the executed command.
     * @throws IOException errors
     */
    public static String execCommand(Map<String, String> env, String... cmd) throws IOException {
        return execCommand(env, cmd, 0L);
    }

    /**
     * Execute the shell command
     * @throws IOException errors
     */
    public void execute() throws IOException {
        this.run();
    }

    @Override
    protected String[] getExecString() {
        return command;
    }

    @Override
    protected void parseExecResult(BufferedReader lines) throws IOException {
        output = new StringBuffer();
        char[] buf = new char[1024];
        int nRead;
        String line = "";
        while ((nRead = lines.read(buf, 0, buf.length)) > 0) {
            line = new String(buf, 0, nRead);
            output.append(line);
        }
    }

    /**
     *
     * @return the output of the shell command
     */
    public String getOutput() {
        return (output == null) ? "" : output.toString();
    }

    /**
     * Returns the commands of this instance.
     * Arguments with spaces in are presented with quotes round; other
     * arguments are presented raw
     *
     * @return a string representation of the object
     */
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        String[] args = getExecString();
        for (String s : args) {
            if (s.indexOf(' ') >= 0) {
                builder.append('"').append(s).append('"');
            } else {
                builder.append(s);
            }
            builder.append(' ');
        }
        return builder.toString();
    }
}
