/*
 * JBoss, Home of Professional Open Source
 * Copyright 2011, Red Hat, Inc. and/or its affiliates, and individual
 * contributors by the @authors tag. See the copyright.txt in the
 * distribution for a full listing of individual contributors.
 *
 * 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.jboss.solder.servlet.logging;

import java.util.Collection;
import java.util.HashSet;

import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.AfterDeploymentValidation;
import javax.enterprise.inject.spi.AnnotatedMethod;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.ProcessAnnotatedType;
import javax.enterprise.inject.spi.ProcessProducerMethod;

import org.jboss.solder.bean.NarrowingBeanBuilder;
import org.jboss.solder.literal.MessageBundleLiteral;
import org.jboss.solder.logging.MessageLogger;
import org.jboss.solder.messages.MessageBundle;

/**
 * Adds TypedMessageBundleAndLoggerProducers to the deployment, and detects and installs beans for any typed loggers defined.
 * <p/>
 * <strong>TEMPORARY UNTIL GLASSFISH-15735 is resolved</strong>
 *
 * @author Pete Muir
 */
public class TypedMessageBundleAndLoggerExtension implements Extension {
    private final Collection<AnnotatedType<?>> messageLoggerTypes;
    private final Collection<AnnotatedType<?>> messageBundleTypes;
    private Bean<Object> loggerProducerBean;
    private Bean<Object> bundleProducerBean;
    private boolean processTypesInModule = false;
    private static final String MODULE_PACKAGE_PREFIX = "org.jboss.solder.servlet.";

    public TypedMessageBundleAndLoggerExtension() {
        this.messageLoggerTypes = new HashSet<AnnotatedType<?>>();
        this.messageBundleTypes = new HashSet<AnnotatedType<?>>();
        Package cdi = BeanManager.class.getPackage();
		String implementationTitle = cdi.getImplementationTitle();
		if(implementationTitle != null)
			processTypesInModule = System.getProperty("glassfish.version") != null && implementationTitle.contains("Weld")
                && implementationTitle.equals("20110114-1644");
    }

    void detectInterfaces(@Observes ProcessAnnotatedType<?> event, BeanManager beanManager) {
        if (processTypesInModule) {
            AnnotatedType<?> type = event.getAnnotatedType();
            if (type.getJavaClass().getPackage().getName().startsWith(MODULE_PACKAGE_PREFIX)) {
                if (type.isAnnotationPresent(MessageLogger.class)) {
                    messageLoggerTypes.add(type);
                }
                if (type.isAnnotationPresent(MessageBundle.class)) {
                    messageBundleTypes.add(type);
                }
            }
        }
    }

    // according to the Java EE 6 javadoc (the authority according to the powers that be),
    // this is the correct order of type parameters
    void detectProducers(@Observes ProcessProducerMethod<Object, TypedMessageBundleAndLoggerProducers> event) {
        captureProducers(event.getAnnotatedProducerMethod(), event.getBean());
    }

    // according to JSR-299 spec, this is the correct order of type parameters
    @Deprecated
    void detectProducersInverted(@Observes ProcessProducerMethod<TypedMessageBundleAndLoggerProducers, Object> event) {
        captureProducers(event.getAnnotatedProducerMethod(), event.getBean());
    }

    @SuppressWarnings("unchecked")
    void captureProducers(AnnotatedMethod<?> method, Bean<?> bean) {
        if (processTypesInModule) {
            if (method.isAnnotationPresent(TypedMessageLogger.class)) {
                this.loggerProducerBean = (Bean<Object>) bean;
            }
            if (method.isAnnotationPresent(TypedMessageBundle.class)) {
                this.bundleProducerBean = (Bean<Object>) bean;
            }
        }
    }

    void installBeans(@Observes AfterBeanDiscovery event, BeanManager beanManager) {
        if (processTypesInModule) {
            for (AnnotatedType<?> type : messageLoggerTypes) {
                event.addBean(createMessageLoggerBean(loggerProducerBean, type, beanManager));
            }
            for (AnnotatedType<?> type : messageBundleTypes) {
                event.addBean(createMessageBundleBean(bundleProducerBean, type, beanManager));
            }
        }
    }

    private static <T> Bean<T> createMessageLoggerBean(Bean<Object> delegate, AnnotatedType<T> type, BeanManager beanManager) {
        return new NarrowingBeanBuilder<T>(delegate, beanManager).readFromType(type).types(type.getBaseType(), Object.class)
                .create();
    }

    private static <T> Bean<T> createMessageBundleBean(Bean<Object> delegate, AnnotatedType<T> type, BeanManager beanManager) {
        return new NarrowingBeanBuilder<T>(delegate, beanManager).readFromType(type).types(type.getBaseType(), Object.class)
                .addQualifier(MessageBundleLiteral.INSTANCE).create();
    }

    void cleanup(@Observes AfterDeploymentValidation event) {
        // defensively clear the set to help with gc
        this.messageLoggerTypes.clear();
        this.messageBundleTypes.clear();
    }

}
