/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.core.tests.compiler.regression;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

import junit.framework.AssertionFailedError;

import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.tests.util.Util;
import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;

public class AbstractComparableTest extends AbstractRegressionTest {

	class Logger extends Thread {
		StringBuffer buffer;
		InputStream inputStream;
		String type;
		Logger(InputStream inputStream, String type) {
			this.inputStream = inputStream;
			this.type = type;
			this.buffer = new StringBuffer();
		}

		public void run() {
			try {
				BufferedReader reader = new BufferedReader(new InputStreamReader(this.inputStream));
				String line = null;
				while ((line = reader.readLine()) != null) {
					this.buffer./*append(this.type).append("->").*/append(line).append("\n");
				}
				reader.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	// to enable set VM args to -Dcompliance=1.5 -Drun.javac=enabled
	public static final String RUN_SUN_JAVAC = System.getProperty("run.javac");
	public static boolean RUN_JAVAC = CompilerOptions.ENABLED.equals(RUN_SUN_JAVAC);
	public static String JAVAC_OUTPUT_DIR = Util.getOutputDirectory() + File.separator + "javac";
	static int[] DIFF_COUNTERS = new int[3];
	public IPath dirPath;
	
	// Summary display
	static String CURRENT_CLASS_NAME;
	static Map TESTS_COUNTERS = new HashMap();

	/* (non-Javadoc)
	 * @see junit.framework.TestCase#setUp()
	 */
	protected void setUp() throws Exception {
		super.setUp();
		if (RUN_JAVAC) {
			if (!getClass().getName().equals(CURRENT_CLASS_NAME)) {
				CURRENT_CLASS_NAME = getClass().getName();
				System.out.println("***************************************************************************");
				System.out.print("* Comparison with Sun Javac compiler for class ");
				System.out.print(CURRENT_CLASS_NAME.substring(CURRENT_CLASS_NAME.lastIndexOf('.')+1));
				System.out.println(" ("+TESTS_COUNTERS.get(CURRENT_CLASS_NAME)+" tests)");
				System.out.println("***************************************************************************");
				DIFF_COUNTERS[0] = 0;
				DIFF_COUNTERS[1] = 0;
				DIFF_COUNTERS[2] = 0;
			}
		}
	}
	/* (non-Javadoc)
	 * @see junit.framework.TestCase#tearDown()
	 */
	protected void tearDown() throws Exception {
		super.tearDown();
		if (RUN_JAVAC) {
			Integer count = (Integer)TESTS_COUNTERS.get(CURRENT_CLASS_NAME);
			if (count != null) {
				int newCount = count.intValue()-1;
				TESTS_COUNTERS.put(CURRENT_CLASS_NAME, new Integer(newCount));
				if (newCount == 0) {
					if (DIFF_COUNTERS[0]!=0 || DIFF_COUNTERS[1]!=0 || DIFF_COUNTERS[2]!=0) {
						System.out.println("===========================================================================");
						System.out.println("Results summary:");
					}
					if (DIFF_COUNTERS[0]!=0)
						System.out.println("	- "+DIFF_COUNTERS[0]+" test(s) where Javac found errors/warnings but Eclipse did not");
					if (DIFF_COUNTERS[1]!=0)
						System.out.println("	- "+DIFF_COUNTERS[1]+" test(s) where Eclipse found errors/warnings but Javac did not");
					if (DIFF_COUNTERS[2]!=0)
						System.out.println("	- "+DIFF_COUNTERS[2]+" test(s) where Eclipse and Javac did not have same output");
					System.out.println("\n");
				}
			}
		}
	}

	public AbstractComparableTest(String name) {
		super(name);
	}

	/*
	 * Toggle compiler in mode -1.5
	 */
	protected Map getCompilerOptions() {
		Map options = super.getCompilerOptions();
		options.put(CompilerOptions.OPTION_Compliance, CompilerOptions.VERSION_1_5);
		options.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_1_5);	
		options.put(CompilerOptions.OPTION_TargetPlatform, CompilerOptions.VERSION_1_5);
		options.put(CompilerOptions.OPTION_ReportFinalParameterBound, CompilerOptions.WARNING);
		options.put(CompilerOptions.OPTION_ReportUnnecessaryTypeCheck, CompilerOptions.WARNING);
		options.put(CompilerOptions.OPTION_ReportMissingOverrideAnnotation, CompilerOptions.WARNING);
		options.put(CompilerOptions.OPTION_ReportMissingDeprecatedAnnotation, CompilerOptions.WARNING);
		options.put(CompilerOptions.OPTION_ReportIncompleteEnumSwitch, CompilerOptions.WARNING);
		return options;
	}

	/*######################################
	 * Specific method to let tests Sun javac compilation available...
	 #######################################*/
	/*
	 * Cleans up the given directory by removing all the files it contains as well
	 * but leaving the directory.
	 * @throws TargetException if the target path could not be cleaned up
	 */
	protected void cleanupDirectory(File directory) {
		if (!directory.exists()) {
			return;
		}
		String[] fileNames = directory.list();
		for (int i = 0; i < fileNames.length; i++) {
			File file = new File(directory, fileNames[i]);
			if (file.isDirectory()) {
				cleanupDirectory(file);
			} else {
				if (!file.delete())
					System.out.println("Could not delete file " + file.getPath());
			}
		}
		if (!directory.delete())
			System.out.println("Could not delete directory " + directory.getPath());
	}

	/*
	 * Write given source test files in current output sub-directory.
	 * Use test name for this sub-directory name (ie. test001, test002, etc...)
	 */
	protected void printFiles(String[] testFiles) {
		for (int i=0, length=testFiles.length; i<length; i++) {
			System.out.println(testFiles[i++]);
			System.out.println(testFiles[i]);
		}
		System.out.println("");
	}

	/*#########################################
	 * Override basic runConform and run Negative methods to compile test files
	 * with Sun compiler (if specified) and compare its results with ours.
	 ##########################################*/
	/* (non-Javadoc)
	 * @see org.eclipse.jdt.core.tests.compiler.regression.AbstractRegressionTest#runConformTest(java.lang.String[], java.lang.String)
	 */
	protected void runConformTest(String[] testFiles,
			String expectedSuccessOutputString, String[] classLib,
			boolean shouldFlushOutputDirectory, String[] vmArguments,
			Map customOptions, ICompilerRequestor clientRequestor) {
		try {
			super.runConformTest(testFiles, expectedSuccessOutputString,
					classLib, shouldFlushOutputDirectory, vmArguments,
					customOptions, clientRequestor);
		} catch (AssertionFailedError e) {
			throw e;
		} finally {
			if (RUN_JAVAC)
				runJavac(testFiles, null, expectedSuccessOutputString, shouldFlushOutputDirectory);
		}
	}

	/*
	 * Run Sun compilation using javac.
	 * Use JRE directory to retrieve javac bin directory and current classpath for
	 * compilation.
	 * Launch compilation in a thread and verify that it does not take more than 5s
	 * to perform it. Otherwise abort the process and log in console.
	 */
	protected void runJavac(String[] testFiles, final String expectedProblemLog, final String expectedSuccessOutputString, boolean shouldFlushOutputDirectory) {
		try {
			if (shouldFlushOutputDirectory)
				cleanupDirectory(new File(JAVAC_OUTPUT_DIR));

			// Write files in dir
			IPath dirFilePath = writeFiles(testFiles);

			String testName = testName();
			Process compileProcess = null;
			Process execProcess = null;
			try {
				// Compute classpath
				String[] classpath = Util.concatWithClassLibs(JAVAC_OUTPUT_DIR, false);
				StringBuffer cp = new StringBuffer();
				cp.append(" -classpath .;"); // start with the current directory which contains the source files
				int length = classpath.length;
				for (int i = 0; i < length; i++) {
					if (classpath[i].indexOf(" ") != -1) {
						cp.append("\"" + classpath[i] + "\"");
					} else {
						cp.append(classpath[i]);
					}
					if (i<(length-1)) cp.append(";");
				}
				// Compute command line
				IPath jdkDir = (new Path(Util.getJREDirectory())).removeLastSegments(1);
				IPath javacPath = jdkDir.append("bin").append("javac.exe");
				StringBuffer cmdLine = new StringBuffer(javacPath.toString());
				cmdLine.append(cp);
				cmdLine.append(" -d ");
				cmdLine.append(JAVAC_OUTPUT_DIR.indexOf(" ") != -1 ? "\"" + JAVAC_OUTPUT_DIR + "\"" : JAVAC_OUTPUT_DIR);
				cmdLine.append(" -source 1.5 -deprecation -Xlint:unchecked "); // enable recommended warnings
				if (this.dirPath.equals(dirFilePath)) {
					cmdLine.append("*.java");
				} else {
					IPath subDirPath = dirFilePath.append("*.java").removeFirstSegments(this.dirPath.segmentCount());
					String subDirName = subDirPath.toString().substring(subDirPath.getDevice().length());
					cmdLine.append(subDirName);
				}
//				System.out.println(testName+": "+cmdLine.toString());
//				System.out.println(GenericTypeTest.this.dirPath.toFile().getAbsolutePath());
				// Launch process
				compileProcess = Runtime.getRuntime().exec(cmdLine.toString(), null, this.dirPath.toFile());
	            // Log errors
	            Logger errorLogger = new Logger(compileProcess.getErrorStream(), "ERROR");            

	            // Log output
	            Logger outputLogger = new Logger(compileProcess.getInputStream(), "OUTPUT");

	            // start the threads to run outputs (standard/error)
	            errorLogger.start();
	            outputLogger.start();

	            // Wait for end of process
				int exitValue = compileProcess.waitFor();

				// Compare compilation results
				if (expectedProblemLog == null) {
					if (exitValue != 0) {
						System.out.println("----------------------------------------");
						System.out.println(testName+" - Javac has found error(s) but Eclipse expects conform result:\n");
						System.out.println(errorLogger.buffer.toString());
						printFiles(testFiles);
						DIFF_COUNTERS[0]++;
					} else if (errorLogger.buffer.length() > 0) {
						System.out.println("----------------------------------------");
						System.out.println(testName+" - Javac has found warning(s) but Eclipse expects conform result:\n");
						System.out.println(errorLogger.buffer.toString());
						printFiles(testFiles);
						DIFF_COUNTERS[0]++;
					} else if (expectedSuccessOutputString != null) {
						// Compute command line
						IPath javaPath = jdkDir.append("bin").append("java.exe");
						StringBuffer javaCmdLine = new StringBuffer(javaPath.toString());
						javaCmdLine.append(cp);
						// assume executable class is name of first test file
						javaCmdLine.append(' ').append(testFiles[0].substring(0, testFiles[0].indexOf('.')));
						execProcess = Runtime.getRuntime().exec(javaCmdLine.toString(), null, this.dirPath.toFile());
						Logger logger = new Logger(execProcess.getInputStream(), "OUTPUT");
						logger.start();

						exitValue = execProcess.waitFor();
						String javaOutput = logger.buffer.toString().trim();
						if (!expectedSuccessOutputString.equals(javaOutput)) {
							System.out.println("----------------------------------------");
							System.out.println(testName+" - Javac and Eclipse runtime output is not the same:");
							System.out.println(expectedSuccessOutputString);
							System.out.println(javaOutput);
							System.out.println("\n");
							printFiles(testFiles);
							DIFF_COUNTERS[2]++;
						}
					}
				} else if (exitValue == 0) {
					if (errorLogger.buffer.length() == 0 && expectedProblemLog.length() > 0) {
						System.out.println("----------------------------------------");
						System.out.println(testName+" - Eclipse has found error(s)/warning(s) but Javac did not find any:");
						System.out.println(expectedProblemLog);
						printFiles(testFiles);
						DIFF_COUNTERS[1]++;
					} else if (expectedProblemLog.indexOf("ERROR") >0 ){
						System.out.println("----------------------------------------");
						System.out.println(testName+" - Eclipse has found error(s) but Javac only found warning(s):");
						System.out.println("javac:");
						System.out.println(errorLogger.buffer.toString());
						System.out.println("eclipse:");
						System.out.println(expectedProblemLog);
						printFiles(testFiles);
						DIFF_COUNTERS[1]++;
					} else {
						// TODO (frederic) compare warnings in each result and verify they are similar...
//						System.out.println(testName+": javac has found warnings :");
//						System.out.print(errorLogger.buffer.toString());
//						System.out.println(testName+": we're expecting warning results:");
//						System.out.println(expectedProblemLog);
					}
				} else if (errorLogger.buffer.length() == 0) {
					System.out.println("----------------------------------------");
					System.out.println(testName+" - Eclipse has found error(s)/warning(s) but Javac did not find any:");
					System.out.println(expectedProblemLog);
					printFiles(testFiles);
					DIFF_COUNTERS[1]++;
				}
			} catch (IOException ioe) {
				System.out.println(testName+": Not possible to launch Sun javac compilation!");
			} catch (InterruptedException e1) {
				if (compileProcess != null) compileProcess.destroy();
				if (execProcess != null) execProcess.destroy();
				System.out.println(testName+": Sun javac compilation was aborted!");
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// Clean up written file(s)
			IPath testDir =  new Path(Util.getOutputDirectory()).append(testName());
			cleanupDirectory(testDir.toFile());
		}
	}

	/* (non-Javadoc)
	 * Override to compile test files with Sun compiler if specified and compare its results with ours.
	 * @see org.eclipse.jdt.core.tests.compiler.regression.AbstractRegressionTest#runNegativeTest(java.lang.String[], java.lang.String)
	 */
	protected void runNegativeTest(String[] testFiles,
			String expectedProblemLog, String[] classLib,
			boolean shouldFlushOutputDirectory, Map customOptions,
			boolean generateOutput) {
		try {
			super.runNegativeTest(testFiles, expectedProblemLog, classLib,
					shouldFlushOutputDirectory, customOptions, generateOutput);
		} catch (AssertionFailedError e) {
			throw e;
		} finally {
			if (RUN_JAVAC)
				runJavac(testFiles, expectedProblemLog, null, shouldFlushOutputDirectory);
		}
	}

	/*
	 * Write given source test files in current output sub-directory.
	 * Use test name for this sub-directory name (ie. test001, test002, etc...)
	 */
	protected IPath writeFiles(String[] testFiles) {
		// Compute and create specific dir
		IPath outDir = new Path(Util.getOutputDirectory());
		this.dirPath =  outDir.append(testName());
		File dir = this.dirPath.toFile();
		if (!dir.exists()) {
			dir.mkdirs();
		}

		// For each given test files
		IPath dirFilePath = null;
		for (int i=0, length=testFiles.length; i<length; i++) {
			String contents = testFiles[i+1];
			String fileName = testFiles[i++];
			IPath filePath = this.dirPath.append(fileName);
			if (fileName.lastIndexOf('/') >= 0) {
				dir = filePath.removeLastSegments(1).toFile();
				if (!dir.exists()) {
					dir.mkdirs();
				}
			}
			if (dirFilePath == null|| (filePath.segmentCount()-1) < dirFilePath.segmentCount()) {
				dirFilePath = filePath.removeLastSegments(1);
			}
			Util.writeToFile(contents, filePath.toString());
		}
		return dirFilePath;
	}
}
