/*
 * Copyright 2010-2012 VMware and contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springsource.loaded.testgen;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.junit.Assert;
import org.junit.runner.Description;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.ParentRunner;
import org.junit.runners.model.InitializationError;
import org.springsource.loaded.test.infra.IResult;
import org.springsource.loaded.test.infra.ResultException;


/**
 * This test runner in injects an IChoiceGenerator into the test class it is running. The choice generator is injected by setting
 * choiceGenerator field in the test instance.
 * <p>
 * For now this test runner assumes the test class is using JUnit 3 style method conventions for setup and teardown, and should
 * provide only a single 'test' method to run the tests.
 * 
 * @author kdvolder
 */
public class ExploreAllChoicesRunner extends ParentRunner<GeneratedTest> {
	private static boolean generatedTestsOn = Boolean.parseBoolean(System.getProperty("springloaded.tests.generatedTests", "true"));

	private Class<? extends GenerativeSpringLoadedTest> testClass;
	private List<GeneratedTest> children = null;
	private boolean predictResults;

	public ExploreAllChoicesRunner(Class<? extends GenerativeSpringLoadedTest> testClass) throws InitializationError {
		super(testClass);
		this.testClass = testClass;
		this.predictResults = testClass.isAnnotationPresent(PredictResult.class);
	}

	@Override
	protected List<GeneratedTest> getChildren() {
		if (!generatedTestsOn)
			return Collections.emptyList();
		if (children != null)
			return children;
		List<GeneratedTest> newChildren = new ArrayList<GeneratedTest>();
		try {
			SystematicChoiceGenerator choiceGenerator = new SystematicChoiceGenerator();
			do {
				GenerativeTest test = testClass.newInstance();

				//Inject a choice generator into the test!
				test.choiceGenerator = choiceGenerator;
				test.generative = true;

				try {
					test.setup();
					IResult r = null;
					if (predictResults) {
						try {
							r = test.test(); //run test in generative mode
						} catch (ResultException e) {
							r = e;
						} catch (RejectedChoice e) {
							//Choices shouldn't be rejected during test run only during setup!
							throw new IllegalStateException(e);
						}
					}
					addTest(newChildren, new GeneratedTest(choiceGenerator.choices, r, test.getConfigDescription()));
				} catch (RejectedChoice e) {
					//Ignore this test
				} finally {
					test.teardown();
				}
			} while (choiceGenerator.backtrack());
			children = newChildren;
			return newChildren;
		} catch (Exception e) {
			//TODO: [...] use JUnit framework to handle this more gracefully
			//  This probably means overriding one of the validation methods and, moving most of this code into validation
			//  and storing the children during validation so that this method here will only have to return the stored
			//  children and should never raise any errors.
			throw new Error(e);
		}
	}

	protected void addTest(List<GeneratedTest> newChildren, GeneratedTest test) {
		//		System.out.println("Adding "+test.getDisplayName());
		newChildren.add(test);
	}

	@Override
	protected Description describeChild(GeneratedTest child) {
		return Description.createTestDescription(testClass, sanitise(child.getDisplayName()));
	}

	/**
	 * Certain characters confuse the Eclipse JUnit view... replace those with harmless ones.
	 */
	private String sanitise(String displayName) {
		return displayName.replace('(', '[').replace(')', ']').replace('\n', ' ').replace('\r', ' ');
	}

	@Override
	protected void runChild(GeneratedTest testPredicted, RunNotifier notifier) {
		notifier.fireTestStarted(describeChild(testPredicted));
		try {
			//Determine expected test result first:
			IResult expectedResult = testPredicted.getExpectedResult();
			if (expectedResult == null) {
				//Suite was not created with @PredictResult must predict it now
				GenerativeTest test = testClass.newInstance();

				//Inject a choice generator into the test!
				test.choiceGenerator = new SystematicChoiceGenerator(testPredicted.getChoices());
				test.generative = true;
				try {
					test.setup();
					expectedResult = test.test();
				} catch (ResultException e) {
					expectedResult = e;
				} finally {
					test.teardown();
				}
			}

			//Run the test again and verify
			GenerativeTest test = testClass.newInstance();

			//Inject a choice generator into the test!
			test.choiceGenerator = new SystematicChoiceGenerator(testPredicted.getChoices());
			test.generative = false;
			try {
				test.setup();
				Assert.assertEquals(testPredicted.getConfigDescription(), test.getConfigDescription());
				IResult actual;
				try {
					actual = test.test(); //run test in verify mode
				} catch (ResultException e) {
					actual = e;
				}
				test.assertEqualIResults(expectedResult, actual);
			} finally {
				test.teardown();
			}
		} catch (Throwable e) {
			notifier.fireTestFailure(new Failure(describeChild(testPredicted), e));
		} finally {
			notifier.fireTestFinished(describeChild(testPredicted));
		}
	}

}
