/**
 * Copyright © 2008-2009 DRIVER PROJECT (ICM UW)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package eu.dnetlib.resultset.impl.containers;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;

import eu.dnetlib.common.ws.dataprov.DataProviderException;
import eu.dnetlib.common.ws.dataprov.IDataProviderExt;
import eu.dnetlib.common.ws.dataprov.ResultsResponse;
import eu.dnetlib.resultset.impl.IndexResultSetFaultMessage;
import eu.dnetlib.resultset.impl.PullResultSetObject;
import eu.dnetlib.resultset.impl.PullResultSetObjectUtils;
import eu.dnetlib.resultset.impl.ResultSetObject;
import eu.dnetlib.resultset.impl.ResultSetObject.ResultSetStatus;
import eu.dnetlib.resultset.impl.ResultSetObject.ResultSetType;

/**
 * ResultSetObject container. Data retrieved from IndexDataProvider is not cached.
 * @author Marek Horst
 * @version 0.01
 *
 */
public class ResultSetObjectNoCacheContainer implements
		IResultSetObjectContainer {

	protected static final Logger log = Logger.getLogger(ResultSetObjectNoCacheContainer.class);
	
	Map <String,ResultSetObject> rsData = Collections.synchronizedMap(
			new HashMap<String, ResultSetObject>());
	
	/**
	 * Internal index data provider.
	 */
	private IDataProviderExt indexDataProvider;
	

	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.containers.IResultSetObjectContainer#get(java.lang.String)
	 */
	public ResultSetObject get(String rsId) {
		return rsData.get(rsId);
	}
	
	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.containers.IResultSetObjectContainer#getWithStoredData(java.lang.String)
	 */
	public ResultSetObject getWithStoredData(String rsId) {
//		no difference for non-caching container
		return get(rsId);
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.containers.IResultSetObjectContainer#getStatusFromBulkData()
	 */
	public String getStatusFromBulkData(String rsId) throws IndexResultSetFaultMessage {
		
		ResultsResponse currentBulkDataStatus = null;
		
		ResultSetObject rsObject = get(rsId);
		
		if (rsObject==null)
			throw new IndexResultSetFaultMessage("ResultSet "+rsId+" doesn't exist!", 
					IndexResultSetFaultMessage.ERROR_INVALID_RS);
		
		PullResultSetObject pullRsObject = (PullResultSetObject) rsObject;
		
		try {
			currentBulkDataStatus = indexDataProvider.getNumberOfResults(pullRsObject.getBdId());
			
		} catch (DataProviderException e) {
			String msg = "DataProviderException has been thrown when cheking status of " +
						"BulkData!";
			log.error(msg+ " :"+e);
			throw new IndexResultSetFaultMessage(msg + " "
					+e.getMessage(), e, IndexResultSetFaultMessage.ERROR_UNKNOWN);
		}
		
		if (!IDataProviderExt.STATUS_OPEN.equalsIgnoreCase(pullRsObject.getStatus().toString())) {
			pullRsObject.setStatus(ResultSetStatus.CLOSED);
			pullRsObject.setPullRsSize(currentBulkDataStatus.getTotal());
            store(pullRsObject);
        }
		
		return currentBulkDataStatus.getStatus();
			
	}
	
	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.containers.IResultSetObjectContainer#getNumberOfResultsFromBulkData(java.lang.String)
	 */
	public int getNumberOfResultsFromBulkData(String rsId) throws IndexResultSetFaultMessage {
		
		ResultSetObject rsObject = get(rsId);
		
		if (rsObject==null)
			throw new IndexResultSetFaultMessage("ResultSet "+rsId+" doesn't exist!", 
					IndexResultSetFaultMessage.ERROR_INVALID_RS);
		
		PullResultSetObject pullRsObject = (PullResultSetObject) rsObject;
		
		try {
			int currentNumber = indexDataProvider.getNumberOfResults(pullRsObject.getBdId()).getTotal();
			
			if (IDataProviderExt.STATUS_OPEN.equalsIgnoreCase(pullRsObject.getStatus().toString())
					&& currentNumber != 0) {
				pullRsObject.setPullRsSize(currentNumber);
	            store(pullRsObject);
			}
			
			return currentNumber; 
		} catch (DataProviderException e) {
			String msg = "DataProviderException has been thrown when cheking status of " +
						"BulkData!";
			log.error(msg+ " :"+e);
			throw new IndexResultSetFaultMessage(msg + " "
					+e.getMessage(), e, IndexResultSetFaultMessage.ERROR_UNKNOWN);
		}
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.containers.IResultSetObjectContainer#getData(java.lang.String, int, int)
	 */
	public String[] getDataOld(String rsId, int fromPosition, int toPosition) 
		throws IndexResultSetFaultMessage {

		long lastAccessTime = System.currentTimeMillis();
		long startTime = lastAccessTime;
		ResultSetObject rsObject = get(rsId);
		
		if (rsObject==null)
			throw new IndexResultSetFaultMessage("ResultSet "+rsId+" doesn't exist!", 
					IndexResultSetFaultMessage.ERROR_INVALID_RS);
		
		if (fromPosition<1)
			fromPosition=1;
		
		log.debug("Get Data 1:" +fromPosition +" - " +toPosition);
		
		if (rsObject.getResultSetType() == ResultSetType.PUSH_RS) {
			throw new IndexResultSetFaultMessage("ResultSetObjectNoCacheContainer " +
					"does not support PUSH ResultSet data storage.", 
					IndexResultSetFaultMessage.ERROR_UNSUPPORTED);
		} else {
			PullResultSetObject pullRsObject = (PullResultSetObject) rsObject;
			pullRsObject.setLastAccessTime(lastAccessTime);
			
			if (pullRsObject.getSize()==0) {				
				log.warn("No records in ResultSet!");
				return new String[0];
			}
			
			if (fromPosition>pullRsObject.getSize())
				return new String[0];
			
			if (toPosition>pullRsObject.getSize())
				toPosition=pullRsObject.getSize();
			
			log.debug("Get Data 2:" +fromPosition +" - " +toPosition);
			
//			updating data from data provider if needed
			int totalElements = pullRsObject.getSize();
			int initialPageSize = pullRsObject.getInitialPageSize();
			int startPageNumber = (fromPosition-1)/initialPageSize + 1;
			int lastAvailaibleElementNumber = initialPageSize*startPageNumber;
			int currentPageNumber = startPageNumber;
			boolean stopWorking = false;

//			using temp data to avoid writing pullResultSet object
			List<String[]> tempWorkData = new ArrayList<String[]>(pullRsObject.getData().size());
			for (int i=0; i<pullRsObject.getData().size(); i++)
				tempWorkData.add(new String[0]);
			
			
			//			TODO there is no need to retrieve all page data from data provider
			while (!stopWorking) {
				//log.debug("working on curentPageNumber: "+currentPageNumber);
				lastAvailaibleElementNumber = initialPageSize*currentPageNumber;
				int pckgStartElement = initialPageSize*(currentPageNumber-1) + 1;
				
				if (lastAvailaibleElementNumber>=toPosition) {
					stopWorking = true;
				} 
				int pckgEndElement = lastAvailaibleElementNumber<totalElements?
						lastAvailaibleElementNumber:totalElements;
				try {
					log.debug("getting bulkData for bdId1: "+pullRsObject.getBdId()+
							", fromPosition: "+pckgStartElement+
							", toPosition: "+pckgEndElement);
					String[] pageData = indexDataProvider.getSimpleBulkData(pullRsObject.getBdId(), 
							pckgStartElement, pckgEndElement);
					if (pageData == null){
						throw new IndexResultSetFaultMessage(   "pageData is null", IndexResultSetFaultMessage.ERROR_UNKNOWN);
					}
					if (pageData.length == 0){
						log.debug("returning empty result.");
						return new String[0];
					}
					tempWorkData.set(currentPageNumber-1, pageData);

				} catch (DataProviderException e) {
					log.error("A DataProviderException has been thrown when getting results " +
							"from  data provider!",e);
						throw new IndexResultSetFaultMessage("A DataProviderException has been thrown when getting results " +
							"from data provider" + 
							"; internal error message: "+e.getMessage(), e, IndexResultSetFaultMessage.ERROR_UNKNOWN);
				} catch(Exception e){
					log.error("exception in getData.", e);
					throw new IndexResultSetFaultMessage("exception in getData", e, IndexResultSetFaultMessage.ERROR_UNKNOWN);
				}
				currentPageNumber = currentPageNumber+1;
			}
			String[] result = PullResultSetObjectUtils.getData(fromPosition, toPosition,
					pullRsObject.getInitialPageSize(), pullRsObject.getSize(), tempWorkData);
			log.debug("getData processing time: "+(System.currentTimeMillis()-startTime) + " ms");
			
			return result;
		}
	}
	
	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.containers.IResultSetObjectContainer#getData(java.lang.String, int, int)
	 */
	public String[] getData(String rsId, int fromPosition, int toPosition) 
		throws IndexResultSetFaultMessage {

		long lastAccessTime = System.currentTimeMillis();
		long startTime = lastAccessTime;
		ResultSetObject rsObject = get(rsId);
		
		if (rsObject==null)
			throw new IndexResultSetFaultMessage("ResultSet "+rsId+" doesn't exist!", 
					IndexResultSetFaultMessage.ERROR_INVALID_RS);
		
		if (fromPosition<1)
			fromPosition=1;
		
		//log.debug("Intial range of pages for dataprovider:" +fromPosition +" - " +toPosition);
		
		if (rsObject.getResultSetType() == ResultSetType.PUSH_RS) {
			throw new IndexResultSetFaultMessage("ResultSetObjectNoCacheContainer " +
					"does not support PUSH ResultSet data storage.", 
					IndexResultSetFaultMessage.ERROR_UNSUPPORTED);
		} else {
			PullResultSetObject pullRsObject = (PullResultSetObject) rsObject;
			pullRsObject.setLastAccessTime(lastAccessTime);
			
			if (pullRsObject.getSize()==0) {				
				log.warn("No records in ResultSet!");
				return new String[0];
			}
			
			if (fromPosition>pullRsObject.getSize()) {
				String err = "Out of range: " +fromPosition +" - " +toPosition +
						" , there is only "+ pullRsObject.getSize() + 
						" records in ResultSet"; 
				log.warn(err);
				return new String[0];
			}	
			
			if (toPosition>pullRsObject.getSize()) {
				log.warn(toPosition+ " range limit is bigger then the number" +
						" of available records ("+pullRsObject.getSize()+")");
				toPosition=pullRsObject.getSize();
			}
			
			int initialPageSize = pullRsObject.getInitialPageSize();
			int firstElement = fromPosition;
			int lastElement = toPosition;
			
			int numberOfExpectedElements = lastElement - firstElement + 1;

			if (numberOfExpectedElements > initialPageSize) {
				int elementsDiff = numberOfExpectedElements - initialPageSize;
				lastElement = lastElement - elementsDiff;
			}	
			
			try {
				log.debug("Final range of pages for dataprovider:" +firstElement +" - " +lastElement);
				
				String[] results = indexDataProvider.getSimpleBulkData(pullRsObject.getBdId(), 
						firstElement, lastElement);
			
				if (results == null | results.length == 0){
					log.debug("returning empty result.");
					return new String[0];
				} else {
					log.debug("Pick up "+results.length+" records from dataprovider in: "+(System.currentTimeMillis()-startTime) + " ms");
					return results;
				}
				
			} catch (DataProviderException e) {
				log.error("A DataProviderException has been thrown when getting results " +
						"from  data provider!",e);
					throw new IndexResultSetFaultMessage("A DataProviderException has been thrown when getting results " +
						"from data provider" + 
						"; internal error message: "+e.getMessage(), e, IndexResultSetFaultMessage.ERROR_UNKNOWN);
			} catch(Exception e){
				log.error("exception in getData.", e);
				throw new IndexResultSetFaultMessage("exception in getData", e, IndexResultSetFaultMessage.ERROR_UNKNOWN);
			}
			
					}
	}
	
	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.containers.IResultSetObjectContainer#store(eu.dnetlib.resultset.impl.ResultSetObject)
	 */
	public void store(ResultSetObject rsObject) throws IndexResultSetFaultMessage {
		if (rsObject==null)
			return;
		if (rsObject.getResultSetType()==ResultSetType.PUSH_RS)
			throw new IndexResultSetFaultMessage("ResultSetObjectNoCacheContainer " +
					"does not support PUSH ResultSet data storage.",
					IndexResultSetFaultMessage.ERROR_UNSUPPORTED); 
		rsData.put(rsObject.getResultSetId(), rsObject);
	}
	
	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.containers.IResultSetObjectContainer#remove(java.lang.String)
	 */
	public ResultSetObject remove(String rsId) {
		return rsData.remove(rsId);
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.containers.IResultSetObjectContainer#cleanup()
	 */
	public int cleanup() {
		log.debug("starting cleanup operations...");
		int removedObjectsCount = 0;
		long currentTime = System.currentTimeMillis();
		synchronized(rsData) {
			Set<String> keySet = rsData.keySet();
			Iterator<String> keysIt = keySet.iterator();
			while (keysIt.hasNext()) {
				String currentKey = keysIt.next();
				ResultSetObject currentRsObj = rsData.get(currentKey);
				if (currentTime > currentRsObj.getExpirationTime()) {
					log.debug("removing rs: "+currentKey);
					keysIt.remove();
					removedObjectsCount++;
				}
			}
		}
		log.debug("cleanup operations finished");
		return removedObjectsCount;
	}

	/**
	 * Returns internal index data provider.
	 * @return internal index data provider
	 */
	public IDataProviderExt getIndexDataProvider() {
		return indexDataProvider;
	}

	/**
	 * Sets internal index data provider.
	 * @param indexDataProvider
	 */
	public void setIndexDataProvider(IDataProviderExt indexDataProvider) {
		this.indexDataProvider = indexDataProvider;
	}
	
}
