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

package de.rcenvironment.core.utils.executor;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Assert;

import de.rcenvironment.core.toolkitbridge.transitional.TextStreamWatcherFactory;
import de.rcenvironment.core.utils.common.textstream.TextStreamWatcher;
import de.rcenvironment.core.utils.common.textstream.receivers.CapturingTextOutReceiver;

/**
 * Common test setups for all {@link CommandLineExecutor} implementations to ensure consistency and
 * avoid code duplication. For each implementation, a subclass should provide top-level JUnit tests
 * that call each method of this class with a configured {@link CommandLineExecutor}.
 * 
 * @author Robert Mischke
 * 
 */
abstract class CommonExecutorTests {

    protected static final String MESSAGE_UNEXPECTED_STDOUT = "Unexpected StdOut: ";

    protected static final String MESSAGE_UNEXPECTED_STDERR = "Unexpected StdErr: ";

    protected static final String MESSAGE_LINUX_SPECIFIC_TEST_SKIPPED = "Test is only runnable on Linux; skipping";

    protected static final String MESSAGE_WINDOWS_SPECIFIC_TEST_SKIPPED = "Test is only runnable on Windows; skipping";

    protected static final int DEFAULT_TEST_TIMEOUT = 10000;

    /**
     * A simple holder for StdOut/StdErr strings.
     * 
     */
    public final class OutputHolder {

        private String stdout;

        private String stderr;

        OutputHolder(String stdout, String stderr) {
            this.stdout = stdout;
            this.stderr = stderr;
        }

        public String getStdout() {
            return stdout;
        }

        public String getStderr() {
            return stderr;
        }

    }

    protected final Log log = LogFactory.getLog(getClass());

    // FIXME new tests not cross-checked on Linux yet!

    protected void testCrossPlatformEcho(CommandLineExecutor executor) throws IOException, InterruptedException {
        executor.start("echo expected test output");

        OutputHolder output = waitAndCaptureOutput(executor);

        // check StdErr first as it may contain helpful information on failure
        Assert.assertEquals(MESSAGE_UNEXPECTED_STDERR, "", output.getStderr());
        Assert.assertEquals(MESSAGE_UNEXPECTED_STDOUT, "expected test output\n", output.getStdout());
    }

    protected void testCrossPlatformMultiLineEcho(CommandLineExecutor executor) throws IOException, InterruptedException {
        executor.startMultiLineCommand(new String[] { "echo expected line1", "echo expected line2" });

        OutputHolder output = waitAndCaptureOutput(executor);

        // check StdErr first as it may contain helpful information on failure
        Assert.assertEquals(MESSAGE_UNEXPECTED_STDERR, "", output.getStderr());
        Assert.assertEquals(MESSAGE_UNEXPECTED_STDOUT, "expected line1\n" + "expected line2\n", output.getStdout());
    }

    protected void testLinuxEnvironmentSetting(CommandLineExecutor executor) throws IOException, InterruptedException {
        executor.setEnv("TEST1", "val1");
        executor.start("echo key1=$TEST1, key2=$UNDEF");

        OutputHolder output = waitAndCaptureOutput(executor);

        // check StdErr first as it may contain helpful information on failure
        Assert.assertEquals(MESSAGE_UNEXPECTED_STDERR, "", output.getStderr());
        Assert.assertEquals(MESSAGE_UNEXPECTED_STDOUT, "key1=val1, key2=\n", output.getStdout());
    }

    protected void testLinuxProvidedInputStream(CommandLineExecutor executor) throws UnsupportedEncodingException, IOException,
        InterruptedException {
        // data to be sent to STDIN
        final String testInputData = "test input";
       
        ByteArrayInputStream stdinStream = new ByteArrayInputStream(testInputData.getBytes("UTF-8"));
        executor.start("read in; echo received: $in", stdinStream);
        OutputHolder output = waitAndCaptureOutput(executor);

        // STDERR should be empty, and the test data should have been piped to STDOUT with a prefix
        Assert.assertEquals(MESSAGE_UNEXPECTED_STDERR, "", output.getStderr());
        Assert.assertEquals(MESSAGE_UNEXPECTED_STDOUT, "received: " + testInputData + "\n", output.getStdout());
    }

    protected void testWindowsEnvironmentSetting(CommandLineExecutor executor) throws IOException, InterruptedException {
        executor.setEnv("TEST1", "val1");
        executor.start("echo key1=%TEST1%, key2=");

        OutputHolder output = waitAndCaptureOutput(executor);
        // check StdErr first as it may contain helpful information on failure
        Assert.assertEquals(MESSAGE_UNEXPECTED_STDERR, "", output.getStderr());
        Assert.assertEquals(MESSAGE_UNEXPECTED_STDOUT, "key1=val1, key2=\n", output.getStdout());
    }

    protected void testWindowsProvidedInputStream(CommandLineExecutor executor) throws UnsupportedEncodingException, IOException,
        InterruptedException {
        // data to be sent to STDIN
//        final String testInputData = "test input\n";
//
//        ByteArrayInputStream stdinStream = new ByteArrayInputStream(testInputData.getBytes("UTF-8"));
//        executor.start("SET /P tmpVar= && echo %tmpVar%", stdinStream);
//        executor.start("echo %tmpVar%");
//        
//        OutputHolder output = waitAndCaptureOutput(executor);
//
        // STDERR should be empty, and the test data should have been piped to STDOUT with a prefix
//        Assert.assertEquals(MESSAGE_UNEXPECTED_STDERR, "", output.getStderr());
//        Assert.assertEquals(MESSAGE_UNEXPECTED_STDOUT, "received: " + testInputData + "\n", output.getStdout());
    }

    
    /**
     * Common utility method.
     */
    protected void waitAndGatherOutput(CommandLineExecutor executor, final CapturingTextOutReceiver outReceiver,
        final CapturingTextOutReceiver errReceiver) throws IOException, InterruptedException {
        final TextStreamWatcher stdoutWatcher = TextStreamWatcherFactory.create(executor.getStdout(), outReceiver).start();
        final TextStreamWatcher stderrWatcher = TextStreamWatcherFactory.create(executor.getStderr(), errReceiver).start();
        executor.waitForTermination();
        stdoutWatcher.waitForTermination();
        stderrWatcher.waitForTermination();
    }

    /**
     * Common utility method.
     */
    protected OutputHolder waitAndCaptureOutput(CommandLineExecutor executor) throws IOException, InterruptedException {
        // execute
        final CapturingTextOutReceiver outReceiver = new CapturingTextOutReceiver();
        final CapturingTextOutReceiver errReceiver = new CapturingTextOutReceiver();
        waitAndGatherOutput(executor, outReceiver, errReceiver);
        // wrap & return output
        return new OutputHolder(outReceiver.getBufferedOutput(), errReceiver.getBufferedOutput());
    }
}
