/**
 * Copyright 2008-2009 DRIVER PROJECT (Bielefeld University)
 * Original author: Marek Imialek <marek.imialek at uni-bielefeld.de>
 *
 * 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.data.sts.dataprov;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
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.data.sts.das.DataAccessServiceException;
import eu.dnetlib.data.sts.das.lls.LLSLookUpResponse;
import eu.dnetlib.data.sts.lls.LLSAccessFacade;

/**
 * Simple mdstore data provider.
 * 
 * @author <a href="mailto:marek.imialek at uni-bielefeld.de">Marek Imialek</a>
 * @version
 */
public class SimpleStoreDataProvider implements IDataAccessServiceDataProvider {

	/** The Constant log. */
	protected static final Logger log = Logger
			.getLogger(SimpleStoreDataProvider.class);

	/** The Constant DEFAULT_EXPIRY_TIME. */
	public static final int DEFAULT_EXPIRY_TIME = 86400;

	/** The rand. */
	Random rand;

	/** The data. */
	Map<String, DataProviderProperties> data;

	/** lucene storage directory. */
	private String luceneStorageDirectory;

	/** Default data provider expiry time (in seconds). */
	private int defaultExpiryTime;

	private LLSAccessFacade llsAccessFacade;

	/**
	 * Instantiates a new simple store data provider.
	 */
	public SimpleStoreDataProvider() {
		this.defaultExpiryTime = SimpleStoreDataProvider.DEFAULT_EXPIRY_TIME;
		this.rand = new Random();
		// synchronized data access
		this.data = Collections
				.synchronizedMap(new HashMap<String, DataProviderProperties>());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.unibielefeld.driver.is.dataprov.IStoreDataProvider#createBulkData(de.unibielefeld.driver.is.dataprov.CreateBulkDataDTO)
	 */
	public String createBulkData(CreateBulkDataDTO bulkDataDTO)
			throws DataProviderException {
		String bdId = generateBulkDataId();
		
		if (bulkDataDTO.getType() == CreateBulkDataDTO.Type.DELIVER) {
			
			CreateBasicBulkDataDTO searchBulkDataDTO = 
				(CreateBasicBulkDataDTO) bulkDataDTO;
			
			DataProviderSearchProperties properties = 
				new DataProviderSearchProperties(
					this.defaultExpiryTime, searchBulkDataDTO.getCache());
			
			properties.setStructureId(searchBulkDataDTO.getId());
			properties.setObjectTypes(searchBulkDataDTO.getObjectTypes());
			properties.setStructureSubtype(searchBulkDataDTO.getStructureSubtype());
			properties.setTotalNumberOfResults(searchBulkDataDTO
					.getTotalNumberOfResults());
			properties.setReturnURIsFlag(searchBulkDataDTO.getReturnURIsFlag());
			
			if (properties != null)
				this.data.put(bdId, properties);
			else
				throw new DataProviderException(
						"DataProviderSearchProperties cannot be null!");
			return bdId;

		} else
			throw new DataProviderException(
					"Unsupported CreateBulkDataDTO type!");
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.unibielefeld.driver.is.dataprov.IDataProvider#getBulkData(java.lang.String,
	 *      int, int)
	 */
	public List<String> getBulkData(String bd_id, int fromPosition, int toPosition)
			throws DataProviderException {
		return Arrays.asList(getSimpleBulkData(bd_id, fromPosition, toPosition));
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.unibielefeld.driver.is.dataprov.IDataProvider#getSimpleBulkData(java.lang.String,
	 *      int, int)
	 */
	public String[] getSimpleBulkData(String bd_id, int fromPosition,
			int toPosition) throws DataProviderException {

		log.debug("getBulkData(" + bd_id + "," + fromPosition + ","
				+ toPosition + ") call received");

		if (bd_id == null)
			throw new DataProviderException("BdId cannot be null!");

		if (fromPosition > toPosition)
			throw new DataProviderException(
					"fromPosition is greater than toPosition!");

		DataProviderProperties properties = this.data.get(bd_id);

		if (properties == null)
			throw new DataProviderException(
					"No DataProviderProperties found for bd_id: " + bd_id);

		if (properties.getType() == DataProviderProperties.Type.DELIVER)
			return getSearchBulkData((DataProviderSearchProperties) properties,
					fromPosition, toPosition, bd_id);
		else
			throw new DataProviderException(
					"Unsupported DataProviderProperties type!");

	}

	/**
	 * Returns bulkData for search query.
	 * 
	 * @param properties the properties
	 * @param fromPosition the from position
	 * @param toPosition the to position
	 * @param bdId 
	 * 
	 * @return bulkData for search query
	 * 
	 * @throws DataProviderException the data provider exception
	 */
	private String[] getSearchBulkData(DataProviderSearchProperties properties,
			int fromPosition, int toPosition, String bdId) throws DataProviderException {

		if (fromPosition > properties.getTotalNumberOfResults()) {
			log.warn("fromPosition: " + fromPosition
					+ " is greater than the number of results: "
					+ properties.getTotalNumberOfResults()
					+ ", returning empty results!");
			return new String[0];
		}

		if (toPosition > properties.getTotalNumberOfResults())
			toPosition = properties.getTotalNumberOfResults();

		ArrayList<String> currentSearchResults = new ArrayList<String>();
		
		if (properties.getCache() != null && properties.getCache().size() != 0) {
			log.info("Getting results from cache: Range: "
					+ "fromPosition: " + fromPosition + ", toPosition: "
					+ toPosition);
			
			if (toPosition-fromPosition >= properties.getCache().size())
				currentSearchResults = properties.getCache();
			else {
				ArrayList<String> cache = properties.getCache();
			
				for (int i=0; i<= toPosition-fromPosition; i++) {
					if (i >= cache.size()) {
						currentSearchResults.add(cache.get(cache.size()-1));
						cache.remove(cache.size()-1);
					} else {
						currentSearchResults.add(cache.get(i));
						cache.remove(i);
					}
				}
				
				properties.setCache(cache);
				this.data.put(bdId, properties);
			}	
			
		} else {
			log.info("Getting results for the next pages: Range: "
					+ "fromPosition: " + fromPosition + ", toPosition: "
					+ toPosition);
			LLSLookUpResponse searchingResponse;
			try {
				searchingResponse = llsAccessFacade.searchObjects(
						properties.getStructureId(), 
						properties.getStructureSubtype(), 
						fromPosition, toPosition, properties.getReturnURIsFlag());
			} catch (DataAccessServiceException e) {
				throw new DataProviderException("Problem by searching storage for" +
						"the next pages: "+e);
			}
			if (searchingResponse != null && 
					searchingResponse.getResponseResults() != null) {	
				
				currentSearchResults = 
					(ArrayList<String>) searchingResponse.getResponseResults();
				
				log.debug("Found "+currentSearchResults.size() +
				" in BulkData");
				
			} else {
				log.warn("No more results found !");
			}	

		}

		if (currentSearchResults == null)
			throw new DataProviderException("Got null results! " + "mdId: "
					+ properties.getStructureId() + ", mdFormat: "
					+ properties.getStructureSubtype() + ", from: ");
		
		return prepareSearchBulkData(currentSearchResults);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.unibielefeld.driver.is.dataprov.IDataProvider#getNumberOfResults(java.lang.String)
	 */
	public ResultsResponse getNumberOfResults(String bd_id)
			throws DataProviderException {
		if (bd_id == null)
			throw new DataProviderException("BdId cannot be null!");
		DataProviderProperties properties = this.data.get(bd_id);
		ResultsResponse result = new ResultsResponse();
		result.setStatus(IDataProviderExt.STATUS_CLOSED);
		if (properties == null) {
			log.debug("getNumberOfResults(" + bd_id + ") call, results: 0");
			result.setTotal(0);
			return result;
		} else {
			log.debug("getNumberOfResults(" + bd_id + ") call, results: "
					+ properties.getTotalNumberOfResults());
			result.setTotal(properties.getTotalNumberOfResults());
			return result;
		}
	}

	/**
	 * Generate bulk data id.
	 * 
	 * @return the string
	 */
	private String generateBulkDataId() {
		return "bdid-" + System.currentTimeMillis() + "-"
				+ this.rand.nextInt(1000);
	}

	/**
	 * Converts search results to bulk data.
	 * 
	 * @param searchResults the search results
	 * 
	 * @return parsed String[] of search results
	 * 
	 * @throws DataProviderException the data provider exception
	 */
	private String[] prepareSearchBulkData(ArrayList<String> searchResults)
			throws DataProviderException {
		if (searchResults == null || searchResults.size() == 0)
			return new String[0];
		
		String[] result = new String[searchResults.size()];
	
		for (int i = 0; i < searchResults.size(); i++)
			result[i] = searchResults.get(i);
		return result;
	}

	/**
	 * Returns default data provider expiry time (in seconds).
	 * 
	 * @return default data provider expiry time
	 */
	public int getDefaultExpiryTime() {
		return this.defaultExpiryTime;
	}

	/**
	 * Sets default data provider expiry time (in seconds).
	 * 
	 * @param defaultExpiryTime the default expiry time
	 */
	public void setDefaultExpiryTime(int defaultExpiryTime) {
		this.defaultExpiryTime = defaultExpiryTime;
	}

	/**
	 * Returns lucene storage directory.
	 * 
	 * @return luceneStorageDirectory
	 */
	public String getLuceneStorageDirectory() {
		return this.luceneStorageDirectory;
	}

	/**
	 * Sets lucene storage directory.
	 * 
	 * @param luceneStorageDirectory the lucene storage directory
	 */
	public void setLuceneStorageDirectory(String luceneStorageDirectory) {
		this.luceneStorageDirectory = luceneStorageDirectory;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.unibielefeld.driver.ICleanable#cleanup()
	 */
	/**
	 * Cleanup.
	 */
	public void cleanup() {
		log.debug("starting cleanup operations...");
		long currentTime = System.currentTimeMillis();
		synchronized (this.data) {
			Set<String> keySet = this.data.keySet();
			Iterator<String> keysIt = keySet.iterator();
			while (keysIt.hasNext()) {
				String currentKey = keysIt.next();
				DataProviderProperties currentProps = this.data.get(currentKey);
				if (currentTime > currentProps.getExpirationTime()) {
					log.debug("removing data prov: " + currentKey);
					keysIt.remove();
				}
			}
		}
		log.debug("cleanup operations finished");
	}

	/**
	 * Fetch the entire contents of a text file, and return it in a String.
	 * 
	 * @param aFile is a file which already exists and can be read.
	 * 
	 * @return the contents
	 * 
	 * @throws DataProviderException the data provider exception
	 */
	static public String getContents(File aFile) throws DataProviderException {

		StringBuffer contents = new StringBuffer("UTF-8");

		try {
			// use buffering, reading one line at a time
			// BufferedReader input = new BufferedReader(new FileReader(aFile));
			BufferedReader input = new BufferedReader(new InputStreamReader(
					new FileInputStream(aFile), "UTF8"));
			try {
				String line = null;

				while ((line = input.readLine()) != null) {
					contents.append(line);
					contents.append(System.getProperty("line.separator"));
				}

				byte[] utf8 = contents.toString().getBytes("UTF-8");
				String outputString = new String(utf8);

				return outputString;

			} finally {
				input.close();
			}
		} catch (IOException ex) {
			log.error("Problem by reading record content" + ex.getMessage());
			throw new DataProviderException("Problem by reading record content"
					+ ex.getMessage());
		}

	}
	
	public LLSAccessFacade getLlsAccessFacade() {
		return llsAccessFacade;
	}

	public void setLlsAccessFacade(LLSAccessFacade llsAccessFacade) {
		this.llsAccessFacade = llsAccessFacade;
	}

}
