/*
 * 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.apache.maven.surefire.common.junit3;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import org.apache.maven.surefire.api.util.ReflectionUtils;

/**
 * Reflection facade for JUnit3 classes
 *
 */
public final class JUnit3Reflector {
    private static final String TEST_CASE = "junit.framework.Test";

    private static final String TEST_RESULT = "junit.framework.TestResult";

    private static final String TEST_LISTENER = "junit.framework.TestListener";

    private static final String TEST = "junit.framework.Test";

    private static final String ADD_LISTENER_METHOD = "addListener";

    private static final String RUN_METHOD = "run";

    private static final String TEST_SUITE = "junit.framework.TestSuite";

    private static final Class<?>[] EMPTY_CLASS_ARRAY = {};

    private static final Object[] EMPTY_OBJECT_ARRAY = {};

    private final Class<?>[] interfacesImplementedByDynamicProxy;

    private final Class<?> testResultClass;

    private final Method addListenerMethod;

    private final Method testInterfaceRunMethod;

    private final Class<?> testInterface;

    private final Class<?> testCase;

    private final Constructor<?> testsSuiteConstructor;

    public JUnit3Reflector(ClassLoader testClassLoader) {
        testResultClass = ReflectionUtils.tryLoadClass(testClassLoader, TEST_RESULT);
        testCase = ReflectionUtils.tryLoadClass(testClassLoader, TEST_CASE);
        testInterface = ReflectionUtils.tryLoadClass(testClassLoader, TEST);
        interfacesImplementedByDynamicProxy =
                new Class[] {ReflectionUtils.tryLoadClass(testClassLoader, TEST_LISTENER)};
        Class<?>[] constructorParamTypes = {Class.class};

        Class<?> testSuite = ReflectionUtils.tryLoadClass(testClassLoader, TEST_SUITE);

        // The interface implemented by the dynamic proxy (TestListener), happens to be
        // the same as the param types of TestResult.addTestListener

        if (isJUnit3Available()) {
            testsSuiteConstructor = ReflectionUtils.getConstructor(testSuite, constructorParamTypes);
            addListenerMethod = tryGetMethod(testResultClass, ADD_LISTENER_METHOD, interfacesImplementedByDynamicProxy);
            testInterfaceRunMethod = getMethod(testInterface, RUN_METHOD, testResultClass);
        } else {
            testsSuiteConstructor = null;
            addListenerMethod = null;
            testInterfaceRunMethod = null;
        }
    }

    // Switch to reflectionutils when building with 2.7.2
    private static Method tryGetMethod(Class<?> clazz, String methodName, Class<?>... parameters) {
        try {
            return clazz.getMethod(methodName, parameters);
        } catch (NoSuchMethodException e) {
            return null;
        }
    }

    private static Method getMethod(Class<?> clazz, String methodName, Class<?>... parameters) {
        try {
            return clazz.getMethod(methodName, parameters);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("When finding method " + methodName, e);
        }
    }

    public Object constructTestObject(Class<?> testClass)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, InstantiationException {
        Object testObject = createInstanceFromSuiteMethod(testClass);

        if (testObject == null && testCase.isAssignableFrom(testClass)) {
            testObject = testsSuiteConstructor.newInstance(testClass);
        }

        if (testObject == null) {
            Constructor<?> testConstructor = getTestConstructor(testClass);

            if (testConstructor.getParameterTypes().length == 0) {
                testObject = testConstructor.newInstance(EMPTY_OBJECT_ARRAY);
            } else {
                testObject = testConstructor.newInstance(testClass.getName());
            }
        }
        return testObject;
    }

    private static Object createInstanceFromSuiteMethod(Class<?> testClass)
            throws IllegalAccessException, InvocationTargetException {
        Object testObject = null;
        try {
            Method suiteMethod = testClass.getMethod("suite", EMPTY_CLASS_ARRAY);

            if (Modifier.isPublic(suiteMethod.getModifiers()) && Modifier.isStatic(suiteMethod.getModifiers())) {
                testObject = suiteMethod.invoke(null, EMPTY_OBJECT_ARRAY);
            }
        } catch (NoSuchMethodException e) {
            // No suite method
        }
        return testObject;
    }

    private static Constructor<?> getTestConstructor(Class<?> testClass) throws NoSuchMethodException {
        try {
            return testClass.getConstructor(String.class);
        } catch (NoSuchMethodException e) {
            return testClass.getConstructor(EMPTY_CLASS_ARRAY);
        }
    }

    public Class<?>[] getInterfacesImplementedByDynamicProxy() {
        return interfacesImplementedByDynamicProxy;
    }

    public Class<?> getTestResultClass() {
        return testResultClass;
    }

    public Method getAddListenerMethod() {
        return addListenerMethod;
    }

    public Method getTestInterfaceRunMethod() {
        return testInterfaceRunMethod;
    }

    public Class<?> getTestInterface() {
        return testInterface;
    }

    public Method getRunMethod(Class<?> testClass) {
        return getMethod(testClass, RUN_METHOD, getTestResultClass());
    }

    public boolean isJUnit3Available() {
        return testResultClass != null;
    }
}
