package eu.dnetlib.data.textengine.resultset.pull;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

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

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import eu.dnetlib.common.ws.dataprov.DataProviderException;
import eu.dnetlib.common.ws.dataprov.IDataProviderExt;
import eu.dnetlib.common.ws.dataprov.ResultsResponse;
import eu.dnetlib.data.textengine.resultset.ResultsetException;
import eu.dnetlib.resultset.impl.CreatePullRSType;
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.ResultSetStatus;
import eu.dnetlib.resultset.impl.ResultSetObject.ResultSetType;
import eu.dnetlib.resultset.impl.containers.IResultSetObjectContainer;
import eu.dnetlib.resultset.impl.utils.Utils;

public class PullResultset {

	private static final Log log = LogFactory.getLog(PullResultset.class);
	
	private IDataProviderExt internalDataProvider;

	/**
	 * ResultSetObject container.
	 */
	private IResultSetObjectContainer rsObjectContainer;

	/**
	 * Internal data provider address. This is the one and only supported consumer reference.
	 */
	private String internalDataProviderServiceAddress;
	private String serviceAddress;
	private String wsdlLocation;

	public W3CEndpointReference createPullRS(String dataProviderServiceAddress, 
			String bdId, int initialPageSize, int expiryTime, 
			CreatePullRSType createPullRS_type) throws ResultsetException{
		W3CEndpointReference epr = null;
		
		if (dataProviderServiceAddress==null || bdId==null)
			throw new ResultsetException("Neither dataProviderServiceAddress " +
					"nor bdId can be null!");

		if (initialPageSize<1)
			throw new ResultsetException("initialPageSize value can't be less than 1!");
		if (expiryTime<1)
			throw new ResultsetException("expiryTime value can't be less than 1!");
		log.debug("internalDataProvAddress: " + internalDataProviderServiceAddress);
		if (!dataProviderServiceAddress.equals(internalDataProviderServiceAddress))
			throw new ResultsetException("Only internal data provider is supported!");
		String rsId = generateInternalResultSetId();

		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 = internalDataProvider.getNumberOfResults(bdId);
				totalSize = resResp.getTotal();
			} catch (DataProviderException e) {
				log.error("Exception occured when getting number of results " +
						"from internal data provider!",e);
				throw new ResultsetException("Couldn't retrieve number of results " +
						"from internal data provider" + 
						"; internal error message: "+e.getMessage() ,e);
			}
		}
		PullResultSetObject rsObject = new PullResultSetObject(rsId, expiryTime, 
				dataProviderServiceAddress, bdId, initialPageSize, totalSize, createPullRS_type);

		try {
			rsObjectContainer.store(rsObject);
		} catch (IndexResultSetFaultMessage e) {
			log.error(e);
			throw new ResultsetException(e);
		}
		epr = buildW3CResultSetEPR(rsId);
		log.debug(epr.toString());
		return epr;
	}

	/**
	 * Builds W3C ResultSet EPR.
	 * @param rsId
	 * @return W3C ResultSet EPR
	 * @throws ResultsetException
	 */
	private W3CEndpointReference buildW3CResultSetEPR(String rsId) throws ResultsetException{
		try {
			return Utils.buildW3CResultSetEPR(serviceAddress, rsId, wsdlLocation);
		} catch (ParserConfigurationException e) {
			throw new ResultsetException("Problem by creating EPR:"); 
		}
	}

	private String generateInternalResultSetId() {
		return "rs-" + UUID.randomUUID().toString();
	}

	public String getInternalDataProviderServiceAddress() {
		return internalDataProviderServiceAddress;
	}

	public void setInternalDataProviderServiceAddress(
			String internalDataProviderServiceAddress) {
		this.internalDataProviderServiceAddress = internalDataProviderServiceAddress;
	}

	public void setInternalDataProvider(IDataProviderExt internalDataProvider) {
		this.internalDataProvider = internalDataProvider;
	}

	public IDataProviderExt getInternalDataProvider() {
		return internalDataProvider;
	}

	public IResultSetObjectContainer getRsObjectContainer() {
		return rsObjectContainer;
	}

	public void setRsObjectContainer(IResultSetObjectContainer rsObjectContainer) {
		this.rsObjectContainer = rsObjectContainer;
	}

	public String getServiceAddress() {
		return serviceAddress;
	}

	public void setServiceAddress(String serviceAddress) {
		this.serviceAddress = serviceAddress;
	}

	public String getWsdlLocation() {
		return wsdlLocation;
	}

	public void setWsdlLocation(String wsdlLocation) {
		this.wsdlLocation = wsdlLocation;
	}

	public void closeRS(String rsId) throws ResultsetException{
		ResultSetObject result = rsObjectContainer.get(rsId);
		if (result==null)
			throw new ResultsetException("ResultSet "+rsId+" doesn't exist!");
		else {
			result.setStatus(ResultSetStatus.CLOSED);
			try {
				rsObjectContainer.store(result);
			} catch (IndexResultSetFaultMessage e) {
				log.error(e);
				throw new ResultsetException(e);
			}
		}		
	}

	public int getNumberOfElements(String rsId) throws ResultsetException{
		if (rsId==null)
			throw new ResultsetException("Got null rsId!");
		ResultSetObject result = rsObjectContainer.get(rsId);
		if (result==null)
			throw new ResultsetException("ResultSet "+rsId+" doesn't exist!");
		else
			return result.getSize();
	}

	public String getProperty(String rsId, String name) throws ResultsetException{
		if (rsId==null)
			throw new ResultsetException("Got null rsId!");
		if (name==null)
			throw new ResultsetException("Got null property name!");
		
		ResultSetObject result = rsObjectContainer.get(rsId);
		if (result==null)
			throw new ResultsetException("ResultSet "+rsId+" doesn't exist!");
		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 ResultsetException("Unsupported ResultSet type!");
			}
			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 ResultsetException("Unsupported ResultSet status!");
			}
			else if (name.equals("expiryTime")) {
				return ""+result.getExpiryTime();
			}
			else if (name.equals("total")) {
				return ""+result.getSize();
			}
			else {
				throw new ResultsetException("Property name '"+name+
						"' is not supported");
			}
			
		}
	}

	public String getRSStatus(String rsId) throws ResultsetException{
		if (rsId==null)
			throw new ResultsetException("Got null rsId!");
		ResultSetObject result = rsObjectContainer.get(rsId);
		if (result==null)
			throw new ResultsetException("ResultSet "+rsId+" doesn't exist!");
		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 ResultsetException("Unsupported ResultSet status!");
		}
	}

	public List<String> getResult(String rsId, int fromPosition,
			int toPosition, String requestMode) throws ResultsetException{
		if (rsId==null)
			throw new ResultsetException("Got null rsId!");
		if (fromPosition>toPosition || toPosition < 1)
			return new ArrayList<String>(Arrays.asList(new String[0]));

		try {
			return new ArrayList<String>(Arrays.asList(rsObjectContainer.getData(rsId, fromPosition, toPosition)));
		} catch (IndexResultSetFaultMessage e) {
			log.error(e);
			throw new ResultsetException(e);
		}
	}

	public String populateRS(String rsId, List<String> elements) throws ResultsetException{
		if (rsId==null)
			throw new ResultsetException("Got null rsId!");
		if (elements==null || elements.isEmpty()  
				|| elements.size()==0)
			return rsId;
		
		ResultSetObject rsObject = rsObjectContainer.get(rsId);
		if (rsObject==null)
			throw new ResultsetException("ResultSet "+rsId+" doesn't exist!");
		if (!(rsObject.getResultSetType()==ResultSetType.PUSH_RS))
			throw new ResultsetException("Populate can be performed on push result sets only!");
		
		PushResultSetObject pushRsObject = (PushResultSetObject) rsObject;	
		String[] foundResult = pushRsObject.getData();
		if (foundResult==null || foundResult.length==0) {
			pushRsObject.setData(elements.toArray(new String[0]));
			try {
				rsObjectContainer.store(pushRsObject);
			} catch (IndexResultSetFaultMessage e) {
				log.error(e);
				throw new ResultsetException(e);
			}
		} else {
			String[] joinedTables = ResultSetUtils.joinStringTables(foundResult, 
					elements.toArray(new String[0]));
			pushRsObject.setData(joinedTables);
			try {
				rsObjectContainer.store(pushRsObject);
			} catch (IndexResultSetFaultMessage e) {
				log.error(e);
				throw new ResultsetException(e);
			}
		}
		return rsId;
	}

}
