/**
 * Copyright (C) 2013 Oldterns
 *
 * This file may be modified and distributed under the terms
 * of the MIT license. See the LICENSE file for details.
 */
package com.oldterns.vilebot.db;

import java.util.Set;

import redis.clients.jedis.Jedis;

public class KarmaDB
    extends RedisDB
{
    private static final String keyOfKarmaSortedSet = "noun-karma";

    /**
     * Change the karma of a noun by an integer.
     * 
     * @param noun The noun to change the karma of
     * @param mod The amount to change the karma by, may be negative.
     */
    public static void modNounKarma( String noun, int mod )
    {
        Jedis jedis = pool.getResource();
        try
        {
            jedis.zincrby( keyOfKarmaSortedSet, mod, noun );
        }
        finally
        {
            pool.returnResource( jedis );
        }
    }

    /**
     * Get the karma of a noun.
     * 
     * @param noun The noun to query to karma of
     * @return Integer iff the noun has a defined value, else null
     */
    public static Integer getNounKarma( String noun )
    {
        Jedis jedis = pool.getResource();
        Double karma;
        try
        {
            karma = jedis.zscore( keyOfKarmaSortedSet, noun );
        }
        finally
        {
            pool.returnResource( jedis );
        }

        if ( karma == null )
        {
            return null;
        }

        return Integer.valueOf( Long.valueOf( Math.round( karma ) ).intValue() );
    }

    /**
     * Get the rank of a noun based on its karma.
     * 
     * @param noun The noun to query the rank of
     * @return Integer iff the noun has a defined value, else null
     */
    public static Integer getNounRank( String noun )
    {
        Jedis jedis = pool.getResource();
        Long rank;
        try
        {
            rank = jedis.zrevrank( keyOfKarmaSortedSet, noun );
        }
        finally
        {
            pool.returnResource( jedis );
        }

        if ( rank == null )
        {
            return null;
        }

        return Integer.valueOf( rank.intValue() + 1 );
    }

    /**
     * Get the rank of a noun based on its karma, starting at most negative karma.
     * 
     * @param noun The noun to query the reverse rank of
     * @return Integer iff the noun has a defined value, else null
     */
    public static Integer getNounRevRank( String noun )
    {
        Jedis jedis = pool.getResource();
        Long rank;
        try
        {
            rank = jedis.zrank( keyOfKarmaSortedSet, noun );
        }
        finally
        {
            pool.returnResource( jedis );
        }

        if ( rank == null )
        {
            return null;
        }

        return Integer.valueOf( rank.intValue() + 1 );
    }

    /**
     * Get noun from a karma rank (Rank 1 is the member with the highest karma).
     * 
     * @param rank The rank to get the noun of.
     * @return String The noun iff the rank exists, else null.
     */
    public static String getRankNoun( long rank )
    {
        Set<String> nouns = getRankNouns( rank - 1, rank );

        if ( nouns != null && nouns.iterator().hasNext() )
        {
            return nouns.iterator().next();
        }
        return null;
    }

    /**
     * Get nouns from karma ranks.
     * 
     * @param lower The lower rank to get the nouns of.
     * @param upper The upper rank to get the nouns of.
     * @return String The noun iff the rank exists, else null.
     */
    public static Set<String> getRankNouns( long lower, long upper )
    {
        Set<String> nouns;

        Jedis jedis = pool.getResource();
        try
        {
            nouns = jedis.zrevrange( keyOfKarmaSortedSet, lower, upper );
        }
        finally
        {
            pool.returnResource( jedis );
        }

        if ( nouns == null || nouns.size() == 0 )
        {
            return null;
        }

        return nouns;
    }

    /**
     * Get noun from a karma rank, starting with the lowest ranks (Rank 1 would be the member with the least karma).
     * 
     * @param rank The reversed rank to get the noun of.
     * @return String The noun iff the rank exists, else null.
     */
    public static String getRevRankNoun( long rank )
    {
        Set<String> nouns = getRevRankNouns( rank - 1, rank );

        if ( nouns != null && nouns.iterator().hasNext() )
        {
            return nouns.iterator().next();
        }
        return null;
    }

    /**
     * Get nouns from a karma rank, starting with the lowest ranks.
     * 
     * @param lower The lower rank to get the nouns of.
     * @param upper The upper rank to get the nouns of.
     * @return String The noun iff the rank exists, else null.
     */
    public static Set<String> getRevRankNouns( long lower, long upper )
    {
        Set<String> nouns;

        Jedis jedis = pool.getResource();
        try
        {
            nouns = jedis.zrange( keyOfKarmaSortedSet, lower, upper );
        }
        finally
        {
            pool.returnResource( jedis );
        }

        if ( nouns == null || nouns.size() == 0 )
        {
            return null;
        }

        return nouns;
    }

    /**
     * Remove noun from the karma/rank set.
     * 
     * @param noun The noun to remove, if it exists.
     * @return true iff the noun existed before removing it.
     */
    public static boolean remNoun( String noun )
    {
        Long existed;

        Jedis jedis = pool.getResource();
        try
        {
            existed = jedis.zrem( keyOfKarmaSortedSet, noun );
        }
        finally
        {
            pool.returnResource( jedis );
        }

        if ( existed == null || existed != 1 )
        {
            return false;
        }
        return true;
    }

    public static long getTotalKarma()
    {
        Jedis jedis = pool.getResource();
        long totalKarma;
        try
        {
            Set<String> members = jedis.zrange( keyOfKarmaSortedSet, 0, -1 );
            totalKarma = sum( members, jedis );
        }
        finally
        {
            pool.returnResource( jedis );
        }
        return totalKarma;
    }

    private static long sum( Set<String> members, Jedis jedis )
    {
        long sum = 0;
        for ( String member : members )
        {
            sum += jedis.zscore( keyOfKarmaSortedSet, member );
        }
        return sum;
    }

}