// (C)2007 Attila Kovacs <attila@submm.caltech.edu>

import java.util.*;


public class ReductionThread extends Thread {
	Vector<LabocaScan[]> scans = new Vector<LabocaScan[]>();
	MiniCRUSH crush;
	Hashtable<String, String> options;
	SkyMap map;
	public Vector<LabocaScan> completed = new Vector<LabocaScan>();
	Vector<String> ordering;

	public ReductionThread(MiniCRUSH crush) {
		this.crush = crush;
		options = crush.options;
	}

	public void addScan(LabocaScan[] scan) { 
		scans.add(scan); 
		for(int k=0; k<scan.length; k++) crush.queue.add(scan[k]);
	}

	public void run() {
		iterate();
	}

	public void setOrdering(Vector<String> ordering) { this.ordering = ordering; }

	public void iterate() {
		map = crush.map;
		
		Vector<String> tasks = new Vector<String>();
		for(String task : ordering) if(options.containsKey(task)) tasks.add(task);

		boolean isRobust = false, isGainRobust = false;
		boolean solveGains = options.containsKey("gains");
		
		if(options.containsKey("estimator")) if(options.get("estimator").equals("median")) isRobust = true;
		if(options.containsKey("gains")) if(options.get("gains").equals("median")) isGainRobust = true;

		SkyMap scanMap = null;
		if(tasks.contains("sourcemap")) scanMap = map.getSkyMap();
		
		for(int i=0; i<scans.size(); i++) {				
			LabocaScan[] scan = scans.get(i);
			int subscans = scan.length;
			
			if(tasks.contains("sourcemap")) scanMap.reset();
			
			boolean scanMapOK = false;
			
			for(int k=0; k<subscans; k++) {
				LabocaScan subscan = scans.get(i)[k];				
				subscan.comments = new String();
				subscan.resetFiltering();
				
	
				for(String task : tasks) { 
					boolean success = true;

					subscan.noLastDependents();
					
					if(task.equals("offsets")) {
						subscan.comments += "[O]";
						subscan.removeOffsets(subscan.allChannels, 0, subscan.frames, isRobust);	    
					}
					else if(task.equals("drifts")) {
						int driftN = (int) Math.ceil(getFilterT(subscan, "drifts", 2.0*Unit.sec) / subscan.integrationTime);
						if(subscan.isChopped) subscan.centerChopped(crush.beamFWHM / 2.0, isRobust);
						else subscan.getDrifts(driftN, isRobust);
						//subscan.highPassFilter(driftN * subscan.integrationTime);
					}
					else if(task.equals("decorrelate")) subscan.getArrayCorrelated(true, solveGains && !options.get("decorrelate").contains("nogains"), isRobust, isGainRobust);
					else if(task.equals("weighting")) getWeights(subscan);
					else if(task.equals("time-weighting")) {
						subscan.getFrameWeights(ArrayUtil.getPaddedFFTSize((int) Math.ceil(getFilterT(subscan, "time-weighting", subscan.integrationTime)/subscan.integrationTime)));
					}
					else if(task.equals("despike")) despike(subscan, options.get("despike"));
					else if(task.equals("despike2")) despike(subscan, options.get("despike2"));
					else if(task.equals("despike3")) despike(subscan, options.get("despike3"));
					else if(task.equals("sky")) subscan.getSkyGradients(isRobust);
					else if(task.equals("accel")) subscan.getAccelCoeffs(isGainRobust);				
					else if(task.equals("boxes")) subscan.getBoxCorrelated(solveGains && !options.get("boxes").contains("nogains"), isRobust, isGainRobust);
					else if(task.equals("cables")) subscan.getCableCorrelated(solveGains && !options.get("cables").contains("nogains"), isRobust, isGainRobust);
					else if(task.equals("twisting")) subscan.getTwisting(isRobust);
					else if(task.equals("amps")) subscan.getAmpCorrelated(solveGains && !options.get("amps").contains("nogains"), isRobust, isGainRobust);
					else if(task.equals("wafers")) subscan.getWaferCorrelated(solveGains && !options.get("wafers").contains("nogains"), isRobust, isGainRobust);
					else if(task.equals("regions")) subscan.getRegionCorrelated(solveGains && !options.get("regions").contains("nogains"), isRobust, isGainRobust);
					else if(task.equals("whiten")) {
						double tolerance = 1.2; // relative spectral power...
						try { tolerance = Double.parseDouble(options.get("whiten")); }
						catch(NumberFormatException e) {}
						
						subscan.whitenTo(tolerance, crush.beamFWHM);
						if(tasks.indexOf("whiten") > tasks.indexOf("weighting")) getWeights(subscan);
					}
					else if(task.equals("neighbours")) {
						if(!options.containsKey("extended") || map.generation > 0) subscan.removeNeighbours(true);	
					}
					else if(task.equals("sourcemap") && !options.containsKey("photometry")) {		
						boolean mappingOK = true;
						
						if(options.containsKey("nefd-range")) {	
							StringTokenizer tokens = new StringTokenizer(options.get("nefd-range"), "-, \t");
							double minNEFD = 0.0, maxNEFD = Double.NaN;
							if(tokens.countTokens() > 1) minNEFD = Double.parseDouble(tokens.nextToken());
							maxNEFD = Double.parseDouble(tokens.nextToken());	
							double nefd = subscan.nefd / subscan.averageFiltering;
							if(nefd < minNEFD || nefd > maxNEFD) mappingOK = false;
						}
						
						if(mappingOK) {
							double minExp = subscan.integrationTime * (options.containsKey("scanmap-redundancy") ? 
								Integer.parseInt(options.get("scanmap-redundancy")) : 3);
							
							scanMap.addDirect(subscan.getSourceIncrement(crush.sourceSize, minExp, options.containsKey("scanmap-correction")), 1.0);
							scanMapOK = true;
						}
					}
					else success = false;
					
					if(success) {
						subscan.assignLastDependentsTo(task);		
						subscan.calcAverageFiltering();
					}
					
					Thread.yield();
				}
			}

			
			if(tasks.contains("sourcemap") && scanMapOK && !options.containsKey("photometry")) {
				scanMap.normalize();
				
				if(crush.base != null) scanMap.addImage(crush.base);
				if(options.containsKey("scanmapdespike")) scanMap.despike(Double.parseDouble(options.get("scanmapdespike")));
				
				scanMap.sanitize();

				double scanWeight = 1.0;				
				//if(options.containsKey("scanweighting") && (!options.containsKey("extended") || options.containsKey("deep")))
				if(options.containsKey("scanweighting"))
					scanWeight = options.get("scanweighting").equalsIgnoreCase("robust") ? scanMap.getRobustWeight(false) : scanMap.getWeight(false);

				if(options.containsKey("scanmaps")) {
					scanMap.fileName = MiniCRUSH.workPath + "/scan-" + (int)scan[0].getMJD() + "-" + scan[0].serialNo + ".fits";
					if(options.containsKey("unit")) scanMap.setUnit(options.get("unit"));
					try { scanMap.write(); }
					catch(Exception e) { e.printStackTrace(); }
				}
					
				for(int k=0; k<subscans; k++) scan[k].weight = scanWeight;
				map.addDirect(scanMap, scanWeight);			
				
				Thread.yield();
			}

			
			for(int k=0; k<subscans; k++) crush.checkout(scan[k]);
		}
	}

	
	public void getWeights(LabocaScan subscan) {
		String method = options.get("weighting");
		if(method.equalsIgnoreCase("robust")) subscan.getRobustPixelWeights();
		else if(method.equalsIgnoreCase("differential")) subscan.getDifferencialPixelWeights();
		else subscan.getPixelWeights();	
	}
	

	public double getFilterT(LabocaScan scan, String key, double defaultValue) {
		if(!options.containsKey(key)) return defaultValue;
		if(options.get(key) == null) return defaultValue;
		if(options.get(key).length() == 0) return defaultValue;
		
		double hipassT = Double.NaN;
		
		boolean extended = options.containsKey("extended");
		double stability = options.containsKey("stability") ? 
				Double.parseDouble(options.get("stability")) * Unit.s : 10.0*Unit.s; 
		
		if(options.get(key).equalsIgnoreCase("auto")) {
			if(extended) { 
				double sourceSize = crush.sourceSize;
				double crossingTime = 1.0 * Unit.s; 
				
				if(Double.isNaN(sourceSize) || sourceSize <= 0.0) {					
					if(options.containsKey("faint") | options.containsKey("deep")) crossingTime = crush.beamFWHM / scan.aveScanSpeed;
				}
				else crossingTime = sourceSize / scan.aveScanSpeed;
				
				hipassT = Math.max(stability, 10.0*crossingTime);
			}
			else {
				hipassT = stability;
			}
		}		
		else hipassT = Double.parseDouble(options.get(key));
		
		return hipassT;
	}
	
	boolean despikedNeighbours = false;
	
	public void despike(LabocaScan subscan, String args) {
		StringTokenizer tokens = new StringTokenizer(args, " ,");
		String method = tokens.nextToken();
		
		double level = Double.parseDouble(tokens.nextToken());
		double flagFraction = options.containsKey("spikefraction") ? Double.parseDouble(options.get("spikefraction")) : 1.0;
		int flagCount = options.containsKey("spikecount") ? Integer.parseInt(options.get("spikecount")) : Integer.MAX_VALUE;
		int frameSpikes = options.containsKey("framespikes") ? Integer.parseInt(options.get("framespikes")) : subscan.channels;
		int featureWidth = 1;
		
		if(method.equalsIgnoreCase("neighbours")) {
			subscan.despikeNeighbouring(level);
			despikedNeighbours = true;
		}
		else if(method.equalsIgnoreCase("absolute")) 
			subscan.despikeAbsolute(level);
		else if(method.equalsIgnoreCase("gradual")) 
			subscan.despikeGradual(level, 0.1);
		else if(method.equalsIgnoreCase("features")) {
			int maxT = subscan.driftN / 2;
			
			if(options.containsKey("spikewidth")) if(!options.get("spikewidth").equalsIgnoreCase("auto"))
				maxT = (int)Math.ceil(Double.parseDouble(options.get("spikewidth")) * Unit.s / subscan.integrationTime);

			subscan.despikeFeatures(level, maxT);
			featureWidth = maxT;
		}
	
		int regularSpikes = LabocaFrame.SAMPLE_SPIKE_FLAGS & ~LabocaFrame.SAMPLE_SPIKY_FEATURE;
			
		// Flag spiky frames first assumes that spikes tend to be caused in many pixels at once
		// rather than some pixels being inherently spiky...
		// Only do these for regular spikes (not features)...
		if(!method.equalsIgnoreCase("features")) subscan.flagSpikyFrames(regularSpikes, frameSpikes);
		
		if(method.equalsIgnoreCase("features")) {
			double featureFraction = 1.0 - Math.exp(-2*featureWidth*flagFraction);
			subscan.flagSpikyChannels(LabocaFrame.SAMPLE_SPIKY_FEATURE, featureFraction, 2*featureWidth*flagCount, LabocaPixel.FLAG_FEATURES);
		}
		else if(despikedNeighbours) subscan.flagSpikyChannels(regularSpikes, 2*flagFraction, 2*flagCount, LabocaPixel.FLAG_SPIKY);
		else subscan.flagSpikyChannels(regularSpikes, flagFraction, flagCount, LabocaPixel.FLAG_SPIKY);
	
	}

	
}
	
