package org.gcube.portlets.user.speciesdiscovery.server.service;

import static org.gcube.data.spd.client.plugins.AbstractPlugin.classification;
import static org.gcube.data.spd.client.plugins.AbstractPlugin.executor;
import static org.gcube.data.spd.client.plugins.AbstractPlugin.manager;
import static org.gcube.data.spd.client.plugins.AbstractPlugin.occurrence;
import static org.gcube.data.streams.dsl.Streams.convert;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.apache.log4j.Logger;
import org.gcube.application.framework.core.session.ASLSession;
import org.gcube.common.core.scope.GCUBEScope;
import org.gcube.common.scope.api.ScopeProvider;
import org.gcube.data.spd.client.proxies.Classification;
import org.gcube.data.spd.client.proxies.Executor;
import org.gcube.data.spd.client.proxies.Manager;
import org.gcube.data.spd.client.proxies.Occurrence;
import org.gcube.data.spd.model.OccurrencePoint;
import org.gcube.data.spd.model.Properties;
import org.gcube.data.spd.model.Property;
import org.gcube.data.spd.model.ResultElement;
import org.gcube.data.spd.model.TaxonomyItem;
import org.gcube.data.spd.model.util.Capabilities;
import org.gcube.data.spd.stubs.exceptions.InvalidIdentifierException;
import org.gcube.data.spd.stubs.types.CapabilityDescription;
import org.gcube.data.spd.stubs.types.PluginDescription;
import org.gcube.data.spd.stubs.types.PluginProperty;
import org.gcube.data.spd.stubs.types.RepositoryInfo;
import org.gcube.data.spd.stubs.types.Status;
import org.gcube.data.streams.Stream;
import org.gcube.portlets.user.speciesdiscovery.server.stream.CloseableIterator;
import org.gcube.portlets.user.speciesdiscovery.server.stream.IteratorPointInfo;
import org.gcube.portlets.user.speciesdiscovery.server.util.StorageUtil;
import org.gcube.portlets.user.speciesdiscovery.shared.Coordinate;
import org.gcube.portlets.user.speciesdiscovery.shared.DataSourceCapability;
import org.gcube.portlets.user.speciesdiscovery.shared.DataSourceModel;
import org.gcube.portlets.user.speciesdiscovery.shared.DataSourceRepositoryInfo;
import org.gcube.portlets.user.speciesdiscovery.shared.SearchFilters;
import org.gcube.portlets.user.speciesdiscovery.shared.SearchServiceException;
import org.gcube.portlets.user.speciesdiscovery.shared.SearchType;
import org.gcube.portlets.user.speciesdiscovery.shared.SpeciesCapability;

//import org.gcube.contentmanager.storageclient.model.protocol.smp.Handler;

/**
 * @author "Federico De Faveri defaveri@isti.cnr.it"
 *
 */
public class SpeciesService {

	protected Logger logger = Logger.getLogger(SpeciesService.class);

	protected GCUBEScope scope;
	protected ASLSession session;

	protected Manager call;
	protected Occurrence occurrencesCall;
	protected Classification classificationCall;
	protected Executor executorCall;


	public SpeciesService(GCUBEScope scope, ASLSession session) throws Exception
	{
		this(scope);
		this.session = session;
	}
	
	
	public SpeciesService(GCUBEScope scope) throws Exception
	{
		this.scope = scope;
		ScopeProvider.instance.set(scope.toString());
//		this.call = manager().at( URI.create("http://node24.d.d4science.research-infrastructures.eu:9000")).withTimeout(3, TimeUnit.MINUTES).build();
//		this.occurrencesCall =  occurrences().at( URI.create("http://node24.d.d4science.research-infrastructures.eu:9000")).withTimeout(3, TimeUnit.MINUTES).build();
//	    this.classificationCall = classification().at( URI.create("http://node24.d.d4science.research-infrastructures.eu:9000")).withTimeout(3, TimeUnit.MINUTES).build();
		System.out.println("CALLING MANAGER ");
		this.call = manager().withTimeout(3, TimeUnit.MINUTES).build();
		this.executorCall = executor().withTimeout(3, TimeUnit.MINUTES).build();
		this.occurrencesCall =  occurrence().withTimeout(3, TimeUnit.MINUTES).build();
	    this.classificationCall = classification().withTimeout(3, TimeUnit.MINUTES).build();
	}
	
	
	public SpeciesService(GCUBEScope scope, boolean instanceOnlyOccurrence) throws Exception
	{
		this.scope = scope;
		
		if(instanceOnlyOccurrence){
			ScopeProvider.instance.set(scope.toString());
			System.out.println("CALLING OCCURRENCE MANAGER ");
			this.occurrencesCall =  occurrence().withTimeout(3, TimeUnit.MINUTES).build();
		}
	}


	public CloseableIterator<ResultElement> searchByFilters(String searchTerm, SearchType searchType, SearchFilters searchFilters) throws SearchServiceException {
		logger.trace("searchByFilters searchTerm: "+searchTerm+ " usearchFilters: "+searchFilters);

		try {
			System.out.println("query building...");
			String query = QueryBuilder.buildQuery(searchTerm, searchType, searchFilters);
//			System.out.println("query build - OK " + query);
			logger.trace("query build - OK " + query);
//			System.out.println("query: "+query);
			return searchByQuery(query);
		} catch (Exception e) {
			logger.error("Error calling the Species Service: " + e.getMessage(), e);
			throw new SearchServiceException("Error calling the Species Service: "+e.getMessage());
		}
	}
	
	
	public CloseableIterator<TaxonomyItem> retrieveTaxonomyById(Stream<String> streamIds) throws SearchServiceException {
		logger.trace("retrieveTaxonomyById...");

		try {
			ScopeProvider.instance.set(scope.toString());
			Stream<TaxonomyItem> stream = classificationCall.getTaxaByIds(streamIds);
			return new StreamIterator<TaxonomyItem>(stream);
		} catch (Exception e) {
			logger.error("Error calling the Species Service: " + e.getMessage(), e);
			throw new SearchServiceException("Error calling the Species Service: "+e.getMessage());
		}
	}
	
	
	public CloseableIterator<TaxonomyItem> retrieveSynonymsById(String id) throws SearchServiceException {
		logger.trace("retrieveSynonymsById...");

		try {
			ScopeProvider.instance.set(scope.toString());
			Stream<TaxonomyItem> stream = classificationCall.getSynonymsById(id);
			return new StreamIterator<TaxonomyItem>(stream);
		} catch (Exception e) {
			logger.error("Error calling the Species Service: " + e.getMessage(), e);
			throw new SearchServiceException("Error calling the Species Service: "+e.getMessage());
		}
	}


	public CloseableIterator<ResultElement> searchByQuery(String query) throws SearchServiceException {
		logger.trace("searchByQuery query: "+query);
//		System.out.println("searchByQuery query: "+query);

		try {
			ScopeProvider.instance.set(scope.toString());
//			System.err.println("ScopeProvider SCOPE "+ScopeProvider.instance.get());
			logger.trace("call species service search...");
//			System.out.println("call species service search...");
			Stream<ResultElement> stream = call.search(query);
			return new StreamIterator<ResultElement>(stream);
		} catch (Exception e) {
			logger.error("Error calling the Species Service: " + e.getMessage(), e);
			throw new SearchServiceException("Error calling the Species Service: "+e.getMessage());
		}
	}
	
	
	public Stream<ResultElement> searchByQuery2(String query) throws SearchServiceException {
		logger.trace("searchByQuery query: "+query);
//		System.out.println("searchByQuery query: "+query);

		try {
			ScopeProvider.instance.set(scope.toString());
//			System.err.println("ScopeProvider SCOPE "+ScopeProvider.instance.get());
			logger.trace("call species service search...");
			System.out.println("call species service search...");
			return call.search(query);
		} catch (Exception e) {
			logger.error("Error calling the Species Service: " + e.getMessage(), e);
			throw new SearchServiceException("Error calling the Species Service: "+e.getMessage());
		}
	}
	

	public List<DataSourceModel> getPlugins() throws SearchServiceException {
		logger.trace("getPlugins...");
		try {

			List<DataSourceModel> listDsModel = new ArrayList<DataSourceModel>();

			ScopeProvider.instance.set(scope.toString());
			List<PluginDescription> plugin = call.getPluginsDescription();
			
			if(plugin!=null){
				
				logger.trace("*****PluginDescription is NOT null - length: " + plugin.size());
				
				for (int i = 0; i < plugin.size(); i++) {
	
					PluginDescription pluginDescription = plugin.get(i);
					List<CapabilityDescription> listCapabilityDescription = pluginDescription.getCapabilities();
					
					ArrayList<DataSourceCapability> listCapabilities = new ArrayList<DataSourceCapability>(); //new Array List DataSourceCapability
					logger.trace("getCapabilities for..." + pluginDescription.getName());
					for (CapabilityDescription capabilityDescription : listCapabilityDescription){
						List<String> properties = capabilityDescription.getProperties();
						ArrayList<String> listProperties = properties!=null?new ArrayList<String>(properties):new ArrayList<String>();
						ArrayList<SpeciesCapability> listPropertiesEnum = new ArrayList<SpeciesCapability>();
	
						for (String property : listProperties) 
							listPropertiesEnum.add(getFilterCapabilityFromProperties(property));
	
						listCapabilities.add(new DataSourceCapability(getGxtCapabilityValueFromCapability(capabilityDescription.getName()), listPropertiesEnum));
					}
					
					RepositoryInfo rep = pluginDescription.getRepositoryInfo();
					
					//CREATE DataSourceRepositoryInfo
					DataSourceRepositoryInfo dsInfo = new DataSourceRepositoryInfo();
					
					if(rep!=null){
//						System.out.println("DESCRIPTION REPOSITORY: " + rep.getDescription());
						dsInfo.setLogoUrl(rep.getLogoUrl());
						dsInfo.setPageUrl(rep.getReferencePageUrl());
						dsInfo.setProperties(getPropertiesFromRepositoryInfoType(rep));
						dsInfo.setDescription(rep.getDescription());
//						dsInfo = new DataSourceRepositoryInfo(rep.getLogoUrl(), rep.getReferencePageUrl(),getPropertiesFromRepositoryInfoType(rep), rep.getDescription());	
						logger.trace("DataSourceRepositoryInfo :"+dsInfo);	
//						logger.trace("Repository description size: " + rep.getDescription().length());
					}
					
					listDsModel.add(new DataSourceModel(pluginDescription.getName(), pluginDescription.getName(), pluginDescription.getDescription(), listCapabilities, dsInfo));	

				}
			}
			else
				logger.trace("*****PluginDescription is null");

			return listDsModel;

		} catch (Exception e) {
			logger.error("Error calling the Species Service: " + e.getMessage(), e);
//			System.out.println("Error calling the Species Service: " + e);
			e.printStackTrace();
			throw new SearchServiceException("Error calling the Species Service: "+e.getMessage());
		}
	}
	
	private Map<String, String> getPropertiesFromRepositoryInfoType(RepositoryInfo rep){
		
		Map<String, String> mapProperties = new HashMap<String, String>();
		
		if(rep.getProperties()==null){
			logger.trace("*****Properties From RepositoryInfoType is null");
			return mapProperties;
		}
		
		for (PluginProperty prop : rep.getProperties())
			
			mapProperties.put(prop.getKey(), prop.getValue());
		
		return mapProperties;
	}

	private SpeciesCapability getFilterCapabilityFromProperties(String property){

		if(property.compareTo(Properties.DateFrom.name())==0)
			return SpeciesCapability.FROMDATE;
		else if(property.compareTo(Properties.DateTo.name())==0)
			return SpeciesCapability.TODATE;
		else if(property.compareTo(Properties.CoordinateTo.name())==0)
			return SpeciesCapability.UPPERBOUND;
		else if(property.compareTo(Properties.CoordinateFrom.name())==0)
			return SpeciesCapability.LOWERBOUND;

		return SpeciesCapability.UNKNOWN;
	}

	private SpeciesCapability getGxtCapabilityValueFromCapability(String capability){

		if(capability.compareTo(Capabilities.Classification.name())==0)
			return SpeciesCapability.TAXONOMYITEM;
		else if(capability.compareTo(Capabilities.Occurences.name())==0)
			return SpeciesCapability.RESULTITEM;
		else if(capability.compareTo(Capabilities.NamesMapping.name())==0)
			return SpeciesCapability.NAMESMAPPING;
		else if(capability.compareTo(Capabilities.Synonims.name())==0)
			return SpeciesCapability.SYNONYMS;

		return SpeciesCapability.UNKNOWN;
	}

	protected List<Property> createFilterProperties(SearchFilters searchFilters)
	{
		List<Property> properties = new ArrayList<Property>();
		if (searchFilters.getUpperBound()!=null) properties.add(new Property(Properties.CoordinateTo, convertCoordinate(searchFilters.getUpperBound())));
		if (searchFilters.getLowerBound()!=null) properties.add(new Property(Properties.CoordinateFrom, convertCoordinate(searchFilters.getLowerBound())));

		if (searchFilters.getFromDate()!=null) {
			Calendar fromDate = Calendar.getInstance();
			fromDate.setTime(searchFilters.getFromDate());
			properties.add(new Property(Properties.DateFrom, fromDate));
		}
		if (searchFilters.getToDate()!=null) {
			Calendar toDate = Calendar.getInstance();
			toDate.setTime(searchFilters.getToDate());
			properties.add(new Property(Properties.DateTo, toDate));
		}

		return properties;
	}

	protected org.gcube.data.spd.model.Coordinate convertCoordinate(Coordinate coordinate)
	{
		return new org.gcube.data.spd.model.Coordinate(coordinate.getLatitude(), coordinate.getLongitude());
	}

	public CloseableIterator<OccurrencePoint> getOccurrencesByKeys(List<String> keys) throws SearchServiceException {
		try {
			Stream<String> keysStream = convert(keys);
			ScopeProvider.instance.set(scope.toString());
			Stream<OccurrencePoint> stream =  occurrencesCall.getByKeys(keysStream);
			return new StreamIterator<OccurrencePoint>(stream);
		} catch (Exception e) {
			logger.error("Error calling the Species Service: " + e.getMessage(), e);
			throw new SearchServiceException("Error calling the Species Service: "+e.getMessage());
		}
	}
	
	public CloseableIterator<OccurrencePoint> getOccurrencesByIds(List<String> ids) throws SearchServiceException {
		try {
			Stream<String> idsStream = convert(ids);
			ScopeProvider.instance.set(scope.toString());
			Stream<OccurrencePoint> stream =  occurrencesCall.getByIds(idsStream);
			return new StreamIterator<OccurrencePoint>(stream);
		} catch (Exception e) {
			logger.error("Error calling the Species Service: " + e.getMessage(), e);
			throw new SearchServiceException("Error calling the Species Service: "+e.getMessage());
		}
	}

//	public String generateMapFromOccurrencePoints(List<String> keys) throws SearchServiceException {
//		try {
//			Stream<String> keysStream = convert(keys);
//			ScopeProvider.instance.set(scope.toString());
//			return occurrencesCall.getLayerByIds(keysStream);
//		} catch (Exception e) {
//			logger.error("Error calling the Species Service: " + e.getMessage(), e);
//			throw new SearchServiceException("Error calling the Species Service: "+e.getMessage());
//		}
//	}
	
	public String generateMapFromOccurrencePoints(IteratorPointInfo streamKey) throws SearchServiceException {
		try {
//			Stream<String> keysStream = convert(keys);
			ScopeProvider.instance.set(scope.toString());
			
			return occurrencesCall.createLayer(streamKey);
		} catch (Exception e) {
			logger.error("Error calling the Species Service: " + e.getMessage(), e);
			throw new SearchServiceException("Error calling the Species Service: "+e.getMessage());
		}
	}
	

//	public File getOccurrencesAsDarwinCoreByIds(List<String> ids) throws SearchServiceException {
//		try {
//			Stream<String> keysStream = convert(ids);
//			ScopeProvider.instance.set(scope.toString());
//			File occurrenceFile = occurrencesCall.getDarwinCoreByIds(keysStream);
//			return occurrenceFile;
//		} catch (Exception e) {
//			logger.error("Error calling the Species Service: " + e.getMessage(), e);
//			throw new SearchServiceException("Error calling the Species Service: "+e.getMessage());
//		}
//	}
//	
//	public File getOccurrencesAsDarwinCoreArchive(List<String> ids) throws SearchServiceException {
//		try {
//			Stream<String> keysStream = convert(ids);
//			ScopeProvider.instance.set(scope.toString());
//			File occurrenceFile = classificationCall.getDarwinCoreArchive(keysStream);
//			return occurrenceFile;
//		} catch (Exception e) {
//			logger.error("Error calling the Species Service: " + e.getMessage(), e);
//			throw new SearchServiceException("Error calling the Species Service: "+e.getMessage());
//		}
//	}
	
	public  StreamIterator<TaxonomyItem> getTaxonChildrenByParentId(String parentId) throws Exception{
		
		try {
			ScopeProvider.instance.set(scope.toString());
			Stream<TaxonomyItem> items = classificationCall.getTaxonChildrenById(parentId);
			return new StreamIterator<TaxonomyItem>(items);
		} catch (Exception e) {
//			e.printStackTrace();
			logger.error("Error calling the Species Service: " + e.getMessage(), e);
			throw new Exception("Error calling the Species Service: "+e.getMessage());
		}
	}

	public Status getTaxonomyJobById(String jobId) {

		ScopeProvider.instance.set(scope.toString());
		Status status = null;
		
		try{
		
			status = this.executorCall.getStatus(jobId);
				
		}catch (InvalidIdentifierException e) {
			logger.error("Error on service for get job by Id - InvalidIdentifierException");
			status = null;
			
		}catch (Exception e) {
			logger.error("Error on service for get job by Id: " + e.getMessage(), e);
			status = null;
//			return new Status();
		}
		
		return status;
	}

	public InputStream getTaxonomyJobFileById(String jobIdentifier) throws Exception {

		InputStream is = null;
		ScopeProvider.instance.set(scope.toString());
		
		try {
			String url = this.executorCall.getResultLink(jobIdentifier);
			logger.trace("URL returned by species service is: "+url);
			is = StorageUtil.getInputStreamByStorageClient(url);
		
		} catch (Exception e) {
//			e.printStackTrace();
			logger.error("Error saving file: "+e.getMessage(), e);
			throw new Exception("Error saving file: "+e.getMessage());
		}

		return is;
	}

	public String createTaxonomyJobForDWCAByChildren(String taxonomyId) throws Exception {
		
		ScopeProvider.instance.set(scope.toString());
		
		try {
			return this.executorCall.createDwCAByChildren(taxonomyId);
		
		} catch (Exception e) {
			logger.error("Error in createTaxonomyJob: "+e.getMessage(), e);
			throw new Exception("Error in createTaxonomyJob: "+e.getMessage());
		}
	}
	
	public String createTaxonomyJobForDWCAByIds(List<String> ids) throws Exception {
		
		ScopeProvider.instance.set(scope.toString());
		
		try {
			Stream<String> keysStream = convert(ids);
			return executorCall.createDwCAByIds(keysStream);
		} catch (Exception e) {
			logger.error("Error in createTaxonomyJobForDWCA: "+e.getMessage(), e);
			throw new Exception("Error in createTaxonomyJobForDWCA: "+e.getMessage());
		}
	}

	public void cancelTaxonomyJobById(String jobIdentifier){
		
		try{
			ScopeProvider.instance.set(scope.toString());		
			this.executorCall.removeJob(jobIdentifier);
		}catch (Exception e) {
			logger.error("Error on service for remove job: " + e.getMessage(), e);
		}
	}


	public Status getOccurrenceJobById(String jobId) {
		
		try{
			ScopeProvider.instance.set(scope.toString());
			return this.executorCall.getStatus(jobId); //CHANGE INTO OCCURRENCE JOB ************************************************************************************** //TODO
				
		}catch (InvalidIdentifierException e) {
			logger.error("Error on service for get job by Id - InvalidIdentifierException");
			return null;
			
		}catch (Exception e) {
			logger.error("Error on service for get job by Id: " + e.getMessage());
			return null;
		}
	}
	
	
	public String createOccurrenceCSVJob(Stream<String> streamKey) throws Exception{
		
		try {
			ScopeProvider.instance.set(scope.toString());
			return this.executorCall.createCSV(streamKey);
		
		} catch (Exception e) {
			logger.error("Error in createOccurrenceCSVJob: "+e.getMessage(), e);
			throw new Exception("Error in createOccurrenceCSVJob: "+e.getMessage());
		}
	}
	
	
	public String createOccurrenceDARWINCOREJob(Stream<String> streamKey) throws Exception{
		
		try {
			ScopeProvider.instance.set(scope.toString());
			return this.executorCall.createDarwincoreFromOccurrenceKeys(streamKey);
		
		} catch (Exception e) {
			logger.error("Error in createOccurrenceDARWINCOREJob: "+e.getMessage(), e);
			throw new Exception("Error in createOccurrenceDARWINCOREJob: "+e.getMessage());
		}
	}
	
	
	public String createOccurrenceCSVOpenModellerJob(Stream<String> streamKey) throws Exception{
	
		try {
			ScopeProvider.instance.set(scope.toString());
			return this.executorCall.createCSVforOM(streamKey);
		
		} catch (Exception e) {
			logger.error("Error in createOccurrenceCSVOpenModellerJob: "+e.getMessage(), e);
			throw new Exception("Error in createOccurrenceCSVOpenModellerJob: "+e.getMessage());
		}
	}


	public void cancelOccurrenceJobById(String jobIdentifier){
		
		try{
			ScopeProvider.instance.set(scope.toString());		
			this.executorCall.removeJob(jobIdentifier); //CHANGE INTO OCCURRENCE JOB ************************************************************************************** ** //TODO
		}catch (Exception e) {
			logger.error("Error on service for remove job: " + e.getMessage(), e);
		}
	}


	public InputStream getOccurrenceJobFileById(String jobIdentifier) throws Exception {
		
		InputStream is = null;
		ScopeProvider.instance.set(scope.toString());
		
		try {
			String url = this.executorCall.getResultLink(jobIdentifier); //CHANGE INTO OCCURRENCE JOB ************************************************************************************** ** //TODO
			logger.trace("URL returned by species service is: "+url);
			is = StorageUtil.getInputStreamByStorageClient(url);
		
		} catch (Exception e) {
//			e.printStackTrace();
			logger.error("Error saving file: "+e.getMessage(), e);
			throw new Exception("Error saving file: "+e.getMessage());
		}

		return is;
	}


}
