@Override
  public void run() {
    try {
      throwIfAborted();

      TimingLogger timingLogger = new TimingLogger(getTag(), "next");

      // Begin an immediate transaction
      dbDataPortal.beginTransaction();
      timingLogger.addSplit("Transaction start");

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

      // Enter a fake rating entry to calculate the next song on the predicted future
      Integer meSongId = null;
      if (currentSong != null) {
        double fractionPlayed = (getCalculationCase() == Case.Positive) ? 0.66d : 0.33d;
        playLog.writeToPlayLog(
            new Date(),
            new PlaylistSong<BaseArtist, BaseAlbum>(currentSong, SongSource.SMART_SHUFFLE),
            true,
            (int) (currentSong.getDuration() * fractionPlayed));

        try {
          meSongId = otherDataProvider.getMusicExplorerSongId(currentSong);
        } catch (DataUnavailableException e) {
          Log.w(getTag(), e);
        }
      }
      timingLogger.addSplit("Rating entry");

      // Get the song proposals
      AgentsTiming proposalTimes = new AgentsTiming();
      {
        proposedSongs = getProposedSongs(proposalTimes);
      }
      timingLogger.addSplit("Proposals");

      // Get the agent votes for the proposals
      Map<IAgent, List<SongVote>> agentVotes;
      AgentsTiming voteTimes = new AgentsTiming();
      {
        agentVotes = new HashMap<IAgent, List<SongVote>>();
        songVotes = getVotesForProposed(proposedSongs, agentVotes, voteTimes);
      }
      timingLogger.addSplit("Votes");

      // Fill the song->agentVotes map
      agentVotesBySong =
          new HashMap<BaseSong<BaseArtist, BaseAlbum>, Map<IAgent, Float>>(proposedSongs.size());
      for (BaseSong<BaseArtist, BaseAlbum> song : proposedSongs) {
        agentVotesBySong.put(song, new HashMap<IAgent, Float>(agents.size()));
      }
      for (Map.Entry<IAgent, List<SongVote>> entry : agentVotes.entrySet()) {
        for (SongVote vote : entry.getValue()) {
          Map<IAgent, Float> agentVote = agentVotesBySong.get(vote.getSong());
          agentVote.put(entry.getKey(), vote.getVote());
        }
      }

      // Add long time not listened songs
      addLongTimeNotListenedSongProposals();

      /*// Order the next songs by rating
      Collections.sort(proposedSongs, new Comparator<BaseSong<BaseArtist, BaseAlbum>>() {

      	@Override
      	public int compare(BaseSong<BaseArtist, BaseAlbum> left, BaseSong<BaseArtist, BaseAlbum> right) {
      		Double leftVote = songVotes.get(left);
      		Double rightVote = songVotes.get(right);

      		return rightVote.compareTo(leftVote);
      	}
      });*/

      // Randomize the song proposals by their vote
      proposedSongs = getShuffledSongsAtWeightedRandom(songVotes);

      // FIXME @sämy: this is only for debugging
      StringBuffer proposalsSb = new StringBuffer();
      proposalsSb.append("song ident:overall vote");
      for (IAgent agent : agents) {
        proposalsSb.append(':');
        proposalsSb.append(agent.getIdentifier());
        proposalsSb.append(String.format(" (%.2f)", agentWeights.get(agent)));
      }
      proposalsSb.append('|');
      for (BaseSong<BaseArtist, BaseAlbum> song : proposedSongs) {
        proposalsSb.append(song); // song ident
        proposalsSb.append(':');
        proposalsSb.append(String.format("%.3f", songVotes.get(song))); // overall vote

        Map<IAgent, Float> agentVotes2 = agentVotesBySong.get(song);
        for (IAgent agent : agents) {
          proposalsSb.append(String.format(":%.3f", agentVotes2.get(agent)));
        }
        proposalsSb.append('|');
      }
      Log.d(getTag(), "Proposals: " + proposalsSb.toString());
      // end debug only

      // never ever call dbDataPortal.setTransactionSucessful() !!
      dbDataPortal
          .endTransaction(); // ROLLBACK the transaction. Things below that line get written
                             // persistently to the db!

      // Write a log entry about this run
      NextSongCalculationLogEntry.Builder log =
          NextSongCalculationLogEntry.createInstance()
              .setPredictionCase(getCalculationCase())
              .setCurrentSong((meSongId != null) ? meSongId : 0)
              .setProposalTimes(proposalTimes)
              .setVoteTimes(voteTimes);
      logManager.addLogEntry(log.build());

      // Write the time used in the different parts to the debug log
      timingLogger.dumpToLog();
    } catch (AbortException e) {
      // Just ignore it, we want to land here
    } finally {
      if (dbDataPortal.inTransaction()) {
        dbDataPortal.endTransaction(); // never ever call dbDataPortal.setTransactionSucessful() !!
      }
    }
  }