package apostov;

import static com.google.common.collect.Iterators.concat;
import static com.google.common.collect.Maps.toMap;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;

import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.apache.commons.math3.fraction.Fraction;
import org.apache.commons.math3.util.Combinations;
import org.apache.commons.math3.util.CombinatoricsUtils;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;

public class HandResultEnumerator {

	public ImmutableMap<HolecardHand, Fraction> enumerateBoardsAndMeasureWins(final ImmutableList<HolecardHand> candidates) {
		return enumerateBoardsAndMeasureWins(candidates, ImmutableList.of());
	}
	
	public ImmutableMap<HolecardHand, Fraction> enumerateBoardsAndMeasureWins(
			final ImmutableList<HolecardHand> candidates,
			final ImmutableList<Card> partialBoard)
	{
		assert partialBoard.isEmpty() || Board.ACCEPTABLE_PARTIAL_BOARD_SIZES.contains(partialBoard.size());
		
		final ImmutableList<Card> deck = new DeckFactory().newDeck(
				Stream.concat(
						candidates.stream()
							.map(HolecardHand::getHolecardsAsList)
							.flatMap(Collection::stream),
						partialBoard.stream())
					.collect(toSet())
		);
		
		final ShowdownEvaluator evaluator = new ShowdownEvaluator();
		final int numberOfUnknownCards = 5 - partialBoard.size();
		final int numberOfTestedBoards = (int) CombinatoricsUtils.binomialCoefficient(deck.size(), numberOfUnknownCards);
		
		final ForkJoinPool pool = new ForkJoinPool(Runtime.getRuntime().availableProcessors());
		final Callable<? extends Map<HolecardHand, Fraction>> successChanceComputationTask = 
			() -> StreamSupport.stream(new Combinations(deck.size(), numberOfUnknownCards).spliterator(), true)
				.map(a -> stream(a).mapToObj(i -> deck.get(i)).collect(toList()))
				.map(pickedCards -> {
					assert pickedCards.size() == numberOfUnknownCards;
					final Iterator<Card> fullBoard = concat(partialBoard.iterator(), pickedCards.iterator());
					return Board.from(fullBoard);
				})
				.map(b -> evaluator.evaluateShowdown(candidates, b))
				.map(winningCandidates -> toMap(winningCandidates, c -> new Fraction(1, numberOfTestedBoards * winningCandidates.size())))
				.map(Map::entrySet)
				.flatMap(Collection::stream)
				.collect(Collectors.toConcurrentMap(
						Map.Entry::getKey,
						Map.Entry::getValue,
						(x, y) -> x.add(y)));
		
		final Map<HolecardHand, Fraction> overallSuccessChanceByHand;
		try {
			overallSuccessChanceByHand = pool.submit(successChanceComputationTask).get();
		} catch (final InterruptedException | ExecutionException e) {
			throw new RuntimeException(e);
		} finally {
			pool.shutdown();
		}
		
		/* In the case where one or several candidates never win, we still want them to
		 * have a Fraction representing their odds of winnings (i.e. Fraction.ZERO)
		 * instead of null */
		for (final HolecardHand candidate : candidates)
			if (overallSuccessChanceByHand.containsKey(candidate) == false)
				overallSuccessChanceByHand.put(candidate, Fraction.ZERO);
		
		return ImmutableMap.copyOf(overallSuccessChanceByHand);
	}
	
}

@@ -121,24 +121,66 @@
 aps.toMap;%0D%0A
+import static java.lang.Math.toIntExact;%0D%0A
 import stati
@@ -1807,35 +1807,36 @@
 ize();%0D%0A%09%09final 
-int
+long
  numberOfTestedB
@@ -1844,16 +1844,16 @@
 ards
- = (int)
+AsLong =
  Com
@@ -2117,16 +2117,30 @@
 %09%09%09() -%3E
+ %7B%0D%0A%09%09%09%09return
  StreamS
@@ -2227,24 +2227,25 @@
  true)%0D%0A%09%09%09%09
+%09
 .map(a -%3E st
@@ -2295,24 +2295,25 @@
 toList()))%0D%0A
+%09
 %09%09%09%09.map(pic
@@ -2332,16 +2332,17 @@
 %7B%0D%0A%09%09%09%09%09
+%09
 assert p
@@ -2385,16 +2385,17 @@
 Cards;%0D%0A
+%09
 %09%09%09%09%09fin
@@ -2483,24 +2483,25 @@
 r());%0D%0A%09%09%09%09%09
+%09
 return Board
@@ -2527,12 +2527,14 @@
 %09%09%09%09
+%09
 %7D)%0D%0A
+%09
 %09%09%09%09
@@ -2587,24 +2587,25 @@
 s, b))%0D%0A%09%09%09%09
+%09
 .map(winning
@@ -2664,16 +2664,27 @@
 tion(1, 
+toIntExact(
 numberOf
@@ -2695,16 +2695,22 @@
 edBoards
+AsLong
  * winni
@@ -2731,22 +2731,24 @@
 ize())))
+)
 %0D%0A%09%09%09%09
+%09
 .map(Map
@@ -2756,24 +2756,25 @@
 :entrySet)%0D%0A
+%09
 %09%09%09%09.flatMap
@@ -2795,24 +2795,25 @@
 tream)%0D%0A%09%09%09%09
+%09
 .collect(Col
@@ -2838,16 +2838,17 @@
 ntMap(%0D%0A
+%09
 %09%09%09%09%09%09Ma
@@ -2867,24 +2867,25 @@
 Key,%0D%0A%09%09%09%09%09%09
+%09
 Map.Entry::g
@@ -2900,16 +2900,17 @@
 %0D%0A%09%09%09%09%09%09
+%09
 (x, y) -
@@ -2920,24 +2920,31 @@
 .add(y)));%0D%0A
+%09%09%09%7D;%0D%0A
 %09%09%0D%0A%09%09final 

Use a safer version of a long-to-int downcasting