models.Room.java Source code

Java tutorial

Introduction

Here is the source code for models.Room.java

Source

/*
 * Copyright (c) 2011 - 2016 by  Edward J. Becker
 *
 * This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package models;

import com.google.common.collect.Iterables;
import com.google.gson.annotations.Expose;
import controllers.*;
import jobs.YoutubeJob;
import play.*;
import play.db.jpa.*;

//import javax.persistence.*;
import java.util.*;
import java.util.stream.Collectors;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.PostLoad;
import javax.persistence.Transient;
import play.cache.Cache;
import javax.persistence.*;

import org.hibernate.annotations.Type;

/**
 *
 * @author Edward Becker
 */
@Entity
@Table(name = "room", uniqueConstraints = @UniqueConstraint(columnNames = { "name" }))
public class Room extends Model {

    public static final long serialVersionUID = 7997607919719264459L;
    @Transient
    public final static byte ROOM_TYPE_REPUBLIC = 0; ////"Republic";    // Every user gets a chance to play [default]
    @Transient
    public final static byte ROOM_TYPE_DEMOCRACY = 1; //"Democracy";  // Users vote up and down songs in the queue
    @Transient
    public final static byte ROOM_TYPE_MONARCHY = 2; // "Monarchy";    // Only owners and moderators can play music
    @Transient
    public final static byte ROOM_TYPE_DICTATORSHIP = 3; // "Dictatorship"; // Only owners can play music
    @Transient
    public final static byte ROOM_MODE_RDIO = 0;
    @Transient
    public final static byte ROOM_MODE_YOUTUBE = 1;
    @Transient
    //    @Expose
    public final static String[] roomTypes = { "Republic", "Democracy", "Monarchy", "Dictatorship" };
    @Expose
    public int roomType = 0;
    @Transient
    @Expose
    public String roomTypeAsString; // = "Republic";
    @ManyToOne
    public User currentUser; // Current user playing a song
    @OneToOne
    @Expose
    public User owner; // Owner of the room
    @OneToOne
    public User moderator; // Moderator of the room
    @Expose
    @Column(nullable = false)
    public String name; // name of room
    @Expose
    public String slugifyName; // Slugify name of Room
    // Music
    public Date trackStartTime; // Time the track started
    @OneToOne
    @Expose
    public LibraryItem currentPlayingLibrary;
    @OneToMany(mappedBy = "room", cascade = CascadeType.ALL)
    public List<RecentTrack> RecentTracks;
    public Date createdAt; // Time room was created
    public Long lastPlayingUserId;
    @OneToOne
    public LibraryItem queuedSong;
    public boolean skipRequested = false;
    public Date lastJobTime = new Date();
    public Date lastSongJobTime = new Date();
    public boolean idle = false;
    @Expose
    @Lob
    @Type(type = "org.hibernate.type.TextType")
    public String recommendationLogs;
    // The following two items (albumRating and artistRating) are used when pulling up the room in the 
    // roomview to make it easy to rate the album and artist from the room view.
    @Transient
    @Expose
    public AlbumRating albumRating;
    @Transient
    @Expose
    public ArtistRating artistRating;
    @Expose
    public float roomRatingAdjustment = 100.0f; // Multiplier that adjusts how important the room rating is ; 20f.0
    @Expose
    public float roomRecommendationNumberAdjustment = 1f; // Indicates how important the recommendation # is //.5f; //.05f; // 5.0f; // 50.0f; // 5.0f;
    @Expose
    public float roomRecentlyPlayedPenalty = 100.0f; // 10-100? Divisor for rating penalizing number of plays within last 90 days
    @Expose
    public static float roomDaysToIncludeInPenalty = 120f;
    @Expose
    public float roomLastXDaysCountPenalty = 100000.0f;
    @Expose
    public int hoursToNotRepeatSong = 72;
    @Expose
    public boolean allowChristmasMusic = false;
    @Expose
    public int roomArtistPlays = 10;
    @Expose
    public int roomArtistPlayHours = 3;
    @Expose
    public int roomAlbumPlays = 10;
    @Expose
    public int roomAlbumPlayHours = 3;
    @Expose
    public int roomMinimumRating = 40;
    @Expose
    @Transient
    public List<User> usersInRoomModel = null;
    @Expose
    @Transient
    public long position = 0; // Position in room

    @Expose
    @Transient
    public Boolean isOwner; // Is user owner of this room? Convienance for view
    @Expose
    @Transient
    public Long tokens; // Convenienance until we use a user view
    @Expose
    @Transient
    public int numOfUsersInRoom;

    @Expose
    public int roomMode = 0; // 0 - RDIO  1-YOUTUBE

    @Expose
    public int privacyType = 0; // 0 -> Public  1 -> Requires room code

    @Expose
    public String privacyCode;

    @Expose
    public String banner;

    @Expose
    public String seedQuery = "Dave Matthews Band Crush"; // If room has nothing going on, seed with this song

    //    @Expose
    //    @Transient
    //    public boolean youTubeMode = true;

    /**
     *
     */
    @PostLoad
    public void init() {

        checkSlugify();
    }

    public void checkSlugify() {
        if (slugifyName == null && name != null) {
            slugifyName = play.templates.JavaExtensions.slugify(name);
            Logger.info("Slugified " + name + " / " + slugifyName);
            save();
        }
    }

    public Long roomRatingForTrackNoConvertNoIgnoreUser(Track track) {
        return Room.roomRatingForTrack(this, track, false, null).rating;
    }

    //    public final static byte roomRatingForTrackFast(Room room, Track track, boolean convert, User userToIgnore) {
    //        long numOfUsersWhoRated = 0;
    //        long totalratingpoints = 0;
    //        List<User> roomUsers = room.getUsersInRoom();
    //        LibraryItem litem;
    //        byte ratingNum = -1;
    //        ArtistRating artistRating;
    //        for (User roomUser : roomUsers) {
    //
    //            if (userToIgnore != null && roomUser.id.equals(userToIgnore.id) && roomUsers.size() > 1) {
    //                continue;
    //            }
    //            // Find track in users library
    //            if (room.currentPlayingLibrary == null) {
    //                Logger.info("Null current playing library");
    //                return -01;
    //            }
    //
    //
    //            litem = LibraryItem.find("byUserAndTrack", roomUser, track).first();
    //            //Logger.info("Calcing for: " + roomUser.firstName);
    //            // @TODO look at this. is rating coming throughly as 0?
    //            // @TODO I think this can be fixed and this todo can be removed (I changed it from 0 to -1)
    //            if (litem != null && litem.rating > -1) {
    //                numOfUsersWhoRated++;
    //                totalratingpoints = totalratingpoints + litem.rating;
    //                //Logger.info("Rating: " + litem.rating);
    //            } else {
    //                // See if there is an album rating
    //                //Logger.info(roomUser.firstName + " :ALBUMRATINGUSER:" + track.artist + "/" + track.album_name);
    //                AlbumRating albumRating = null;
    //                if (track.album_name != null) {
    //                    AlbumRating.find("byUserAndArtist_NameIlikeAndAlbum_NameIlike", roomUser, track.artist, track.album_name).first();
    //                }
    //                if (albumRating != null && albumRating.rating > -1) {
    //                    numOfUsersWhoRated++;
    //                    totalratingpoints = totalratingpoints + albumRating.rating;
    //                    //Logger.info("Albumrating:" + albumRating.rating);
    //                } else {
    //                    // See if there is an artist rating
    //                    artistRating = ArtistRating.find("byUserAndArtistNameIlike", roomUser, track.artist).first();
    //                    if (artistRating != null && artistRating.rating > -1) {
    //                        numOfUsersWhoRated++;
    //                        totalratingpoints = totalratingpoints + artistRating.rating;
    //                       // Logger.info("ArtistRating: " + artistRating.rating);
    //                    } else {
    //                        // @TODO TEMPORARY LOVE EVERYTHING HACK
    //                        // THIS WAS A HORRIBLE BUG!  DONT EVER DO THIS!
    //                     //   numOfUsersWhoRated++;
    //                   //     totalratingpoints = totalratingpoints + 100;
    //                    }
    //                }
    //            }
    //        }
    //        //Logger.info("Num of users who rated:" + numOfUsersWhoRated);
    //        if (numOfUsersWhoRated > 0) {
    //            //Logger.info("Calculated a room rating of: " + (totalratingpoints / numOfUsersWhoRated));
    //
    //
    //            // Convert to a 20,50, 80, 100
    //
    //            ratingNum = (byte) (totalratingpoints / numOfUsersWhoRated);
    //
    //            if (convert) {
    //                if (ratingNum > 90) {
    //                    ratingNum = 100;
    //                } else if (ratingNum > 75) {
    //                    ratingNum = 80;
    //                } else if (ratingNum > 49) {
    //                    ratingNum = 50;
    //                } else if (ratingNum > 15) {
    //                    ratingNum = 20;
    //                } else {
    //                    ratingNum = 0;
    //                }
    //            }
    //        }
    //        return ratingNum;
    //    }

    public final static Rating roomRatingForTrack(Room room, Track track, boolean convert, User userToIgnore) {
        long numOfUsersWhoRated = 0;
        long totalratingpoints = 0;
        List<User> roomUsers = room.getUsersInRoom();
        LibraryItem litem;
        long ratingNum = -1L;
        ArtistRating artistRating;
        for (User roomUser : roomUsers) {

            if (userToIgnore != null && roomUser.id.equals(userToIgnore.id) && roomUsers.size() > 1) {
                continue;
            }
            // Find track in users library
            if (room.currentPlayingLibrary == null) {
                Logger.info("Null current playing library");
                Rating rating = new Rating();
                return rating;
            }

            litem = LibraryItem.find("byUserAndTrack", roomUser, track).first();

            if (litem != null) {
                // Logger.info("Album name:" + track.album_name + litem.album);
            }
            if (litem != null && litem.rating > -1) {
                numOfUsersWhoRated++;
                totalratingpoints = totalratingpoints + litem.rating;
            } else {
                // See if there is an album rating
                AlbumRating albumRating = null;
                if (track.album_name != null) {
                    //Logger.info("Looking for album rating of " + track.album_name);
                    albumRating = AlbumRating
                            .find("byUserAndArtist_NameAndAlbum_Name", roomUser, track.artist, track.album_name)
                            .first();
                }

                if (albumRating == null) {
                    //Logger.info("Did not find album rating " + track.album_name);
                } else {
                    if (albumRating.rating != -1) {
                        //Logger.info("Found album " + albumRating.album_name + " for user " + albumRating.user.firstName + " rating with " + albumRating.rating);
                    }
                }
                if (albumRating != null && albumRating.rating > -1) {
                    numOfUsersWhoRated++;
                    totalratingpoints = totalratingpoints + albumRating.rating;
                    // Logger.info("Rating for album " + albumRating.album_name + " looking for track " + track.title + " is " + albumRating.rating);
                } else {
                    // See if there is an artist rating
                    artistRating = ArtistRating.find("byUserAndArtistName", roomUser, track.artist).first();
                    if (artistRating != null && artistRating.rating > -1) {
                        //Logger.info("Using artist rating for " + track.title + "/" + track.artist);
                        numOfUsersWhoRated++;
                        totalratingpoints = totalratingpoints + artistRating.rating;
                    }
                    //                    else {
                    //                        // @TODO TEMPORARY LOVE EVERYTHING HACK
                    //                        numOfUsersWhoRated++;
                    //                        totalratingpoints = totalratingpoints + 100;
                    //                    }
                }
            }
        }
        //Logger.info("Num of users who rated:" + numOfUsersWhoRated); // @TODO maybe add this to room data so that we can display it in the room's lobby?
        if (numOfUsersWhoRated > 0) {
            //Logger.info("Calculated a room rating of: " + (totalratingpoints / numOfUsersWhoRated) + " for " + track.title);

            // Convert to a 20,50, 80, 100

            ratingNum = (int) (totalratingpoints / numOfUsersWhoRated);

            if (convert) {
                if (ratingNum > 90) {
                    ratingNum = 100;
                } else if (ratingNum > 75) {
                    ratingNum = 80;
                } else if (ratingNum > 49) {
                    ratingNum = 50;
                } else if (ratingNum > 15) {
                    ratingNum = 20;
                } else {
                    ratingNum = 0;
                }
            }
        }

        Rating ratingObj = new Rating();
        ratingObj.rating = ratingNum;
        ratingObj.numOfUsersWhoRated = numOfUsersWhoRated;
        return ratingObj;
    }

    //    List<User> getNonLurkersInRoom() {
    //
    //        long lurktime = new Date().getTime() - (1000 * 60 * 60 * 10);
    //
    //        List<User> users = User.find("room = ? and IsOnline= ? and lastNonLurkAction > ? and  Order By Id asc", this, true, new Date(lurktime)).fetch();
    //
    //        return users;
    //    }

    private static List<User> getUsersInRoom(Room room, boolean needPlayableTrack) {

        List<User> users = null;

        if (!needPlayableTrack) {
            users = User.find("room = ?1 and IsOnline= ?2 Order By Id asc", room, true).fetch();
        } else {
            users = User
                    .find("room = ?1 and IsOnline= ?2 and noPlayableTrack = ?3 Order By Id asc", room, true, false)
                    .fetch();
        }
        return users;
    }

    public String displayUsersInRoom() {
        List<User> users = getUsersInRoom();

        String userDisplayString = "";

        int i = 0;
        for (User user : users) {
            userDisplayString = userDisplayString + user.firstName;
            i++;
            if (i < users.size()) {
                userDisplayString = userDisplayString + ",";
            }

        }
        return userDisplayString;
    }
    //
    //    public List<User> getUsersInRoom(boolean needPlayabaleTrack) {
    //
    //        return getUsersInRoom(this, needPlayabaleTrack);
    //
    //    }

    public List<User> getUsersInRoom() {

        return getUsersInRoom(this, false);

    }

    /*
     * Puts a user into a room
     *
     */
    public void enterRoom(User user) {
        Logger.info(user.firstName + " attempting to enter room " + this.name);

        if (user.room != null && user.room.id == this.id) {
            Logger.info("User already in room " + this.name);
            return;
        }
        user.noPlayableTrack = false;
        //ChatRoom.get().join(user.firstName, this);

        // If user is currently in a different room, remove them

        if (user.room != null) {
            Logger.info(user.firstName + " left " + this.name + ".");
        } else {
            Logger.info("From a null room cometh " + user.firstName);
        }
        //ChatRoom.get().leave(user.firstName, this);
        user.lastRoom = this;
        user.slotInRoom = -1;

        user.isOnline = true;// @TODO this should be instantiated from user controller not room
        user.room = this;

        if (name.equalsIgnoreCase(user.nickname)) {
            if (moderator == null || !moderator.nickname.equalsIgnoreCase(user.nickname)) {
                Logger.info("Setting moderator of room " + name + " to user " + user.nickname);
            }
        }
        user.lastAccess = new Date();
        Logger.info(user.firstName + " joined " + this.name + ". Slot[" + user.slotInRoom + "]");
        user.save();

        idle = false;

        if (currentPlayingLibrary == null && retrieveRoomQueue().items.size() < 1) {
            LibraryManager.searchAndQueueItem(user.nickname, this, seedQuery, user);
        }
        Logger.info("Saving room");
        this.save();
        Logger.info("Saved room");
    }

    public void leaveRoom(User user) {

        // Ignore if user is not already in this room

        if (!user.room.equals(this)) {
            return;
        }

        Logger.info(user.firstName + " left " + this.name + ".");
        ChatRoom.get().leave(user.firstName, this);
        user.lastRoom = this;
        user.slotInRoom = -1;
        user.room = null;
        user.save();
        this.save();
    }

    public String rating_checked_tag(long tag_rating) {
        long roomrating = roomRating(this, null);
        if (roomrating == -1 || tag_rating > roomrating) {
            return "unchecked";
        }
        return "checked";
    }

    public Long rawRoomRating() {
        Track track = currentPlayingLibrary.track;
        return roomRatingForTrack(this, track, false, null).rating;
    }

    public static Long roomRating(Room room, User userToIgnore) {
        Track track = room.currentPlayingLibrary.track;
        return roomRatingForTrack(room, track, true, userToIgnore).rating;
    }

    public Date trackLastPlayed(Track track) {

        // @TODO this should be specifically looking for the particular track id, but let's get real...

        // Actually we don't need this, because we are going to look for it by each user below anyway.
        // Although, this is specific to the room. But who cares, lets save some time
        // ugh damn repeats, let's put it back in
        RecentTrack lastPlayed = RecentTrack
                .find("room = ?1 and track.title like ?2 and playedAt is not null order by playedAt desc", this,
                        "%" + Auricle.stripTitleMetaData(track.title) + "%")
                .first();
        //        RecentTrack lastPlayed = null;

        // Go through user's libraries and see when it played for them
        // @TODO make this configurable

        Date lastPlayedDate = null;

        if (lastPlayed != null) {
            lastPlayedDate = lastPlayed.playedAt;
        }
        if (true == true) {

            LibraryItem litem;
            List<User> roomUsers = getUsersInRoom();
            for (User user : roomUsers) {
                litem = LibraryItem
                        .find("byUserAndTitleIlikeAndArtistIlikeAndLastplayedIsNotNullOrderByLastplayedDesc", user,
                                "%" + Auricle.stripTitleMetaData(track.title).toLowerCase() + "%", track.artist)
                        .first();
                if (litem == null) {
                    continue;
                }
                if (litem.lastPlayed != null) {
                    if (lastPlayedDate == null) {
                        //Logger.info("Last played for " + user.firstName + " at " + litem.lastPlayed);
                        lastPlayedDate = litem.lastPlayed;
                    } else {
                        if (lastPlayedDate.before(litem.lastPlayed)) {
                            Logger.info("User " + user.firstName + " heard " + track.title + " more recently at:"
                                    + litem.lastPlayed + " than " + lastPlayedDate);
                            lastPlayedDate = litem.lastPlayed;
                        }
                    }
                }
            }
        }
        return lastPlayedDate;
    }

    public void queueSong(LibraryItem newLibraryItem) {

        Logger.info("Room.QUEUESONG: room " + name + " is " + newLibraryItem.title + " by " + newLibraryItem.artist
                + " and was last played (user/room)" + newLibraryItem.lastPlayed + "/"
                + trackLastPlayed(newLibraryItem.track) + "/AK:" + newLibraryItem.track.albumKey);
        LibraryManager.innerQueueAdd(newLibraryItem, newLibraryItem.user, true, newLibraryItem.user.queue, true);
        newLibraryItem.user.queue.updatePositions();
        save();
    }

    private static void addToRecentTracks(Room room, models.Track currentTrack, LibraryItem litem) {
        // Add old song to recent tracksLong(Room.roomRatingForTrack(room, currentTrack, false, null));
        Long roomrating = -1L;
        if (!room.skipRequested) {
            roomrating = Room.roomRatingForTrack(room, currentTrack, false, null).rating;
        }
        RecentTrack rt = new RecentTrack(room, currentTrack, roomrating, litem);
        room.RecentTracks.add(rt);
        rt.save();

    }

    public void playQueuedSong() {

        Logger.info("PlayQueuedSong");

        if (currentPlayingLibrary != null) {
            currentPlayingLibrary = LibraryItem.findById(currentPlayingLibrary.id); // force reload
        }

        RoomQueue retrieveRoomQueue = retrieveRoomQueue();
        List<User> usersInRoom = getUsersInRoom();
        Date nowDate = new Date();
        LibraryItem nextSong = null;

        if (retrieveRoomQueue == null || retrieveRoomQueue.items.size() < 1) {
            //Logger.info("Trying to play queued song with no items in queue.");

            if (queuedSong == null) {
                // Logger.error("Early extit from Room.playqueuedsong()");

                save();
                return;
            } else {
                Logger.info("Using Room Queued Song");
                nextSong = queuedSong;
                queuedSong = null;
            }
        }

        if (nextSong == null) {
            nextSong = retrieveRoomQueue().items.get(0);
        }
        Logger.info("QueS: " + nextSong.title);
        queuedSong = null;

        if (currentPlayingLibrary != null) {
            currentPlayingLibrary.timesPlayed = currentPlayingLibrary.timesPlayed + 1;
            //currentPlayingLibrary.save();
            addToRecentTracks(this, currentPlayingLibrary.track, currentPlayingLibrary);
            Long roomRating = Room.roomRatingForTrack(this, currentPlayingLibrary.track, false, null).rating;

            // This is used for sorting recommendations
            currentPlayingLibrary.roomRating = roomRating.intValue();
            if ((roomRating > this.roomMinimumRating && roomRating != -1)) {
                int newTokens = usersInRoom.size();
                currentPlayingLibrary.user.tokens = currentPlayingLibrary.user.tokens + newTokens;
                currentPlayingLibrary.user.save();
                ChatRoom.get().roomSay(this.name,
                        currentPlayingLibrary.user.firstName + " received " + newTokens + " tokens!", this);

            }
            // @TODO Go through each players library and updated timesListened
            // Now remove any votes for this song
            List<Vote> votes = Vote.find("byLibraryItem", currentPlayingLibrary).fetch();

            for (Vote vote : votes) {
                vote.delete();
            }
            Logger.info("Deleted " + votes.size() + " vote(s)");

            // Reset playable tracks on users and update time this item played
            LibraryItem litem;

            for (User user : usersInRoom) {
                if (user.noPlayableTrack) {
                    user.noPlayableTrack = false;
                    user.save();
                }
                if (currentPlayingLibrary.user.id != user.id) {
                    Logger.info("Trying to find user library item for user " + user.firstName + " against track "
                            + currentPlayingLibrary.track.title);
                    litem = LibraryManager.findUsersLibraryItem(user, currentPlayingLibrary.track,
                            LibraryManager.UNRATED, false);
                    litem.lastPlayed = nowDate;
                    Logger.info("Updated lastPlayed for user " + user.firstName + " with user ID " + user.id
                            + " and litemID " + litem.id + " for track  " + currentPlayingLibrary.track.title
                            + " ID " + currentPlayingLibrary.id);
                    try {
                        litem.save();
                    } catch (Exception e) {
                        e.printStackTrace();
                        Logger.error("ERROR SAVING library item duringPlayQueuedSong()");
                    }
                } else {
                    currentPlayingLibrary.lastPlayed = nowDate;
                }
                currentPlayingLibrary.save();
            }
        }

        // Now currentplaying library to new song
        currentPlayingLibrary = LibraryItem.findById(nextSong.id);

        trackStartTime = new Date(new Date().getTime() + (1000 * Auricle.ADJUSTMENT_DELAY));

        position = ((new Date().getTime() - trackStartTime.getTime()) / 1000);

        Logger.info("Track start pos:" + position);
        currentUser = nextSong.user;

        skipRequested = false;

        if (currentPlayingLibrary.track.bestYoutubeMatch() == null
                || currentPlayingLibrary.track.bestYoutubeMatch().getId() == null) {
            Logger.info("Need to look up missing you tube match for :" + currentPlayingLibrary.track.artist + ":"
                    + currentPlayingLibrary.track.title);

            YoutubeJob yj = new YoutubeJob(currentPlayingLibrary.track);
            yj.loadYoutubes(currentPlayingLibrary.track);
        }

        YouTubeVideo youTubeVideo = currentPlayingLibrary.track.bestYoutubeMatch();
        //currentPlayingLibrary.track.youtube_id = youTubeVideo.videoID.toString();
        //currentPlayingLibrary.track.youtube_duration = youTubeVideo.duration;
        Logger.info("Queing youtube id:" + currentPlayingLibrary.track.youtube_id);

        Cache.delete("skip" + this.id);

        ChatRoom.get().roomSay(this.name, "Now playing: " + currentPlayingLibrary.title + ".", this);
        ChatRoom.get().stop("stop", this);

        // nextSong.user.save();

        for (UserQueueItem uqi : nextSong.user.queue.userQueueItems) {
            if (uqi.libraryItem.id.equals(nextSong.lid)) {
                Users.rawQueueRemove(nextSong.user, uqi);
                break;
            }
        }
    }

    // @TODO remove this old? 6/23/16
    //    public static Date getNewestOfLibraryAndRecentTrackDate(LibraryItem litem, Date date) {
    //
    //        if (litem.lastPlayed.before(date)) {
    //            return date;
    //        } else {
    //            return litem.lastPlayed;
    //        }
    //
    //    }

    public static String ratingAdjective(Long rating) {

        if (rating > 90) {
            return "awesome";
        } else if (rating > 50) {
            return "very good";
        } else if (rating > 25) {
            return "ok";
        } else if (rating > 15) {
            return "bad";
        } else {
            return "awful";
        }
    }

    public long trackCount(Date sinceDate, Track track) {

        // Logger.info("Track: " + track.title + " / " + sinceDate + " / " + this.name);
        return RecentTrack.count("track = ?1 and playedAt > ?2 and room = ?3", track, sinceDate, this);
    }

    // Retrieves next turn's user
    public User nextUser(boolean needsPlayabaleTrack) {

        if (this.currentPlayingLibrary != null) {
            return nextUser(this.currentPlayingLibrary.user, needsPlayabaleTrack);
        } else {
            return getUsersInRoom().get(0);
        }

    }

    public User nextUserPlayableTrack(User startingUser) {
        if (startingUser == null) {
            return null;
        }
        List<User> usersInRoomAndOnlineAndPlayable = User
                .find("room = ?1 and isOnline = ?2 and noPlayableTrack = ?3  order by id asc", this, true, false)
                .fetch();

        if (usersInRoomAndOnlineAndPlayable.size() == 1) {
            return null;
        }
        Iterable<User> users = Iterables.cycle(usersInRoomAndOnlineAndPlayable);

        Iterator<User> iterator = users.iterator();

        while (!iterator.next().id.equals(startingUser.id)) {

        }
        return iterator.next();

    }

    public User nextUser(User startingUser) {

        List<User> usersInRoomAndOnline = getUsersInRoom();

        if (usersInRoomAndOnline.size() == 1) {
            return startingUser;
        }

        Iterable<User> users = Iterables.cycle(usersInRoomAndOnline);
        Iterator<User> iterator = users.iterator();

        while (!iterator.next().equals(startingUser))
            ;

        return (User) iterator.next();

    }

    public User nextUser(User startingUser, boolean needsPlayabaleTrack) {

        if (needsPlayabaleTrack) {
            return nextUserPlayableTrack(startingUser);
        } else {
            return nextUser(startingUser);
        }

    }

    public RoomQueue retrieveRoomQueue() {
        //Logger.info("retrieveRoomQueue");
        RoomQueue roomQueue = null;

        if (getUsersInRoom() == null || getUsersInRoom().size() < 1) {
            return new RoomQueue(); // Just return null room queue
        }
        switch (roomType) {
        case Room.ROOM_TYPE_DEMOCRACY:
            roomQueue = retrieveRoomQueueDemocracy();
            break;
        case Room.ROOM_TYPE_DICTATORSHIP:
            roomQueue = retrieveRoomQueueDictatorship();
            break;
        case Room.ROOM_TYPE_MONARCHY:
            roomQueue = retrieveRoomQueueMonarchy();
            break;
        case Room.ROOM_TYPE_REPUBLIC:
            roomQueue = retrieveRoomQueueRepublic();
            break;

        }

        if (roomQueue != null && roomQueue.items != null && queuedSong == null && !roomQueue.items.isEmpty()) {
            queuedSong = roomQueue.items.get(0);
            // save();
        }
        return roomQueue;
    }

    // @ TODO this will need to be more efficient right in DB, for now it works
    private RoomQueue retrieveRoomQueueDemocracy() {
        RoomQueue roomQueue = retrieveRoomQueueRepublic();

        for (LibraryItem litem : roomQueue.items) {
            // See if there's a vote in the database for each Library Item
            List<Vote> fetch = Vote.find("byLibraryItem", litem).fetch();
            litem.votes = fetch;
            litem.numberOfvotes = fetch.size();
        }

        // Now sort

        Collections.sort(roomQueue.items, new VoteComparator());

        return roomQueue;

    }

    private RoomQueue retrieveRoomQueueDictatorship() {
        RoomQueue roomQueue = new RoomQueue();

        roomQueue.items = new ArrayList<LibraryItem>();
        if (owner.queue.userQueueItems == null) {
            return roomQueue;
        }
        for (UserQueueItem uqi : owner.queue.userQueueItems) {
            uqi.libraryItem.recommendationDebugLogLink = "/Library/debug?libraryItemId=" + uqi.libraryItem.id;
            roomQueue.items.add(uqi.libraryItem);
        }

        return roomQueue;

    }

    private RoomQueue retrieveRoomQueueMonarchy() {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    public RoomQueue calculateRoomQueue(Room room) {

        RoomQueue roomQueue = new RoomQueue();
        roomQueue.items = new ArrayList<>();

        List<User> usersOnline = getUsersInRoom();

        int numOfUsers = usersOnline.size();

        if (numOfUsers == 0) {
            Logger.error("Calculating room queue for empty room");
            return roomQueue;
        }

        if (numOfUsers == 1) {
            Logger.info("One user" + usersOnline.get(0).nickname);
            Logger.info(usersOnline.get(0).queue.userQueueItems.toString());
            if (usersOnline.get(0).queue != null && usersOnline.get(0).queue.userQueueItems != null) {
                usersOnline.get(0).queue.userQueueItems.forEach(e -> Logger.info(e.libraryItem.title));
                usersOnline.get(0).queue.userQueueItems.forEach(e -> roomQueue.items.add(e.libraryItem));
            }
            return roomQueue;
        }

        User firstUser = room.currentPlayingLibrary != null ? room.currentPlayingLibrary.user : usersOnline.get(0);

        Iterator iterUsers = Iterables.cycle(usersOnline).iterator();
        User nextUser = (User) iterUsers.next();

        if (!nextUser.getId().equals(firstUser.getId())) {
            while (!nextUser.getId().equals(firstUser.getId())) {
                //Logger.info(nextUser.nickname);
                nextUser = (User) iterUsers.next();
            }
        }
        nextUser = (User) iterUsers.next();
        int queueIndex = 0;
        boolean addedAQueue = true;

        while (addedAQueue == true) {

            addedAQueue = false;

            for (int i = 0; i < numOfUsers; i++) {

                if (queueIndex < nextUser.queue.userQueueItems.size()) {
                    Logger.info("ASDASD" + room.name + " / "
                            + nextUser.queue.userQueueItems.get(queueIndex).libraryItem.title);
                    roomQueue.items.add(nextUser.queue.userQueueItems.get(queueIndex).libraryItem);
                    addedAQueue = true;
                }
                nextUser = (User) iterUsers.next();
            }
            queueIndex++;
        }
        return roomQueue;
    }

    private RoomQueue retrieveRoomQueueRepublic() {

        Logger.info("RRQR");
        return calculateRoomQueue(this);
        //
        //        RoomQueue roomQueue = new RoomQueue();
        //        User nextUser;
        //        if (currentPlayingLibrary == null || currentPlayingLibrary.user.isOnline == false) {
        //            Logger.error("@TODO FIX ME in retrieveRoom with null library");
        //            nextUser = getUsersInRoom().get(0);
        //
        //        } else {
        //            nextUser = nextUser(currentPlayingLibrary.user, false);
        //        }
        //        if (nextUser == null) {
        //            Logger.error("We somehow have null next user!");
        //            return null;
        //        }
        //
        //
        //        int i = 0;
        //        boolean hadHit = false;
        //
        //
        //        roomQueue.items = new ArrayList<>();
        //        List<User> usersInRoom = getUsersInRoom();
        //
        //
        //        int numOfUsers = usersInRoom.size();
        //        int counter = 1;
        //
        //        if (numOfUsers < 2){
        //            roomQueue.items = usersInRoom.get(0).queue.userQueueItems.stream().
        //                    map(q -> q.libraryItem).collect(Collectors.toList());
        //            return roomQueue;
        //
        //        }
        //
        //
        //        while (nextUser != null) {
        //            // Logger.info("Processing " + nextUser.firstName);
        //
        //            if (nextUser.queue == null) {
        //                nextUser.queue = new UserQueue();
        //                nextUser.queue.userQueueItems = new ArrayList<>();
        //                nextUser.queue.save();
        //                nextUser.save();
        //            }
        //
        //            if (i < nextUser.queue.userQueueItems.size()) {
        //                LibraryItem litem = nextUser.queue.userQueueItems.get(i).libraryItem;
        //                litem.recommendationDebugLogLink = "/Library/debug?libraryItemId=" + litem.id;
        //                roomQueue.items.add(litem);
        //
        //                hadHit = true;
        //            }
        //            if (counter % numOfUsers == 0) {
        //                // We've looped through the users, check if we had a hit
        //                if (!hadHit) {
        //                    break;
        //                }
        //                // Reset hit
        //                hadHit = false;
        //                i++;
        //            }
        //            counter++;
        //
        //            nextUser = nextUser(nextUser, false);
        //
        //        }
        //
        //        return roomQueue;

    }
}