package com.github.mkolisnyk.cucumber.runner;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import org.junit.runner.Description;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.ParentRunner;
import org.junit.runners.model.InitializationError;

import com.github.mkolisnyk.cucumber.runner.runtime.BaseRuntimeOptionsFactory;
import com.github.mkolisnyk.cucumber.runner.runtime.ExtendedRuntimeOptions;

import cucumber.api.CucumberOptions;
import cucumber.runtime.ClassFinder;
import cucumber.runtime.Runtime;
import cucumber.runtime.RuntimeOptions;
import cucumber.runtime.RuntimeOptionsFactory;
import cucumber.runtime.io.MultiLoader;
import cucumber.runtime.io.ResourceLoader;
import cucumber.runtime.io.ResourceLoaderClassFinder;
import cucumber.runtime.junit.Assertions;
import cucumber.runtime.junit.JUnitReporter;
import cucumber.runtime.model.CucumberFeature;

public class ExtendedCucumber extends ParentRunner<ExtendedFeatureRunner> {
    private final JUnitReporter jUnitReporter;
    private final List<ExtendedFeatureRunner> children = new ArrayList<ExtendedFeatureRunner>();
    private final Runtime runtime;
    private final ExtendedRuntimeOptions[] extendedOptions;
    private Class clazzValue;
    private int retryCount = 0;
    private int threadsCount = 1;
    private boolean runPreDefined = true;

    public ExtendedCucumber(Class clazz) throws Exception {
        super(clazz);
        this.clazzValue = clazz;
        ClassLoader classLoader = clazz.getClassLoader();
        Assertions.assertNoCucumberAnnotatedMethods(clazz);

        RuntimeOptionsFactory runtimeOptionsFactory = new RuntimeOptionsFactory(clazz);
        RuntimeOptions runtimeOptions = runtimeOptionsFactory.create();

        ResourceLoader resourceLoader = new MultiLoader(classLoader);
        runtime = createRuntime(resourceLoader, classLoader, runtimeOptions);
        extendedOptions = ExtendedRuntimeOptions.init(clazz);
        for (ExtendedRuntimeOptions option : extendedOptions) {
            retryCount = Math.max(retryCount, option.getRetryCount());
            threadsCount = Math.max(threadsCount, option.getThreadsCount());
        }

        final List<CucumberFeature> cucumberFeatures = runtimeOptions.cucumberFeatures(resourceLoader);
        jUnitReporter = new JUnitReporter(
                runtimeOptions.reporter(classLoader),
                runtimeOptions.formatter(classLoader),
                runtimeOptions.isStrict());
        addChildren(cucumberFeatures);
    }
    public ExtendedCucumber(
            Class clazz, CucumberOptions baseOptions,
            ExtendedCucumberOptions[] extendedOptionsValue, boolean runPreDefinedValue) throws Exception {
        super(clazz);
        this.clazzValue = clazz;
        this.runPreDefined = runPreDefinedValue;
        ClassLoader classLoader = clazz.getClassLoader();
        Assertions.assertNoCucumberAnnotatedMethods(clazz);
        BaseRuntimeOptionsFactory runtimeOptionsFactory = new BaseRuntimeOptionsFactory(clazz);
        RuntimeOptions runtimeOptions = runtimeOptionsFactory.create(baseOptions);

        ResourceLoader resourceLoader = new MultiLoader(classLoader);
        runtime = createRuntime(resourceLoader, classLoader, runtimeOptions);

        extendedOptions = ExtendedRuntimeOptions.init(extendedOptionsValue);
        for (ExtendedRuntimeOptions option : extendedOptions) {
            retryCount = Math.max(retryCount, option.getRetryCount());
            threadsCount = Math.max(threadsCount, option.getThreadsCount());
        }

        final List<CucumberFeature> cucumberFeatures = runtimeOptions.cucumberFeatures(resourceLoader);
        jUnitReporter = new JUnitReporter(
                runtimeOptions.reporter(classLoader),
                runtimeOptions.formatter(classLoader),
                runtimeOptions.isStrict());
        addChildren(cucumberFeatures);
    }
    protected Runtime createRuntime(ResourceLoader resourceLoader, ClassLoader classLoader,
                                    RuntimeOptions runtimeOptions) throws InitializationError, IOException {
        ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader);
        return new Runtime(resourceLoader, classFinder, classLoader, runtimeOptions);
    }

    @Override
    public List<ExtendedFeatureRunner> getChildren() {
        return children;
    }

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

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

    private void runPredefinedMethods(Class annotation) throws Exception {
        if (!annotation.isAnnotation()) {
            return;
        }
        Method[] methodList = this.clazzValue.getMethods();
        for (Method method : methodList) {
            Annotation[] annotations = method.getAnnotations();
            for (Annotation item : annotations) {
                if (item.annotationType().equals(annotation)) {
                    method.invoke(null);
                    break;
                }
            }
        }
    }

    @Override
    public void run(RunNotifier notifier) {
        try {
            if (this.runPreDefined) {
                runPredefinedMethods(BeforeSuite.class);
            }
            runPredefinedMethods(BeforeSubSuite.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
        super.run(notifier);
        try {
            if (this.runPreDefined) {
                runPredefinedMethods(AfterSuite.class);
            }
            runPredefinedMethods(AfterSubSuite.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
        runtime.printSummary();
        jUnitReporter.done();
        jUnitReporter.close();
        for (ExtendedRuntimeOptions extendedOption : extendedOptions) {
            ReportRunner.run(extendedOption);
        }
    }

    private void addChildren(List<CucumberFeature> cucumberFeatures) throws InitializationError {
        for (CucumberFeature cucumberFeature : cucumberFeatures) {
            children.add(
                    new ExtendedFeatureRunner(cucumberFeature, runtime,
                            jUnitReporter, this.retryCount));
        }
    }
}

@@ -193,16 +193,59 @@
 .List;%0A%0A
+import org.apache.commons.lang.ArrayUtils;%0A
 import o
@@ -4813,24 +4813,28 @@
 private 
-void run
+Method%5B%5D get
 Predefin
@@ -4860,33 +4860,16 @@
 otation)
- throws Exception
  %7B%0A     
@@ -4923,27 +4923,98 @@
   return
-;%0A        %7D
+ new Method%5B%5D %7B%7D;%0A        %7D%0A        Method%5B%5D filteredMethodList = new Method%5B%5D %7B%7D;
 %0A       
@@ -5311,37 +5311,253 @@
     
-method.invoke(null);%0A        
+ArrayUtils.add(filteredMethodList, method);%0A                %7D%0A            %7D%0A        %7D%0A        return filteredMethodList;%0A    %7D%0A%0A    private void runPredefinedMethods(Class annotation) throws Exception %7B%0A        if (!annotation.isAnnotation()) %7B%0A
     
@@ -5568,32 +5568,165 @@
     
-b
 re
-ak;%0A                %7D
+turn;%0A        %7D%0A        Method%5B%5D methodList = getPredefinedMethods(annotation);%0A        for (Method method : methodList) %7B%0A            method.invoke(null);
 %0A   
@@ -5726,33 +5726,38 @@
 l);%0A            
-%7D
+break;
 %0A        %7D%0A    %7D

add more general method which returns the list of methods with specific annotation