/*******************************************************************************
 * Copyright (c) 2010, 2015 Sonatype, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Stuart McCulloch (Sonatype, Inc.) - initial API and implementation
 *******************************************************************************/
package org.eclipse.sisu.inject;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.util.Iterator;

import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Qualifier;

import junit.framework.TestCase;

import org.eclipse.sisu.BeanEntry;
import org.eclipse.sisu.inject.RankedBindingsTest.Bean;
import org.eclipse.sisu.inject.RankedBindingsTest.BeanImpl;

import com.google.inject.AbstractModule;
import com.google.inject.Binding;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.name.Names;
import com.google.inject.util.Providers;

public class LocatedBeansTest
    extends TestCase
{
    @Qualifier
    @Retention( RUNTIME )
    public @interface Marked
    {
        String value();
    }

    @Marked( "MarkedBean1" )
    static class MarkedBeanImpl1
        implements Bean
    {
    }

    @Marked( "MarkedBean2" )
    static class MarkedBeanImpl2
        implements Bean
    {
    }

    @Marked( "MarkedBean3" )
    static class MarkedBeanProvider
        implements Provider<Bean>
    {
        public Bean get()
        {
            return new MarkedBeanImpl1();
        }
    }

    Injector injector;

    @Override
    public void setUp()
        throws Exception
    {
        injector = Guice.createInjector( new AbstractModule()
        {
            @Override
            protected void configure()
            {
                bind( Bean.class ).to( BeanImpl.class ); // also seen as @Named("default")

                bind( Bean.class ).annotatedWith( Named.class ).to( BeanImpl.class ); // only in unrestricted search

                bind( Bean.class ).annotatedWith( Names.named( "Named1" ) ).toProvider( Providers.of( new BeanImpl() ) );
                bind( Bean.class ).annotatedWith( Names.named( "Named2" ) ).to( BeanImpl.class );

                bind( Bean.class ).annotatedWith( Marked.class ).to( BeanImpl.class ); // only in unrestricted search

                bind( Bean.class ).annotatedWith( MarkedBeanImpl1.class.getAnnotation( Marked.class ) ).to( MarkedBeanImpl1.class );
                bind( Bean.class ).annotatedWith( Names.named( "Marked2" ) ).to( MarkedBeanImpl2.class );
                bind( Bean.class ).annotatedWith( Names.named( "Marked3" ) ).toProvider( MarkedBeanProvider.class );
            }
        } );
    }

    public void testCacheConcurrency()
    {
        final LocatedBeans<Annotation, Bean> beans = locate( Key.get( Bean.class ) );

        final Iterator<BeanEntry<Annotation, Bean>> itr1 = beans.iterator();
        final Iterator<BeanEntry<Annotation, Bean>> itr2 = beans.iterator();

        Bean a, b;

        a = itr1.next().getValue();
        assertSame( a, itr2.next().getValue() );
        a = itr1.next().getValue();
        assertSame( a, itr2.next().getValue() );
        a = itr1.next().getValue();
        assertSame( a, itr2.next().getValue() );

        a = itr1.next().getValue();

        for ( final Binding<Bean> binding : beans.beans.bindings() )
        {
            beans.beans.remove( binding );
        }

        b = itr2.next().getValue();

        assertFalse( a == b );

        a = itr1.next().getValue();
        assertSame( a, itr2.next().getValue() );
        a = itr1.next().getValue();
        assertSame( a, itr2.next().getValue() );
        a = itr1.next().getValue();
        assertSame( a, itr2.next().getValue() );
    }

    public void testUnrestrictedSearch()
    {
        final LocatedBeans<Annotation, Bean> beans = locate( Key.get( Bean.class ) );
        final Iterator<BeanEntry<Annotation, Bean>> itr = beans.iterator();

        assertTrue( itr.hasNext() );
        assertEquals( QualifyingStrategy.DEFAULT_QUALIFIER, itr.next().getKey() );
        assertTrue( itr.hasNext() );
        assertEquals( QualifyingStrategy.BLANK_QUALIFIER, itr.next().getKey() );
        assertTrue( itr.hasNext() );
        assertEquals( Names.named( "Named1" ), itr.next().getKey() );
        assertTrue( itr.hasNext() );
        assertEquals( Names.named( "Named2" ), itr.next().getKey() );
        assertTrue( itr.hasNext() );
        assertEquals( QualifyingStrategy.BLANK_QUALIFIER, itr.next().getKey() );
        assertTrue( itr.hasNext() );
        assertEquals( MarkedBeanImpl1.class.getAnnotation( Marked.class ), itr.next().getKey() );
        assertTrue( itr.hasNext() );
        assertEquals( Names.named( "Marked2" ), itr.next().getKey() );
        assertTrue( itr.hasNext() );
        assertEquals( Names.named( "Marked3" ), itr.next().getKey() );
        assertFalse( itr.hasNext() );
    }

    public void testNamedSearch()
    {
        final LocatedBeans<Named, Bean> beans = locate( Key.get( Bean.class, Named.class ) );
        final Iterator<BeanEntry<Named, Bean>> itr = beans.iterator();

        assertTrue( itr.hasNext() );
        assertEquals( QualifyingStrategy.DEFAULT_QUALIFIER, itr.next().getKey() );
        assertTrue( itr.hasNext() );
        assertEquals( Names.named( "Named1" ), itr.next().getKey() );
        assertTrue( itr.hasNext() );
        assertEquals( Names.named( "Named2" ), itr.next().getKey() );
        assertTrue( itr.hasNext() );
        assertEquals( Names.named( "Marked2" ), itr.next().getKey() );
        assertTrue( itr.hasNext() );
        assertEquals( Names.named( "Marked3" ), itr.next().getKey() );
        assertFalse( itr.hasNext() );
    }

    public void testNamedWithAttributesSearch()
    {
        final LocatedBeans<Named, Bean> beans = locate( Key.get( Bean.class, Names.named( "Named2" ) ) );
        final Iterator<BeanEntry<Named, Bean>> itr = beans.iterator();

        assertTrue( itr.hasNext() );
        assertEquals( Names.named( "Named2" ), itr.next().getKey() );
        assertFalse( itr.hasNext() );
    }

    public void testMarkedSearch()
    {
        final LocatedBeans<Marked, Bean> beans = locate( Key.get( Bean.class, Marked.class ) );
        final Iterator<BeanEntry<Marked, Bean>> itr = beans.iterator();

        assertTrue( itr.hasNext() );
        assertEquals( MarkedBeanImpl1.class.getAnnotation( Marked.class ), itr.next().getKey() );
        assertTrue( itr.hasNext() );
        assertEquals( MarkedBeanImpl2.class.getAnnotation( Marked.class ), itr.next().getKey() );
        assertTrue( itr.hasNext() );
        assertEquals( MarkedBeanProvider.class.getAnnotation( Marked.class ), itr.next().getKey() );
        assertFalse( itr.hasNext() );
    }

    public void testMarkedWithAttributesSearch()
    {
        final LocatedBeans<Marked, Bean> beans =
            locate( Key.get( Bean.class, MarkedBeanImpl2.class.getAnnotation( Marked.class ) ) );
        final Iterator<BeanEntry<Marked, Bean>> itr = beans.iterator();

        assertTrue( itr.hasNext() );
        assertEquals( MarkedBeanImpl2.class.getAnnotation( Marked.class ), itr.next().getKey() );
        assertFalse( itr.hasNext() );
    }

    private <Q extends Annotation, T> LocatedBeans<Q, T> locate( final Key<T> key )
    {
        final RankedBindings<T> bindings = new RankedBindings<T>( key.getTypeLiteral(), null );
        for ( final Binding<T> b : injector.findBindingsByType( key.getTypeLiteral() ) )
        {
            bindings.add( b, 0 );
        }
        return new LocatedBeans<Q, T>( key, bindings, null );
    }
}
