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

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.cxf.helpers.IOUtils;
import org.apache.log4j.Logger;

import pl.edu.icm.yadda.service2.ArchiveContentPartFacade;
import pl.edu.icm.yadda.service2.YaddaObjectID;
import pl.edu.icm.yadda.service2.YaddaObjectMeta.STATUS;
import pl.edu.icm.yadda.service2.catalog.CountingIterator;
import pl.edu.icm.yadda.service2.exception.ServiceException;
import pl.edu.icm.yadda.service3.ArchiveObject2Meta;
import pl.edu.icm.yadda.service3.ArchiveObjectFacade;
import pl.edu.icm.yadda.service3.archive.IArchiveFacade2;
import eu.dnetlib.data.sts.constants.DepotServiceConstants;
import eu.dnetlib.data.sts.das.DataAccessServiceException;
import eu.dnetlib.data.sts.das.lls.ILLSAccessFacade;
import eu.dnetlib.data.sts.das.lls.LLSLookUpResponse;
import eu.dnetlib.data.sts.ds.DepotServiceException;
import eu.dnetlib.data.sts.utils.OssUtils;

/**
 * The Class LLSAccessFacade.
 * 
 * @author <a href="mailto:marek.imialek at uni-bielefeld.de">Marek Imialek</a>
 */
public class LLSAccessFacade implements ILLSAccessFacade {

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

	/** The Constant oss protocol. */
	public static final String protocol = LLSStoreFacade.protocol;
	
	/** The Constant contentType. */
	public static final String contentType = LLSStoreFacade.contentType;
	
	/** The lls access facade. */
	private IArchiveFacade2 llsArchiveFacade;

	/** The initial page size. */
	private int initialPageSize = 50;
	
	/** The data distributor uri. */
	private String dataDistributorUri;

	
	/* (non-Javadoc)
	 * @see eu.dnetlib.data.sts.das.lls.ILLSAccessFacade#searchObjects(java.lang.String, java.lang.String, int, int, boolean)
	 */
	public LLSLookUpResponse searchObjects (String storeDataStructureId,
			String structureSubtype, int startPosition, 
			int toPosition, boolean returnUris) 
			throws DataAccessServiceException {
		
		String searchedPath = protocol+ storeDataStructureId + "/"
				+ structureSubtype;
		log.info("Srearching Storage for: " + searchedPath);
		
		LLSLookUpResponse llsLookUpResponse = new LLSLookUpResponse();
		CountingIterator<ArchiveObject2Meta> response = null;
	
		try {
			response = 
				llsArchiveFacade.queryObjects(searchedPath, true);
			
		} catch (ServiceException e) {
			String msg = "Failed to query object" + searchedPath;
			log.error(msg);
			throw (DataAccessServiceException) new DataAccessServiceException(
				msg).initCause(e);
		}
		
		if (response == null || response.count() == 0) {
			log.warn("No objects found in the storage!");
			return null;
		}
		
		//log.debug(response.count()+ " found in the storage.");
		List<String> results = new ArrayList<String>();
		int counter = 0;
		int collected = 0;

		if (response.hasNext()) {	
			
			ArchiveObject2Meta object = response.next();
			ArchiveObjectFacade foundObject = null;
			
			try {
				
				foundObject = 
					llsArchiveFacade.getObject(
							object.getId(), 
							new String[]{contentType}, 
							true);	
			} catch (ServiceException e) {
				String msg = " Exception occured when try get object " 
					+object.getId() +" from the storage : "+e;
				log.error(msg);
					
			}
			
			if (foundObject != null) {
				
				Map<String, List<YaddaObjectID>> children = foundObject.getChildren();
				Iterator<String> chIter = children.keySet().iterator();
			
				while (chIter.hasNext() && counter<toPosition) {
					
					if (collected >= initialPageSize)
						break;
					
					//log.debug(counter+"<="+initialPageSize)	;
					ArchiveObjectFacade foundSingleObject = null;
				
					try {
						String key = chIter.next();
						List<YaddaObjectID> values = children.get(key);
						Iterator<YaddaObjectID> iter = values.iterator();
						while (iter.hasNext()){
							YaddaObjectID value = iter.next();
					
							if (returnUris){
								foundSingleObject = 
									llsArchiveFacade.getObject(
											value, 
											new String[]{"BasicContent"}, 
											false, false, true);
							}
							else {
								foundSingleObject = 
									llsArchiveFacade.getObject(
											value, 
											new String[]{contentType}, 
											false);
							}
						}
						
					} catch (ServiceException e) {
						String msg = " Exception occured when try get object " 
							+object.getId() +" from the storage : "+e;
						log.error(msg);
					
					}
					
					if (foundSingleObject == null || foundSingleObject.getStatus() == STATUS.DELETED)
						continue;
					
					counter++;
					//log.debug("1Start position: "+ startPosition + "=" + counter);
					if (counter < startPosition)
						continue;

					//log.debug("continue");
					if (returnUris){
						String internalObjectUri = foundSingleObject.getPath();
					
						if (internalObjectUri != null) {
							String externalObjectUri = 
								OssUtils.createExternalObjectUri(
									foundSingleObject.getPath(), 
									getLlsDataDistributorUri());
							log.debug("Object's External URI: " +externalObjectUri);
							results.add(externalObjectUri);
						}	
					} else {
						ArchiveContentPartFacade contentPartFacade = 
							foundSingleObject.getPart(contentType);
						if ( contentPartFacade != null) {
							//log.debug("Converting InputStream to String.");
							String convertedObject = convertObject(contentPartFacade);
							if (convertedObject != null){
								results.add(convertedObject);
							}else 
								log.error("Object " +
								"not converted from InputStream to String");
						}
					}
					collected++;
				}
				log.debug("Finished work on "+results.size()+" results");
			}
		}
		
		if (results == null){
			log.warn("No objects found in the storage!");
			return null;
		}
		else {
			llsLookUpResponse.setResponseResults(results);
			llsLookUpResponse.setResponseSize(counter);
			return llsLookUpResponse;
		}
	}
	
	/* (non-Javadoc)
	 * @see eu.dnetlib.data.sts.das.lls.ILLSAccessFacade#searchObjectsByTags(java.lang.String[], int, int, boolean)
	 */
	public LLSLookUpResponse searchObjectsByTags(
			String[] searchedTags, int startPosition, int toPosition,
			boolean returnUris) throws DataAccessServiceException {
		
		if (toPosition<initialPageSize)
			initialPageSize = toPosition;
		
		LLSLookUpResponse llsLookUpResponse = new LLSLookUpResponse();
		CountingIterator<ArchiveObject2Meta> response = null;
		
		try {
			response = 
				llsArchiveFacade.listObjects(null, null, searchedTags, false);
		} catch (ServiceException e) {
			throw (DataAccessServiceException) new DataAccessServiceException(
				"Failed to search storage").initCause(e);
		}

		if (response == null){
			log.warn("No objects found in the storage!");
			return null;
		}
		
		List<String> results = new ArrayList<String>();
		
		int counter = 0;
		int collected = 0;
		
		llsLookUpResponse.setResponseSize(response.count());
		
		while (response.hasNext() && counter<toPosition) {
			
			if (collected >= initialPageSize)
				break;
			
			ArchiveObject2Meta object = response.next();
			
			counter++;
			if (counter < startPosition)
				continue;
			
			if (!object.getStatus().isDeleted()) {
				
				ArchiveObjectFacade foundObject = null;
				
				try {
					
					foundObject = 
						llsArchiveFacade.getObject(
								object.getId(), 
								new String[]{contentType}, 
								true);	
				} catch (ServiceException e) {
					String msg = " Exception occured when try get object " 
						+object.getId() +" from the storage : "+e;
					log.error(msg);
						
				}
				
				ArchiveContentPartFacade contentPartFacade = 
					foundObject.getPart(contentType);
				
				if (returnUris){
					String internalObjectUri = foundObject.getPath();
					
					if (internalObjectUri != null) {
						String externalObjectUri = 
							OssUtils.createExternalObjectUri(
									foundObject.getPath(), 
									getLlsDataDistributorUri());
						log.debug("Object's External URI: " +externalObjectUri);
						results.add(externalObjectUri);
					}	
				} else {
					log.debug("Converting InputStream to String.");
					String convertedObject = convertObject(contentPartFacade);
					if (convertedObject != null)
						results.add(convertedObject);
					else 
						log.error("Object " +
								"not converted from InputStream to String");
				}
				
				collected++;
			}
				
		}
		
		if (results == null){
			log.warn("No objects found in the storage!");
			return null;
		}
		else {
			
			llsLookUpResponse.setResponseResults(results);
			return llsLookUpResponse;
		}
	}

	/**
	 * Gets the object sdo.
	 * 
	 * @param objectId the object id
	 * @param storeDataStructId the store data struct id
	 * 
	 * @return the object sdo
	 * 
	 * @throws DataAccessServiceException the data access service exception
	 */
	public String getObjectSDO (String storeDataStructId, String objectId) 
		throws DataAccessServiceException {
		
		String objecIdForSearching = 
			protocol + storeDataStructId+"/"+
			DepotServiceConstants.STORE_DATA_STRUCTURE_SUBTYPE_SDOS+
			"/"+ objectId + "-sdos";
		
		ArchiveObjectFacade singleObjectFacade = 
			getSingleObjectFacade(objecIdForSearching, false);
		
		if (singleObjectFacade == null) {
			log.warn("Object "+objectId+" not found in store data structure "+
					storeDataStructId);
			return null;
		}
		
		ArchiveContentPartFacade contentPartFacade = 
			singleObjectFacade.getPart(contentType);
		
		String objectSDO = convertObject(contentPartFacade);
		
		return objectSDO;
	}
	
	/**
	 * Gets the object url.
	 * 
	 * @param objectId the object id
	 * @param storeDataStructId the store data struct id
	 * 
	 * @return the object url
	 * 
	 * @throws DataAccessServiceException the data access service exception
	 */
	public String getObjectUrl (String storeDataStructId, String objectId) 
		throws DataAccessServiceException {
		
		String objectURL = null;
		
		String objecIdForSearching = 
			protocol + storeDataStructId+"/"+
			DepotServiceConstants.STORE_DATA_STRUCTURE_SUBTYPE_OBJECTS+
			"/"+ objectId;
		
		ArchiveObjectFacade singleObjectFacade = 
			getSingleObjectFacade(objecIdForSearching, true);
		
		if (singleObjectFacade == null) {
			log.warn("Object "+objectId+" not found in store data structure "+
					storeDataStructId);
			return null;
		}
			
		String internalObjectUri = singleObjectFacade.getPath();
		
		if (internalObjectUri != null) {
			objectURL = 
				OssUtils.createExternalObjectUri(
						singleObjectFacade.getPath(), 
						getLlsDataDistributorUri());
		}
		return objectURL;
	}
	

	/**
	 * Gets the single object facade.
	 * 
	 * @param objectName the object name
	 * 
	 * @return the single object facade
	 * 
	 * @throws DepotServiceException the depot service exception
	 */
	private ArchiveObjectFacade getSingleObjectFacade (String objectName, boolean uri)
		throws DataAccessServiceException {

		if (!objectName.contains(protocol))
			objectName = protocol + objectName;
		log.debug("Searching for object: " + objectName);

		try {

			ArchiveObject2Meta searchObject = 
				llsArchiveFacade.queryUniqueObject(objectName, true);
		
			if (searchObject != null) {
				
				ArchiveObjectFacade foundObject = 
					llsArchiveFacade.getObject(searchObject.getId(), 
							new String[]{contentType}, false, false, uri);

				return foundObject;
			} else {
				String message = "Object "+objectName+" " +
				"not found in the storage.";
				log.error(message);
				throw new DataAccessServiceException("Object or data structure not found!");
			}

		} catch (ServiceException e) {
			log.debug("Problem by getting object " +
					objectName + " from the storage: " +e );
		} 

		return null;
	}

	public InputStream getSingleObject (String objectName)
		throws Exception {
	
		ArchiveObjectFacade singleObjectFacade = 
			getSingleObjectFacade(objectName, false);
	
		ArchiveContentPartFacade contentPartFacade = 
			singleObjectFacade.getPart(contentType);
			
		InputStream is = contentPartFacade.getData();
	
		return is; 
	}

	/**
	 * Get objects and covert them from InputStream to string.
	 * 
	 * @param contentPartFacade the content part facade
	 * 
	 * @return object string
	 */
	private String convertObject (ArchiveContentPartFacade contentPartFacade) {
		
		InputStream is = contentPartFacade.getData();
		String result = null;
		
		try {
			String objectToText = IOUtils.readStringFromStream(is);
			
			if (objectToText != null)
				result = objectToText;
			is.close();
		} catch (IOException e) {
			String msg = "Failed to convert object from " +
					"Inputstream to Text.";
			log.error(msg);
		} finally {
			try {
				is.close();
			} catch (IOException e) {
				log.error("Failed to close InputStream");
			}
		}
		
		return result;
	}
	
	/**
	 * Gets the lls archive facade.
	 * 
	 * @return the lls archive facade
	 */
	public IArchiveFacade2 getLlsArchiveFacade() {
		return llsArchiveFacade;
	}

	/**
	 * Sets the lls archive facade.
	 * 
	 * @param llsArchiveFacade the new lls archive facade
	 */
	public void setLlsArchiveFacade(IArchiveFacade2 llsArchiveFacade) {
		this.llsArchiveFacade = llsArchiveFacade;
	}

	/**
	 * Sets the initial page size.
	 * 
	 * @param initialPageSize the new initial page size
	 */
	public void setInitialPageSize(int initialPageSize) {
		this.initialPageSize = initialPageSize;
	}

	/**
	 * Gets the initial page size.
	 * 
	 * @return the initial page size
	 */
	public int getInitialPageSize() {
		return initialPageSize;
	}
	
	/**
	 * Sets the lls data distributor uri.
	 * 
	 * @param dataDistributorUri the new lls data distributor uri
	 */
	public void setLlsDataDistributorUri(String dataDistributorUri) {
		this.dataDistributorUri = dataDistributorUri;
	}
	
	/**
	 * Gets the lls data distributor uri.
	 * 
	 * @return the lls data distributor uri
	 */
	public String getLlsDataDistributorUri() {
		return this.dataDistributorUri;
	}

}
