/*
 * Copyright 2006-2021 DLR, Germany
 * 
 * SPDX-License-Identifier: EPL-1.0
 * 
 * https://rcenvironment.de/
 */

package de.rcenvironment.core.utils.executor;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.exec.OS;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import de.rcenvironment.core.toolkitbridge.transitional.ConcurrencyUtils;

/**
 * A {@link CommandLineExecutor} that executes the given commands locally.
 * 
 * @author Robert Mischke
 * 
 */
public class LocalCommandLineExecutor extends AbstractCommandLineExecutor implements CommandLineExecutor {

    /** Top-level command token template for Linux invocation. */
    private static final String[] LINUX_SHELL_TOKENS = { "/bin/sh", "-c", "[command]" };

    /** Top-level command token template for Windows invocation. */
    private static final String[] WINDOWS_SHELL_TOKENS = { "cmd.exe", "/c", "[command]" };

    private File workDir;

    private Process process;

    private Log log = LogFactory.getLog(getClass());

    /**
     * Creates a local executor with the given path as its working directory. If the given path is
     * not a directory, it is created.
     * 
     * @param workDirPath the directory on the local system to use for execution
     * 
     * @throws IOException if the given {@link File} is not a directory and also could not be
     *         created
     */
    public LocalCommandLineExecutor(File workDirPath) throws IOException {

        this.workDir = workDirPath;
    }

    @Override
    public void start(String commandString) throws IOException {
        start(commandString, null);
    }

    @Override
    public void start(String commandString, final InputStream stdinStream) throws IOException {

        if (!workDir.isDirectory()) {
            // try to create the work directory
            workDir.mkdirs();
            if (!workDir.isDirectory()) {
                throw new IOException("Failed to create provided work directory " + workDir.getAbsolutePath());
            }
        }

        // build environment key/value array
        Set<Entry<String, String>> entries = env.entrySet();
        String[] envArray = new String[entries.size()];
        int pos = 0;
        for (Entry<String, String> entry : entries) {
            envArray[pos++] = entry.getKey() + "=" + entry.getValue();
        }

        // build the top-level token array
        String[] commandTokens;
        if (OS.isFamilyWindows()) {
            commandTokens = Arrays.copyOf(WINDOWS_SHELL_TOKENS, WINDOWS_SHELL_TOKENS.length);
            commandTokens[WINDOWS_SHELL_TOKENS.length - 1] = commandString;
        } else {
            commandTokens = Arrays.copyOf(LINUX_SHELL_TOKENS, LINUX_SHELL_TOKENS.length);
            commandTokens[LINUX_SHELL_TOKENS.length - 1] = commandString;
        }

        // TODO rewrite to use Apache Commons-Exec?
        process = Runtime.getRuntime().exec(commandTokens, envArray, workDir);

        if (stdinStream != null) {
            final OutputStream stdin = process.getOutputStream();
            ConcurrencyUtils.getAsyncTaskService().execute("LocalCommandLineExecutor input stream pipe", () -> {

                try {
                    IOUtils.copy(stdinStream, stdin);
                    stdin.close();
                } catch (IOException e) {
                    // TODO propagate to outside?
                    log.error("Error writing STDIN stream", e);

                }
            });
        }
    }

    @Override
    public String getWorkDirPath() {
        return workDir.getAbsolutePath();
    }

    @Override
    public InputStream getStdout() {
        // TODO make buffered?
        return process.getInputStream();
    }

    @Override
    public InputStream getStderr() {
        // TODO make buffered?
        return process.getErrorStream();
    }

    @Override
    public int waitForTermination() throws IOException, InterruptedException {
        return process.waitFor();
    }

    @Override
    public void uploadFileToWorkdir(File localFile, String remoteLocation) throws IOException {
        File targetFile = new File(workDir, remoteLocation);
        log.debug("Local copy from " + localFile.getAbsolutePath() + " to " + targetFile.getAbsolutePath());
        FileUtils.copyFile(localFile, targetFile);
    };

    @Override
    public void downloadFileFromWorkdir(String remoteLocation, File localFile) throws IOException {
        FileUtils.copyFile(new File(workDir, remoteLocation), localFile);
    }

    @Override
    public void downloadWorkdir(File localDir) throws IOException {
        FileUtils.copyDirectory(workDir, localDir);
    }

    @Override
    public void remoteCopy(String remoteSource, String remoteTarget) throws IOException {
        FileUtils.copyFile(new File(remoteSource), new File(remoteTarget));
    }

    @Override
    public void uploadDirectoryToWorkdir(File localDirectory, String remoteLocation) throws IOException {
        File targetDirectory = new File(workDir, remoteLocation);
        targetDirectory.mkdirs();
        FileUtils.copyDirectory(localDirectory, targetDirectory);
    }

    @Override
    public void downloadDirectoryFromWorkdir(String remoteLocation, File localDirectory) throws IOException {
        FileUtils.copyDirectory(new File(workDir, remoteLocation), localDirectory);
    }

    @Override
    public void downloadFile(String remoteLocation, File localFile) throws IOException {
        FileUtils.copyFile(new File(remoteLocation), localFile);
    }

    @Override
    public void downloadDirectory(String remoteLocation, File localDirectory) throws IOException {
        FileUtils.copyDirectory(new File(remoteLocation), localDirectory);
    }

    @Override
    public void uploadFile(File localFile, String remoteLocation) throws IOException {
        FileUtils.copyFile(localFile, new File(remoteLocation));
    }

    @Override
    public void uploadDirectory(File localDirectory, String remoteLocation) throws IOException {
        FileUtils.copyDirectory(localDirectory, new File(remoteLocation));
    }
}
