package org.commonjava.indy.mem.data;

import org.commonjava.indy.audit.ChangeSummary;
import org.commonjava.indy.change.event.ArtifactStoreUpdateType;
import org.commonjava.indy.conf.DefaultIndyConfiguration;
import org.commonjava.indy.data.IndyDataException;
import org.commonjava.indy.data.NoOpStoreEventDispatcher;
import org.commonjava.indy.data.StoreDataManager;
import org.commonjava.indy.model.core.ArtifactStore;
import org.commonjava.indy.model.core.Group;
import org.commonjava.indy.model.core.RemoteRepository;
import org.commonjava.maven.galley.event.EventMetadata;
import org.jboss.byteman.contrib.bmunit.BMRule;
import org.jboss.byteman.contrib.bmunit.BMRules;
import org.jboss.byteman.contrib.bmunit.BMUnitConfig;
import org.jboss.byteman.contrib.bmunit.BMUnitRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;

/**
 * Created by jdcasey on 10/21/16.
 */
@RunWith( BMUnitRunner.class )
@BMUnitConfig( debug = true )
public class ConcurrencyTest
{

    @BMRules( rules = { @BMRule( name = "init rendezvous", targetClass = "MemoryStoreDataManager",
                                 targetMethod = "<init>",
                                 targetLocation = "ENTRY",
                                 action = "createRendezvous($0, 2, true)" ),
            @BMRule( name = "getGroupsContaining call", targetClass = "MemoryStoreDataManager",
                     targetMethod = "getGroupsContaining",
                     targetLocation = "ENTRY", action = "rendezvous($0); debug(Thread.currentThread().getName() + \": thread proceeding.\")" ), } )
    @Test
    public void deadlockOnGroupContains()
            throws IndyDataException, InterruptedException, ExecutionException
    {
        ExecutorService executor = Executors.newFixedThreadPool( 2 );
        ExecutorCompletionService<Void> completionService = new ExecutorCompletionService<Void>( executor );
        AtomicInteger count = new AtomicInteger( 0 );

        RemoteRepository repo = new RemoteRepository( "central", "http://repo.maven.apache.org/maven2" );

        TestStoreEventDispatcher dispatcher =
                new TestStoreEventDispatcher( repo, completionService, count );

        MemoryStoreDataManager dataManager =
                new MemoryStoreDataManager( dispatcher,
                                            new DefaultIndyConfiguration() );

        dispatcher.setDataManager( dataManager );

        ChangeSummary summary = new ChangeSummary( ChangeSummary.SYSTEM_USER, "Test init" );
        dataManager.storeArtifactStore( repo, summary );

        for ( int i = 0; i < 2; i++ )
        {
            Group group = new Group( "group" + i );
            if ( i % 2 == 0 )
            {
                group.addConstituent( repo );
            }

            dataManager.storeArtifactStore( group, summary );
        }

        for ( int i = 0; i < count.get(); i++ )
        {
            Future<Void> future = completionService.take();
            future.get();
        }
    }

    private static final class TestStoreEventDispatcher
            extends NoOpStoreEventDispatcher
    {
        private RemoteRepository repo;

        private final ExecutorCompletionService<Void> completionService;

        private final AtomicInteger count;

        private StoreDataManager dataManager;

        public TestStoreEventDispatcher( RemoteRepository repo, ExecutorCompletionService<Void> completionService,
                                         AtomicInteger count )
        {
            this.repo = repo;
            this.completionService = completionService;
            this.count = count;
        }

        @Override
        public void updating( ArtifactStoreUpdateType type, EventMetadata eventMetadata,
                              Map<ArtifactStore, ArtifactStore> stores )
        {
            for ( int i = 0; i < 2; i++ )
            {
                completionService.submit( () -> {
                    count.incrementAndGet();

                    Logger logger = LoggerFactory.getLogger( getClass() );
                    logger.debug( "Grabbing groups containing: {}", repo.getKey() );
                    try
                    {
                        assertThat( dataManager.getGroupsContaining( repo.getKey() ).isEmpty(), equalTo( false ) );
                    }
                    catch ( IndyDataException e )
                    {
                        e.printStackTrace();
                        fail( "Failed to retrieve groups containing: " + repo.getKey() );
                    }

                    return null;
                } );
            }
        }

        public void setDataManager( StoreDataManager dataManager )
        {
            this.dataManager = dataManager;
        }
    }
}

@@ -1236,16 +1236,67 @@
 qualTo;%0A
+import static org.hamcrest.CoreMatchers.nullValue;%0A
 import s
@@ -2333,36 +2333,38 @@
 mpletionService%3C
-Void
+String
 %3E completionServ
@@ -2399,20 +2399,16 @@
 Service%3C
-Void
 %3E( execu
@@ -3440,20 +3440,22 @@
  Future%3C
-Void
+String
 %3E future
@@ -3499,19 +3499,46 @@
     
-future.get(
+assertThat( future.get(), nullValue() 
 );%0A 
@@ -3656,24 +3656,57 @@
 tcher%0A    %7B%0A
+        static int counter = 0;%0A%0A
         priv
@@ -3773,36 +3773,38 @@
 mpletionService%3C
-Void
+String
 %3E completionServ
@@ -3809,16 +3809,60 @@
 rvice;%0A%0A
+        private final int idx = counter++;%0A%0A
         
@@ -4038,12 +4038,14 @@
 ice%3C
-Void
+String
 %3E co
@@ -4835,28 +4835,21 @@
         
-assertThat( 
+if (!
 dataMana
@@ -4902,30 +4902,312 @@
 ty()
-, equalTo( false ) );%0A
+)%0A                        %7B%0A                            return null;%0A                        %7D%0A                        else%0A                        %7B%0A                            return Thread.currentThread().getName() + %22%5Bexecution: %22 + idx + %22%5D cannot find any groups containing: %22 + repo.getKey();%0A    
     
@@ -5216,32 +5216,54 @@
               %7D%0A
+                    %7D%0A
                 
@@ -5387,17 +5387,37 @@
     
-    fail(
+%7D%0A%0A                    return
  %22Fa
@@ -5473,66 +5473,8 @@
 ey()
- );%0A                    %7D%0A%0A                    return null
 ;%0A  

improve feedback when concurrency test for getGroupsContaining fails.