/*
 * Copyright 2011 LMAX Ltd.
 *
 * 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 com.lmax.disruptor.dsl.stubs;

import com.lmax.disruptor.util.DaemonThreadFactory;
import org.junit.Assert;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;

public final class StubThreadFactory implements ThreadFactory, TestRule
{
    private static final Logger LOGGER = Logger.getLogger(StubThreadFactory.class.getName());

    private final DaemonThreadFactory threadFactory = DaemonThreadFactory.INSTANCE;
    private final Collection<Thread> threads = new CopyOnWriteArrayList<>();
    private final AtomicBoolean ignoreExecutions = new AtomicBoolean(false);
    private final AtomicInteger executionCount = new AtomicInteger(0);
    private final List<Throwable> threadErrors = Collections.synchronizedList(new ArrayList<>());
    private final List<IgnoredException> ignoredExceptions = new ArrayList<>();

    @Override
    public Thread newThread(final Runnable command)
    {
        executionCount.getAndIncrement();
        Runnable toExecute = () ->
        {
            try
            {
                command.run();
            }
            catch (Throwable t)
            {
                threadErrors.add(t);
            }
        };
        if(ignoreExecutions.get())
        {
            toExecute = new NoOpRunnable();
        }
        final Thread thread = threadFactory.newThread(toExecute);
        thread.setName(command.toString());
        threads.add(thread);
        return thread;
    }

    public void joinAllThreads()
    {
        for (Thread thread : threads)
        {
            if (thread.isAlive())
            {
                try
                {
                    thread.interrupt();
                    thread.join(5000);
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }

            Assert.assertFalse("Failed to stop thread: " + thread, thread.isAlive());
        }

        threads.clear();
    }

    public void ignoreExecutions()
    {
        ignoreExecutions.set(true);
    }

    public int getExecutionCount()
    {
        return executionCount.get();
    }

    @Override
    public Statement apply(final Statement base, final Description description)
    {
        return new Statement()
        {
            @Override
            public void evaluate() throws Throwable
            {
                base.evaluate();
                if (!threadErrors.isEmpty())
                {
                    for (final Throwable threadError : threadErrors)
                    {
                        boolean ignored = false;
                        for (final IgnoredException ignoredException : ignoredExceptions)
                        {
                            if (threadError.getMessage().equalsIgnoreCase(ignoredException.exceptionMessage))
                            {
                                LOGGER.info("Ignoring '" + threadError.getMessage() + "' " +
                                        "because: " + ignoredException.reason);
                                ignored = true;
                                break;
                            }
                        }
                        if (!ignored)
                        {
                            throw threadError;
                        }
                    }
                }
            }
        };
    }

    public void ignoreException(final String exceptionMessage, final String reason)
    {
        ignoredExceptions.add(new IgnoredException(exceptionMessage, reason));
    }

    private static final class IgnoredException
    {
        final String exceptionMessage;
        final String reason;

        IgnoredException(final String exceptionMessage, final String reason)
        {
            this.exceptionMessage = exceptionMessage;
            this.reason = reason;
        }
    }

    private static final class NoOpRunnable implements Runnable
    {
        @Override
        public void run()
        {
        }
    }
}

@@ -694,125 +694,103 @@
 nit.
-Assert;%0Aimport org.junit.rules.TestRule;%0Aimport org.junit.runner.Description;%0Aimport org.junit.runners.model.Statemen
+jupiter.api.extension.AfterEachCallback;%0Aimport org.junit.jupiter.api.extension.ExtensionContex
 t;%0A%0A
@@ -1126,16 +1126,77 @@
 ogger;%0A%0A
+import static org.junit.jupiter.api.Assertions.assertFalse;%0A%0A
 public f
@@ -1254,16 +1254,25 @@
 ry, 
-TestRule
+AfterEachCallback
 %0A%7B%0A 
@@ -2888,27 +2888,38 @@
     
-A
+a
 ssert
-.assertFalse(
+False(thread.isAlive(), 
 %22Fai
@@ -2952,26 +2952,8 @@
 read
-, thread.isAlive()
 );%0A 
@@ -3192,253 +3192,78 @@
 lic 
-Statement apply(final Statement base, final Description description)%0A    %7B%0A        return new Statement()%0A        %7B%0A            @Override%0A            public void evaluate() throws Throwable%0A            %7B%0A                base.evaluate();%0A        
+void afterEach(final ExtensionContext context) throws Exception%0A    %7B%0A
     
@@ -3303,34 +3303,18 @@
         
- 
-       %7B%0A        
+%7B%0A
         
@@ -3378,34 +3378,18 @@
         
-    
-    %7B%0A        
+%7B%0A
         
@@ -3433,32 +3433,24 @@
             
-       
- 
 for (final I
@@ -3515,32 +3515,24 @@
             
-        
 %7B%0A          
@@ -3537,32 +3537,24 @@
             
- 
-       
 if (threadEr
@@ -3643,34 +3643,18 @@
         
-  
-      %7B%0A        
+%7B%0A
         
@@ -3762,24 +3762,16 @@
         
-    
-    
 %22because
@@ -3826,24 +3826,16 @@
         
-        
 ignored 
@@ -3870,31 +3870,15 @@
     
-       
- 
 break;%0A
-        
     
@@ -3907,36 +3907,26 @@
             
-        %7D%0A  
+%7D%0A
             
@@ -3925,30 +3925,24 @@
             
-      
 if (!ignored
@@ -3955,39 +3955,26 @@
             
-        %7B%0A     
+%7B%0A
             
@@ -3985,17 +3985,28 @@
     
-   
 throw 
+new Exception(
 thre
@@ -4012,16 +4012,17 @@
 eadError
+)
 ;%0A      
@@ -4035,258 +4035,33 @@
     
-        %7D%0A                    %7D%0A                %7D%0A            %7D%0A        %7D;%0A    %7D%0A%0A    public void ignoreException(final String exceptionMessage, final String reason)%0A    %7B%0A        ignoredExceptions.add(new IgnoredException(exceptionMessage, reason));
+%7D%0A            %7D%0A        %7D
 %0A   

Rewrite StubThreadFactory in spirit of JUnit 5