/*******************************************************************************
 * 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.ofbiz.testtools;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.taskdefs.optional.junit.JUnitTest;
import org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter;
import org.ofbiz.base.container.Container;
import org.ofbiz.base.container.ContainerException;
import org.ofbiz.base.util.Debug;
import org.ofbiz.entity.Delegator;

import junit.framework.AssertionFailedError;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestFailure;
import junit.framework.TestListener;
import junit.framework.TestResult;
import junit.framework.TestSuite;

/**
 * A Container implementation to run the tests configured through this testtools stuff.
 */
public class TestRunContainer implements Container {

    private static final Debug.OfbizLogger module = Debug.getOfbizLogger(java.lang.invoke.MethodHandles.lookup().lookupClass());
    public static final String logDir = "runtime/logs/test-results/";

    protected String configFile = null;
    protected String component = null;
    protected String suiteName = null;
    protected String testCase = null;
    protected String logLevel = null;

    private String name;

    @Override
    public void init(String[] args, String name, String configFile) {
        this.name = name;
        this.configFile = configFile;
        if (args != null) {
            for (String argument : args) {
                // arguments can prefix w/ a '-'. Just strip them off
                if (argument.startsWith("-")) {
                    int subIdx = 1;
                    if (argument.startsWith("--")) {
                        subIdx = 2;
                    }
                    argument = argument.substring(subIdx);
                }

                // parse the arguments
                if (argument.indexOf("=") != -1) {
                    String argumentName = argument.substring(0, argument.indexOf("="));
                    String argumentVal = argument.substring(argument.indexOf("=") + 1);

                    if ("component".equalsIgnoreCase(argumentName)) {
                        this.component = argumentVal;
                    }
                    if ("suitename".equalsIgnoreCase(argumentName)) {
                        this.suiteName = argumentVal;
                    }
                    if ("case".equalsIgnoreCase(argumentName)) {
                        this.testCase = argumentVal;
                    }
                    if ("loglevel".equalsIgnoreCase(argumentName)) {
                        this.logLevel = argumentVal;
                    }
                }
            }
        }

        // make sure the log dir exists
        File dir = new File(logDir);
        if (!dir.exists())
            dir.mkdir();
    }

    public boolean start() throws ContainerException {
        // configure log4j output logging
        if (logLevel != null) {
            int llevel = Debug.getLevelFromString(logLevel);

            for (int v = 0; v < 9; v++) {
                if (v < llevel) {
                    Debug.set(v, false);
                } else {
                    Debug.set(v, true);
                }
            }
        }

        // get the tests to run
        JunitSuiteWrapper jsWrapper = new JunitSuiteWrapper(component, suiteName, testCase);
        if (jsWrapper.getAllTestList().size() == 0) {
            throw new ContainerException("No tests found (" + component + " / " + suiteName + " / " + testCase + ")");
        }

        boolean failedRun = false;
        for (ModelTestSuite modelSuite: jsWrapper.getModelTestSuites()) {
            Delegator testDelegator = modelSuite.getDelegator();
            TestSuite suite = modelSuite.makeTestSuite();
            JUnitTest test = new JUnitTest();
            test.setName(suite.getName());

            // create the XML logger
            JunitXmlListener xml;
            try {
                xml = new JunitXmlListener(new FileOutputStream(logDir + suite.getName() + ".xml"));
            } catch (FileNotFoundException e) {
                throw new ContainerException(e);
            }

            // per-suite results
            TestResult results = new TestResult();
            results.addListener(new JunitListener());
            results.addListener(xml);

            // add the suite to the xml listener
            xml.startTestSuite(test);
            // run the tests
            suite.run(results);
            test.setCounts(results.runCount(), results.failureCount(), results.errorCount());
            // rollback all entity operations performed by the delegator
            testDelegator.rollback();
            xml.endTestSuite(test);

            if (!results.wasSuccessful()) {
                failedRun = true;
            }

            // display the results
            Debug.logInfo("[JUNIT] Results for test suite: " + suite.getName(), module);
            Debug.logInfo("[JUNIT] Pass: " + results.wasSuccessful() + " | # Tests: " + results.runCount() + " | # Failed: " +
                    results.failureCount() + " # Errors: " + results.errorCount(), module);
            if (Debug.importantOn() && !results.wasSuccessful()) {
                Debug.logInfo("[JUNIT] ----------------------------- ERRORS ----------------------------- [JUNIT]", module);
                Enumeration<?> err = results.errors();
                if (!err.hasMoreElements()) {
                    Debug.logInfo("None", module);
                } else {
                    while (err.hasMoreElements()) {
                        Object error = err.nextElement();
                        Debug.logInfo("--> " + error, module);
                        if (error instanceof TestFailure) {
                            Debug.logInfo(((TestFailure) error).trace(), module);
                        }
                    }
                }
                Debug.logInfo("[JUNIT] ------------------------------------------------------------------ [JUNIT]", module);
                Debug.logInfo("[JUNIT] ---------------------------- FAILURES ---------------------------- [JUNIT]", module);
                Enumeration<?> fail = results.failures();
                if (!fail.hasMoreElements()) {
                    Debug.logInfo("None", module);
                } else {
                    while (fail.hasMoreElements()) {
                        Object failure = fail.nextElement();
                        Debug.logInfo("--> " + failure, module);
                        if (failure instanceof TestFailure) {
                            Debug.logInfo(((TestFailure) failure).trace(), module);
                        }
                    }
                }
                Debug.logInfo("[JUNIT] ------------------------------------------------------------------ [JUNIT]", module);
            }
        }

        if (failedRun) {
            throw new ContainerException("Test run was unsuccessful");
        }
        return true;
    }

    public void stop() throws ContainerException {
    }

    public String getName() {
        return name;
    }

    class JunitXmlListener extends XMLJUnitResultFormatter {

        Map<String, Long> startTimes = new HashMap<String, Long>();

        public JunitXmlListener(OutputStream out) {
            this.setOutput(out);
        }

        @Override
        public void startTestSuite(JUnitTest suite) {
            startTimes.put(suite.getName(), System.currentTimeMillis());
            super.startTestSuite(suite);
        }

        @Override
        public void endTestSuite(JUnitTest suite) throws BuildException {
            long startTime = startTimes.get(suite.getName());
            suite.setRunTime((System.currentTimeMillis() - startTime));
            super.endTestSuite(suite);
        }
    }

    class JunitListener implements TestListener {

        public void addError(Test test, Throwable throwable) {
            Debug.logWarning(throwable, "[JUNIT (error)] - " + getTestName(test) + " : " + throwable.toString(), module);
        }

        public void addFailure(Test test, AssertionFailedError assertionFailedError) {
            Debug.logWarning("[JUNIT (failure)] - " + getTestName(test) + " : " + assertionFailedError.getMessage(), module);
        }

        public void endTest(Test test) {
            Debug.logInfo("[JUNIT] : " + getTestName(test) + " finished.", module);
        }

        public void startTest(Test test) {
           Debug.logInfo("[JUNIT] : " + getTestName(test) + " starting...", module);
        }

        private String getTestName(Test test) {
            if (test instanceof TestCase) {
                return ((TestCase)test).getName();
            } else {
                return test.getClass().getName();
            }

        }
    }
}
