/*
 * 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 org.junit.ComparisonFailure;
import org.junit.runner.RunWith;
import org.springsource.loaded.test.infra.IResult;
import org.springsource.loaded.test.infra.Result;
import org.springsource.loaded.test.infra.ResultException;


/**
 * This class is intended to be subclassed to create 'generated' tests. It needs to be run with the {@link ExploreAllChoicesRunner}
 * test runner, using the {@link RunWith} annotation.
 * <p>
 * To create a generative test two things come together:
 * 
 * <ul>
 * <li>A mechanism to create different test configurations based on 'random' choices. These random choices are made by the test's
 * 'setup' method calling the provided 'choice' methods.
 * 
 * <li>A mechanism to run the same test twice in two different execution contexts. It is the responsibility of the test subclass to
 * setup the appropriate execution context. See {@link GenerativeSpringLoadedTest} for an example.
 * </ul>
 * 
 * The test runner is responsible for injecting implementations of the IChoiceGenerator interface.
 * <p>
 * On a first run, the test runner will provide a 'recording' choice generator. The test is run multiple times until all possible
 * choices have been explored. For each test run the choices are recorded together with the observed test result for those choices.
 * This is used to populate the test tree.
 * <p>
 * Then the tests are run again replaying the recorded choices. The result is compared with the result from the first run. The test
 * fails if the results are not equal (using whatever implementation of equals is provided by the result objects.
 * 
 * @author kdvolder
 */
public abstract class GenerativeTest {

	/**
	 * Injected by the test runner. Use the ChoiceGenerator to implement some logic to choose test parameters. Either in your setup
	 * method or your actual test method.
	 */
	public IChoiceGenerator choiceGenerator = null;

	/**
	 * This field is set by the test runner to indicate whether the test is currently in 'generative' mode, or 'replay/verify' mode.
	 * This flag is mostly intended for the setup method so it can setup an appropriate execution context.
	 */
	public boolean generative;

	/**
	 * This method should setup the test, using the provided choice generator to construct/choose a test configuration.
	 */
	public void setup() throws Exception, RejectedChoice {
	}

	public void teardown() throws Exception {
	}

	/**
	 * There should be only one test method in this type of test, this is it!
	 * <p>
	 * The test method will be run twice by the runner, once in a 'generative' mode and once in 'verifying' mode.
	 * <p>
	 * In generative mode, it is ok to throw RejectedChoice exception, this will cause the test to be ignored. The test method
	 * itself shouldn't need to know what mode it is running in. The test runner should inject the necessary context dependencies
	 * for the code to be identical in both cases.
	 * <p>
	 * The only obligation the test method has is to ensure that, given a deterministic set of choices is made by the injected
	 * ChoiceGenerator, the test method's behavior should also be deterministic.
	 * 
	 * @throws ResultExeption if the test produces an expected exception as result.
	 * @throws RejectedChoice (in generative mode only) if the generated test should be ignored.
	 * @throws Exception any other exception should be treated as an unexpected error and make the test fail.
	 * @return Result encapsulating the expected result of the test.
	 */
	public abstract Result test() throws ResultException, Exception;

	/**
	 * @return Use choice generator to pick an element from a bunch of Strings
	 * @throws RejectedChoice
	 */
	protected <T> T choice(T... options) throws RejectedChoice {
		return options[choice(options.length)];
	}

	/**
	 * @return number in range 0 (inclusive) to 'hi' (exclusive)
	 * @throws RejectedChoice if 'hi' is negative
	 */
	protected int choice(int hi) throws RejectedChoice {
		return choice(0, hi);
	}

	/**
	 * @return number in range 'lo' (inclusive) to 'hi' (exclusive)
	 * @throws RejectedChoice if 'lo' >= 'hi'
	 */
	protected int choice(int lo, int hi) throws RejectedChoice {
		if (lo >= hi) {
			throw new RejectedChoice(); //Nothing to choose from
		}
		if (hi - lo == 1) {
			//only one choice
			return lo;
		}

		//Use kind of 'binary search' for efficient choice making
		if (choice())
			return choice(lo, (lo + hi) / 2);
		else
			return choice((lo + hi) / 2, hi);
	}

	protected boolean choice() {
		boolean b = choiceGenerator.nextBoolean();
		return b;
	}

	/**
	 * Override this and make it return something other than null to create a nicer name in the JUnit runner view. Beware that this
	 * name must be unique or the Eclipse JUnit runner view will get confused displaying the results (though tests should still run
	 * ok).
	 * <p>
	 * Typically, you should override this to return a string that describes the values for the configuration parameter values of
	 * the test instance.
	 * <p>
	 * If not overridden, the choices 'bitString' will be displayed. This is unique, but not very informative.
	 * 
	 * @return A String uniquely identifying the test or null.
	 */
	public String getConfigDescription() {
		return null;
	}

	/**
	 * This method is called by the test runner to compare predicted results against actual results.
	 * <p>
	 * Override this method to customise how you want these compared.
	 * 
	 * @param expected Result from the 'generative' test run.
	 * @param actual Result from the actual test run.
	 */
	final protected void assertEqualIResults(IResult expected, IResult actual) {
		if (expected.equals(actual)) {
			return;
		}
		if (expected.getClass() != actual.getClass()) {
			//One is an Exception and the other one isn't. these's no way these should
			//ever be treated as equivalent!
			throw new ComparisonFailure(null, expected.toString(), actual.toString());
		}
		if (expected instanceof Result) {
			assertEqualResults((Result) expected, (Result) actual);
		} else if (expected instanceof ResultException) {
			assertEqualExceptions((ResultException) expected, (ResultException) actual);
		} else {
			//I don't what it is?? There are only two implementations of the interface
			throw new ComparisonFailure(null, expected.toString(), actual.toString());
		}
	}

	/**
	 * This method gets called to compare two ResultExceptions, but only when the standard equals method returned false. Subclasses
	 * may override this to relax the equality check.
	 */
	protected void assertEqualExceptions(ResultException expected, ResultException actual) {
		throw new ComparisonFailure(null, expected.toString(), actual.toString());
	}

	/**
	 * This method gets called to compare two Results, but only when the standard equals method returned false. Subclasses may
	 * override this to relax the equality check.
	 */
	protected void assertEqualResults(Result expected, Result actual) {
		throw new ComparisonFailure(null, expected.toString(), actual.toString());
	}

}
