/* 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.indexing;

import iitb.con.indexing.isam.ISAMTree;
import iitb.con.indexing.isam.LeafNode;
import iitb.con.io.BufferedFileAdapter;
import iitb.con.io.IOAdapter;
import iitb.con.util.KeyList;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;

/**
 * Caches the meta-index of the index structure. Basically it caches the 
 * non-leaf nodes of the index tree, which improves the query search. 
 * 
 * @author Prathab K
 *
 */
public class IndexCache {

    /** Cache list <index name as key, meta-index as value> */
    private Map<String,KeyList> cacheList;

    /** Block size of a node */
    private static final int BLOCK_SIZE = 4096;
    
    /** IndexCache instance */
    private static IndexCache indexCacheInstance = null;
    
    /** Cache list of opened files */
    private Map<String, IOAdapter> openedFileCache;
    
    
    private IndexCache(){
        cacheList = new HashMap<String, KeyList>();
        openedFileCache = new HashMap<String, IOAdapter>();
        
    }
    
    /**
     * Singleton method to get the IndexCache instance 
     * @return <tt>IndexCache</tt> instance
     */
    public static IndexCache getInstance() {
        if(indexCacheInstance == null)
            indexCacheInstance = new IndexCache();
        return indexCacheInstance;
    }
    
    /**
     * Retrieves the instance ids from the index for the specified type's attribute.<br>
     * If index is not present in the cache, it is read from the index file and
     * placed in the cache.
     * @param <K> attribute data type 
     * @param typeId type id 
     * @param attributeName attribute name
     * @param value attribute value
     * @return instance ids as int array
     */
    public <K extends Comparable<K>> int[] getInstancesIds(short typeId, String attributeName,
            K value) {
        String fileName = IndexBuilder.getIndexFileName(typeId, attributeName);
        
        String indexName = typeId + "_" + attributeName;
        
        KeyList<K,Short> metaIndex = cacheList.get(indexName);
        try {
            if(metaIndex != null) {
                return getValues(fileName, metaIndex, value);
            } else {
                if(IndexBuilder.hasIndex(typeId, attributeName)) {
                    
                    IndexTree<K> isamTree = new ISAMTree(fileName,"r");
                    metaIndex = isamTree.getMetaIndex(value);
                    if(metaIndex != null) {
                        cacheList.put(indexName, metaIndex);
                        return getValues(fileName, metaIndex, value);
                    }
                    isamTree.close();
                }else {
                    return null;
                }
            }
        }catch(IOException ie) {
           ie.printStackTrace();
        }
        return null;
    }
    
    /**
     * Returns the values for the given key from the meta-index.
     * @param <K> data type
     * @param fileName index file name
     * @param metaIndex meta index as <tt>KeyList</tt>
     * @param key key to be searched
     * @return matched values for the specified key
     * @throws IOException if the file operation fails
     * @see KeyList
     */
    private <K extends Comparable<K>> int[] getValues(String fileName, KeyList<K,Short> metaIndex, K key) 
    throws IOException {
        Short blockId = (Short) metaIndex.getPrev(key);
        
        if(blockId != null) {
            //IOAdapter leafNodesFile = new BufferedFileAdapter(fileName + IndexTree.LEAF_EXT, "r");
            IOAdapter leafNodesFile = getFile(fileName);
            byte[] b = leafNodesFile.read(getLocation(blockId), BLOCK_SIZE);
            LeafNode<K> leafNode = new LeafNode<K>(key);
            ByteBuffer buf = ByteBuffer.wrap(b);
            int blockSize = buf.getInt();
            buf.limit(blockSize);
            while(buf.hasRemaining()) {
                /*leafNode.deSerialize(buf);
                if(leafNode.key.compareTo(key) == 0)
                    return leafNode.instanceId;*/
                if(leafNode.attributeDeSerialize(buf, key) != null)
                    return leafNode.instanceId;
            }
            //leafNodesFile.close();
        }
        return null;
    }
    
    private IOAdapter getFile(String fileName) throws IOException {
        if(openedFileCache.containsKey(fileName))
            return openedFileCache.get(fileName);
        IOAdapter leafNodesFile = new BufferedFileAdapter(fileName + IndexTree.LEAF_EXT, "r");
        openedFileCache.put(fileName, leafNodesFile);
        return leafNodesFile;
    }
    
    /**
     * Returns the file location for the specified block id 
     * @param blockId block id in the index file 
     * @return file location
     */
    private long getLocation(int blockId) {
        return ((blockId * BLOCK_SIZE) - BLOCK_SIZE);
    }
    
    /**
     * Clears the Index Cache List
     */
    public void clear(){
        cacheList.clear();
        openedFileCache.clear();
        indexCacheInstance = null;
    }
}