/*
 * 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.event.resolve.typeWithParameters;

import static org.jboss.cdi.tck.cdi.Sections.BM_OBSERVER_METHOD_RESOLUTION;
import static org.jboss.cdi.tck.cdi.Sections.OBSERVERS_ASSIGNABILITY;
import static org.jboss.cdi.tck.cdi.Sections.OBSERVER_METHODS;
import static org.jboss.cdi.tck.tests.event.resolve.typeWithParameters.AbstractObserver.buildActionId;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;

import javax.enterprise.event.Event;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.ObserverMethod;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.Set;
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.ActionSequence;
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 CheckTypeParametersWhenResolvingObserversTest extends AbstractTest {

    @Deployment
    public static WebArchive createTestArchive() {
        return new WebArchiveBuilder().withTestClassPackage(CheckTypeParametersWhenResolvingObserversTest.class).build();
    }

    @Inject
    Event<Box<Integer, String, Random>> event;

    @Test
    @SpecAssertions({ @SpecAssertion(section = OBSERVERS_ASSIGNABILITY, id = "b"), @SpecAssertion(section = BM_OBSERVER_METHOD_RESOLUTION, id = "a") })
    public void testResolvingChecksTypeParameters() {
        verifyObserver(new StringList(), 1, StringListObserver.class);
        verifyObserver(new IntegerList(), 1, IntegerListObserver.class);
        verifyObserver(new CharacterList(), 0);
    }

    @Test
    @SpecAssertion(section = OBSERVERS_ASSIGNABILITY, id = "b")
    public void testParameterizedEventTypeAssignableToRawType() {
        Box<Integer, String, Random> box = new Box<Integer, String, Random>();
        event.fire(box);
        assertTrue(RawTypeObserver.OBSERVED);
        verifyObserver(new RawTypeObserver.BoxWithDifferentTypeParameters(), 1, RawTypeObserver.class);
        verifyObserver(new RawTypeObserver.BoxWithObjectTypeParameters(), 1, RawTypeObserver.class);
    }

    @Test
    @SpecAssertions({ @SpecAssertion(section = OBSERVERS_ASSIGNABILITY, id = "c"), @SpecAssertion(section = OBSERVER_METHODS, id = "ab") })
    public void testObservedEventTypeParameterIsActualType() {
        ActionSequence.reset();
        Foo<String> fooString = new Foo.FooString();
        verifyObserver(fooString, 1, FooObserver.class);
        getCurrentManager().fireEvent(fooString);
        verifyEvent(FooObserver.SEQUENCE, 1, buildActionId(FooObserver.class, fooString));
    }

    @Test
    @SpecAssertions({ @SpecAssertion(section = OBSERVERS_ASSIGNABILITY, id = "d") })
    public void testObservedEventTypeParameterIsActualTypeNested() {
        ActionSequence.reset();
        Foo<List<String>> fooStringList = new Foo.FooStringList();
        verifyObserver(fooStringList, 1, FooObserver.class);
        getCurrentManager().fireEvent(fooStringList);
        verifyEvent(FooObserver.SEQUENCE_NESTED, 1, buildActionId(FooObserver.class, fooStringList));
    }

    @Test
    @SpecAssertions({ @SpecAssertion(section = OBSERVERS_ASSIGNABILITY, id = "e") })
    public void testObservedEventTypeParameterIsWildcard() {

        ActionSequence.reset();

        Qux<String> quxString = new Qux.QuxString();
        Qux<List<String>> quxStringList = new Qux.QuxStringList();
        Qux<Number> quxNumber = new Qux.QuxNumber();

        verifyObserver(quxString, 1, WildcardObserver.class);
        verifyObserver(quxNumber, 2, WildcardObserver.class);
        verifyObserver(quxStringList, 2, WildcardObserver.class);

        getCurrentManager().fireEvent(quxString);
        getCurrentManager().fireEvent(quxStringList);
        getCurrentManager().fireEvent(quxNumber);

        verifyEvent(WildcardObserver.SEQUENCE, 3, buildActionId(WildcardObserver.class, quxString),
                buildActionId(WildcardObserver.class, quxStringList), buildActionId(WildcardObserver.class, quxNumber));
        verifyEvent(WildcardObserver.SEQUENCE_LOWER, 1, buildActionId(WildcardObserver.class, quxNumber));
        verifyEvent(WildcardObserver.SEQUENCE_UPPER, 1, buildActionId(WildcardObserver.class, quxStringList));
    }

    @Test
    @SpecAssertion(section = OBSERVERS_ASSIGNABILITY, id = "f")
    public void testObservedEventTypeParameterIsTypeVariable() {

        ActionSequence.reset();

        Duck<String> duckString = new Duck.DuckString();
        Duck<Integer> duckInteger = new Duck.DuckInteger();

        verifyObserver(duckString, 1, TypeVariableObserver.class);
        verifyObserver(duckInteger, 2, TypeVariableObserver.class);

        getCurrentManager().fireEvent(duckString);
        getCurrentManager().fireEvent(duckInteger);

        verifyEvent(TypeVariableObserver.SEQUENCE_TYPE_VAR, 2, buildActionId(TypeVariableObserver.class, duckString),
                buildActionId(TypeVariableObserver.class, duckInteger));
        verifyEvent(TypeVariableObserver.SEQUENCE_TYPE_VAR_UPPER, 1, buildActionId(TypeVariableObserver.class, duckInteger));

    }

    @Test
    @SpecAssertion(section = OBSERVERS_ASSIGNABILITY, id = "a")
    public void testEventTypeAssignableToATypeVariable() {

        ActionSequence.reset();

        Bar bar = new Bar();
        Baz baz = new Baz();

        verifyObserver(bar, 1, TypeVariableObserver.class);
        verifyObserver(baz, 1, TypeVariableObserver.class);

        getCurrentManager().fireEvent(new Bar());
        getCurrentManager().fireEvent(new Baz());
        getCurrentManager().fireEvent(new StringList());

        verifyEvent(TypeVariableObserver.SEQUENCE_UPPER, 2, buildActionId(TypeVariableObserver.class, bar),
                buildActionId(TypeVariableObserver.class, baz));
    }

    /**
     * Quick and dirty test for various event type resolution use cases.
     */
    @Test
    public void testEventTypeResolution() {
        int expectedMatches = 5;
        Dog<?, ?> dogStringNumber = new Dog.DogStringNumber();
        verifyObserver(dogStringNumber, expectedMatches, DogObserver.class);
        getCurrentManager().fireEvent(dogStringNumber);
        verifyEvent(DogObserver.SEQUENCE, expectedMatches);

    }

    public static class CharacterList extends ArrayList<Character> {
        private static final long serialVersionUID = 1L;
    }

    public static class StringList extends ArrayList<String> {
        private static final long serialVersionUID = 1L;
    }

    public static class IntegerList extends ArrayList<Integer> {
        private static final long serialVersionUID = 1L;
    }

    public static class StringListObserver {
        public boolean wasNotified = false;

        public void observer(@Observes ArrayList<String> event) {
            wasNotified = true;
        }
    }

    public static class IntegerListObserver {
        public boolean wasNotified = false;

        public void observer(@Observes ArrayList<Integer> event) {
            wasNotified = true;
        }
    }

    private void verifyObserver(Object event, int expectedNumberOfObservers, Class<?>... expectedObserverTypes) {

        if (expectedNumberOfObservers < expectedObserverTypes.length) {
            throw new IllegalArgumentException("Invalid expected arguments");
        }

        Set<ObserverMethod<? super Object>> observers = getCurrentManager().resolveObserverMethods(event);
        assertEquals(observers.size(), expectedNumberOfObservers);

        List<Class<?>> observerTypes = Arrays.asList(expectedObserverTypes);
        for (ObserverMethod<? super Object> observer : observers) {
            assertTrue(observerTypes.contains(observer.getBeanClass()));
        }
    }

    /**
     * @param sequenceName
     * @param expectedSequenceSize
     * @param actions Sequence data must contain all specified actions
     */
    private void verifyEvent(String sequenceName, int expectedSequenceSize, String... actions) {

        List<String> sequenceData = ActionSequence.getSequenceData(sequenceName);
        assertNotNull(sequenceData);
        assertEquals(sequenceData.size(), expectedSequenceSize);

        for (String action : actions) {
            assertTrue(sequenceData.contains(action));
        }
    }
}
