/*
 * Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package jdk.nashorn.internal.test.framework;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import jdk.nashorn.tools.Shell;
import org.testng.Assert;
import org.testng.ITest;
import org.testng.annotations.Test;

/**
 * Compiles a single JavaScript script source file and executes the resulting
 * class. Optionally, output from running the script is compared against the
 * corresponding .EXPECTED file.
 */
@SuppressWarnings("javadoc")
public final class ScriptRunnable extends AbstractScriptRunnable implements ITest {
    public ScriptRunnable(final String framework, final File testFile, final List<String> engineOptions, final Map<String, String> testOptions,  final List<String> scriptArguments) {
        super(framework, testFile, engineOptions, testOptions, scriptArguments);

        if (this.shouldRun) {
          // add --dump-on-error option always so that we can get detailed error msg.
          engineOptions.add("-doe");
        }
    }

    @Override
    public String getTestName() {
        return testFile.getAbsolutePath();
    }

    @Test
    @Override
    public void runTest() throws IOException {
        try {
            super.runTest();
        } catch(final AssertionError e) {
            throw new AssertionError("Failed executing test " + testFile, e);
        }
    }

    @Override
    protected void execute() {
        if (fork) {
            executeInNewProcess();
        } else {
            executeInThisProcess();
        }
    }

    // avoid direct System.out.println - use reporter to capture
    @Override
    protected void log(final String msg) {
        org.testng.Reporter.log(msg, true);
    }

    // throw Assert fail - but log as well so that user can see this at console
    @Override
    protected void fail(final String msg) {
        log(msg);
        Assert.fail(msg);
    }

    @Override
    protected void compile() throws IOException {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        final ByteArrayOutputStream err = new ByteArrayOutputStream();
        final List<String> args = getCompilerArgs();
        int errors;

        try {
            errors = evaluateScript(out, err, args.toArray(new String[0]));
        } catch (final AssertionError e) {
            final PrintWriter writer = new PrintWriter(err);
            e.printStackTrace(writer);
            writer.flush();
            errors = 1;
        }

        if (errors != 0 || checkCompilerMsg) {
            if (expectCompileFailure || checkCompilerMsg) {
                try (PrintStream outputDest = new PrintStream(new FileOutputStream(errorFileName))) {
                    TestHelper.dumpFile(outputDest, new StringReader(new String(err.toByteArray())));
                    outputDest.println("--");
                }
            } else {
                log(new String(err.toByteArray()));
            }

            if (errors != 0 && !expectCompileFailure) {
                fail(String.format("%d errors compiling %s", errors, testFile));
            }
            if (checkCompilerMsg) {
                compare(errorFileName, expectedFileName, true);
            }
        }

        if (expectCompileFailure && errors == 0) {
            fail(String.format("No errors encountered compiling negative test %s", testFile));
        }
    }

    private void executeInThisProcess() {
        final List<String> args = getRuntimeArgs();
        final File outputFileHandle = new File(outputFileName);
        final File errorFileHandle  = new File(errorFileName);

        try (OutputStream outputFile = new FileOutputStream(outputFileName); OutputStream errorFile = new FileOutputStream(errorFileName)) {
            final int errors = evaluateScript(outputFile, errorFile, args.toArray(new String[0]));

            if (errors != 0 || errorFileHandle.length() > 0) {
                if (expectRunFailure) {
                    return;
                }

                if (!ignoreStdError) {
                    if (outputFileHandle.length() > 0) {
                        TestHelper.dumpFile(outputFileHandle);
                    }
                    fail(TestHelper.fullContent(errorFileHandle));
                }
            }

            if (compare) {
                compare(outputFileName, expectedFileName, false);
            }
        } catch (final IOException e) {
            if (!expectRunFailure) {
                fail("Failure running test " + testFile + ": " + e.getMessage());
                // else success
            }
        }
    }

    private void executeInNewProcess() {

        final String separator = System.getProperty("file.separator");
        final List<String> cmd = new ArrayList<>();

        cmd.add(System.getProperty("java.home") + separator + "bin" + separator + "java");
        for (final String str : forkJVMOptions) {
            if(!str.isEmpty()) {
                cmd.add(str);
        }
        }
        cmd.add(Shell.class.getName());
        // now add the rest of the "in process" runtime arguments
        cmd.addAll(getRuntimeArgs());

        final File outputFileHandle = new File(outputFileName);
        final File errorFileHandle = new File(errorFileName);

        try {
            final ProcessBuilder pb = new ProcessBuilder(cmd);
            pb.redirectOutput(outputFileHandle);
            pb.redirectError(errorFileHandle);
            final Process process = pb.start();

            final int exitCode = process.waitFor();

            if (exitCode != 0 || errorFileHandle.length() > 0) {
                if (expectRunFailure) {
                    return;
                }
                if (!ignoreStdError) {
                    if (outputFileHandle.length() > 0) {
                        TestHelper.dumpFile(outputFileHandle);
                    }
                    fail(TestHelper.fullContent(errorFileHandle));
                }
            }

            if (compare) {
                compare(outputFileName, expectedFileName, false);
            }
        } catch (final IOException | InterruptedException e) {
            if (!expectRunFailure) {
                fail("Failure running test " + testFile + ": " + e.getMessage());
                // else success
            }
        }
    }

    private void compare(final String outputFileName0, final String expectedFileName0, final boolean compareCompilerMsg) throws IOException {
        final File expectedFile = new File(expectedFileName0);

        BufferedReader expected;
        if (expectedFile.exists()) {
            // copy expected file overwriting existing file and preserving last
            // modified time of source
            try {
                Files.copy(FileSystems.getDefault().getPath(expectedFileName0),
                        FileSystems.getDefault().getPath(copyExpectedFileName),
                        StandardCopyOption.REPLACE_EXISTING,
                        StandardCopyOption.COPY_ATTRIBUTES);
            } catch (final IOException ex) {
                fail("failed to copy expected " + expectedFileName + " to " + copyExpectedFileName + ": " + ex.getMessage());
            }
            expected = new BufferedReader(new InputStreamReader(new FileInputStream(expectedFileName0)));
        } else {
            expected = new BufferedReader(new StringReader(""));
        }

        try (BufferedReader actual = new BufferedReader(new InputStreamReader(new FileInputStream(outputFileName0)));
             BufferedReader expected0 = expected){
            compare(actual, expected0, compareCompilerMsg);
        }
    }
}
