/*******************************************************************************
 * Copyright (c) 2010, 2013 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.space;

import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.inject.Qualifier;

import org.eclipse.sisu.BeanScanning;

import com.google.inject.Binder;
import com.google.inject.Module;
import com.google.inject.spi.Element;
import com.google.inject.spi.Elements;
import com.google.inject.spi.MembersInjectorLookup;
import com.google.inject.spi.ProviderLookup;

/**
 * Guice {@link Module} that automatically binds types annotated with {@link Qualifier} annotations.
 */
public class SpaceModule
    implements Module
{
    // ----------------------------------------------------------------------
    // Implementation fields
    // ----------------------------------------------------------------------

    private static ConcurrentMap<String, List<Element>> cachedElementsMap;

    private final boolean caching;

    final ClassSpace space;

    final ClassFinder finder;

    // ----------------------------------------------------------------------
    // Constructors
    // ----------------------------------------------------------------------

    public SpaceModule( final ClassSpace space )
    {
        this( space, BeanScanning.ON );
    }

    public SpaceModule( final ClassSpace space, final ClassFinder finder )
    {
        caching = false;

        this.space = space;
        this.finder = finder;
    }

    public SpaceModule( final ClassSpace space, final BeanScanning scanning )
    {
        caching = BeanScanning.CACHE == scanning;

        this.space = space;
        switch ( scanning )
        {
            case OFF:
                finder = null;
                break;
            case INDEX:
                finder = new SisuIndexClassFinder( false );
                break;
            case GLOBAL_INDEX:
                finder = new SisuIndexClassFinder( true );
                break;
            default:
                finder = new DefaultClassFinder();
                break;
        }
    }

    // ----------------------------------------------------------------------
    // Public methods
    // ----------------------------------------------------------------------

    public final void configure( final Binder binder )
    {
        binder.bind( ClassSpace.class ).toInstance( space );

        if ( caching )
        {
            recordAndReplayElements( binder );
        }
        else if ( null != finder )
        {
            new ClassSpaceScanner( finder, space ).accept( visitor( binder ) );
        }
    }

    // ----------------------------------------------------------------------
    // Customizable methods
    // ----------------------------------------------------------------------

    protected ClassSpaceVisitor visitor( final Binder binder )
    {
        return new QualifiedTypeVisitor( new QualifiedTypeBinder( binder ) );
    }

    // ----------------------------------------------------------------------
    // Implementation methods
    // ----------------------------------------------------------------------

    private final void recordAndReplayElements( final Binder binder )
    {
        synchronized ( SpaceModule.class )
        {
            if ( null == cachedElementsMap )
            {
                cachedElementsMap = new ConcurrentHashMap<String, List<Element>>();
            }
        }

        boolean alreadyCached = true;

        /*
         * Record elements first time round
         */
        final String key = space.toString();
        List<Element> cachedElements = cachedElementsMap.get( key );
        if ( null == cachedElements )
        {
            final List<Element> elements = Elements.getElements( new Module()
            {
                public void configure( final Binder recorder )
                {
                    new ClassSpaceScanner( finder, space ).accept( visitor( recorder ) );
                }
            } );
            cachedElements = cachedElementsMap.putIfAbsent( key, elements );
            if ( null == cachedElements )
            {
                cachedElements = elements;
                alreadyCached = false;
            }
        }

        /*
         * Then replay onto current binder
         */
        for ( final Element e : cachedElements )
        {
            if ( alreadyCached )
            {
                // lookups have state so we replace them with duplicates when replaying...
                if ( e instanceof ProviderLookup<?> )
                {
                    binder.getProvider( ( (ProviderLookup<?>) e ).getKey() );
                    continue;
                }
                if ( e instanceof MembersInjectorLookup<?> )
                {
                    binder.getMembersInjector( ( (MembersInjectorLookup<?>) e ).getType() );
                    continue;
                }
            }
            e.applyTo( binder );
        }
    }
}

@@ -1364,63 +1364,162 @@
 tic 
-ConcurrentMap%3CString, List%3CElement%3E%3E cachedElementsMap;
+final class RecordedElements%0A    %7B%0A        static final ConcurrentMap%3CString, List%3CElement%3E%3E cache = new ConcurrentHashMap%3CString, List%3CElement%3E%3E();%0A    %7D
 %0A%0A  
@@ -3812,229 +3812,8 @@
   %7B%0A
-        synchronized ( SpaceModule.class )%0A        %7B%0A            if ( null == cachedElementsMap )%0A            %7B%0A                cachedElementsMap = new ConcurrentHashMap%3CString, List%3CElement%3E%3E();%0A            %7D%0A        %7D%0A%0A
     
@@ -3827,28 +3827,26 @@
 lean already
-Cach
+Us
 ed = true;%0A%0A
@@ -3979,23 +3979,17 @@
 lement%3E 
-cachedE
+e
 lements 
@@ -3982,36 +3982,38 @@
 ent%3E elements = 
-cach
+Record
 edElementsMap.ge
@@ -4006,19 +4006,22 @@
 Elements
-Map
+.cache
 .get( ke
@@ -4038,39 +4038,33 @@
    if ( null == 
-cachedE
+e
 lements )%0A      
@@ -4099,24 +4099,25 @@
 lement%3E 
-elements
+recording
  = Eleme
@@ -4370,39 +4370,33 @@
  );%0A            
-cachedE
+e
 lements = cached
@@ -4389,20 +4389,22 @@
 ments = 
-cach
+Record
 edElemen
@@ -4405,19 +4405,22 @@
 Elements
-Map
+.cache
 .putIfAb
@@ -4430,24 +4430,25 @@
 t( key, 
-elements
+recording
  );%0A    
@@ -4468,23 +4468,17 @@
 null == 
-cachedE
+e
 lements 
@@ -4509,23 +4509,17 @@
         
-cachedE
+e
 lements 
@@ -4520,24 +4520,25 @@
 ments = 
-elements
+recording
 ;%0A      
@@ -4554,20 +4554,18 @@
  already
-Cach
+Us
 ed = fal
@@ -4695,15 +4695,9 @@
 e : 
-cachedE
+e
 leme
@@ -4740,12 +4740,10 @@
 eady
-Cach
+Us
 ed )

Use initialization-on-demand pattern