/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.jena.testing_framework.manifest;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.junit.internal.runners.ErrorReportingRunner;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.ParentRunner;
import org.junit.runners.Suite;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.RunnerBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.apache.jena.shared.JenaException;

/**
 * Class that runs the Manifest annotated tests.
 * 
 * Used with <code>@RunWith( ManifestSuite.class )</code> this class loads a 
 * manifest test suite file and adds the tests.
 * <p>
 * Tests annotated with <code>@RunWith( ManifestSuite.class )</code> must
 * have a <code>ManifestFile</code> annotation specifying the path to the manifest file
 * </p>
 */
public class ManifestSuite extends ParentRunner<Runner> {
	private static final Logger LOG = LoggerFactory
			.getLogger(ManifestSuite.class);
	private final List<Runner> fRunners;
	private final ManifestItemHandler itemHandler;
	private final ManifestFile mf;

	/**
	 * Called reflectively on classes annotated with
	 * <code>@RunWith(Suite.class)</code>
	 * 
	 * @param cls
	 *            the root class
	 * @param builder
	 *            builds runners for classes in the suite
	 * @throws Throwable
	 */
	public ManifestSuite(Class<? extends ManifestItemHandler> cls,
			RunnerBuilder builder) throws Throwable {
		super(cls);

		List<Throwable> errors = new ArrayList<>();

		mf = cls.getAnnotation(ManifestFile.class);
		if (mf == null) {
			throw new IllegalStateException(
					"ManifestSuite requries ManifestFile annotation");
		}
		itemHandler = cls.newInstance();

		Runner[] runner = new Runner[1];
		try {
			runner[0] = oneManifest(new Manifest(mf.value()),
					new ArrayList<Runner>());
		} catch (JenaException ex) {
			runner[0] = new ErrorReportingRunner(null, ex);
		}

		if (!errors.isEmpty()) {
			throw new InitializationError(errors);
		}
		fRunners = Collections.unmodifiableList(Arrays.asList(runner));
	}

	private Runner oneManifest(final Manifest manifest, List<Runner> r) {

		// Recurse
		for (Iterator<String> iter = manifest.includedManifests(); iter
				.hasNext();) {
			try {
				r.add(oneManifest(new Manifest(iter.next()),
						new ArrayList<Runner>()));
			} catch (JenaException ex) {
				r.add(new ErrorReportingRunner(null, ex));
			}
		}
		itemHandler.setTestRunnerList(r);
		manifest.apply(itemHandler);
		try {
			return new Suite((Class<?>) null, r) {

				@Override
				protected String getName() {
					return manifest.getName();
				}

			};
		} catch (InitializationError e) {
			return new ErrorReportingRunner(null, e);
		}
	}

	@Override
	protected List<Runner> getChildren() {
		return fRunners;
	}

	@Override
	protected Description describeChild(Runner child) {
		return child.getDescription();
	}

	@Override
	protected void runChild(Runner child, RunNotifier notifier) {
		child.run(notifier);
	}

	/**
	 * Returns a name used to describe this Runner
	 */
	@Override
	protected String getName() {
		return String.format("%s - %s", super.getName(), mf.value());
	}
}
