/*
 * Copyright 2014 Red Hat, Inc. and/or its affiliates.
 *
 * 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.drools.workbench.models.testscenarios.backend;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.drools.core.impl.StatefulKnowledgeSessionImpl;
import org.drools.workbench.models.testscenarios.shared.Scenario;
import org.junit.internal.AssumptionViolatedException;
import org.junit.internal.runners.model.EachTestNotifier;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.model.InitializationError;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.rule.FactHandle;

public class ScenarioRunner4JUnit extends Runner {

    private final int maxRuleFirings;
    // The description of the test suite
    private Description descr;
    // the actual scenario test to be executed
    private List<Scenario> scenarios;
    private Map<String, KieSession> ksessions;

    public ScenarioRunner4JUnit(final Scenario scenario,
                                final Map<String, KieSession> ksessions,
                                final int maxRuleFirings) throws InitializationError {
        this.scenarios = new ArrayList<Scenario>();
        this.scenarios.add(scenario);
        this.ksessions = ksessions;
        this.descr = Description.createSuiteDescription("Scenario test cases");
        this.maxRuleFirings = maxRuleFirings;
    }

    public ScenarioRunner4JUnit(final Scenario scenario,
                                final Map<String, KieSession> ksessions) throws InitializationError {
        this(scenario,
             ksessions,
             0);
    }

    public ScenarioRunner4JUnit(final List<Scenario> scenarios,
                                final Map<String, KieSession> ksessions) throws InitializationError {
        this(scenarios,
             ksessions,
             0);
    }

    public ScenarioRunner4JUnit(final List<Scenario> scenarios,
                                final Map<String, KieSession> ksessions,
                                final int maxRuleFirings) throws InitializationError {
        this.scenarios = scenarios;
        this.ksessions = ksessions;
        this.descr = Description.createSuiteDescription("Scenario test cases");
        this.maxRuleFirings = maxRuleFirings;
    }

    @Override
    public Description getDescription() {
        return descr;
    }

    @Override
    public void run(RunNotifier notifier) {
        for (Scenario scenario : scenarios) {
            runScenario(notifier, scenario);
        }
    }

    private void runScenario(RunNotifier notifier,
                             Scenario scenario) {
        Description childDescription = Description.createTestDescription(getClass(),
                                                                         scenario.getName());
        descr.addChild(childDescription);
        EachTestNotifier eachNotifier = new EachTestNotifier(notifier,
                                                             childDescription);
        try {
            eachNotifier.fireTestStarted();

            //If a KieSession is not available, fail fast
            if (ksessions == null || ksessions.values().isEmpty()) {
                eachNotifier.addFailure(new NullKieSessionException("Unable to get a Session to run tests. Check the project for build errors."));
            } else {

                KieSession ksession = getKSession(scenario.getKSessions());

                if (ksession == null) {
                    String ksessionName = getKSessionName(scenario.getKSessions());
                    if (ksessionName == null) {
                        eachNotifier.addFailure(new NullPointerException("Test scenario runner could not find the default knowledge session."));
                    } else {
                        eachNotifier.addFailure(new NullPointerException("Test Scenarios require Stateful KieSession to run."));
                    }
                } else {
                    final ScenarioRunner runner = getScenarioRunner(scenario,
                                                                    ksession);
                    runner.run(scenario);
                    if (!scenario.wasSuccessful()) {
                        StringBuilder builder = new StringBuilder();
                        for (String message : scenario.getFailureMessages()) {
                            builder.append(message).append("\n");
                        }
                        eachNotifier.addFailedAssumption(new AssumptionViolatedException(builder.toString()));
                    }

                    // FLUSSSSSH!
                    for (FactHandle factHandle : ksession.getFactHandles()) {
                        ksession.delete(factHandle);
                    }

                    resetKieSession(ksession);
                }
            }
        } catch (Throwable t) {
            eachNotifier.addFailure(t);
        } finally {
            // has to always be called as per junit docs
            eachNotifier.fireTestFinished();
        }
    }

    private ScenarioRunner getScenarioRunner(final Scenario scenario,
                                             final KieSession ksession) {
        if (isPMMLScenario(scenario)) {
            return new ScenarioPMMLRunner(ksession.getKieBase());
        } else {
            return new ScenarioRunnerImpl(ksession,
                                          maxRuleFirings);
        }
    }

    private boolean isPMMLScenario(final Scenario scenario) {
        return scenario.getModelName() != null;
    }

    private void resetKieSession(final KieSession kieSession) {
        final StatefulKnowledgeSessionImpl statefulKnowledgeSession = (StatefulKnowledgeSessionImpl) kieSession;

        statefulKnowledgeSession.reset();
    }

    private KieSession getKSession(List<String> ksessionNames) {
        String ksessionName = getKSessionName(ksessionNames);
        if (ksessions.containsKey(ksessionName)) {
            return ksessions.get(ksessionName);
        } else {
            return null;
        }
    }

    private String getKSessionName(List<String> ksessionNames) {
        if (ksessionNames != null && !ksessionNames.isEmpty()) {
            return ksessionNames.iterator().next();
        } else {
            return null;
        }
    }
}

@@ -5248,16 +5248,130 @@
 SSSSSH!%0A
+                    // DROOLS-6511 ConcurrentModificationException - fixed by using a new copy of FactHandle list%0A
         
@@ -5411,16 +5411,42 @@
 andle : 
+new ArrayList%3CFactHandle%3E(
 ksession
@@ -5459,24 +5459,25 @@
 ctHandles())
+)
  %7B%0A         

Use ConcurrentHashMap for FactHandleMap (#3743)