package nl.tno.stormcv.fetcher;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import backtype.storm.task.TopologyContext;
import nl.tno.stormcv.model.CVParticle;
import nl.tno.stormcv.model.GroupOfFrames;
import nl.tno.stormcv.model.serializer.CVParticleSerializer;
import nl.tno.stormcv.operation.IBatchOperation;
import nl.tno.stormcv.operation.IOperation;
import nl.tno.stormcv.operation.SequentialFrameOp;
import nl.tno.stormcv.operation.ISingleInputOperation;

/**
 * A meta {@link IFetcher} that enables the use of an Operation directly within the spout. Data fetched is directly executed by the 
 * configured operation. This avoids data transfer from Spout to Bolt but is only useful when the operation can keep up with the 
 * load provided by the fetcher. A typical setup will be some Fetcher emitting Frame objects and a {@link ISingleInputOperation}  
 * that processes the Frame. Hence it is also possible to use a {@link SequentialFrameOp}.
 * 
 * It is possible to provide a {@link IBatchOperation} if the output of the Fetcher is set to emit {@link GroupOfFrames} using the
 * groupOfFramesOutput(...)  function some Fetchers have.
 * 
 * @author Corne Versloot
 *
 */
@SuppressWarnings("rawtypes")
public class FetchAndOperateFetcher implements IFetcher<CVParticle> {

	private static final long serialVersionUID = -1900017732079975170L;
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	private IFetcher fetcher;
	private boolean sio = false;
	private ISingleInputOperation singleInOp;
	private IBatchOperation batchOp;
	private List<CVParticle> results;

	/**
	 * Instantiate an {@link FetchAndOperateFetcher} by providing the Fetcher an Operation to be used.
	 * The operation can be either a SingleInputOpearion or a BatchOperation. BatchOperation's can only
	 * be used if the fetcher is configured to output GroupOfFrame objects instead of Frame's. 
	 * @param fetcher
	 * @param operation
	 */
	public FetchAndOperateFetcher(IFetcher  fetcher, IOperation  operation) {
		this.fetcher = fetcher;
		if(operation instanceof ISingleInputOperation){
			singleInOp = (ISingleInputOperation) operation;
			sio = true;
		}else{
			batchOp = (IBatchOperation) operation;
		}
	}

	@Override
	public void prepare(Map stormConf, TopologyContext context)	throws Exception {
		fetcher.prepare(stormConf, context);
		if(sio) singleInOp.prepare(stormConf, context);
		else batchOp.prepare(stormConf, context);
		results = new ArrayList<CVParticle>();
	}

	@SuppressWarnings("unchecked")
	@Override
	public CVParticleSerializer<CVParticle> getSerializer() {
		if(sio) return singleInOp.getSerializer();
		return batchOp.getSerializer();
	}

	@Override
	public void activate() {
		fetcher.activate();
	}

	@Override
	public void deactivate() {
		fetcher.deactivate();
	}

	@Override
	@SuppressWarnings("unchecked")
	public CVParticle fetchData() {
		// first empty the current list with particles before fetching new data
		if(results.size() > 0) return results.remove(0);
		
		// fetch data from particle
		CVParticle particle = fetcher.fetchData();
		if(particle == null) return null;
		try{
			if(sio) results = singleInOp.execute(particle);
			else if (!sio && particle instanceof GroupOfFrames){
				List<CVParticle> particles = new ArrayList<CVParticle>();
				particles.addAll( ((GroupOfFrames)particle).getFrames() );
				results = batchOp.execute(particles);
			}else{
				logger.warn("BatchOperation configured did not get a GroupOfFrames object from from the Fetcher, got "+particle.getClass().getName()+" instead" );
			}
		}catch(Exception e){
			logger.error("Unable to process fetched data due to: "+e.getMessage(), e);
		}
		if(results.size() > 0) return results.remove(0);
		return null;
	}

}