/*
 * JBoss, Home of Professional Open Source
 * Copyright 2010, Red Hat, Inc., 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.cdi.tck.tests.implementation.simple.lifecycle;

import static org.jboss.cdi.tck.cdi.Sections.BEAN_ARCHIVE_EE;
import static org.jboss.cdi.tck.cdi.Sections.CONCEPTS;
import static org.jboss.cdi.tck.cdi.Sections.CONTEXT;
import static org.jboss.cdi.tck.cdi.Sections.CONTEXTUAL;
import static org.jboss.cdi.tck.cdi.Sections.CONTEXTUAL_REFERENCE;
import static org.jboss.cdi.tck.cdi.Sections.CREATIONAL_CONTEXT;
import static org.jboss.cdi.tck.cdi.Sections.DECLARING_BEAN_CONSTRUCTOR;
import static org.jboss.cdi.tck.cdi.Sections.DECLARING_INJECTED_FIELD;
import static org.jboss.cdi.tck.cdi.Sections.DEPENDENT_OBJECTS_DESTRUCTION;
import static org.jboss.cdi.tck.cdi.Sections.INITIALIZATION;
import static org.jboss.cdi.tck.cdi.Sections.INJECTED_FIELDS;
import static org.jboss.cdi.tck.cdi.Sections.INJECTED_FIELD_QUALIFIERS;
import static org.jboss.cdi.tck.cdi.Sections.LEGAL_BEAN_TYPES;
import static org.jboss.cdi.tck.cdi.Sections.MANAGED_BEAN_LIFECYCLE;
import static org.jboss.cdi.tck.cdi.Sections.MEMBER_LEVEL_INHERITANCE;
import static org.jboss.cdi.tck.cdi.Sections.METHOD_CONSTRUCTOR_PARAMETER_QUALIFIERS;
import static org.jboss.cdi.tck.cdi.Sections.PASSIVATION_CAPABLE_DEPENDENCY;
import static org.jboss.cdi.tck.cdi.Sections.SPECIALIZE_MANAGED_BEAN;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;

import java.lang.annotation.Annotation;

import javax.enterprise.context.Dependent;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.context.spi.Context;
import javax.enterprise.context.spi.Contextual;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.CreationException;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.Specializes;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.util.AnnotationLiteral;

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.cdi.tck.AbstractTest;
import org.jboss.cdi.tck.shrinkwrap.WebArchiveBuilder;
import org.jboss.cdi.tck.util.DependentInstance;
import org.jboss.cdi.tck.util.MockCreationalContext;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.jboss.test.audit.annotations.SpecAssertion;
import org.jboss.test.audit.annotations.SpecAssertions;
import org.jboss.test.audit.annotations.SpecVersion;
import org.testng.annotations.Test;

@SpecVersion(spec = "cdi", version = "2.0")
public class SimpleBeanLifecycleTest extends AbstractTest {
    private static final Annotation TAME_LITERAL = new AnnotationLiteral<Tame>() {
    };

    @Deployment
    public static WebArchive createTestArchive() {
        return new WebArchiveBuilder().withTestClassPackage(SimpleBeanLifecycleTest.class).withBeansXml("beans.xml").build();
    }

    @Test
    @SpecAssertions({ @SpecAssertion(section = DECLARING_BEAN_CONSTRUCTOR, id = "f"), @SpecAssertion(section = DECLARING_BEAN_CONSTRUCTOR, id = "g"),
            @SpecAssertion(section = METHOD_CONSTRUCTOR_PARAMETER_QUALIFIERS, id = "d") })
    public void testInjectionOfParametersIntoBeanConstructor() {
        assert getBeans(FishPond.class).size() == 1;
        FishPond fishPond = getContextualReference(FishPond.class);
        assert fishPond.goldfish != null;
        assert fishPond.goldfish instanceof Goldfish;
        assert fishPond.goose != null;
    }

    @Test
    @SpecAssertions({ @SpecAssertion(section = PASSIVATION_CAPABLE_DEPENDENCY, id = "b") })
    public void testSerializeRequestScoped() throws Exception {
        Cod codInstance = getContextualReference(Cod.class);

        byte[] bytes = passivate(codInstance);
        Object object = activate(bytes);
        codInstance = (Cod) object;
        assert getCurrentConfiguration().getBeans().isProxy(codInstance);
    }

    @Test
    @SpecAssertions({ @SpecAssertion(section = PASSIVATION_CAPABLE_DEPENDENCY, id = "b") })
    public void testSerializeSessionScoped() throws Exception {
        Bream instance = getContextualReference(Bream.class);

        byte[] bytes = passivate(instance);
        Object object = activate(bytes);
        instance = (Bream) object;
        assert getCurrentConfiguration().getBeans().isProxy(instance);
    }

    @Test
    @SpecAssertion(section = DECLARING_BEAN_CONSTRUCTOR, id = "g")
    public void testQualifierTypeAnnotatedConstructor() {
        getContextualReference(Duck.class);
        assert Duck.constructedCorrectly;
    }

    @Test
    @SpecAssertions({ @SpecAssertion(section = SPECIALIZE_MANAGED_BEAN, id = "ac") })
    public void testSpecializedBeanExtendsManagedBean() {
        assert MountainLion.class.getAnnotation(Specializes.class) != null;
        Bean<Lion> bean = null;
        Bean<Lion> specializedBean = null;
        for (Bean<Lion> lionBean : getBeans(Lion.class, TAME_LITERAL)) {
            if (lionBean.getBeanClass().equals(Lion.class)) {
                bean = lionBean;
            } else if (lionBean.getBeanClass().equals(MountainLion.class)) {
                specializedBean = lionBean;
            }
        }

        assert bean == null;
        assert specializedBean != null;
        assert specializedBean.getBeanClass().getSuperclass().equals(Lion.class);
    }

    @Test
    @SpecAssertions({ @SpecAssertion(section = CREATIONAL_CONTEXT, id = "d"), @SpecAssertion(section = CREATIONAL_CONTEXT, id = "g") })
    public void testCreateReturnsSameBeanPushed() {
        final CreationalContext<ShoeFactory> creationalContext = new MockCreationalContext<ShoeFactory>();
        final Contextual<ShoeFactory> bean = getBeans(ShoeFactory.class).iterator().next();
        MockCreationalContext.reset();
        ShoeFactory instance = getCurrentManager().getContext(Dependent.class).get(bean, creationalContext);
        if (MockCreationalContext.isPushCalled()) {
            assert instance == MockCreationalContext.getLastBeanPushed();
        }
    }

    @Test
    @SpecAssertions({ @SpecAssertion(section = MANAGED_BEAN_LIFECYCLE, id = "aa") })
    public void testBeanCreateInjectsDependenciesAndInvokesInitializerToInstantiateInstance() {
        MockCreationalContext.reset();
        final CreationalContext<FishPond> creationalContext = new MockCreationalContext<FishPond>();
        final Contextual<FishPond> bean = getBeans(FishPond.class).iterator().next();
        FishPond fishPond = bean.create(creationalContext);
        assert fishPond != null;
        assert fishPond.goldfish != null;
        assert fishPond.goldfish instanceof Goldfish;
        assert fishPond.goose != null;
        assert fishPond.salmon != null;
        assert fishPond.postConstructCalled; // required by Managed Bean specification
    }

    @Test
    @SpecAssertions({ @SpecAssertion(section = CONCEPTS, id = "g"), @SpecAssertion(section = LEGAL_BEAN_TYPES, id = "b"),
            @SpecAssertion(section = LEGAL_BEAN_TYPES, id = "k"), @SpecAssertion(section = INITIALIZATION, id = "ja") })
    public void testManagedBean() {
        assert getBeans(RedSnapper.class).size() == 1;
        assert getContextualReference(RedSnapper.class) instanceof RedSnapper;
        RedSnapper redSnapper = getContextualReference(RedSnapper.class);
        redSnapper.ping();
        assert redSnapper.isTouched();
    }

    @Test
    @SpecAssertions({ @SpecAssertion(section = MANAGED_BEAN_LIFECYCLE, id = "aa"), @SpecAssertion(section = DECLARING_BEAN_CONSTRUCTOR, id = "aa"),
            @SpecAssertion(section = INJECTED_FIELD_QUALIFIERS, id = "a"), @SpecAssertion(section = INJECTED_FIELDS, id = "a"),
            @SpecAssertion(section = DECLARING_INJECTED_FIELD, id = "aa"), @SpecAssertion(section = BEAN_ARCHIVE_EE, id = "jg") })
    public void testCreateInjectsFieldsDeclaredInJava() {
        assert getBeans(TunaFarm.class).size() == 1;
        TunaFarm tunaFarm = getContextualReference(TunaFarm.class);
        assert tunaFarm.tuna != null;
        assert tunaFarm.tuna.getName().equals("Ophir");

        assert tunaFarm.qualifiedTuna != null;
        assert tunaFarm.qualifiedTuna.getName().equals("qualifiedTuna");
    }

    @Test
    @SpecAssertions({ @SpecAssertion(section = CONTEXT, id = "l") })
    public void testContextCreatesNewInstanceForInjection() {
        Context requestContext = getCurrentManager().getContext(RequestScoped.class);
        Bean<Tuna> tunaBean = getBeans(Tuna.class).iterator().next();
        assert requestContext.get(tunaBean) == null;
        TunaFarm tunaFarm = getContextualReference(TunaFarm.class);
        assert tunaFarm.tuna != null;
    }

    @Test
    @SpecAssertions({ @SpecAssertion(section = MANAGED_BEAN_LIFECYCLE, id = "aa"), @SpecAssertion(section = MANAGED_BEAN_LIFECYCLE, id = "ba") })
    public void testPostConstructPreDestroy() {
        assert getBeans(Farm.class).size() == 1;
        Bean<Farm> farmBean = getBeans(Farm.class).iterator().next();
        CreationalContext<Farm> creationalContext = getCurrentManager().createCreationalContext(farmBean);
        Farm farm = farmBean.create(creationalContext);
        assert farm.founded != null;
        assert farm.initialStaff == 20;
        assert farm.closed == null;
        farmBean.destroy(farm, creationalContext);
        assert farm.closed != null;
        assert farm.farmOffice.noOfStaff == 0;
    }

    @Test
    @SpecAssertions({ @SpecAssertion(section = CONTEXTUAL_REFERENCE, id = "aa"), @SpecAssertion(section = MANAGED_BEAN_LIFECYCLE, id = "ba"),
            @SpecAssertion(section = CONTEXTUAL_REFERENCE, id = "c") })
    public void testContextualDestroyDisposesWhenNecessary() {
        final Bean<Goose> gooseBean = getBeans(Goose.class).iterator().next();
        final CreationalContext<Goose> gooseCc = getCurrentManager().createCreationalContext(gooseBean);
        final Goose goose = gooseBean.create(gooseCc);
        // If the bean has a pseudo-scope, the container must obtain a contextual instance
        assert !getCurrentConfiguration().getBeans().isProxy(goose);
        assert !EggProducer.isEggDisposed();
        assert !Egg.isEggDestroyed();
        gooseBean.destroy(goose, gooseCc);
        assert EggProducer.isEggDisposed();
        assert !Egg.isEggDestroyed();
    }

    @Test
    @SpecAssertions({ @SpecAssertion(section = CONTEXTUAL, id = "ab") })
    public void testContextualDestroyCatchesException() {
        Bean<Cod> codBean = getBeans(Cod.class).iterator().next();
        CreationalContext<Cod> creationalContext = getCurrentManager().createCreationalContext(codBean);
        Cod codInstance = codBean.create(creationalContext);
        codInstance.ping();
        codBean.destroy(codInstance, creationalContext);
        assertTrue(Cod.isExpcetionThrown());
    }

    @Test
    @SpecAssertions({ @SpecAssertion(section = DEPENDENT_OBJECTS_DESTRUCTION, id = "a") })
    public void testDependentsDestroyedAfterPreDestroy() {
        Bean<FishPond> pondBean = getBeans(FishPond.class).iterator().next();
        CreationalContext<FishPond> creationalContext = getCurrentManager().createCreationalContext(pondBean);
        FishPond fishPond = pondBean.create(creationalContext);
        pondBean.destroy(fishPond, creationalContext);
        assert Salmon.isBeanDestroyed();
    }

    @Test(dataProvider = ARQUILLIAN_DATA_PROVIDER)
    @SpecAssertion(section = MEMBER_LEVEL_INHERITANCE, id = "baa")
    public void testSubClassInheritsPostConstructOnSuperclass(Instance<Object> instance) {
        OrderProcessor.postConstructCalled = false;
        assertEquals(getBeans(CdOrderProcessor.class).size(), 1);
        getContextualReference(CdOrderProcessor.class).order();
        assertTrue(OrderProcessor.postConstructCalled);

        assertNotNull(instance);
        OrderProcessor.postConstructCalled = false;
        instance.select(CdOrderProcessor.class).get().order();
        assertTrue(OrderProcessor.postConstructCalled);
    }

    @Test
    @SpecAssertion(section = MEMBER_LEVEL_INHERITANCE, id = "bac")
    public void testIndirectSubClassInheritsPostConstructOnSuperclass() {
        OrderProcessor.postConstructCalled = false;
        assert getBeans(IndirectOrderProcessor.class).size() == 1;
        getContextualReference(IndirectOrderProcessor.class).order();
        assert OrderProcessor.postConstructCalled;
    }

    @Test
    @SpecAssertion(section = MEMBER_LEVEL_INHERITANCE, id = "bba")
    public void testSubClassInheritsPreDestroyOnSuperclass() {
        OrderProcessor.preDestroyCalled = false;
        assert getBeans(CdOrderProcessor.class).size() == 1;

        DependentInstance<CdOrderProcessor> bean = newDependentInstance(CdOrderProcessor.class);
        CdOrderProcessor instance = bean.get();
        assertFalse(instance.equals(null));
        bean.destroy();
        assertTrue(OrderProcessor.preDestroyCalled);
    }

    @Test
    @SpecAssertion(section = MEMBER_LEVEL_INHERITANCE, id = "bbc")
    public void testIndirectSubClassInheritsPreDestroyOnSuperclass() {
        OrderProcessor.preDestroyCalled = false;
        assert getBeans(IndirectOrderProcessor.class).size() == 1;
        DependentInstance<IndirectOrderProcessor> bean = newDependentInstance(IndirectOrderProcessor.class);
        assertFalse(bean.get().equals(null));
        bean.destroy();
        assertTrue(OrderProcessor.preDestroyCalled);
    }

    @Test
    @SpecAssertion(section = MEMBER_LEVEL_INHERITANCE, id = "baa")
    public void testSubClassDoesNotInheritPostConstructOnSuperclassBlockedByIntermediateClass() {
        assert getBeans(NovelOrderProcessor.class).size() == 1;
        OrderProcessor.postConstructCalled = false;
        getContextualReference(NovelOrderProcessor.class).order();
        assert !OrderProcessor.postConstructCalled;
    }

    @Test
    @SpecAssertion(section = MEMBER_LEVEL_INHERITANCE, id = "bba")
    public void testSubClassDoesNotInheritPreDestroyConstructOnSuperclassBlockedByIntermediateClass() {
        OrderProcessor.preDestroyCalled = false;
        assert getBeans(NovelOrderProcessor.class).size() == 1;
        DependentInstance<NovelOrderProcessor> bean = newDependentInstance(NovelOrderProcessor.class);
        NovelOrderProcessor instance = bean.get();
        assertFalse(instance.equals(null));
        bean.destroy();
        assertFalse(OrderProcessor.preDestroyCalled);
    }

    @Test(expectedExceptions = CreationException.class)
    @SpecAssertion(section = CONTEXTUAL, id = "aa")
    public void testCreationExceptionWrapsCheckedExceptionThrownFromCreate() {
        assert getBeans(Lorry_Broken.class).size() == 1;
        getContextualReference(Lorry_Broken.class);
    }

    @Test(expectedExceptions = FooException.class)
    @SpecAssertion(section = CONTEXTUAL, id = "aa")
    public void testUncheckedExceptionThrownFromCreateNotWrapped() {
        assert getBeans(Van_Broken.class).size() == 1;
        getContextualReference(Van_Broken.class);
    }

}
