
package de.mpg.mpiz_koeln.featureClient;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.biomoby.shared.MobyService;
import org.biomoby.shared.data.MobyContentInstance;
import org.biomoby.shared.data.MobyDataInstance;
import org.biomoby.shared.data.MobyDataJob;
import org.biomoby.shared.data.MobyDataObject;
import org.biomoby.shared.data.MobyDataObjectSet;

/**
 * The class <tt>FeatureClientResult</tt> stores the results of a service call from the {@link FeatureClient}.<br>
 * <br>
 * It provides some utility methods to retrieve a webservice result depending if the service was called only once
 * {@link FeatureClientResult#getSingleCallResult()} or multiple times
 * {@link FeatureClientResult#getMultipleCallResult()}. <br>
 * <br>
 * It additionally stores the {@link MobyService} itself to determine which service returned this result. This is needed
 * as this structure is used in case of calling several webservices. In that case one needs to know which results comes
 * from which service.
 * 
 * @author <A HREF="mailto:andreas.groscurth@gmail.com">Andreas Groscurth</A>
 */
public class FeatureClientResult {
	private MobyService mobyService; // the service
	private MobyContentInstance result; // the result from the service

	/**
	 * Creates a new <tt>FeatureClientResult</tt> with the result of a moby service.
	 * 
	 * @param mobyService the service which was called
	 * @param instance the service result
	 */
	FeatureClientResult( MobyService mobyService, MobyContentInstance instance ) {
		this.mobyService = mobyService;
		this.result = instance;
	}

	/**
	 * Returns the BioMOBY service which was called
	 * 
	 * @return the web service
	 */
	public MobyService getMobyService() {
		return mobyService;
	}

	/**
	 * Returns the result for a simple, one-time call (so non multiple call). The results are returned as a list, as a
	 * service might have returned a set of objects. If a service only returns one object the list has the size one.
	 * 
	 * @param <T> the concrete MobyObject (such as AminoAcidSequence etc)
	 * @return a collection with the result objects
	 * @throws FeatureClientException if the casting in the concrete object failed
	 */
	public <T> Collection< T > getSingleCallResult() throws FeatureClientException {
		try {
			Collection< T > collection = new ArrayList< T >();
			// get the result from the only job from the service call
			MobyDataInstance[] instances = result.get( result.keySet().iterator().next() ).getPrimaryData();
			// converts the result into concrete moby data types and puts them into the collection
			fillResultCollection( collection, instances );
			return collection;
		}
		catch ( Exception e ) {
			throw new FeatureClientException( e );
		}
	}

	/**
	 * Returns the result from a multiple job call for a specific job id. If a service was called with the simple object
	 * type the id is the object identifier. If a service was called with a complex data type or more than one input,
	 * the user had to give a specific identifier which is then used here.
	 * 
	 * @param <T> the concrete MobyObject (such as AminoAcidSequence etc)
	 * @param id the job id - normally the identifier of the input object
	 * @return a collection of returned objects or null if the job id could not be found
	 * @throws FeatureClientException
	 */
	public <T> Collection< T > getMulipleCallResult(String id) throws FeatureClientException {
		try {
			Collection< T > collection = new ArrayList< T >();
			// get the specific job for the given id
			MobyDataJob dataJob = result.get( id );

			// if no job could be found null is returned
			if ( dataJob == null ) {
				return null;
			}
			fillResultCollection( collection, dataJob.getPrimaryData() );
			return collection;
		}
		catch ( Exception e ) {
			throw new FeatureClientException( e );
		}
	}

	/**
	 * Returns the result from a multiple job call. The map stores for each job identifier the list of returned objects.
	 * If a service was called with the simple object type the id of the object is the identifier. If a service was
	 * called with a complex data type or more than one input, the user had to give a specific identifier which is then
	 * also used here.
	 * 
	 * @param <T> the concrete MobyObject (such as AminoAcidSequence etc)
	 * @return a map mapping the job id with the corresponding collection of result objects
	 * @throws FeatureClientException
	 */
	public <T> Map< String, Collection< T > > getMultipleCallResult() throws FeatureClientException {
		try {
			Map< String, Collection< T > > map = new HashMap< String, Collection< T > >();

			// for each job fill the collection of results and put it into the map
			for ( MobyDataJob job : result.values() ) {
				Collection< T > collection = new ArrayList< T >();
				fillResultCollection( collection, job.getPrimaryData() );
				map.put( job.getID(), collection );
			}

			return map;
		}
		catch ( Exception e ) {
			throw new FeatureClientException( e );
		}
	}

	public <T> Collection< T > getMulitpliCallResultAsOne() throws FeatureClientException {
		try {
			Collection< T > collection = new ArrayList< T >();
			for ( MobyDataJob job : result.values() ) {
				fillResultCollection( collection, job.getPrimaryData() );
			}
			return collection;
		}
		catch ( Exception e ) {
			throw new FeatureClientException( e );
		}
	}

	/**
	 * Fills the given collection with the concrete data type object converted from the given instance array.
	 * 
	 * @param <T> the little trick - this method is highly type unsafe !
	 * @param collection the collection to fill
	 * @param instances the service results
	 * @throws Exception
	 */
	@SuppressWarnings("unchecked")
	private <T> void fillResultCollection(Collection collection, MobyDataInstance[] instances) throws Exception {
		for ( MobyDataInstance instance : instances ) {
			// if we have a object set iterate over each single entry
			if ( instance instanceof MobyDataObjectSet ) {
				MobyDataObjectSet dataObjectSet = ( MobyDataObjectSet ) instance;
				for ( MobyDataObject mobyDataObject : dataObjectSet ) {
					// convert the current object
					collection.add( FeatureClientUtility.convertServiceOutput( mobyDataObject ) );
				}
			}
			else {
				// convert the current object
				collection.add( FeatureClientUtility.convertServiceOutput( ( MobyDataObject ) instance ) );
			}
		}
	}
}