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

import org.apache.log4j.Logger;
import org.hibernate.Hibernate;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import eu.dnetlib.common.ws.dataprov.DataProviderException;
import eu.dnetlib.common.ws.dataprov.IDataProviderExt;
import eu.dnetlib.resultset.impl.IndexResultSetFaultMessage;
import eu.dnetlib.resultset.impl.PullResultSetObject;
import eu.dnetlib.resultset.impl.PushResultSetObject;
import eu.dnetlib.resultset.impl.ResultSetConstants;
import eu.dnetlib.resultset.impl.ResultSetObject;
import eu.dnetlib.resultset.impl.ResultSetUtils;
import eu.dnetlib.resultset.impl.ResultSetObject.ResultSetType;

/**
 * Hibernate3 based ResultSetObject container.
 * KeepAliveTime is currently unsupported. Use refreshExpiryTime() instead.
 * @author Marek Horst
 * @version 0.01
 *
 */
public class ResultSetObjectHibernateContainer extends HibernateDaoSupport implements
		IResultSetObjectContainer {

	protected static final Logger log = Logger.getLogger(ResultSetObjectHibernateContainer.class);	
	
	
	/**
	 * Internal index data provider.
	 */
	private IDataProviderExt indexDataProvider;
	
	/**
	 * Caching flag. If set to false no RS data will be stored in rsObjectContainer.
	 */
	private boolean cachingEnabled;
	
	/**
	 * Spring destroy-metod.
	 */
	public void destroy() {
		log.debug("destroy-method invoked");
		getSession().flush();
		getSession().close();
//		TODO not sure if hypersonic will flush all data as well;
		log.debug("destroy-method finished");
	}
	
	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.containers.IResultSetObjectContainer#cleanup()
	 */
	public int cleanup() {
		log.debug("starting cleanup operations...");
		String query = "DELETE FROM ResultSetObject " +
				"where (creationTime + (expiryTime * 1000)) < :currentTime";
		Session ses = getSession();
		Transaction tx = ses.beginTransaction(); 
		int updatedEntities = getSession().createQuery(query).
			setLong("currentTime", System.currentTimeMillis()).executeUpdate();
		tx.commit();
		ses.close();
		log.debug("cleanup operations finished");
		return updatedEntities;
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.containers.IResultSetObjectContainer#get(java.lang.String)
	 */
	public ResultSetObject get(String rsId) {
		if (rsId==null)
			throw new InvalidParameterException("rsId cannot be null!");
		return (ResultSetObject) getHibernateTemplate().get(ResultSetObject.class, rsId);
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.containers.IResultSetObjectContainer#getWithStoredData(java.lang.String)
	 */
	public ResultSetObject getWithStoredData(String rsId) {
		Session ses = getSession();
		try {
			ResultSetObject rsObject = (ResultSetObject) ses.get(ResultSetObject.class, rsId);
			if (rsObject==null)
				return null;
			if (rsObject.getResultSetType()==ResultSetType.PULL_RS) {
//				lazy data initialization
				PullResultSetObject pullRsObject = (PullResultSetObject) rsObject;
				Hibernate.initialize(pullRsObject.getData());
			}
			
			return rsObject;
		} finally {
			if (ses!=null)
				ses.close();
		}
	}
	
	/* (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 {
		Session ses = getSession();
		long startTime;
		try {
			startTime = System.currentTimeMillis();
			ResultSetObject rsObject = (ResultSetObject) ses.get(ResultSetObject.class, rsId);
			log.debug("ses.get() time: " + (System.currentTimeMillis() - startTime));
			if (rsObject==null)
				throw new IndexResultSetFaultMessage("ResultSet "+rsId+" doesn't exist!", 
						IndexResultSetFaultMessage.ERROR_INVALID_RS);
			
			if (fromPosition<1)
				fromPosition=1;
			
			if (rsObject.getResultSetType()==ResultSetType.PUSH_RS) {
//				PUSH RS
				PushResultSetObject pushRsObject = (PushResultSetObject) rsObject;
//				adapting fromPosition offset
				fromPosition--;
				if (toPosition!=ResultSetConstants.RESULT_SET_END_POSITION_UNSPECIFIED)
				toPosition--;
				
				String[] result = pushRsObject.getData();
				if (result==null ||	fromPosition > result.length)
					return new String[0];
				
				if (toPosition==ResultSetConstants.RESULT_SET_END_POSITION_UNSPECIFIED) {
					if (fromPosition==0) {
						return result;
					}else
						return ResultSetUtils.getSubArray(result,fromPosition,result.length-1);
				} else {
					if (toPosition<fromPosition)
						return new String[0];
					if (toPosition > result.length-1)
						toPosition = result.length-1;
					return ResultSetUtils.getSubArray(result,fromPosition,toPosition);
				}
			} else {
//				PULL RS
				PullResultSetObject pullRsObject = (PullResultSetObject) rsObject;
				if (pullRsObject.getSize()==0)
					return new String[0];
				
				if (fromPosition>pullRsObject.getSize())
					return new String[0];
//					fromPosition=pullRsObject.getSize();
					
				if (toPosition>pullRsObject.getSize())
					toPosition=pullRsObject.getSize();

//				updating data from data provider if needed
				int totalElements = pullRsObject.getSize();
				int initialPageSize = pullRsObject.getInitialPageSize();
				int startPageNumber = (fromPosition-1)/initialPageSize + 1;
				log.debug("start page number: "+startPageNumber);
				int lastAvailaibleElementNumber = initialPageSize*startPageNumber;
				int currentPageNumber = startPageNumber;
				boolean stopWorking = false;
				boolean updatedData = false;
				
				while (!stopWorking) {
					log.debug("working on curentPageNumber: "+currentPageNumber);
					if (!pullRsObject.isPageRetreived(currentPageNumber)) {
						lastAvailaibleElementNumber = initialPageSize*currentPageNumber;
						int pckgStartElement = initialPageSize*(currentPageNumber-1) + 1;
						log.debug("pckgStartElement: "+pckgStartElement);
						if (lastAvailaibleElementNumber>=toPosition) {
							stopWorking = true;
						} 
						int pckgEndElement = lastAvailaibleElementNumber<totalElements?
								lastAvailaibleElementNumber:totalElements;
						log.debug("pckgEndElement: "+pckgEndElement);
						try {
							log.debug("getting bulkData for bdId: "+pullRsObject.getBdId()+
									", fromPosition: "+pckgStartElement+
									", toPosition: "+pckgEndElement);
							String[] pageData = indexDataProvider.getSimpleBulkData(pullRsObject.getBdId(), 
									pckgStartElement, pckgEndElement);
							pullRsObject.setPageRetrieved(currentPageNumber);
							pullRsObject.setPageData(currentPageNumber, pageData);
							updatedData = true;
						} catch (DataProviderException e) {
							log.error("Exception occured when getting results " +
									"from index data provider!",e);
								throw new IndexResultSetFaultMessage("Exception occured when getting results " +
									"from index data provider" + 
									"; internal error message: "+e.getMessage(), e, IndexResultSetFaultMessage.ERROR_UNKNOWN);
						}
					} else {
//						initializing lazy collection data (do I need that, should be initialized aut. when getData(from, to)
//						Hibernate.initialize(pullRsObject.getData().get(currentPageNumber));
						
						lastAvailaibleElementNumber = initialPageSize*currentPageNumber;
						if (lastAvailaibleElementNumber>=toPosition) {
							stopWorking = true;
						} 
					}
					currentPageNumber = currentPageNumber+1;
				}
//				updating data in rsObjectContainer
				if (updatedData && isCachingEnabled()) {
					startTime = System.currentTimeMillis();
					ses.update(pullRsObject);
					log.debug("ses.update() time: " + (System.currentTimeMillis() - startTime));
//					ses.persist(pullRsObject);
//					ses.saveOrUpdate(pullRsObject);
//					is it required? should be stored in memory anyway
					startTime = System.currentTimeMillis();
					ses.flush();
					log.debug("ses.flush() time: " + (System.currentTimeMillis() - startTime));
				}
				return pullRsObject.getData(fromPosition, toPosition);
			}
		} finally {
			if (ses!=null) {
				startTime = System.currentTimeMillis();
				ses.close();
				log.debug("ses.close() time: " + (System.currentTimeMillis() - startTime));
			}
		}
	}
	
	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.containers.IResultSetObjectContainer#remove(java.lang.String)
	 */
	public ResultSetObject remove(String rsId) {
		if (rsId==null)
			throw new InvalidParameterException("rsId cannot be null!");
		ResultSetObject delObj = (ResultSetObject) getHibernateTemplate().get(ResultSetObject.class, rsId);
		if (delObj!=null) {
			getHibernateTemplate().delete(delObj);
			return delObj;
		} else
			return null;
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.containers.IResultSetObjectContainer#store(eu.dnetlib.resultset.impl.ResultSetObject)
	 */
	public void store(ResultSetObject rsObject) {
		if (rsObject!=null)
			getHibernateTemplate().saveOrUpdate(rsObject);
	}

	/**
	 * 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;
	}
	
	/**
	 * Returns caching flag.
	 * @return caching flag
	 */
	public boolean isCachingEnabled() {
		return cachingEnabled;
	}

	/**
	 * Sets caching flag.
	 * @param cachingEnabled
	 */
	public void setCachingEnabled(boolean cachingEnabled) {
		this.cachingEnabled = cachingEnabled;
	}
	
}
