/**
 * Copyright (C) . - All Rights Reserved.
 * Unauthorized copying of this file via any medium is
 * strictly prohibited Proprietary and Confidential.
 * Written by .
 */

package pda.core.trace.path;

import pda.common.conf.Constant;
import pda.common.java.Subject;
import pda.common.utils.*;
import pda.core.trace.Runner;
import pda.core.trace.inst.Instrument;
import pda.core.trace.inst.visitor.MethodInstrumentVisitor;
import pda.core.trace.inst.visitor.TestMethodInstrumentVisitor;

import java.io.*;
import java.util.HashSet;
import java.util.Set;

/**
 * @author
 *
 */
public class Collector {

	private final static String __name__ = "@Collector ";

	/**
	 * collect the failed test cases and the methods covered by them
	 * 
	 * @param subject : the subject to be tested
	 * @return a pair that contains the
	 *         <p>
	 *         ids of failted test cases
	 *         </p>
	 *         and
	 *         the
	 *         <p>
	 *         ids of covered methods
	 *         </p>
	 */
	public static Pair<Set<Integer>, Set<Integer>> collectFailedTestAndCoveredMethod(Subject subject) {
		// run all test
		try {
			ExecuteCommand.executeDefects4JTest(CmdFactory.createTestSuiteCmd(subject));
		} catch (Exception e) {
			LevelLogger.fatal(__name__ + "#collectAllTestCases run test failed !", e);
			return null;
		}
		Set<Integer> failedTest = findFailedTestFromFile(Constant.STR_TMP_D4J_OUTPUT_FILE);
		Utils.copyFile(Constant.STR_TMP_D4J_OUTPUT_FILE, Utils.join(subject.getOutBase(), "original_test.log"));
		Set<Integer> coveredMethods = collectCoveredMethod(subject, failedTest);
		return new Pair<Set<Integer>, Set<Integer>>(failedTest, coveredMethods);
	}

	/**
	 * collect all failed test cases by parsing the d4j output information
	 * 
	 * @param outputFilePath
	 *                       : defects4j output file path
	 * @return a set of method ids of failed test cases
	 */
	public static Set<Integer> findFailedTestFromFile(String outputFilePath) {
		if (outputFilePath == null) {
			LevelLogger.error(__name__ + "#findFailedTestFromFile OutputFilePath is null.");
			return null;
		}
		File file = new File(outputFilePath);
		BufferedReader bReader = null;
		try {
			bReader = new BufferedReader(new FileReader(file));
		} catch (FileNotFoundException e) {
			LevelLogger.error(__name__ + "@findFailedTestFromFile BufferedReader init failed.");
			return null;
		}

		String line = null;
		Set<Integer> failedTest = new HashSet<>();
		try {
			while ((line = bReader.readLine()) != null) {
				String trimed = line.trim();
				if (trimed.startsWith(Constant.ANT_BUILD_FAILED)) {
					LevelLogger.error(__name__ + "#findFailedTestFromFile Ant build failed.");
					break;
				}
				if (trimed.startsWith("Failing tests:")) {
					String count = trimed.substring("Failing tests:".length());
					int failingCount = Integer.parseInt(count.trim());
					while (failingCount > 0) {
						line = bReader.readLine();
						int index = line.indexOf("-");
						// if (index > 0 ) {
						if (index > 0 && !line.contains("MainTest")) {
							String testStr = line.substring(index + 2).trim();
							String[] testInfo = testStr.split("::");
							if (testInfo.length != 2) {
								LevelLogger
										.error(__name__ + "#findFailedTestFromFile Failed test cases format error !");
								System.exit(0);
							}
							// convert "org.jfree.chart.Clazz::test" to
							// "org.jfree.chart.Clazz#void#test#?"
							String identifierString = testInfo[0] + "#void#" + testInfo[1] + "#?";
							int methodID = Identifier.getIdentifier(identifierString);
							failedTest.add(methodID);
						}
						failingCount--;
					}
				}
			}
			bReader.close();
		} catch (IOException e) {
			LevelLogger.fatal(__name__ + "#findFailedTestFromFile Read line from file failed.", e);
		} finally {
			if (bReader != null) {
				try {
					bReader.close();
				} catch (IOException e) {
				}
			}
		}
		return failedTest;
	}

	/**
	 * collect all executed methods for given test cases {@code testcases}
	 *
	 * @param subject
	 *                  : current subject, e.g., chart_1_buggy
	 * @param testcases
	 *                  :a set of method ids of test cases to be collected
	 * @return a set of method ids covered by the given test cases
	 */
	public static Set<Integer> collectCoveredMethod(Subject subject, Set<Integer> testcases) {
		subject.backupSource();
		MethodInstrumentVisitor methodInstrumentVisitor = new MethodInstrumentVisitor();
		String subjectSourcePath = Utils.join(subject.getHome(), subject.getSsrc());
		Instrument.execute(subjectSourcePath, methodInstrumentVisitor);
		TestMethodInstrumentVisitor newTestMethodInstrumentVisitor = new TestMethodInstrumentVisitor(testcases, false);
		String subjectTestPath = Utils.join(subject.getHome(), subject.getTsrc());
		Instrument.execute(subjectTestPath, newTestMethodInstrumentVisitor);
		Set<Integer> allMethods = new HashSet<>();
		for (Integer methodID : testcases) {
			String methodString = Identifier.getMessage(methodID);
			String[] methodInfo = methodString.split("#");
			if (methodInfo.length < 4) {
				LevelLogger.error(__name__ + "#collectCoveredMethod method string format error : " + methodString);
				System.exit(1);
			}
			String clazzpath = methodInfo[0];
			String methodName = methodInfo[2];
			String singleTest = clazzpath + "::" + methodName;
			boolean success = Runner.testSingleCase(subject, singleTest);
			if (!success) {
				LevelLogger
						.error(__name__ + "#collectCoveredMethod build subject failed when running single test case.");
				System.exit(0);
			}
			Set<Integer> coveredMethodes = ExecutionPathBuilder
					.collectAllExecutedMethods(Constant.STR_TMP_INSTR_OUTPUT_FILE);

			if (coveredMethodes != null) {
				allMethods.addAll(coveredMethodes);
			}
		}

		subject.restoreSource();
		return allMethods;
	}

}
