/**
   * Returns the set of songs which are proposed by the agents.
   *
   * @param proposalTimes
   * @return The songs
   */
  private List<BaseSong<BaseArtist, BaseAlbum>> getProposedSongs(AgentsTiming proposalTimes) {
    Set<IAgent> agents = agentManager.getAgents();

    // Using set here to ensure no duplicate entries
    Set<BaseSong<BaseArtist, BaseAlbum>> proposedSongs =
        new HashSet<BaseSong<BaseArtist, BaseAlbum>>(SONG_PROPOSAL_USAGE_COUNT * agents.size());
    for (IAgent agent : agents) {
      throwIfAborted();

      StopWatch stopWatch = StopWatch.start();

      List<BaseSong<BaseArtist, BaseAlbum>> agentProposals =
          new ArrayList<BaseSong<BaseArtist, BaseAlbum>>(agent.suggestSongs(SONG_PROPOSAL_COUNT));
      // Get #SONG_PROPOSAL_USAGE_COUNT items of them at random
      while (agentProposals.size() > SONG_PROPOSAL_USAGE_COUNT) {
        agentProposals.remove(RandomProvider.getRandom().nextInt(agentProposals.size()));
      }
      proposedSongs.addAll(agentProposals);

      proposalTimes.addAgentTiming(agent, stopWatch.stop());
    }

    return new ArrayList<BaseSong<BaseArtist, BaseAlbum>>(proposedSongs);
  }
  /**
   * Returns the average votes of the agents for the given songs. The votes gets weighted by the
   * importance of the agents.
   *
   * @param proposedSongs The proposed songs
   * @param agentsVotes (out) The votes for all the songs by agent
   * @param voteTimes
   * @return The weighted mean votes for the songs
   */
  private Map<BaseSong<BaseArtist, BaseAlbum>, Double> getVotesForProposed(
      List<BaseSong<BaseArtist, BaseAlbum>> proposedSongs,
      Map<IAgent, List<SongVote>> agentsVotes,
      AgentsTiming voteTimes) {

    Set<IAgent> agents = agentManager.getAgents();

    // Init the ratings list & the weight sum table
    Map<BaseSong<BaseArtist, BaseAlbum>, Double> votes =
        new HashMap<BaseSong<BaseArtist, BaseAlbum>, Double>();
    Map<BaseSong<BaseArtist, BaseAlbum>, Double> weightSums =
        new HashMap<BaseSong<BaseArtist, BaseAlbum>, Double>(agents.size());
    for (BaseSong<BaseArtist, BaseAlbum> song : proposedSongs) {
      votes.put(song, 0.0d);
      weightSums.put(song, 0.0d);
    }

    // Calculate the song ratings
    for (IAgent agent : agents) {
      throwIfAborted();

      // Get the agent vote
      List<SongVote> agentVotes;

      StopWatch stopWatch = StopWatch.start();
      {
        agentVotes = agent.vote(Collections.unmodifiableList(proposedSongs));
      }
      voteTimes.addAgentTiming(agent, stopWatch.stop());

      // Fill up the list to ensure that a vote for all songs exist
      List<BaseSong<BaseArtist, BaseAlbum>> noVote =
          new ArrayList<BaseSong<BaseArtist, BaseAlbum>>(proposedSongs);
      for (int i = 0; i < agentVotes.size(); ) {
        final SongVote vote = agentVotes.get(i);
        int idx = noVote.indexOf(vote.getSong());
        if (idx >= 0) {
          noVote.remove(idx);
          ++i;
        } else {
          // No vote for this song is required
          agentVotes.remove(i);
        }
      }
      for (BaseSong<BaseArtist, BaseAlbum> song : noVote) {
        agentVotes.add(new SongVote(song, 0.0f));
      }
      agentsVotes.put(agent, agentVotes);

      for (SongVote vote : agentVotes) {
        final BaseSong<BaseArtist, BaseAlbum> song = vote.getSong();

        double oldRating = votes.get(song);
        double agentWeight = getAgentWeights().get(agent);
        double oldW = weightSums.get(song);
        double newW = oldW + agentWeight;
        double newRating =
            (oldRating * oldW + vote.getVote() * agentWeight)
                / newW; // Continuous, weighted mean calculation
        votes.put(song, newRating);
        weightSums.put(song, newW);
      }
    }

    return votes;
  }