package com.netflix.governator.guice.concurrent;

import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;

import junit.framework.Assert;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.Test;

import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.inject.AbstractModule;
import com.google.inject.ImplementedBy;
import com.google.inject.Injector;
import com.netflix.governator.annotations.NonConcurrent;
import com.netflix.governator.guice.LifecycleInjector;
import com.netflix.governator.guice.LifecycleInjectorMode;
import com.netflix.governator.guice.lazy.FineGrainedLazySingleton;

public class TestConcurrentSingleton {
    private static Logger LOG = LoggerFactory.getLogger(TestConcurrentSingleton.class);
    
    public static interface IParent {
        
    }
    
    @FineGrainedLazySingleton
    public static class Parent implements IParent {
        @Inject
        Parent(@NonConcurrent NonConcurrentSingleton nonConcurrent, 
                SlowChild1 child1, 
                SlowChild2 child2, 
                Provider<SlowChild3> child3, 
                NonSingletonChild child4, 
                NonSingletonChild child5) {
            LOG.info("Parent start");
            LOG.info("Parent end");
        }
        
        @PostConstruct
        public void init() {
            LOG.info("PostConstruct");
        }
    }
    
    @Singleton
    public static class NonConcurrentSingleton {
        @Inject
        public NonConcurrentSingleton(Recorder recorder) throws InterruptedException {
            recorder.record(getClass());
            LOG.info("Child1 start");
            TimeUnit.SECONDS.sleep(1);
            LOG.info("Child1 end");
        }
    }
    
    @FineGrainedLazySingleton
    public static class SlowChild1 {
        @Inject
        public SlowChild1(Recorder recorder) throws InterruptedException {
            recorder.record(getClass());
            LOG.info("Child1 start");
            TimeUnit.SECONDS.sleep(1);
            LOG.info("Child1 end");
        }
    }
    
    @FineGrainedLazySingleton
    public static class SlowChild2 {
        @Inject
        public SlowChild2(Recorder recorder) throws InterruptedException {
            recorder.record(getClass());
            LOG.info("Child2 start");
            TimeUnit.SECONDS.sleep(1);
            LOG.info("Child2 end");
        }
    }
    
    @ImplementedBy(SlowChild3Impl.class)
    public static interface SlowChild3 {
        
    }
    
    @FineGrainedLazySingleton
    public static class SlowChild3Impl implements SlowChild3 {
        @Inject
        public SlowChild3Impl(Recorder recorder) throws InterruptedException {
            recorder.record(getClass());
            LOG.info("Child3 start");
            TimeUnit.SECONDS.sleep(1);
            LOG.info("Child3 end");
        }
    }
    
    public static class NonSingletonChild {
        private static AtomicInteger counter = new AtomicInteger();
        
        @Inject
        public NonSingletonChild(Recorder recorder) throws InterruptedException {
            recorder.record(getClass());
            int count = counter.incrementAndGet();
            LOG.info("NonSingletonChild start " + count);
            TimeUnit.SECONDS.sleep(1);
            LOG.info("NonSingletonChild end " + count);
        }
    }
    
    @FineGrainedLazySingleton
    public static class Recorder {
        Map<Class<?>, Integer> counts = Maps.newHashMap();
        Map<Class<?>, Long> threadIds = Maps.newHashMap();
        Set<Long> uniqueThreadIds = Sets.newHashSet();
        
        public synchronized void record(Class<?> type) {
            threadIds.put(type, Thread.currentThread().getId());
            uniqueThreadIds.add(Thread.currentThread().getId());
            if (!counts.containsKey(type)) {
                counts.put(type, 1);
            }
            else {
                counts.put(type, counts.get(type)+1);
            }
        }
        
        public int getUniqueThreadCount() {
            return uniqueThreadIds.size();
        }
        
        public int getTypeCount(Class<?> type) {
            Integer count = counts.get(type);
            return count == null ? 0 : count;
        }
        
        public long getThreadId(Class<?> type) {
            return threadIds.get(type);
        }
    }
    
    @Test
    public void shouldInitInterfaceInParallel() {
        Injector injector = LifecycleInjector.builder()
                .withMode(LifecycleInjectorMode.SIMULATED_CHILD_INJECTORS)
                .withModules(new AbstractModule() {
                    @Override
                    protected void configure() {
                        bind(IParent.class).to(Parent.class);
                        bind(Parent.class).toProvider(ConcurrentProviders.of(Parent.class));
                    }
                })
                .build().createInjector();
        
        // This confirms that the Provider is called via the interface binding
        injector.getInstance(IParent.class);
        Recorder recorder = injector.getInstance(Recorder.class);
        
        Assert.assertEquals(5, recorder.getUniqueThreadCount());
        Assert.assertEquals(1, recorder.getTypeCount(SlowChild1.class));
        Assert.assertEquals(1, recorder.getTypeCount(SlowChild2.class));
        Assert.assertEquals(0, recorder.getTypeCount(SlowChild3.class));        // Only the provider was injected
        Assert.assertEquals(2, recorder.getTypeCount(NonSingletonChild.class));
        Assert.assertEquals(1, recorder.getThreadId(NonConcurrentSingleton.class));
        
    }
}

@@ -1853,38 +1853,54 @@
       LOG.info(%22
-Child1
+NonConcurrentSingleton
  start%22);%0A      
@@ -1946,38 +1946,54 @@
       LOG.info(%22
-Child1
+NonConcurrentSingleton
  end%22);%0A        

Fix typos