/**
   * Constructs a new statistics data transfer object for the specified {@link Game}.
   *
   * @param game game to get statistics for
   * @param user user to get statistics for
   */
  public GameBetStatisticsDTO(Game game, User user) {
    this();

    List<Bet> bets = game.getBets();
    numberOfBets = bets.size();

    Map<Integer, UserBet> userBetsMap =
        user != null
            ? new ArrayList<>(user.getBets())
                .stream()
                .collect(Collectors.toMap(b -> b.getBet().getId(), b -> b))
            : null; // BUGFIX: new ArrayList<>(...) needed in eclipselink < 2.7
    List<Map<Bet, BigDecimal>> checkGroups = new ArrayList<>();

    for (Bet bet : bets) {
      evaluated &= bet.getOccurred() != null;

      BigDecimal betPotentialGain = BigDecimal.ZERO;
      List<UserBet> userBets =
          user == null
              ? bet.getUserBets()
              : userBetsMap.containsKey(bet.getId())
                  ? Arrays.asList(userBetsMap.get(bet.getId()))
                  : Arrays.asList();
      numberOfUserBets += userBets.size();

      for (UserBet userBet : userBets) {
        betsStake = betsStake.add(userBet.getStake());
        betPotentialGain = betPotentialGain.add(userBet.getPotentialGain());

        if (Boolean.TRUE.equals(userBet.getBet().getOccurred()))
          betsAmountWon = betsAmountWon.add(userBet.getGain());
        else if (Boolean.FALSE.equals(userBet.getBet().getOccurred()))
          betsAmountLost = betsAmountLost.add(userBet.getGain().negate());
      }

      betsPotentialGain = betsPotentialGain.add(betPotentialGain);

      boolean addedToCheck = false;
      for (Map<Bet, BigDecimal> checkGroup : checkGroups)
        if (addedToCheck = checkGroup.keySet().stream().noneMatch(b -> compatible(bet, b))) {
          checkGroup.put(bet, betPotentialGain);
          break;
        }
      if (!addedToCheck) checkGroups.add(MapBuilder.single(bet, betPotentialGain));
    }

    betsAmountSum = betsAmountWon.subtract(betsAmountLost);

    betsPotentialLoss =
        checkGroups
            .stream()
            .map(g -> g.values().stream().max((a, b) -> a.compareTo(b)).get())
            .reduce((a, b) -> a.add(b))
            .orElse(betsPotentialLoss);
  }
  private boolean compatible(Bet bet1, Bet bet2) {

    Map<BetType, BiPredicate<Bet, Bet>> subMap;
    BiPredicate<Bet, Bet> check;

    if ((subMap = COMPATIBLE_CHECKS.getOrDefault(bet1.getType(), null)) != null
        && (check = subMap.getOrDefault(bet2.getType(), null)) != null)
      return check.test(bet1, bet2);
    else if ((subMap = COMPATIBLE_CHECKS.getOrDefault(bet2.getType(), null)) != null
        && (check = subMap.getOrDefault(bet1.getType(), null)) != null)
      return check.test(bet2, bet1);
    else return true;
  }