/*
 * Copyright 2008 the original author or authors.
 *
 * 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.powermock.modules.junit3.internal.impl;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestResult;
import junit.framework.TestSuite;
import org.powermock.core.spi.PowerMockTestListener;
import org.powermock.modules.junit3.internal.PowerMockJUnit3RunnerDelegate;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Vector;

@SuppressWarnings("unchecked")
public class PowerMockJUnit3RunnerDelegateImpl extends TestSuite implements PowerMockJUnit3RunnerDelegate {

    private final Method[] methodsToRun;

    private Class<?> testClass;

    public PowerMockJUnit3RunnerDelegateImpl(final Class<?> theClass, Method[] methodsToRun, String name,
            PowerMockTestListener[] powerListeners) {
        this(theClass, methodsToRun, powerListeners);
        testClass = theClass;
        setName(name);
    }

    /**
     * Constructs a TestSuite from the given class. Adds all the methods
     * starting with "test" as test cases to the suite. Parts of this method was
     * cut'n'pasted on the train between Malm� and Stockholm.
     */
    public PowerMockJUnit3RunnerDelegateImpl(final Class<?> theClass, Method[] methodsToRun, PowerMockTestListener[] powerMockTestListeners) {
        testClass = theClass;
        this.methodsToRun = methodsToRun;
        setName(theClass.getName());

        try {
            getTestConstructor(theClass); // Avoid generating multiple error
            // messages
        } catch (NoSuchMethodException e) {
            addTest(warning("Class " + theClass.getName() + " has no public constructor TestCase(String name) or TestCase()"));
            return;
        }

        if (!Modifier.isPublic(theClass.getModifiers())) {
            addTest(warning("Class " + theClass.getName() + " is not public"));
            return;
        }

        Class<?> superClass = theClass;
        Vector<?> names = new Vector<Object>();
        Method addTestMethod = null;
        Method[] declaredMethods = TestSuite.class.getDeclaredMethods();
        for (Method method : declaredMethods) {
            /*
             * Since the TestSuite class is loaded by another classloader we
             * look up the method this way (which is not fail-proof, but good
             * enough).
             */
            if (method.getName().equals("addTestMethod")) {
                addTestMethod = method;
            }
        }

        if (addTestMethod == null) {
            throw new RuntimeException("Internal error: Failed to get addTestMethod for JUnit3.");
        }

        addTestMethod.setAccessible(true);

        while (Test.class.isAssignableFrom(superClass)) {
            for (int i = 0; i < methodsToRun.length; i++) {
                try {
                    addTestMethod.invoke(this, methodsToRun[i], names, theClass);
                } catch (Exception e) {
                    throw new RuntimeException("Internal error: Failed to execute addTestMethod for JUnit3.");
                }
            }
            superClass = superClass.getSuperclass();
        }
        if (testCount() == 0) {
            addTest(warning("No tests found in " + theClass.getName()));
        }
    }

    @Override
    public Class<?> getTestClass() {
        return testClass;
    }

    @Override
    public void run(TestResult result) {
        final ClassLoader classloader = this.getClass().getClassLoader();
        final Thread currentThread = Thread.currentThread();
        final ClassLoader originalClassLoader = currentThread.getContextClassLoader();
        currentThread.setContextClassLoader(classloader);
        try {
            super.run(result);
        } finally {
            currentThread.setContextClassLoader(originalClassLoader);
        }
    }

    /**
     * Returns a test which will fail and log a warning message.
     */
    public static Test warning(final String message) {
        return new TestCase("warning") {
            @Override
            protected void runTest() {
                fail(message);
            }
        };
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("Methods ran by this test runner delegate:\n");
        for (Method method : methodsToRun) {
            builder.append(method).append("\n");
        }
        return builder.toString();
    }

}
