// Path: src/test/java/net/bourgau/philippe/concurrency/kata/CountingOutput.java
// public class CountingOutput implements Output {
//     private AtomicInteger messageCount = new AtomicInteger();
// 
//     @Override
//     public void write(String line) {
//         messageCount.incrementAndGet();
//     }
// 
//     public void reset() {
//         messageCount.set(0);
//     }
// 
//     public IntegerAssert assertMessageCount() {
//         return Assertions.assertThat(messageCount.get());
//     }
// 
// }
// 
// Path: src/test/java/net/bourgau/philippe/concurrency/kata/Implementations.java
// public class Implementations {
// 
//     public static Collection<Object[]> all() {
//         return simple(
//                 new MonoThread(),
//                 new UnboundedSync(),
//                 new UnboundedConcurrent(),
//                 new BoundedConcurrent(),
//                 new BoundedFineGrained(),
//                 new ActorsRealThreads(),
//                 new ActorsGreenThreads(),
//                 new CSP());
//     }
// 
//     public static Collection<Object[]> bounded() {
//         return simple(
//                 new MonoThread(),
//                 new BoundedConcurrent(),
//                 new BoundedFineGrained(),
//                 new ActorsGreenThreads(),
//                 new CSP());
//     }
// 
//     public static Collection<Object[]> multithreaded() {
//         return simple(
//                 new UnboundedSync(),
//                 new UnboundedConcurrent(),
//                 new BoundedConcurrent(),
//                 new BoundedFineGrained(),
//                 new ActorsRealThreads(),
//                 new ActorsGreenThreads(),
//                 new CSP());
//     }
// 
//     public static Collection<Object[]> simple(Object... values) {
//         ArrayList<Object[]> parameters = new ArrayList<>();
//         for (Object value : values) {
//             parameters.add(new Object[]{value});
//         }
//         return parameters;
//     }
// 
//     public static Collection<Object[]> complex(Object[]... objects) {
//         ArrayList<Object[]> parameters = new ArrayList<>();
//         for (Object[] object : objects) {
//             parameters.add(object);
//         }
//         return parameters;
//     }
// 
//     public static Collection<Object[]> crossProduct(Collection<Object[]> xs, Collection<Object[]> ys) {
//         ArrayList<Object[]> parameters = new ArrayList<>();
//         for (Object[] x : xs) {
//             for (Object[] y : ys) {
//                 parameters.add(ArrayUtils.addAll(x, y));
//             }
//         }
//         return parameters;
//     }
// }
// 
// Path: src/test/java/net/bourgau/philippe/concurrency/kata/MatchingOutput.java
// public class MatchingOutput extends CountingOutput {
//     private final String subMessage;
// 
//     public MatchingOutput(String subMessage) {
//         this.subMessage = subMessage;
//     }
// 
//     @Override
//     public void write(String line) {
//         if (line.contains(subMessage)) {
//             super.write(line);
//         }
//     }
// }
// 
// Path: src/test/java/net/bourgau/philippe/concurrency/kata/NullOutput.java
// public class NullOutput implements Output {
//     @Override
//     public void write(String line) {
//     }
// }


import net.bourgau.philippe.concurrency.kata.CountingOutput;
import net.bourgau.philippe.concurrency.kata.Implementations;
import net.bourgau.philippe.concurrency.kata.MatchingOutput;
import net.bourgau.philippe.concurrency.kata.NullOutput;
import net.bourgau.philippe.concurrency.kata.common.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import static com.jayway.awaitility.Awaitility.await;
import static com.jayway.awaitility.Duration.FIVE_SECONDS;
                    observer.announce(Message.login(LOGIN_MESSAGE));
                }
            }
        };
    }

    private void enterObserver() throws Exception {
        observer.enter();
        await().until(messageCountIs(1));
        countingOutput.reset();
    }

    private Client newClient(int i) {
        return implementation.newClient("Client#" + i, chatRoom, new NullOutput());
    }

    private Runnable messageCountIs(final int expectedMessageCount) {
        return new Runnable() {
            @Override
            public void run() {
                countingOutput.assertMessageCount().isEqualTo(expectedMessageCount);
            }
        };
    }

    private void runThreads() throws InterruptedException {
        for (Thread thread : threads) {
            thread.start();
        }

// Path: src/test/java/net/bourgau/philippe/concurrency/kata/CountingOutput.java
// public class CountingOutput implements Output {
//     private AtomicInteger messageCount = new AtomicInteger();
// 
//     @Override
//     public void write(String line) {
//         messageCount.incrementAndGet();
//     }
// 
//     public void reset() {
//         messageCount.set(0);
//     }
// 
//     public IntegerAssert assertMessageCount() {
//         return Assertions.assertThat(messageCount.get());
//     }
// 
// }
// 
// Path: src/test/java/net/bourgau/philippe/concurrency/kata/Implementations.java
// public class Implementations {
// 
//     public static Collection<Object[]> all() {
//         return simple(
//                 new MonoThread(),
//                 new UnboundedSync(),
//                 new UnboundedConcurrent(),
//                 new BoundedConcurrent(),
//                 new BoundedFineGrained(),
//                 new ActorsRealThreads(),
//                 new ActorsGreenThreads(),
//                 new CSP());
//     }
// 
//     public static Collection<Object[]> bounded() {
//         return simple(
//                 new MonoThread(),
//                 new BoundedConcurrent(),
//                 new BoundedFineGrained(),
//                 new ActorsGreenThreads(),
//                 new CSP());
//     }
// 
//     public static Collection<Object[]> multithreaded() {
//         return simple(
//                 new UnboundedSync(),
//                 new UnboundedConcurrent(),
//                 new BoundedConcurrent(),
//                 new BoundedFineGrained(),
//                 new ActorsRealThreads(),
//                 new ActorsGreenThreads(),
//                 new CSP());
//     }
// 
//     public static Collection<Object[]> simple(Object... values) {
//         ArrayList<Object[]> parameters = new ArrayList<>();
//         for (Object value : values) {
//             parameters.add(new Object[]{value});
//         }
//         return parameters;
//     }
// 
//     public static Collection<Object[]> complex(Object[]... objects) {
//         ArrayList<Object[]> parameters = new ArrayList<>();
//         for (Object[] object : objects) {
//             parameters.add(object);
//         }
//         return parameters;
//     }
// 
//     public static Collection<Object[]> crossProduct(Collection<Object[]> xs, Collection<Object[]> ys) {
//         ArrayList<Object[]> parameters = new ArrayList<>();
//         for (Object[] x : xs) {
//             for (Object[] y : ys) {
//                 parameters.add(ArrayUtils.addAll(x, y));
//             }
//         }
//         return parameters;
//     }
// }
// 
// Path: src/test/java/net/bourgau/philippe/concurrency/kata/MatchingOutput.java
// public class MatchingOutput extends CountingOutput {
//     private final String subMessage;
// 
//     public MatchingOutput(String subMessage) {
//         this.subMessage = subMessage;
//     }
// 
//     @Override
//     public void write(String line) {
//         if (line.contains(subMessage)) {
//             super.write(line);
//         }
//     }
// }
// 
// Path: src/test/java/net/bourgau/philippe/concurrency/kata/NullOutput.java
// public class NullOutput implements Output {
//     @Override
//     public void write(String line) {
//     }
// }


// Path: src/test/java/net/bourgau/philippe/concurrency/kata/tests/ConcurrencyTest.java
import net.bourgau.philippe.concurrency.kata.CountingOutput;
import net.bourgau.philippe.concurrency.kata.Implementations;
import net.bourgau.philippe.concurrency.kata.MatchingOutput;
import net.bourgau.philippe.concurrency.kata.NullOutput;
import net.bourgau.philippe.concurrency.kata.common.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import static com.jayway.awaitility.Awaitility.await;
import static com.jayway.awaitility.Duration.FIVE_SECONDS;

                    observer.announce(Message.login(LOGIN_MESSAGE));
                }
            }
        };
    }

    private void enterObserver() throws Exception {
        observer.enter();
        await().until(messageCountIs(1));
        countingOutput.reset();
    }

    private Client newClient(int i) {
        return implementation.newClient("Client#" + i, chatRoom, new NullOutput());
    }

    private Runnable messageCountIs(final int expectedMessageCount) {
        return new Runnable() {
            @Override
            public void run() {
                countingOutput.assertMessageCount().isEqualTo(expectedMessageCount);
            }
        };
    }

    private void runThreads() throws InterruptedException {
        for (Thread thread : threads) {
            thread.start();
        }

        for (Thread thread : threads) {