/*******************************************************************************
 * Copyright (c) 2010 Attila Kovacs <attila_kovacs@post.harvard.edu>.
 * All rights reserved. 
 * 
 * This file is part of crush.
 * 
 *     crush is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     crush is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 * 
 *     You should have received a copy of the GNU General Public License
 *     along with crush.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Contributors:
 *     Attila Kovacs <attila_kovacs@post.harvard.edu> - initial API and implementation
 ******************************************************************************/

// Copyright (c) 2007,2008,2009,2010 Attila Kovacs

package crush;

import java.util.*;

import util.ArrayUtil;
import util.Configurator;
import util.Unit;


public class Pipeline extends Thread {
	Vector<Scan<?, ?>> scans = new Vector<Scan<?, ?>>();
	CRUSH crush;
	
	Vector<String> ordering = new Vector<String>();
	boolean isRobust = false, isGainRobust = false;
	
	public Pipeline(CRUSH crush) {
		this.crush = crush;
	}

	public boolean hasOption(String name) {
		return crush.isConfigured(name);
	}
	
	public Configurator option(String name) {
		return crush.get(name);
	}
	
	public void addScan(Scan<?, ?> scan) { 
		scans.add(scan); 
		for(Integration<?, ?> integration : scan) crush.queue.add(integration);
	}

	@Override
	public void run() {
		try { iterate(); }
		catch(InterruptedException e) { System.err.println("\nInterrupted!"); }
	}

	public void setOrdering(Vector<String> ordering) { this.ordering = ordering; }
	
	public synchronized void iterate() throws InterruptedException {	
		if(hasOption("estimator")) if(option("estimator").equals("median")) isRobust = true;
		if(hasOption("gains")) {
			Configurator gains = option("gains");
			gains.mapValueTo("estimator");
			if(gains.isConfigured("estimator")) if(gains.get("estimator").equals("median")) isGainRobust = true; 
			else isGainRobust = false;
		}

		SourceModel<?,?> scanSource = null;
		if(ordering.contains("source")) scanSource = crush.source.copy();
					
		for(Scan<?, ?> scan : scans) {				
			if(scanSource != null) scanSource.reset();
			boolean hasSource = false;
				
			for(Integration<?, ?> integration: scan) {				
				integration.comments = new String();
					
				for(String task : ordering) if(hasOption(task)) perform(task, integration);
				
				// Extract source ALWAYS at the end, independently of what was requested...
				// The supplier of the tasks should generally make sure that the source
				// is extracted at the end.
				if(ordering.contains("source")) if(hasOption("source")) {
					boolean mapping = true;
					if(hasOption("source.nefd")) if(!option("source.nefd").getRange(true).contains(integration.nefd)) mapping = false;
					if(mapping) {
						scanSource.add(integration);
						hasSource = true;
					}
				}	
			}
			
			if(hasSource) {
				scanSource.process(scan);	
				crush.source.add(scanSource, scan.weight);
				scanSource.postprocess(scan);
				Thread.yield();
			}
			
			for(Integration<?, ?> integration : scan) crush.checkout(integration);
		}
		
		// Run the garbage collector...
		System.gc();
	}
	
	public synchronized void perform(String task, Integration<?,?> integration) { 
		if(task.equals("offsets")) {
			if(integration.chopper != null) integration.getChopOffsets();
			integration.removeDrifts(integration.instrument, integration.size(), isRobust, isRobust);	    
		}
		else if(task.equals("drifts")) {
			Configurator driftOptions = option("drifts");
			String method = driftOptions.isConfigured("method") ? driftOptions.get("method").getValue() : "blocks"; 
			int driftN = integration.getFilterFrames(option("drifts").getValue(), 10.0*Unit.sec);
			if(method.equalsIgnoreCase("fft")) integration.highPassFilter(driftN * integration.instrument.samplingInterval);
			else integration.removeDrifts(integration.instrument, driftN, isRobust, false);
		}
		else if(task.startsWith("correlated")) {
			String modalityName = task.substring(task.indexOf('.')+1);
			Modality<?> modality = integration.instrument.modalities.get(modalityName);
			// Override with global settings...
			modality.setOptions(option(task));
			if(modality instanceof CorrelatedModality) if(!hasOption("gains")) modality.solveGains = false;
			if(!modality.decorrelate(integration, isRobust, isGainRobust)) return;
		}
		else if(task.equals("weighting")) getWeights(integration);
		else if(task.equals("weighting.frames")) {
			int n = hasOption("weighting.frames.resolution") ? integration.getFilterFrames(option("weighting.frames.resolution").getValue(), 10.0 * Unit.s) : 1;
			integration.getTimeWeights(ArrayUtil.getPaddedFFTSize(n));
		}
		else if(task.startsWith("despike")) integration.despike(option(task));
		else if(task.equals("whiten")) {
			integration.whiten();
			if(ordering.indexOf("whiten") > ordering.indexOf("weighting")) getWeights(integration);
		}
		else return;
		
		integration.comments += " ";	

		Thread.yield();
		
	}
	
	public void getWeights(Integration<?, ?> integration) {
		String method = "rms";
		Configurator weighting = option("weighting");
		weighting.mapValueTo("method");
		
		if(weighting.isConfigured("method")) method = weighting.get("method").getValue().toLowerCase();
		
		if(method.equals("robust")) integration.getRobustPixelWeights();
		else if(method.equals("differential")) integration.getDifferencialPixelWeights();
		else integration.getPixelWeights();	
	}
	
}
	
