package nz.ac.auckland.integration.testing;

import nz.ac.auckland.integration.testing.specification.OrchestratedTestSpecification;
import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.Suite;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * A heavily modified version of JUnit's Parameterized class that
 * assumes there's a public static void configure() method available
 * to configure a set of test specifications which will then be retrieved
 * using the getSpecifications method of OrchestratedTest
 * <p/>
 * Any copyright for similar code will be under the EPL license for JUnit
 *
 * @author David MacDonald <d.macdonald@auckland.ac.nz>
 */
public class OrchestratedParameterized extends Suite {

    private class TestClassRunnerForParameters extends BlockJUnit4ClassRunner {
        private final OrchestratedTestSpecification specification;
        private final String name;

        TestClassRunnerForParameters(Class<?> type,
                                     OrchestratedTestSpecification specification,
                                     String name) throws InitializationError {
            super(type);
            this.specification = specification;
            this.name = name;
        }

        @Override
        public Object createTest() throws Exception {
            Constructor co = getTestClass().getJavaClass().getConstructor();
            OrchestratedTest test = (OrchestratedTest)co.newInstance();
            test.setSpecification(specification);

            return test;
        }

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

        @Override
        protected String testName(FrameworkMethod method) {
            return method.getName() + getName();
        }

        @Override
        protected void validateConstructor(List<Throwable> errors) {
            validateOnlyOneConstructor(errors);
        }

        @Override
        protected Statement classBlock(RunNotifier notifier) {
            return childrenInvoker(notifier);
        }

        @Override
        protected Annotation[] getRunnerAnnotations() {
            return new Annotation[0];
        }
    }

    private final ArrayList<Runner> runners = new ArrayList<Runner>();

    @SuppressWarnings("unchecked")
    public OrchestratedParameterized(Class<OrchestratedTestBuilder> klass) throws Throwable {
        super(klass, Collections.<Runner>emptyList());

        //we want to recursive go up object tree to invoke configure() to 'simulate' inheritance
        Class currentClass = klass;

        try {
            while (currentClass != OrchestratedTestBuilder.class) {
                Method configureMethod = currentClass.getMethod("configure");
                configureMethod.invoke(null);
                currentClass = currentClass.getSuperclass();
            }
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("A public static void configure() method must be defined", e);
        }

        Method specificationsMethod = klass.getMethod("getSpecifications");
        List<OrchestratedTestSpecification> specifications = (List) specificationsMethod.invoke(null);

        createRunnersForParameters(specifications);
    }

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

    private void createRunnersForParameters(List<OrchestratedTestSpecification> specifications) throws InitializationError {
        int i = 0;
        for (OrchestratedTestSpecification specification : specifications) {
            String name = String.format("%d: %s", i, specification.getDescription());
            TestClassRunnerForParameters runner = new TestClassRunnerForParameters(
                    getTestClass().getJavaClass(), specification,
                    name);
            runners.add(runner);
            i++;
        }
    }
}

@@ -2711,16 +2711,26 @@
 d(Class%3C
+? extends 
 Orchestr
@@ -2840,181 +2840,33 @@
     
-//we want to recursive go up object tree to invoke configure() to 'simulate' inheritance%0A        Class currentClass = klass;%0A%0A        try %7B%0A            while (currentClas
+Method getSpecification
 s 
-!
 = Or
@@ -2896,441 +2896,93 @@
 lass
-) %7B%0A                Method configureMethod = currentClass.getMethod(%22configure%22);%0A                configureMethod.invoke(null);%0A                currentClass = currentClass.
+.getDeclaredMethod(%22
 getS
-u
 pe
-rclass();%0A            %7D%0A        %7D catch (NoSuchMethodException e) %7B%0A            throw new RuntimeException(%22A public static void configure() method must be defined%22, e);%0A        %7D%0A%0A        Method specificationsMethod = klass.getMethod(%22getSpecifications%22
+cifications%22);%0A        getSpecifications.setAccessible(true
 );%0A 
@@ -3047,18 +3047,20 @@
 = (List)
- s
+getS
 pecifica
@@ -3068,22 +3068,16 @@
 ions
-Method
 .invoke(
 null
@@ -3076,12 +3076,27 @@
 oke(
-null
+klass.newInstance()
 );%0A%0A

allow for instances rather than static mess.