/*******************************************************************************
 * Copyright 2014 Centre for Development of Advanced Computing(C-DAC), Pune
 * */
package in.cdac.medinfo.csnotk.csnolib.factory;


import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.MMapDirectory;

import in.cdac.medinfo.csnotk.csnolib.commons.Constants;
import in.cdac.medinfo.csnotk.csnolib.util.PropertyReader;
//Logs
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import in.cdac.medinfo.csnotk.csnolib.util.LoggerUtility;

/**
 * @author root
 * Factory implementation of IndexReader
 * Factory method implementation for IndexReader for getting singleton object for
 * IndexReader to read index from given directory
 *
 */
public class IndexReaderFactory {
	//for holding singleton instance of IndexReader
	private static Map<String,IndexReader> indexReaderMap= new HashMap<>(10);
	private Directory directory=null;
	private IndexReader indexReader=null;
	private static IndexReaderFactory indexReaderFactory=null;


	/**
	 * Restricting object creation
	 */
	private IndexReaderFactory(){}

	/**
	 * Returns singleton {@link IndexReaderFactory} object.
	 */
	public static IndexReaderFactory getInstance(){
		if(indexReaderFactory!=null)
			return indexReaderFactory;
		else
			indexReaderFactory=new IndexReaderFactory();
		return indexReaderFactory;
	}

	/**
	 * Returns unique singleton {@link IndexReader} object.
	 * @param refsetId Identifier of Refset/Indexed Directory
	 * @return instance of {@link IndexReader} respective to refsetId/ indexed dir.
	 * @throws IOException if any error occurs while processing.
	 */
	public IndexReader getIndexReader(String refsetId) throws IOException{
		PropertyReader.loadSystemProperties();
		if(indexReaderMap==null)
			indexReaderMap=this.getIndexReaderMap();
		if(!indexReaderMap.containsKey(refsetId)){
				directory=getDirectory(refsetId);
				synchronized(this){
				if(!indexReaderMap.containsKey(refsetId)){
					//getindexReader
					indexReader=DirectoryReader.open(directory);
					indexReaderMap.put(refsetId, indexReader);
					}else
						indexReader=indexReaderMap.get(refsetId);
				}
		}else{
			indexReader=DirectoryReader.openIfChanged((DirectoryReader)indexReaderMap.get(refsetId));
			if(indexReader!=null){
				//close older indexReader before replacing with new
				indexReaderMap.get(refsetId).close();
				indexReaderMap.put(refsetId, indexReader);
				return indexReader;
			}else
				indexReader=indexReaderMap.get(refsetId);
		}
		return indexReader;
	}


	/**
	 * It will be used in reinitializing indexReaderMap object
	 * @return {@link Map<String,@link IndexReader>} of String and indexReader object.
	 */
	private Map<String,IndexReader> getIndexReaderMap(){
		if(indexReaderMap==null)
			indexReaderMap=new HashMap<>(10);
		return indexReaderMap;
	}


	/**
	 * Returns {@link Directory} object.
	 * @param refsetId Identifier of Refset/Indexed Directory
	 * @return instance of {@link Directory} respective to refsetId/ indexed dir.
	 * @throws IOException if any error occurs while processing.
	 */
	private Directory getDirectory(String refsetId) throws IOException{
		PropertyReader.loadSystemProperties();
		synchronized(this){
			if(refsetId==null){
				directory= new MMapDirectory(Paths.get(new File(PropertyReader.getProperty(Constants.DATA_DIR)+ File.separator +Constants.SNOINDEX).getAbsolutePath()));
			}else if( Constants.SNOINDEX.equals(refsetId) ||  Constants.RELATION_INDEX.equals(refsetId) ||  Constants.TCINDEX.equals(refsetId)||  Constants.REFINDEX.equals(refsetId))
				directory= new MMapDirectory(Paths.get(new File(PropertyReader.getProperty(Constants.DATA_DIR)+ File.separator +refsetId).getAbsolutePath()));
			else if( Constants.SIMPLE_MAP_INDEX.equals(refsetId) ||  Constants.SEMINDEX.equals(refsetId)  ||  Constants.REL_TYPE_INDEX.equals(refsetId))
				directory= new MMapDirectory(Paths.get(new File(PropertyReader.getProperty(Constants.DATA_DIR)+ File.separator +refsetId).getAbsolutePath()));
			else
				directory= new MMapDirectory(Paths.get(new File(PropertyReader.getProperty(Constants.DATA_DIR)+File.separator+Constants.REFINDEX).getAbsolutePath()));
			return directory;
		}
		//public RAMDirectory(Directory dir,IOContext context) throws IOException
		//Creates a new RAMDirectory instance from a different Directory implementation. This can be used to
		//load a disk-based index into memory.

		//Warning: This class is not intended to work with huge indexes. Everything beyond several hundred
		//megabytes will waste resources (GC cycles), because it uses an internal buffer size of 1024 bytes,
		//producing millions of byte[1024] arrays. This class is optimized for small memory-resident indexes.
		//It also has bad concurrency on multithreaded environments.
		//Note that the resulting RAMDirectory instance is fully independent from the original Directory (it is a complete copy).
		//Any subsequent changes to the original Directory will not be visible in the RAMDirectory instance.

		//MMapDirectory(_,_) intended for File based directory where disk space is not crucial

	}


	/**
	 * Close all opened {@link IndexReader} created by getIndexReader() in {@link IndexReaderFactory} class
	 */
	private void closeIndexReaders(){
		Logger logger = LogManager.getLogger(IndexReaderFactory.class);
		logger.info(LoggerUtility.Log, "closeIndexReaders() method has been call by GC");
		try{
		if(indexReaderMap!=null){
		for(String refsetId:indexReaderMap.keySet()){
			if(refsetId!=null)
				indexReaderMap.get(refsetId).close();
		}
		}
		}catch(IOException ie){
            logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(ie));
		}finally{
			indexReaderMap=null;
		}
	}

	@Override
	public void finalize(){
		Logger logger = LogManager.getLogger(IndexReaderFactory.class);
		logger.info(LoggerUtility.Log, "destroying IndexReaderFactory object");
		this.closeIndexReaders();

	}



}
