/* Copyright (C) 2009  CSE,IIT Bombay  http://www.cse.iitb.ac.in

This file is part of the ConStore open source storage facility for concept-nets.

ConStore is free software and distributed under the 
Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported License;
you can copy, distribute and transmit the work
with the work attribution in the manner specified by the author or licensor.
You may not use this work for commercial purposes and may not alter, 
transform, or build upon this work.

Please refer the legal code of the license, available at
http://creativecommons.org/licenses/by-nc-nd/3.0/legalcode

ConStore 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.  */

package iitb.con.caching;

import iitb.con.core.Instance;
import iitb.con.ds.InstanceMetaItem;

import java.util.HashMap;
import java.util.Map;

/**
 * MRU cache. The most recently used objects are replaced when the 
 * cache is full.
 * 
 * @author Prathab K
 *
 */
public class MRUCache<K> implements Cache<K> {
    
    /** Cache size */
    private long cacheSize;
    
    /** Current cache size */
    private long currentSize;

    /** Hash map structure for cache */
    private Map<K,CacheObject> cache;
    
    /** Hash map structure to maintain the recent accessed entries */
    private Map<Integer, K> recentAccessed;
    
    /** Recent accessed counter */
    private int recentAccessedCount;
    
    /** Recent accessed size list */
    private static final short RECENT_ACCESS_SIZE = 16; 
    
    /**
     * Initializes the cache with specified size 
     * @param cacheSize cache size in bytes
     */
    public MRUCache(long cacheSize) {
        this.cacheSize = cacheSize;
        cache = new HashMap<K,CacheObject>();
        recentAccessed = new HashMap<Integer,K>();
    }
    

    /**
     * Returns the instance from the cache for the given instance id
     * @param key key used to retrieve the object 
     * @return {@link Instance} if present in cache or else return <tt>NULL</tt> 
     */
    public CacheObject get(K key) {
        CacheObject obj = cache.get(key);
        if(obj != null) {
            //System.out.println("Query Cache Hit");
            return obj;
        }
        //System.out.println("Query Cache Miss");
        return null;
    }

    /**
     * Places the instance into the cache
     * @param key key used to place the cache object
     * @param obj {@link CacheObject} 
     * @return <tt>true</tt> on success
     */
    public boolean put(K key, CacheObject obj) {
        int objSize = obj.size;
        if(((currentSize + objSize) > cacheSize)) {
            while((currentSize + objSize) > cacheSize)
                remove();
                
            add(key, obj);
        } else {
            add(key, obj);
        }
        return true;
    }
    
    /**
     * Adds the cache object to the cache based on the key.
     * Puts the object on the recent accessed list
     * @param key key used to place the object
     * @param obj cache object
     */
    private void add(K key, CacheObject obj) {
        cache.put(key, obj);
        currentSize += obj.size;
        recentAccessedCount %= RECENT_ACCESS_SIZE;
        recentAccessed.put(new Integer(recentAccessedCount++), key);
    }
    
    /**
     * Removes the cache objects based on 
     * the most recently used criteria when the cache is full.
     */
    private void remove() {
        if(recentAccessedCount > 0) {
            recentAccessedCount--;
            K key = recentAccessed.get(recentAccessedCount);
            CacheObject obj = cache.remove(key);
            currentSize -= obj.size;
            
        } else {
            cache.clear();
            currentSize = 0;
        }
        //System.out.println("Query Cache Remove");
    }
    
    /**
     * Returns the size of cache in bytes
     */
    public long getSize() {
        return cacheSize;
    }
    
    /**
     * Returns the available free space
     * @return cache free space
     */
    public long freeSpace() {
        return (cacheSize - currentSize);
    }
    
    /**
     * Clears the cache
     */
    public void clear() {
        cache.clear();
    }

}