//
// $Id$
//
// Vilya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/vilya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.parlor.rating.server.persist;

import java.sql.Timestamp;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import com.samskivert.io.PersistenceException;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.IntMap;
import com.samskivert.util.IntSet;

import com.samskivert.jdbc.depot.DepotRepository;
import com.samskivert.jdbc.depot.EntityMigration;
import com.samskivert.jdbc.depot.PersistenceContext;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.clause.Limit;
import com.samskivert.jdbc.depot.clause.OrderBy;
import com.samskivert.jdbc.depot.clause.QueryClause;
import com.samskivert.jdbc.depot.clause.Where;
import com.samskivert.jdbc.depot.expression.SQLExpression;
import com.samskivert.jdbc.depot.operator.Conditionals.*;
import com.samskivert.jdbc.depot.operator.Logic.And;

import com.threerings.parlor.rating.util.Percentiler;

/**
 * Handles the persistent storage of per-user per-game ratings.
 */
public class RatingRepository extends DepotRepository
{
    /**
     * Initialize the {@link RatingRepository}.
     */
    public RatingRepository (PersistenceContext ctx)
    {
        super(ctx);
    }

    /**
     * Loads the rating for the given player for the given game and returns it as a
     * {@link RatingRecord} object, or null if the player has no previous rating for the game.
     */
    public RatingRecord getRating (int gameId, int playerId)
        throws PersistenceException
    {
        return load(RatingRecord.class, RatingRecord.getKey(gameId, playerId));
    }

    /**
     * Fetch the ratings registered for any of the given players for the given game and return
     * them as a list of {@link RatingRecord} objects. The size of this list is no less than zero
     * and no greater than the number of given players.
     */
    public List<RatingRecord> getRatings (int gameId, Integer... players)
        throws PersistenceException
    {
        if (players.length == 0) {
            return Collections.emptyList();
        }
        return findAll(RatingRecord.class,
                       new Where(new And(new Equals(RatingRecord.GAME_ID_C, gameId),
                                         new In(RatingRecord.PLAYER_ID_C, players))));
    }

    /**
     * Fetch and return all the registered {@link RatingRecord} rows for the given player. Ratings
     * will be returned in order of most recently to least recently updated.
     *
     * @param since ratings last updated more than this number of milliseconds in the past will be
     * ommitted from the result list. Supplying -1 will return all ratings regardless of age.
     * @param count the maximum number of ratings to return or -1 for all ratings.
     */
    public List<RatingRecord> getRatings (int playerId, long since, int count)
        throws PersistenceException
    {
        ArrayList<QueryClause> clauses = new ArrayList<QueryClause>();
        if (since > 0L) {
            Timestamp when = new Timestamp(System.currentTimeMillis() - since);
            clauses.add(new Where(new And(new Equals(RatingRecord.PLAYER_ID_C, playerId),
                                          new GreaterThan(RatingRecord.LAST_UPDATED_C, when))));
        } else {
            clauses.add(new Where(RatingRecord.PLAYER_ID_C, playerId));
        }
        if (count > 0) {
            clauses.add(new Limit(0, count));
        }
        clauses.add(OrderBy.descending(RatingRecord.LAST_UPDATED_C));
        return findAll(RatingRecord.class, clauses.toArray(new QueryClause[clauses.size()]));
    }

    /**
     * Returns the top-ratings for the specified game. Players with equal rating will be sorted
     * most recently played first.
     *
     * @param playerIds an optional list of player ids to which to limit the top-rankings search.
     */
    public List<RatingRecord> getTopRatings (int gameId, int limit, IntSet playerIds)
        throws PersistenceException
    {
        ArrayList<QueryClause> clauses = new ArrayList<QueryClause>();
        if (playerIds == null) {
            clauses.add(new Where(RatingRecord.GAME_ID_C, gameId));
        } else {
            Integer[] pids = playerIds.toArray(new Integer[playerIds.size()]);
            clauses.add(new Where(new And(new Equals(RatingRecord.GAME_ID_C, gameId),
                                          new In(RatingRecord.PLAYER_ID_C, pids))));
        }
        clauses.add(new Limit(0, limit));
        clauses.add(new OrderBy(
                        new SQLExpression[] { RatingRecord.RATING_C, RatingRecord.LAST_UPDATED_C },
                        new OrderBy.Order[] { OrderBy.Order.DESC, OrderBy.Order.DESC }));
        return findAll(RatingRecord.class, clauses.toArray(new QueryClause[clauses.size()]));
    }

    /**
     * Set the rating and experience for a given player and game. This method will either update
     * or create a row.
     */
    public void setRating (int gameId, int playerId, int rating, int experience)
        throws PersistenceException
    {
        store(new RatingRecord(gameId, playerId, rating, experience));
    }

    /**
     * Deletes the specified rating record.
     */
    public void deleteRating (int gameId, int playerId)
        throws PersistenceException
    {
        delete(RatingRecord.class, RatingRecord.getKey(gameId, playerId));
    }

    /**
     * Loads the percentile distribution associated with the specified game. null will never be
     * returned, rather a blank percentiler will be created and returned.
     */
    public Percentiler loadPercentile (int gameId)
        throws PersistenceException
    {
        PercentileRecord record = load(PercentileRecord.class, PercentileRecord.getKey(gameId));
        return (record == null) ? new Percentiler() : new Percentiler(record.data);
    }

    /**
     * Writes the supplied percentiler's data out to the database.
     */
    public void updatePercentile (int gameId, Percentiler tiler)
        throws PersistenceException
    {
        PercentileRecord record = new PercentileRecord();
        record.gameId = gameId;
        record.data = tiler.toBytes();
        store(record);
    }

    @Override // from DepotRepository
    protected void getManagedRecords (Set<Class<? extends PersistentRecord>> classes)
    {
        classes.add(RatingRecord.class);
        classes.add(PercentileRecord.class);
    }
}

@@ -1097,24 +1097,94 @@
 .util.Set;%0A%0A
+import com.google.inject.Singleton;%0Aimport com.google.inject.Inject;%0A%0A
 import com.s
@@ -2026,16 +2026,27 @@
 gs.%0A */%0A
+@Singleton%0A
 public c
@@ -2153,32 +2153,40 @@
 y%7D.%0A     */%0A    
+@Inject 
 public RatingRep

Allow the RatingRepository to be injected.