/*
 * Copyright 2011 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.tests.utils.impl;

import org.powermock.core.IndicateReloadClass;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.core.classloader.annotations.PrepareOnlyThisForTest;
import org.powermock.tests.utils.TestClassesExtractor;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * Implementation of the {@link TestClassesExtractor} interface that extract
 * classes from the {@link PrepareForTest} or {@link PrepareOnlyThisForTest}
 * annotations. It also adds the test case to the array of classes that should
 * be modified.
 * 
 */
public class PrepareForTestExtractorImpl extends AbstractTestClassExtractor {

    public PrepareForTestExtractorImpl(){
        this(false);
    }

    public PrepareForTestExtractorImpl(boolean includeMethods) {
        super(includeMethods);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected String[] getClassesToModify(AnnotatedElement element) {
        Set<String> all = new LinkedHashSet<String>();
        addTestCase(all, element);
        PrepareForTest prepareForTestAnnotation = element.getAnnotation(PrepareForTest.class);
        PrepareOnlyThisForTest prepareOnlyThisForTestAnnotation = element.getAnnotation(PrepareOnlyThisForTest.class);
        final boolean prepareForTestAnnotationPresent = prepareForTestAnnotation != null;
        final boolean prepareOnlyThisForTestAnnotationPresent = prepareOnlyThisForTestAnnotation != null;

        if (!prepareForTestAnnotationPresent && !prepareOnlyThisForTestAnnotationPresent) {
            return null;
        }

        if (prepareForTestAnnotationPresent) {
            final Class<?>[] classesToMock = prepareForTestAnnotation.value();
            for (Class<?> classToMock : classesToMock) {
                if (!classToMock.equals(IndicateReloadClass.class)) {
                    addClassHierarchy(all, classToMock);
                }
            }

            addFullyQualifiedNames(all, prepareForTestAnnotation);
        }

        if (prepareOnlyThisForTestAnnotationPresent) {
            final Class<?>[] classesToMock = prepareOnlyThisForTestAnnotation.value();
            for (Class<?> classToMock : classesToMock) {
                if (!classToMock.equals(IndicateReloadClass.class)) {
                    all.add(classToMock.getName());
                }
            }

            addFullyQualifiedNames(all, prepareOnlyThisForTestAnnotation);
        }

        return all.toArray(new String[all.size()]);

    }

    private void addTestCase(Set<String> all, AnnotatedElement element) {
        Class<?> testClass = null;
        if (element instanceof Class<?>) {
            testClass = (Class<?>) element;
        } else if (element instanceof Method) {
            testClass = ((Method) element).getDeclaringClass();
        }
        addClassHierarchy(all, testClass);
    }

    private void addFullyQualifiedNames(Set<String> all, PrepareForTest annotation) {
        String[] fullyQualifiedNames = annotation.fullyQualifiedNames();
        addFullyQualifiedNames(all, fullyQualifiedNames);
    }

    private void addFullyQualifiedNames(Set<String> all, PrepareOnlyThisForTest annotation) {
        String[] fullyQualifiedNames = annotation.fullyQualifiedNames();
        addFullyQualifiedNames(all, fullyQualifiedNames);
    }

    private void addFullyQualifiedNames(Set<String> all, String[] fullyQualifiedNames) {
        for (String string : fullyQualifiedNames) {
            if (!"".equals(string)) {
                all.add(string);
            }
        }
    }

    private void addClassHierarchy(Set<String> all, Class<?> classToMock) {
        while (classToMock != null && !classToMock.equals(Object.class)) {
            addInnerClassesAndInterfaces(all, classToMock);
            all.add(classToMock.getName());
            classToMock = classToMock.getSuperclass();
        }
    }

    private void addInnerClassesAndInterfaces(Set<String> all, Class<?> classToMock) {
        Class<?>[] declaredClasses = classToMock.getDeclaredClasses();
        for (Class<?> innerClass : declaredClasses) {
            all.add(innerClass.getName());
        }
    }
}
