package eu.dnetlib.data.utility.featureextraction.plugin;

import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

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

import eu.dnetlib.common.utils.XMLSerializer;
import eu.dnetlib.data.utility.featureextraction.FeatureExtractionException;
import eu.dnetlib.data.utility.featureextraction.IExtractionTaskProperties;
import eu.dnetlib.data.utility.featureextraction.dao.IResultDao;
import eu.dnetlib.data.utility.featureextraction.dataprovider.SourceDataProvider;
import eu.dnetlib.data.utility.featureextraction.record.DescriptionRecord;

/**
 * The Class AbstractPlugin.
 * 
 * @author jochen
 */
public abstract class AbstractPlugin implements IAbstractPlugin{

	/** The Constant log. */
	private static final Log log = LogFactory.getLog(AbstractPlugin.class);
	
	/** The dao. */
	protected IResultDao dao;
	
	/** The properties. */
	protected IExtractionTaskProperties properties;
	
	/** The source queue. */
	protected Queue<String> sourceQueue;
	
	/** The source data provider. */
	protected SourceDataProvider sourceDataProvider;
	
	/** The is finished. */
	protected boolean isFinished = false;
	
	/** The total size. */
	private int totalSize = 0;
	
	/** The start time. */
	private long startTime = 0;
	
	/** The start element. */
	private int startElement 	= 1;
	
	/** The end element. */
	private int endElement		= 0;
	
	/** The current size. */
	private int currentSize = 0;
	
	/** The description record handler. */
	protected XMLSerializer<DescriptionRecord> descriptionRecordHandler;
	
	/** The is initialized. */
	private boolean isInitialized = false;
	
	/** The wait for closing rs timeout. */
	private int waitForClosingRSTimeout;
	
	/** The default packages size for rs. */
	private int defaultPackagesSizeForRS = 50;

	/* (non-Javadoc)
	 * @see eu.dnetlib.data.utility.featureextraction.plugin.IAbstractPlugin#init()
	 */
	public void init() throws FeatureExtractionException{
		this.sourceQueue = new LinkedList<String>();
		this.isInitialized = true;
		
		String name = null;
		if (properties.getFeature() != null && properties.getFeature().getName() != null)
			name = properties.getFeature().getName();
		if (!"fulltext".equals(name)) {
			this.totalSize = this.sourceDataProvider.getSize();
		}
	}
	
	
	/**
	 * Next record.
	 * 
	 * @return the string
	 * 
	 * @throws FeatureExtractionException the feature extraction exception
	 */
	public String nextRecord()throws FeatureExtractionException{
		int count = 0;
		while (!isInitialized){
			log.debug("count: " + count);
			if (count == 5){
				throw new FeatureExtractionException("the plugin is not initialized.");
			}
			try {
				count++;
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				throw new FeatureExtractionException(e);
			}
		}
		log.debug("currSize: " + currentSize + " , totalSize: " + totalSize + ", queuesiue: " + sourceQueue.size());
		if (sourceQueue.isEmpty() && currentSize < totalSize){
			log.debug("retrieve records");
			retrieveRecords();
		}
		String record = sourceQueue.poll();
		if (record != null) currentSize++;
		log.debug("current size: " + currentSize);
		if (record == null){
			log.debug("record is null");
		}
		return record;
	}
	
	/**
	 * Retrieve records.
	 * 
	 * @throws FeatureExtractionException the feature extraction exception
	 */
	private void retrieveRecords() throws FeatureExtractionException{
		log.debug("retrieve records");
		int from = currentSize + 1;
		int to   = from + defaultPackagesSizeForRS - 1;
		if (to > totalSize) to = totalSize;
		log.debug("from:" + from + " to: " + to);
		sourceQueue.addAll(sourceDataProvider.getRecords(from, to));
		log.debug("from: " + from + ", to: " + to);
		log.debug("queue-size: " + sourceQueue.size());
	}
	
	/**
	 * Get the next record for full-text extraction plugin
	 * 
	 * @return the string
	 * 
	 * @throws FeatureExtractionException the feature extraction exception
	 */
	public String nextRecordFT()throws FeatureExtractionException{
		int count = 0;
		startTime = 0;
		boolean waitingForResults = true;
		
		while (!isInitialized){
			log.debug("count: " + count);
			if (count == 5){
				throw new FeatureExtractionException("the plugin is not initialized.");
			}
			try {
				count++;
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				throw new FeatureExtractionException(e);
			}
		}
		
		if (sourceQueue.isEmpty()) {
			while (waitingForResults) {
				currentSize = this.sourceDataProvider.getSize();
				log.debug("current size:" + currentSize);
				log.debug("total size:" + totalSize);
				
				if (currentSize != 0 ) {
					if (currentSize > totalSize) {
						waitingForResults = retrieveRecordsFT();
						
					} else {
						waitingForResults = checkRSStatus();
					}	
				} else {
					waitingForResults = checkRSStatus();
				}
			
			}
		}
		
		log.debug("currSize: " + currentSize + " , totalSize: " + totalSize + ", queuesiue: " + sourceQueue.size());
		
		String record = sourceQueue.poll();
		if (record == null){
			log.debug("record is null");
		}
		
		return record;
	}
	
	/**
	 * Retrieve records for full-text plugin.
	 * 
	 * @return true, if successful
	 * 
	 * @throws FeatureExtractionException the feature extraction exception
	 */
	private boolean retrieveRecordsFT() throws FeatureExtractionException{
		
		endElement = startElement + defaultPackagesSizeForRS - 1;
		if (endElement > currentSize)
			endElement = currentSize;
	
		log.debug("Requesting RS: from: " + startElement + ", to: " + endElement);
		log.debug("queue-size: " + sourceQueue.size());
		
		List<String> listOfRecords = sourceDataProvider.getRecords(startElement, endElement);
		
		if (listOfRecords != null && listOfRecords.size() != 0) {
			log.debug("PROCESSING: " +listOfRecords.size());
			totalSize = totalSize + listOfRecords.size();
			sourceQueue.addAll(listOfRecords);
			
			int resDiff = endElement - startElement + 1;
			if (resDiff != listOfRecords.size())
				startElement = totalSize + 1;
			else
				startElement = endElement + 1;
			return false;
			
		} else {
			log.debug("NO records returned from RS");
			return true;
		}	
	}
	
	/**
	 * Check rs status.
	 * 
	 * @return true, if successful
	 * 
	 * @throws FeatureExtractionException the feature extraction exception
	 */
	private boolean checkRSStatus() throws FeatureExtractionException {
		
		String status = this.sourceDataProvider.getStatus();
		
		if ("open".equalsIgnoreCase(status)) {
			
			if (startTime == 0) {
				startTime = System.currentTimeMillis()/1000;
			}
			
			long currentTime = System.currentTimeMillis()/1000;
			long timeDiff = currentTime-startTime;
				
			if ( timeDiff > this.waitForClosingRSTimeout) {
				log.error("Timeout " +
						"after "+timeDiff+"[sec], can not wait any more " +
						"for closing ResultSet"); 
				return false;
			} else {
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					throw new FeatureExtractionException(e);
				}
				return true;
			}
			
		} else if ("closed".equalsIgnoreCase(status)) {
			log.debug("ResultSet is closed, no more records");
			return false;
		} else {
			log.debug("No proper status returned by ResultSet: " + status );
			return false;
		}
	}

	/**
	 * Checks if is finished.
	 * 
	 * @return the isFinished
	 */
	public boolean isFinished() {
		return isFinished;
	}

	/**
	 * Sets the finished.
	 * 
	 * @param isFinished the isFinished to set
	 */
	public void setFinished(boolean isFinished) {
		this.isFinished = isFinished;
	}
	
	/**
	 * Sets the dao.
	 * 
	 * @param dao the dao to set
	 */
	public void setDao(IResultDao dao) {
		this.dao = dao;
	}

	/**
	 * Gets the dao.
	 * 
	 * @return the dao
	 */
	public IResultDao getDao() {
		return dao;
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.data.utility.featureextraction.plugin.IAbstractPlugin#getProperties()
	 */
	public IExtractionTaskProperties getProperties() {
		return properties;
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.data.utility.featureextraction.plugin.IAbstractPlugin#setProperties(eu.dnetlib.data.utility.featureextraction.IExtractionTaskProperties)
	 */
	public void setProperties(IExtractionTaskProperties properties) {
		this.properties = properties;
	}

	/**
	 * Sets the description record handler.
	 * 
	 * @param descriptionRecordHandler the descriptionRecordHandler to set
	 */
	public void setDescriptionRecordHandler(XMLSerializer<DescriptionRecord> descriptionRecordHandler) {
		this.descriptionRecordHandler = descriptionRecordHandler;
	}

	/**
	 * Gets the description record handler.
	 * 
	 * @return the descriptionRecordHandler
	 */
	public XMLSerializer<DescriptionRecord> getDescriptionRecordHandler() {
		return descriptionRecordHandler;
	}

	/**
	 * Gets the source data provider.
	 * 
	 * @return the source data provider
	 */
	public SourceDataProvider getSourceDataProvider() {
		return sourceDataProvider;
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.data.utility.featureextraction.plugin.IAbstractPlugin#setSourceDataProvider(eu.dnetlib.data.utility.featureextraction.dataprovider.SourceDataProvider)
	 */
	public void setSourceDataProvider(SourceDataProvider sourceDataProvider) {
		this.sourceDataProvider = sourceDataProvider;
	}

	/**
	 * Gets the wait for closing rs timeout.
	 * 
	 * @return the waitForClosingRSTimeout
	 */
	public int getWaitForClosingRSTimeout() {
		return waitForClosingRSTimeout;
	}

	/**
	 * Sets the wait for closing rs timeout.
	 * 
	 * @param waitForClosingRSTimeout the waitForClosingRSTimeout to set
	 */
	public void setWaitForClosingRSTimeout(int waitForClosingRSTimeout) {
		this.waitForClosingRSTimeout = waitForClosingRSTimeout;
	}

	/**
	 * Gets the default packages size for rs.
	 * 
	 * @return the defaultPackagesSizeForRS
	 */
	public int getDefaultPackagesSizeForRS() {
		return defaultPackagesSizeForRS;
	}

	/**
	 * Sets the default packages size for rs.
	 * 
	 * @param defaultPackagesSizeForRS the defaultPackagesSizeForRS to set
	 */
	public void setDefaultPackagesSizeForRS(int defaultPackagesSizeForRS) {
		this.defaultPackagesSizeForRS = defaultPackagesSizeForRS;
	}
}
