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

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.jws.WebService;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.ws.wsaddressing.W3CEndpointReference;

import org.apache.log4j.Logger;

import edu.emory.mathcs.backport.java.util.Arrays;
import eu.dnetlib.resultset.api.ICleanable;

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.api.IResultSet;
import eu.dnetlib.resultset.impl.ResultSetObject.ResultSetStatus;
import eu.dnetlib.resultset.impl.ResultSetObject.ResultSetType;
import eu.dnetlib.resultset.impl.containers.IResultSetObjectContainer;
import eu.dnetlib.resultset.impl.utils.Utils;

/**
 * Embedded index ResultSet.
 * @author Marek Horst
 * @version 0.01
 *
 */
@WebService
public class IndexResultSet implements IResultSet, ICleanable {

	protected static final Logger log = Logger.getLogger(IndexResultSet.class);
	
	public static final String RESULTSET_ID_PREFIX = "rs-";
	
	Random rand = new Random();
	
	/**
	 * ResultSetObject container.
	 */
	private IResultSetObjectContainer rsObjectContainer;
	
	/**
	 * Internal index data provider.
	 */
	private IDataProviderExt indexDataProvider;
	
	/**
	 * Internal data provider address. This is the one and only supported consumer refference.
	 */
	private String internalDataProviderServiceAddress;
	
	/**
	 * Result Set service address used for creating ResultSet EPR.
	 */
	private String serviceAddress;
	
	/**
	 * Result Set wsdl location used for creating ResultSet EPR
	 */
	private String wsdlLocation;
	
	/**
	 * ResultSet service version.
	 */
	private String resultSetVersion;
	
	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.IResultSet#closeRS(java.lang.String)
	 */
	public void closeRS(String rsId) throws IndexResultSetFaultMessage {
		ResultSetObject result = rsObjectContainer.get(rsId);
		if (result==null)
			throw new IndexResultSetFaultMessage("ResultSet "+rsId+" doesn't exist!", 
					IndexResultSetFaultMessage.ERROR_INVALID_RS);
		else {
			result.setStatus(ResultSetStatus.CLOSED);
			rsObjectContainer.store(result);
		}
		//return rsId;
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.IResultSet#createPushRS(int, eu.dnetlib.resultset.impl.CreatePushRSType)
	 */
	public W3CEndpointReference createPushRS(int expiryTime,
			CreatePushRSType createPushRS_type) throws IndexResultSetFaultMessage {
		String generatedRsId = generateInternalResultSetId();
		PushResultSetObject pushRs = new PushResultSetObject(generatedRsId,
				expiryTime, createPushRS_type);
		rsObjectContainer.store(pushRs);
		return buildW3CResultSetEPR(generatedRsId);
	}

	/**
	 * Generates internal result set identifier.
	 * @return internal result set id
	 */
	String generateInternalResultSetId() {
		return RESULTSET_ID_PREFIX + System.currentTimeMillis() + 
		"-" + rand.nextInt(1000);
	}
	
	/**
	 * Builds ResultSet EPR.
	 * @param rsId
	 * @return ResultSet EPR
	 */
	String buildResultSetEPR(String rsId) {
		return Utils.buildResultSetEPR(serviceAddress, rsId, wsdlLocation);
	}
	
	/**
	 * Builds W3C ResultSet EPR.
	 * @param rsId
	 * @return W3C ResultSet EPR
	 * @throws IndexResultSetFaultMessage 
	 */
	W3CEndpointReference buildW3CResultSetEPR(String rsId) throws IndexResultSetFaultMessage {
		try {
			return Utils.buildW3CResultSetEPR(serviceAddress, rsId, wsdlLocation);
		} catch (ParserConfigurationException e) {
			throw new IndexResultSetFaultMessage("Problem by creating EPR:", e.toString()); 
		}
	}
	
	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.IResultSet#deleteRS(java.lang.String)
	 */
	public boolean deleteRS(String rsId) {
		if (rsId==null)
			return false;
		ResultSetObject removed = rsObjectContainer.remove(rsId);
		if (removed!=null)
			return true;
		else
			return false;
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.IResultSet#getNumberOfElements(java.lang.String)
	 */
	public int getNumberOfElements(String rsId) throws IndexResultSetFaultMessage {
		if (rsId==null)
			throw new IndexResultSetFaultMessage("Got null rsId!",
					IndexResultSetFaultMessage.ERROR_INVALID_RS);
		ResultSetObject result = rsObjectContainer.get(rsId);
		if (result==null)
			throw new IndexResultSetFaultMessage("ResultSet "+rsId+" doesn't exist!", 
					IndexResultSetFaultMessage.ERROR_INVALID_RS);
		else
			return result.getSize();
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.IResultSet#getNumberOfResults(java.lang.String)
	 */
	public String getNumberOfResults(String bdId) throws IndexResultSetFaultMessage {
		
		log.info("Received rsId:"+bdId);
		
		if (bdId==null)
			throw new IndexResultSetFaultMessage("Got null rsId!",
					IndexResultSetFaultMessage.ERROR_INVALID_RS);
		ResultSetObject result = rsObjectContainer.get(bdId);
		if (result==null)
			throw new IndexResultSetFaultMessage("ResultSet "+bdId+" doesn't exist!", 
					IndexResultSetFaultMessage.ERROR_INVALID_RS);
		else {
			
			//Record3 resultHash = new Record3();
			//resultHash.setStatus(result.getStatus().toString());
			//resultHash.setTotal(result.getSize());
			
			return result.getStatus().toString()+result.getSize();
		}	
	}
	
	
	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.IResultSet#getRSStatus(java.lang.String)
	 */
	public String getRSStatus(String rsId) throws IndexResultSetFaultMessage {
		if (rsId==null)
			throw new IndexResultSetFaultMessage("Got null rsId!",
					IndexResultSetFaultMessage.ERROR_INVALID_RS);
		ResultSetObject result = rsObjectContainer.get(rsId);
		if (result==null)
			throw new IndexResultSetFaultMessage("ResultSet "+rsId+" doesn't exist!", 
					IndexResultSetFaultMessage.ERROR_INVALID_RS);
		else {
			if (result.getStatus()==ResultSetStatus.CLOSED)
				return ResultSetConstants.RESULT_SET_STATUS_CLOSED;
			else if (result.getStatus()==ResultSetStatus.OPEN)
				return ResultSetConstants.RESULT_SET_STATUS_OPEN;
			else
				throw new IndexResultSetFaultMessage("Unsupported ResultSet status!", 
						IndexResultSetFaultMessage.ERROR_UNKNOWN);
		}
	}
	
	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.IResultSet#getProperty(java.lang.String)
	 */
	public String getProperty(String rsId, String name) throws IndexResultSetFaultMessage {
		if (rsId==null)
			throw new IndexResultSetFaultMessage("Got null rsId!",
					IndexResultSetFaultMessage.ERROR_INVALID_RS);
		if (name==null)
			throw new IndexResultSetFaultMessage("Got null property name!",
					IndexResultSetFaultMessage.ERROR_INVALID_RS);
		
		ResultSetObject result = rsObjectContainer.get(rsId);
		if (result==null)
			throw new IndexResultSetFaultMessage("ResultSet "+rsId+" doesn't exist!", 
					IndexResultSetFaultMessage.ERROR_INVALID_RS);
		else {
			if (name.equals("rsType")) {
				if ((result.getResultSetType()==ResultSetType.PUSH_RS)) 
					return "push";
				else if ((result.getResultSetType()==ResultSetType.PULL_RS)) 
					return "pull";
				else
					throw new IndexResultSetFaultMessage("Unsupported ResultSet type!", 
								IndexResultSetFaultMessage.ERROR_UNKNOWN);
			}
			else if (name.equals("rsStatus")) {
				if (result.getStatus()==ResultSetStatus.CLOSED)
					return ResultSetConstants.RESULT_SET_STATUS_CLOSED;
				else if (result.getStatus()==ResultSetStatus.OPEN)
					return ResultSetConstants.RESULT_SET_STATUS_OPEN;
				else
					throw new IndexResultSetFaultMessage("Unsupported ResultSet status!", 
							IndexResultSetFaultMessage.ERROR_UNKNOWN);
			}
			else if (name.equals("expiryTime")) {
				return ""+result.getExpiryTime();
			}
			else if (name.equals("total")) {
				return ""+result.getSize();
			}
			else {
				throw new IndexResultSetFaultMessage("Property name '"+name+
						"' is not supported",IndexResultSetFaultMessage.ERROR_INVALID_RS);
			}
			
		}
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.IResultSet#getResult(java.lang.String, int, int, java.lang.String, eu.dnetlib.resultset.impl.GetResultType)
	 */
	@SuppressWarnings("unchecked")
	public List<String> getResult(String rsId, int fromPosition, int toPosition,
			String requestMode, GetResultType getResult_type) 
		throws IndexResultSetFaultMessage {
		return new ArrayList(Arrays.asList(simpleGetResult(rsId, fromPosition, toPosition, 
				requestMode, getResult_type)));
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.IResultSet#simpleGetResult(java.lang.String, int, int, java.lang.String)
	 */
	public String[] simpleGetResult(String rsId, int fromPosition, int toPosition,
			String requestMode) 
		throws IndexResultSetFaultMessage {
		return simpleGetResult(rsId, fromPosition, toPosition, requestMode, null);
	}
	
	/**
	 * Returns result from RS for given range, request mode and result type.
	 * @param rsId
	 * @param fromPosition
	 * @param toPosition
	 * @param requestMode
	 * @param getResult_type
	 * @return String[]
	 * @throws IndexResultSetFaultMessage
	 */
	public String[] simpleGetResult(String rsId, int fromPosition, int toPosition,
			String requestMode, GetResultType getResult_type) 
		throws IndexResultSetFaultMessage {
//		only default requestMode and resultType are supported.
		if (rsId==null)
			throw new IndexResultSetFaultMessage("Got null rsId!",
					IndexResultSetFaultMessage.ERROR_INVALID_RS);
		if (fromPosition>toPosition || toPosition < 1)
			return new String[0];

		return rsObjectContainer.getData(rsId, fromPosition, toPosition);
	}
	
	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.IResultSet#identify()
	 */
	public String identify() {
		return resultSetVersion;
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.IResultSet#populateRS(java.lang.String, eu.dnetlib.resultset.impl.Array1)
	 */
	public String populateRS(String rsId, List<String> elements) 
		throws IndexResultSetFaultMessage {
		if (rsId==null)
			throw new IndexResultSetFaultMessage("Got null rsId!",
					IndexResultSetFaultMessage.ERROR_INVALID_RS);
		if (elements==null || elements.isEmpty()  
				|| elements.size()==0)
			return rsId;
		
		ResultSetObject rsObject = rsObjectContainer.get(rsId);
		if (rsObject==null)
			throw new IndexResultSetFaultMessage("ResultSet "+rsId+" doesn't exist!",
					IndexResultSetFaultMessage.ERROR_INVALID_RS);
		if (!(rsObject.getResultSetType()==ResultSetType.PUSH_RS))
			throw new IndexResultSetFaultMessage("Populate can be performed on push result sets only!",
					IndexResultSetFaultMessage.ERROR_UNSUPPORTED);
		
		PushResultSetObject pushRsObject = (PushResultSetObject) rsObject;	
		String[] foundResult = pushRsObject.getData();
		if (foundResult==null || foundResult.length==0) {
			pushRsObject.setData(elements.toArray(new String[0]));
			rsObjectContainer.store(pushRsObject);
		} else {
			String[] joinedTables = ResultSetUtils.joinStringTables(foundResult, 
					elements.toArray(new String[0]));
			pushRsObject.setData(joinedTables);
			rsObjectContainer.store(pushRsObject);
		}
		return rsId;
	}
	
	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.IResultSet#refreshExpiryTime(java.lang.String, int)
	 */
	public boolean refreshExpiryTime(String rsId, int expiryTime) 
		throws IndexResultSetFaultMessage {
		if (rsId==null)
			throw new IndexResultSetFaultMessage("Got null rsId!",
					IndexResultSetFaultMessage.ERROR_INVALID_RS);
		if (expiryTime<0)
			throw new IndexResultSetFaultMessage("Expity time value cannot be less than 0!",
					IndexResultSetFaultMessage.ERROR_UNKNOWN);
		
		ResultSetObject rsObject = rsObjectContainer.get(rsId);
		if (rsObject==null)
			throw new IndexResultSetFaultMessage("ResultSet "+rsId+" doesn't exist!",
					IndexResultSetFaultMessage.ERROR_INVALID_RS);
		else {
			rsObject.setExpiryTime(expiryTime);
			rsObjectContainer.store(rsObject);
			return true;
		}
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.IResultSet#removeResult(java.lang.String, int)
	 */
	public String removeResult(String rsId, int position)
		throws IndexResultSetFaultMessage {
		if (rsId==null)
			throw new IndexResultSetFaultMessage("Got null rsId!",
					IndexResultSetFaultMessage.ERROR_INVALID_RS);
		ResultSetObject rsObject = rsObjectContainer.get(rsId);
		if (rsObject==null)
			throw new IndexResultSetFaultMessage("ResultSet "+rsId+" doesn't exist!",
					IndexResultSetFaultMessage.ERROR_INVALID_RS);
		
		if (!(rsObject.getResultSetType()==ResultSetType.PUSH_RS))
			throw new IndexResultSetFaultMessage("Removing result can be performed " +
					"on push result sets only!",
					IndexResultSetFaultMessage.ERROR_UNSUPPORTED);
		
		PushResultSetObject pushRsObject = (PushResultSetObject) rsObject;
		if (position<0 ||
				position>=pushRsObject.getData().length)
			throw new IndexResultSetFaultMessage("Invalid position value: "+position+"!",
					IndexResultSetFaultMessage.ERROR_UNKNOWN);
		String content = pushRsObject.getData()[position];
		pushRsObject.getData()[position] = null;
		rsObjectContainer.store(pushRsObject);
		return content;

	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.IResultSet#createPullRS(java.lang.String, java.lang.String, int, int, eu.dnetlib.resultset.impl.CreatePullRSType)
	 */
	public W3CEndpointReference createPullRS(String dataProviderServiceAddress, 
			String bdId, int initialPageSize, int expiryTime, 
			CreatePullRSType createPullRS_type) 
		throws IndexResultSetFaultMessage {
		if (dataProviderServiceAddress==null || bdId==null)
			throw new IndexResultSetFaultMessage("Neither dataProviderServiceAddress " +
					"nor bdId can be null!",
					IndexResultSetFaultMessage.ERROR_INVALID_PARAM);
		
		if (initialPageSize<1)
			throw new IndexResultSetFaultMessage("initialPageSize value can't be less than 1!",
					IndexResultSetFaultMessage.ERROR_INVALID_PARAM);
		if (expiryTime<1)
			throw new IndexResultSetFaultMessage("expiryTime value can't be less than 1!",
					IndexResultSetFaultMessage.ERROR_INVALID_PARAM);
		
		if (!dataProviderServiceAddress.equals(internalDataProviderServiceAddress))
			throw new IndexResultSetFaultMessage("Only internal data provider is supported!",
					IndexResultSetFaultMessage.ERROR_UNSUPPORTED);
		String rsId = generateInternalResultSetId();
//		support for total parameter set in createPullRS_type
		int totalSize;
		if (createPullRS_type!=null && createPullRS_type.getTotal() !=null) {
			totalSize = createPullRS_type.getTotal();
			log.debug("got total size in createPullRS_type: "+totalSize);
		} else {
			ResultsResponse resResp;
			try {
				resResp = indexDataProvider.getNumberOfResults(bdId);
				totalSize = resResp.getTotal();
			} catch (DataProviderException e) {
				log.error("Exception occured when getting number of results " +
						"from index data provider!",e);
				throw new IndexResultSetFaultMessage("Couldn't retrieve number of results " +
						"from index data provider" + 
						"; internal error message: "+e.getMessage() ,e,
						IndexResultSetFaultMessage.ERROR_UNKNOWN);
			}
		}

		PullResultSetObject rsObject = new PullResultSetObject(rsId, expiryTime, 
				dataProviderServiceAddress, bdId, initialPageSize, totalSize, createPullRS_type);
		
		if (createPullRS_type != null)
			if ("OPEN".equalsIgnoreCase(createPullRS_type.getStyleSheet()))
				rsObject.setStatus(ResultSetStatus.OPEN);
		
//		prefetching is currently disabled, because first page of results is cached in data provider
//		no need for double caching
		/*
		if (totalSize>0) {
			try {
				log.debug("Retrieving first page of results from data provider " +
						"for bdId "+bdId);
				String[] pageData = indexDataProvider.getSimpleBulkData(bdId, 1, 
						initialPageSize<totalSize?initialPageSize:totalSize);
				rsObject.setPageRetrieved(1);
				rsObject.setPageData(1,pageData);
			} 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);
			}
		}
		*/
		rsObjectContainer.store(rsObject);
		return buildW3CResultSetEPR(rsId);
		
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.IResultSet#fusion(int, eu.dnetlib.resultset.impl.Array1)
	 */
	public String fusion(int expiryTime, Array1 eprs) throws IndexResultSetFaultMessage {
		throw new IndexResultSetFaultMessage("This operation is unsupported in this ResultSet implementation!",
				IndexResultSetFaultMessage.ERROR_UNSUPPORTED);
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.IResultSet#preparePullRS(java.lang.String, int, int, eu.dnetlib.resultset.impl.PreparePullRSType)
	 */
	public String preparePullRS(String dataProviderServiceAddress, int initialPageSize, int expiryTime, PreparePullRSType preparePullRS_type) throws IndexResultSetFaultMessage {
		throw new IndexResultSetFaultMessage("This operation is unsupported in this ResultSet implementation!",
				IndexResultSetFaultMessage.ERROR_UNSUPPORTED);
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.IResultSet#rSasResource(java.lang.String, java.lang.String)
	 */
	public String rSasResource(String rsId, String label) throws IndexResultSetFaultMessage {
		throw new IndexResultSetFaultMessage("This operation is unsupported in this ResultSet implementation!",
				IndexResultSetFaultMessage.ERROR_UNSUPPORTED);
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.IResultSet#startPullRS(java.lang.String, java.lang.String, int, eu.dnetlib.resultset.impl.StartPullRSType)
	 */
	public String startPullRS(String rsId, String bdId, int expiryTime, StartPullRSType startPullRS_type) throws IndexResultSetFaultMessage {
		throw new IndexResultSetFaultMessage("This operation is unsupported in this ResultSet implementation!",
				IndexResultSetFaultMessage.ERROR_UNSUPPORTED);
	}

	/* (non-Javadoc)
	 * @see pl.edu.icm.driver.ICleanable#cleanup()
	 */
	public void cleanup() {
		int removedCount = rsObjectContainer.cleanup();
		if (removedCount>0)
			log.info("removed " + removedCount + " ResultSetObjects!");
	}
	
	/**
	 * Returns service address.
	 * @return service address
	 */
	public String getServiceAddress() {
		return serviceAddress;
	}

	/**
	 * Sets service address.
	 * @param serviceAddress
	 */
	public void setServiceAddress(String serviceAddress) {
		this.serviceAddress = serviceAddress;
	}

	/**
	 * Returns WSDL location.
	 * @return WSDL location
	 */
	public String getWsdlLocation() {
		return wsdlLocation;
	}

	/**
	 * Sets WSDL location.
	 * @param wsdlLocation
	 */
	public void setWsdlLocation(String wsdlLocation) {
		this.wsdlLocation = wsdlLocation;
	}

	/**
	 * 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 internal data provider service address.
	 * @return internal data provider service address
	 */
	public String getInternalDataProviderServiceAddress() {
		return internalDataProviderServiceAddress;
	}

	/**
	 * Sets internal data provider service address.
	 * @param internalDataProviderServiceAddress
	 */
	public void setInternalDataProviderServiceAddress(
			String internalDataProviderServiceAddress) {
		this.internalDataProviderServiceAddress = internalDataProviderServiceAddress;
	}

	/**
	 * Returns ResultSetObject container.
	 * @return ResultSetObject container
	 */
	public IResultSetObjectContainer getRsObjectContainer() {
		return rsObjectContainer;
	}

	/**
	 * Sets ResultSetObject container.
	 * @param rsObjectContainer
	 */
	public void setRsObjectContainer(IResultSetObjectContainer rsObjectContainer) {
		this.rsObjectContainer = rsObjectContainer;
	}

	/**
	 * Returns ResultSet service version.
	 * @return ResultSet service version
	 */
	public String getResultSetVersion() {
		return resultSetVersion;
	}

	/**
	 * Sets ResultSet service version.
	 * @param resultSetVersion
	 */
	public void setResultSetVersion(String resultSetVersion) {
		this.resultSetVersion = resultSetVersion;
	}

}
