/*
 * 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.webbeans.test;

import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

import javax.decorator.Decorator;
import javax.enterprise.context.Dependent;
import javax.enterprise.context.spi.Context;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.interceptor.Interceptor;

import org.apache.webbeans.component.AbstractOwbBean;
import org.apache.webbeans.component.BeanAttributesImpl;
import org.apache.webbeans.component.CdiInterceptorBean;
import org.apache.webbeans.component.DecoratorBean;
import org.apache.webbeans.component.InjectionTargetBean;
import org.apache.webbeans.component.ManagedBean;
import org.apache.webbeans.component.ProducerFieldBean;
import org.apache.webbeans.component.ProducerMethodBean;
import org.apache.webbeans.component.WebBeansType;
import org.apache.webbeans.component.creation.BeanAttributesBuilder;
import org.apache.webbeans.component.creation.CdiInterceptorBeanBuilder;
import org.apache.webbeans.component.creation.DecoratorBeanBuilder;
import org.apache.webbeans.component.creation.ManagedBeanBuilder;
import org.apache.webbeans.component.creation.ObserverMethodsBuilder;
import org.apache.webbeans.component.creation.ProducerFieldBeansBuilder;
import org.apache.webbeans.component.creation.ProducerMethodBeansBuilder;
import org.apache.webbeans.config.WebBeansContext;
import org.apache.webbeans.container.BeanManagerImpl;
import org.apache.webbeans.context.DependentContext;
import org.apache.webbeans.deployment.StereoTypeModel;
import org.apache.webbeans.exception.WebBeansConfigurationException;
import org.apache.webbeans.logger.WebBeansLoggerFacade;
import org.apache.webbeans.newtests.AbstractUnitTest;
import org.apache.webbeans.portable.AbstractProducer;
import org.apache.webbeans.portable.events.generics.GProcessAnnotatedType;
import org.apache.webbeans.test.component.decorator.broken.DelegateAttributeIsnotInterface;
import org.apache.webbeans.test.component.decorator.broken.DelegateAttributeMustImplementAllDecoratedTypes;
import org.apache.webbeans.test.component.decorator.broken.MoreThanOneDelegateAttribute;
import org.apache.webbeans.test.component.decorator.broken.PaymentDecorator;
import org.apache.webbeans.test.component.decorator.clean.LargeTransactionDecorator;
import org.apache.webbeans.test.component.decorator.clean.ServiceDecorator;
import org.apache.webbeans.test.component.intercept.webbeans.ActionInterceptor;
import org.apache.webbeans.test.component.intercept.webbeans.TransactionalInterceptor2;
import org.apache.webbeans.test.containertests.ComponentResolutionByTypeTest;
import org.apache.webbeans.test.mock.MockManager;
import org.apache.webbeans.test.servlet.ITestContext;
import org.apache.webbeans.test.servlet.TestListener;
import org.apache.webbeans.test.sterotype.StereoWithNonScope;
import org.apache.webbeans.test.sterotype.StereoWithRequestScope;
import org.apache.webbeans.test.sterotype.StereoWithSessionScope;
import org.apache.webbeans.test.sterotype.StereoWithSessionScope2;
import org.apache.webbeans.util.AnnotationUtil;
import org.apache.webbeans.xml.WebBeansXMLConfigurator;

/**
 * Superclass of all the unit test classes. It defines some methods for
 * subclasses and also do some initializtions for running the tests succesfully.
 * 
 * @author <a href="mailto:gurkanerdogdu@yahoo.com">Gurkan Erdogdu</a>
 * @since 1.0
 * @deprecated
 * Please do not use this class anymore while writing tests. Instead
 * use {@link AbstractUnitTest} class. Also do not add new tests into 
 * the package org.apache.webbeans.test folder. Add your new tests into
 * the org.apache.webbeans.newtests folder.
 * 
 */
public abstract class TestContext implements ITestContext
{
    private Logger logger = WebBeansLoggerFacade.getLogger(TestContext.class);

    /**
     * All unit test classes. It is defined for starting the tests from the
     * ServletContextListener methods
     */
    private static Set<ITestContext> testContexts = new HashSet<ITestContext>();

    /** Test class name */
    private String clazzName;

    /** MockManager is the mock implementation of the {@link BeanManager} */
    private MockManager manager;

    /** Use for XML tests */
    protected WebBeansXMLConfigurator xmlConfigurator = null;

    private WebBeansContext webBeansContext;

    /**
     * Creates new test class.
     * 
     * @param clazzName class name of the test class
     */
    protected TestContext(String clazzName)
    {
        this.clazzName = clazzName;
        TestContext.testContexts.add(this);
        this.manager = new MockManager();
        this.xmlConfigurator = new WebBeansXMLConfigurator();
        this.webBeansContext = WebBeansContext.getInstance();
        webBeansContext.getPluginLoader().startUp();
    }
    

    /**
     * Initialize the tests. NOTE : Actually this has to be defined for each
     * test classes. But for the time being, this super-class globally defines
     * some deployment types, interceptor types, decorator types and
     * stereotypes. If you would like to override default initialization,
     * override these methods in your test classes.
     */
    @Override
    public void init()
    {
        manager.clear();
        WebBeansContext.getInstance().getPluginLoader().startUp();
        
        initInterceptors();
        initDecorators();
        initStereoTypes();
        initDependentContext();            
    }

    protected void initDependentContext()
    {
        DependentContext dependentContext = (DependentContext) WebBeansContext.getInstance().getContextFactory().getStandardContext(Dependent.class);
        dependentContext.setActive(true);
    }

    /**
     * Initialize some predefined interceptors.
     */
    protected void initInterceptors()
    {
        initializeInterceptorType(TransactionalInterceptor2.class);
        initializeInterceptorType(ActionInterceptor.class);

    }

    /**
     * Initialize some predefined decorators.
     */
    protected void initDecorators()
    {
        initializeDecoratorType(DelegateAttributeIsnotInterface.class);
        initializeDecoratorType(MoreThanOneDelegateAttribute.class);
        initializeDecoratorType(PaymentDecorator.class);
        initializeDecoratorType(DelegateAttributeMustImplementAllDecoratedTypes.class);
        initializeDecoratorType(ServiceDecorator.class);
        initializeDecoratorType(LargeTransactionDecorator.class);

    }

    /**
     * Initialize some predefined stereotypes.
     */
    protected void initStereoTypes()
    {
        initDefaultStereoTypes();
        initializeStereoType(StereoWithNonScope.class);
        initializeStereoType(StereoWithRequestScope.class);
        initializeStereoType(StereoWithSessionScope.class);
        initializeStereoType(StereoWithSessionScope2.class);

    }

    /**
     * Default stereo types
     */
    protected void initDefaultStereoTypes()
    {
        initializeStereoType(Interceptor.class);
        initializeStereoType(Decorator.class);
    }


    /**
     * This will be called whenever the test is failed. NOT : This method is
     * used for running the tests from the ServletContextListener. It is
     * not used for normal unit tests.
     * 
     * @see TestListener
     * @see ComponentResolutionByTypeTest
     * @param methodName failed method name
     */
    public void fail(String methodName)
    {
        logger.severe("Test Class: " + clazzName + ",Method Name: " + methodName + " is FAILED");
    }

    /**
     * Initialize all tests. NOT : This method is used for initializing the all
     * tests classes from the ServletContextListener. It is not used for
     * normal unit tests.
     * 
     * @see TestListener
     * @see ComponentResolutionByTypeTest
     */
    public static void initTests()
    {
        Iterator<ITestContext> it = testContexts.iterator();
        while (it.hasNext())
        {
            it.next().init();
        }

    }

    /**
     * Start all tests. NOT : This method is used for starting the all tests
     * classes from the ServletContextListener. It is not used for
     * normal unit tests.
     * 
     * @see TestListener
     * @see ComponentResolutionByTypeTest
     */
    public static void startAllTests(Object object)
    {
        Iterator<ITestContext> it = testContexts.iterator();
        while (it.hasNext())
        {
            it.next().startTests(object);
        }

    }

    /**
     * Ending all tests. NOT : This method is used for ending the all tests
     * classes from the ServletContextListener. It is not used for
     * normal unit tests.
     * 
     * @see TestListener
     * @see ComponentResolutionByTypeTest
     */
    public static void endAllTests(Object object)
    {
        Iterator<ITestContext> it = testContexts.iterator();
        while (it.hasNext())
        {
            it.next().endTests(object);
        }

    }

    /**
     * Defines simple webbeans from the given class.
     * 
     * @param clazz simple webbeans class
     * @return simple webbean
     */
    protected <T> InjectionTargetBean<T> defineManagedBean(Class<T> clazz)
    {
        ManagedBean<T> bean;

        WebBeansContext webBeansContext = WebBeansContext.getInstance();
        bean = define(clazz, WebBeansType.MANAGED, webBeansContext.getAnnotatedElementFactory().newAnnotatedType(clazz));
        if (bean != null)
        {
            if (bean.getProducer() instanceof AbstractProducer)
            {
                AbstractProducer<T> producer = (AbstractProducer<T>)bean.getProducer();
                producer.defineInterceptorStack(bean, bean.getAnnotatedType(), webBeansContext);
            }

            getComponents().add((AbstractOwbBean<?>) bean);
            manager.addBean(bean);

            GProcessAnnotatedType type = new GProcessAnnotatedType(webBeansContext.getAnnotatedElementFactory().newAnnotatedType(clazz));
            manager.fireEvent(type, new Annotation[0]);            
        }

        return bean;
    }

    /**
     * Defines simple webbeans interceptor.
     * 
     * @param clazz interceptor class
     * @return the new interceptor
     */
    @SuppressWarnings("unchecked")
    protected <T> AbstractOwbBean<T> defineInterceptor(Class<T> clazz)
    {
        ManagedBean<T> component = null;

        WebBeansContext webBeansContext = WebBeansContext.getInstance();
        webBeansContext.getWebBeansUtil().checkManagedBeanCondition(clazz);

        webBeansContext.getInterceptorsManager().addEnabledInterceptorClass(clazz);
        AnnotatedType<T> annotatedType = webBeansContext.getAnnotatedElementFactory().newAnnotatedType(clazz);
        BeanAttributesImpl<T> beanAttributes = BeanAttributesBuilder.forContext(getWebBeansContext()).newBeanAttibutes(annotatedType).build();
        CdiInterceptorBeanBuilder<T> ibb = new CdiInterceptorBeanBuilder<T>(webBeansContext, annotatedType, beanAttributes);
        ibb.defineCdiInterceptorRules();
        CdiInterceptorBean<T> bean = ibb.getBean();
        webBeansContext.getInterceptorsManager().addCdiInterceptor(bean);
        return bean;
    }

    /**
     * Defines the simple webbeans decorator.
     * 
     * @param clazz decorator class
     * @return the new decorator
     */
    protected <T> AbstractOwbBean<T> defineDecorator(Class<T> clazz)
    {

        WebBeansContext webBeansContext = WebBeansContext.getInstance();
        if (webBeansContext.getDecoratorsManager().isDecoratorEnabled(clazz))
        {
            AnnotatedType<T> annotatedType = webBeansContext.getBeanManagerImpl().createAnnotatedType(clazz);
            BeanAttributesImpl<T> beanAttributes = BeanAttributesBuilder.forContext(webBeansContext).newBeanAttibutes(annotatedType).build();
            DecoratorBeanBuilder<T> dbb = new DecoratorBeanBuilder<T>(webBeansContext, annotatedType, beanAttributes);
            dbb.defineDecoratorRules();

            DecoratorBean<T> bean = dbb.getBean();
            webBeansContext.getDecoratorsManager().addDecorator(bean);
            return bean;
        }

        return null;
    }

    /**
     * Clear all components in the {@link MockManager}
     */
    protected void clear()
    {
        this.manager.clear();
        WebBeansContext.getInstance().getPluginLoader().startUp();
    }

    /**
     * Gets all components in the {@link MockManager}
     * 
     * @return all components
     */
    protected List<AbstractOwbBean<?>> getComponents()
    {
        return manager.getComponents();
    }

    /**
     * Return the size of the webbeans in the {@link MockManager}
     * 
     * @return the size of the components in the {@link MockManager}
     */
    protected int getDeployedComponents()
    {
        return manager.getDeployedCompnents();
    }

    /**
     * Gets the webbeans instance.
     * 
     * @param name name of the webbean
     * @return the webbeans instance
     */
    protected Object getInstanceByName(String name)
    {
        Bean<?> bean = manager.resolve(manager.getBeans(name));
        if (bean == null)
        {
            return null;
        }

        return manager.getReference(bean, Object.class, manager.createCreationalContext(bean));
    }

    /**
     * Gets the context with given scope type.
     * 
     * @param scopeType scope type
     * @return the context with given scope type
     */
    protected Context getContext(Class<? extends Annotation> scopeType)
    {
        return manager.getContext(scopeType);
    }

    /**
     * Gets the {@link MockManager} instance.
     * 
     * @return manager instance
     */
    protected MockManager getManager()
    {
        return manager;
    }

    protected WebBeansContext getWebBeansContext()
    {
        return webBeansContext;
    }

    /**
     * Return new MockHttpSession
     * 
     * @return new mock session
     */
    protected Object getSession()
    {
        //X TODO huh? WTF...
        return new Object();
    }

    /**
     * Add new stereotype model.
     * 
     * @param stereoClass stereotype class
     */
    protected void initializeStereoType(Class<? extends Annotation> stereoClass)
    {
        WebBeansContext webBeansContext = WebBeansContext.getInstance();
        webBeansContext.getAnnotationManager().checkStereoTypeClass(stereoClass, stereoClass.getDeclaredAnnotations());
        StereoTypeModel model = new StereoTypeModel(webBeansContext, stereoClass);
        webBeansContext.getStereoTypeManager().addStereoTypeModel(model);
    }

    /**
     * Add new interceptor class.
     * 
     * @param interceptorClazz interceptor class
     */
    protected void initializeInterceptorType(Class<?> interceptorClazz)
    {
        WebBeansContext.getInstance().getInterceptorsManager().addEnabledInterceptorClass(interceptorClazz);

    }

    /**
     * Add new deocrator class.
     * 
     * @param decoratorClazz decorator class
     */
    protected void initializeDecoratorType(Class<?> decoratorClazz)
    {
        WebBeansContext.getInstance().getDecoratorsManager().addEnabledDecorator(decoratorClazz);

    }

    /**
     * End tests for sub-class. NOTE : This method is used for ending the all
     * test methods in sub-class from the ServletContextListener. It is
     * not used for normal unit tests.
     * 
     * @see TestListener
     * @see ComponentResolutionByTypeTest
     */
    @Override
    public void endTests(Object ctx)
    {

    }

    /**
     * Start tests for sub-class. NOTE : This method is used for starting the all
     * test methods in sub-class from the ServletContextListener. It is
     * not used for normal unit tests.
     * 
     * @see TestListener
     * @see ComponentResolutionByTypeTest
     */
    @Override
    public void startTests(Object ctx)
    {
    }     

    /**
     * Returns the newly created Simple WebBean Component.
     *
     * @param clazz Simple WebBean Component implementation class
     * @return the newly created Simple WebBean Component
     * @throws WebBeansConfigurationException if any configuration exception occurs
     */
    private <T> ManagedBean<T> define(Class<T> clazz, WebBeansType type, AnnotatedType<T> annotatedType) throws WebBeansConfigurationException
    {
        WebBeansContext webBeansContext = WebBeansContext.currentInstance();
        BeanManagerImpl manager = webBeansContext.getBeanManagerImpl();

        int modifier = clazz.getModifiers();

        if (AnnotationUtil.hasClassAnnotation(clazz, Decorator.class) && AnnotationUtil.hasClassAnnotation(clazz, Interceptor.class))
        {
            throw new WebBeansConfigurationException("ManagedBean implementation class : " + clazz.getName()
                                                     + " may not annotated with both @Interceptor and @Decorator annotation");
        }

        if (!AnnotationUtil.hasClassAnnotation(clazz, Decorator.class) && !AnnotationUtil.hasClassAnnotation(clazz, Interceptor.class))
        {
            webBeansContext.getInterceptorUtil().checkSimpleWebBeansInterceptorConditions(clazz);
        }

        if (Modifier.isInterface(modifier))
        {
            throw new WebBeansConfigurationException("ManagedBean implementation class : " + clazz.getName() + " may not _defined as interface");
        }

        BeanAttributesImpl<T> beanAttributes = BeanAttributesBuilder.forContext(getWebBeansContext()).newBeanAttibutes(annotatedType).build();
        ManagedBeanBuilder<T, ManagedBean<T>> managedBeanCreator = new ManagedBeanBuilder<T, ManagedBean<T>>(webBeansContext, annotatedType, beanAttributes);
        ManagedBean<T> component = managedBeanCreator.getBean();

        webBeansContext.getWebBeansUtil().setInjectionTargetBeanEnableFlag(component);

        //Dropped from the speicification
        //WebBeansUtil.checkSteroTypeRequirements(component, clazz.getDeclaredAnnotations(), "Simple WebBean Component implementation class : " + clazz.getName());

        Set<ProducerMethodBean<?>> producerMethods = new ProducerMethodBeansBuilder(component.getWebBeansContext(), component.getAnnotatedType()).defineProducerMethods(component);
        for (ProducerMethodBean<?> producerMethod : producerMethods)
        {
            // add them one after the other to enable serialization handling et al
            manager.addBean(producerMethod);
        }

        Set<ProducerFieldBean<?>> producerFields = new ProducerFieldBeansBuilder(component.getWebBeansContext(), component.getAnnotatedType()).defineProducerFields(component);
        for (ProducerFieldBean<?> producerField : producerFields)
        {
            // add them one after the other to enable serialization handling et al
            manager.addBean(producerField);
        }

        new ObserverMethodsBuilder<T, InjectionTargetBean<T>>(webBeansContext, component.getAnnotatedType()).defineObserverMethods(component);

        return component;
    }
}
