/*
 * Copyright 2002-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.springframework.config.java.plugin.context;

import java.lang.reflect.Method;

import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ScopeMetadataResolver;


/**
 * TODO: JAVADOC
 * Parser for the &lt;context:component-scan/&gt; element.
 *
 * @author Mark Fisher
 * @author Ramnivas Laddad
 * @author Juergen Hoeller
 * @since 2.5
 */
class GeneralizedComponentScanBeanDefinitionParser {

    /*
    private static final String BASE_PACKAGE_ATTRIBUTE = "base-package";

    private static final String RESOURCE_PATTERN_ATTRIBUTE = "resource-pattern";

    private static final String USE_DEFAULT_FILTERS_ATTRIBUTE = "use-default-filters";

    private static final String ANNOTATION_CONFIG_ATTRIBUTE = "annotation-config";

    private static final String NAME_GENERATOR_ATTRIBUTE = "name-generator";

    private static final String SCOPE_RESOLVER_ATTRIBUTE = "scope-resolver";

    private static final String SCOPED_PROXY_ATTRIBUTE = "scoped-proxy";

    private static final String EXCLUDE_FILTER_ELEMENT = "exclude-filter";

    private static final String INCLUDE_FILTER_ELEMENT = "include-filter";

    private static final String FILTER_TYPE_ATTRIBUTE = "type";

    private static final String FILTER_EXPRESSION_ATTRIBUTE = "expression";
    */


    public void parse(ComponentScanDeclaration scanDec, BeanDefinitionRegistry registry) {
        // Actually scan for bean definitions and register them.
        ClassPathBeanDefinitionScanner scanner = configureScanner(registry, scanDec);

        if(scanDec.isAnnotationConfig())
            AnnotationConfigUtils.registerAnnotationConfigProcessors(registry);

        // TODO: the code below is a workaround. Should be able to call
        /* scanner.doScan(scanDec.getBasePackages()) */
        // directly, but it is currently package-private.
        try {
            Method doScan = ClassPathBeanDefinitionScanner.class.getDeclaredMethod("doScan", String[].class);
            doScan.setAccessible(true);
            // explicitly cast to Object to coerce varargs invocation
            doScan.invoke(scanner, (Object) scanDec.getBasePackages());
        } catch (Exception ex) {
            throw new RuntimeException("Error when trying to reflectively invoke scanner.doScan()", ex);
        }
    }

    protected ClassPathBeanDefinitionScanner configureScanner(BeanDefinitionRegistry registry, ComponentScanDeclaration scanDec) {
        // Delegate bean definition registration to scanner class.
        ClassPathBeanDefinitionScanner scanner = createScanner(registry, scanDec.isUseDefaultFilters());

        if (scanDec.getResourcePattern() != null) {
            scanner.setResourcePattern(scanDec.getResourcePattern());
        }

        try {
            parseBeanNameGenerator(scanner, scanDec);
        }
        catch (Exception ex) {
            throw new RuntimeException(ex);
            //registry.getReaderContext().error(ex.getMessage(), element, ex.getCause());
        }

        try {
            parseScope(scanner, scanDec);
        }
        catch (Exception ex) {
            throw new RuntimeException(ex);
            //registry.getReaderContext().error(ex.getMessage(), element, ex.getCause());
        }

        /*
        try {
            parseTypeFilters(scanner, scanDec);
        }
        catch (Exception ex) {
            throw new RuntimeException(ex);
            //registry.getReaderContext().error(ex.getMessage(), element, ex.getCause());
        }
        */

        return scanner;
    }

    protected ClassPathBeanDefinitionScanner createScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
        return new ClassPathBeanDefinitionScanner(registry, useDefaultFilters);
    }

    protected void parseBeanNameGenerator(ClassPathBeanDefinitionScanner scanner, ComponentScanDeclaration scanDec) {
        if (scanDec.getNameGenerator() != null) {
            BeanNameGenerator beanNameGenerator = (BeanNameGenerator) instantiateUserDefinedStrategy(
                    scanDec.getNameGenerator(), BeanNameGenerator.class,
                    scanner.getResourceLoader().getClassLoader());
            scanner.setBeanNameGenerator(beanNameGenerator);
        }
    }


    /*
    protected void registerComponents(
            XmlReaderContext readerContext, Set<BeanDefinitionHolder> beanDefinitions, Element element) {

        Object source = readerContext.extractSource(element);
        CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), source);

        for (Iterator it = beanDefinitions.iterator(); it.hasNext();) {
            BeanDefinitionHolder beanDefHolder = (BeanDefinitionHolder) it.next();
            AbstractBeanDefinition beanDef = (AbstractBeanDefinition) beanDefHolder.getBeanDefinition();
            beanDef.setSource(readerContext.extractSource(beanDef.getSource()));
            compositeDef.addNestedComponent(new BeanComponentDefinition(beanDefHolder));
        }

        // Register annotation config processors, if necessary.
        boolean annotationConfig = true;
        if (element.hasAttribute(ANNOTATION_CONFIG_ATTRIBUTE)) {
            annotationConfig = Boolean.valueOf(element.getAttribute(ANNOTATION_CONFIG_ATTRIBUTE));
        }
        if (annotationConfig) {
            Set<BeanDefinitionHolder> processorDefinitions =
                    AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source);
            for (BeanDefinitionHolder processorDefinition : processorDefinitions) {
                compositeDef.addNestedComponent(new BeanComponentDefinition(processorDefinition));
            }
        }

        readerContext.fireComponentRegistered(compositeDef);
    }

    @SuppressWarnings("unchecked")
    protected TypeFilter createTypeFilter(Element element, ClassLoader classLoader) {
        String filterType = element.getAttribute(FILTER_TYPE_ATTRIBUTE);
        String expression = element.getAttribute(FILTER_EXPRESSION_ATTRIBUTE);
        try {
            if ("annotation".equals(filterType)) {
                return new AnnotationTypeFilter((Class<Annotation>) classLoader.loadClass(expression));
            }
            else if ("assignable".equals(filterType)) {
                return new AssignableTypeFilter(classLoader.loadClass(expression));
            }
            else if ("regex".equals(filterType)) {
                return new RegexPatternTypeFilter(Pattern.compile(expression));
            }
            else if ("aspectj".equals(filterType)) {
                return new AspectJTypeFilter(expression, classLoader);
            }
            else {
                throw new IllegalArgumentException("Unsupported filter type: " + filterType);
            }
        }
        catch (ClassNotFoundException ex) {
            throw new FatalBeanException("Type filter class not found: " + expression, ex);
        }
    }

    protected void parseTypeFilters(ClassPathBeanDefinitionScanner scanner, ComponentScanDeclaration scanDec) {
        // Parse exclude and include filter elements.
        ClassLoader classLoader = scanner.getResourceLoader().getClassLoader();
        NodeList nodeList = scanDec.getChildNodes();
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                String localName = node.getLocalName();
                if (INCLUDE_FILTER_ELEMENT.equals(localName)) {
                    TypeFilter typeFilter = createTypeFilter((Element) node, classLoader);
                    scanner.addIncludeFilter(typeFilter);
                }
                else if (EXCLUDE_FILTER_ELEMENT.equals(localName)) {
                    TypeFilter typeFilter = createTypeFilter((Element) node, classLoader);
                    scanner.addExcludeFilter(typeFilter);
                }
            }
        }

    }
    */


    protected void parseScope(ClassPathBeanDefinitionScanner scanner, ComponentScanDeclaration scanDec) {
        // Register ScopeMetadataResolver if class name provided.
        if (scanDec.getScopeResolver() != null) {
            if (scanDec.getScopedProxy() != null) {
                throw new IllegalArgumentException(
                        "Cannot define both 'scope-resolver' and 'scoped-proxy' on <component-scan> tag");
            }
            ScopeMetadataResolver scopeMetadataResolver = (ScopeMetadataResolver) instantiateUserDefinedStrategy(
                    scanDec.getScopeResolver(), ScopeMetadataResolver.class,
                    scanner.getResourceLoader().getClassLoader());
            scanner.setScopeMetadataResolver(scopeMetadataResolver);
        }

        scanner.setScopedProxyMode(scanDec.getScopedProxy());
    }

    private Object instantiateUserDefinedStrategy(Class<?> userStrategy, Class<?> strategyType, ClassLoader classLoader) {
        String strategyTypeName = userStrategy.getClass().getTypeParameters()[0].toString();
        String className = userStrategy.getName();

        Object result = null;
        try {
            result = userStrategy.newInstance();
        }
        catch (Exception ex) {
            throw new IllegalArgumentException("Unable to instantiate class [" + className + "] for strategy [" +
                    strategyTypeName + "]. A zero-argument constructor is required", ex);
        }

        return result;
    }

}
