package crush.apex;

import crush.*;

import util.*;
import util.data.*;

import java.util.*;

public class APEXChoppedPhotometry<InstrumentType extends APEXArray<?>, ScanType extends APEXArrayScan<InstrumentType,?>> extends SourceModel<InstrumentType, ScanType> {
	String sourceName;
	double integrationTime;
	WeightedPoint[] flux;
	WeightedPoint sourceFlux = new WeightedPoint();
	
	@Override
	public SourceModel<InstrumentType, ScanType> copy() {
		APEXChoppedPhotometry<InstrumentType, ScanType> copy = (APEXChoppedPhotometry<InstrumentType, ScanType>) super.copy();
		copy.sourceFlux = (WeightedPoint) sourceFlux.clone();
		copy.flux = new WeightedPoint[flux.length];
		for(int i=flux.length; --i >= 0; ) if(flux[i] != null) copy.flux[i] = (WeightedPoint) flux[i].clone();
		return copy;
	}

	public APEXChoppedPhotometry(InstrumentType instrument) {
		super(instrument);
		flux = new WeightedPoint[instrument.storeChannels+1];
		for(int i=flux.length; --i >= 0; ) flux[i] = new WeightedPoint();
	}
	
	@Override
	public void create(Collection<? extends Scan<?,?>> collection) {
		super.create(collection);
		ScanType firstScan = scans.get(0);
		sourceName = firstScan.sourceName;
	}
	
	@Override
	public void add(SourceModel<?, ?> model, double weight) {
		@SuppressWarnings("unchecked")
		APEXChoppedPhotometry<InstrumentType, ScanType> other = (APEXChoppedPhotometry<InstrumentType, ScanType>) model;
		for(int c=flux.length; --c >= 0; ) flux[c].average(other.flux[c]);
		sourceFlux.average(other.sourceFlux);
		integrationTime += other.integrationTime;
	}

	@Override
	public void add(Integration<?, ?> integration) {
		integration.comments += "[Phot]";
		APEXArraySubscan<?,?> subscan = (APEXArraySubscan<?,?>) integration;		
		APEXArray<?> instrument = subscan.instrument;
		integrationTime += subscan.chopper.efficiency * subscan.size() * instrument.integrationTime;
	
		for(PhaseOffsets offsets : subscan.chopper.phases) {
			double sum = 0.0, sumw = 0.0;
			for(APEXPixel pixel : instrument.getObservingChannels()) if(pixel.isUnflagged()) {
				if(pixel == instrument.referencePixel) continue;
				
				final double wG = offsets.weight[pixel.index] * pixel.gain;
				sum += wG * offsets.value[pixel.index];
				sumw += wG * pixel.gain;
			}
			if(sumw > 0.0) {
				final double C = sum/sumw;
				for(APEXPixel pixel : instrument) offsets.value[pixel.index] -= pixel.gain * C;
			}
 		}
	}

	@Override
	public void process(Scan<?, ?> scan) {		
		final WeightedPoint[] left = new WeightedPoint[flux.length];
		final WeightedPoint[] right = new WeightedPoint[flux.length];
		
		for(int c=flux.length; --c >= 0; ) {
			left[c] = new WeightedPoint();
			right[c] = new WeightedPoint();			
		}
		
		for(Integration<?,?> integration : scan) {
			final APEXArraySubscan<?,?> subscan = (APEXArraySubscan<?,?>) integration;
			final double transmission = 0.5 * (subscan.getFirstFrame().transmission + subscan.getLastFrame().transmission);
			
			for(APEXPixel pixel : subscan.instrument.getObservingChannels()) if(pixel.isUnflagged()) {
				WeightedPoint point = null;

				if(subscan.onSourceFlag == Frame.CHOP_LEFT) point = left[pixel.dataIndex];
				else if(subscan.onSourceFlag == Frame.CHOP_RIGHT) point = right[pixel.dataIndex];
				else continue;
				
				WeightedPoint df = integration.getLROffset(pixel);
				df.weight /= Math.max(1.0, integration.getLRChi2(pixel, df.value));
				df.scale(1.0 / transmission / subscan.gain / pixel.gain);
				
				point.average(df);
			}
		}
		
		for(int c=flux.length; --c >=0; ) {
			flux[c] = (WeightedPoint) left[c].clone();
			flux[c].subtract(right[c]);
			flux[c].scale(0.5);
		}
	
		// TODO add all pixels chopping over the source to the source flux...
		Channel refPixel = ((APEXArrayScan<?,?>) scan).get(0).instrument.referencePixel;
		int refIndex = refPixel.dataIndex;
		sourceFlux.copy(flux[refIndex]);
		flux[refIndex].noData();
		
	}
	

	@Override
	public void sync() {
		double jansky = instrument.janskyPerBeam();	
		
		DataPoint F = new DataPoint(sourceFlux);
		F.scale(1.0/jansky);
		
		System.err.print("Flux: " + F.toString(Util.e3) + " Jy/beam.");	
	}


	@Override
	public void sync(Integration<?, ?> integration) {		
	}


	@Override
	public void setBase() {
	}
	
	@Override
	public void reset() {
		super.reset();
		for(int i=flux.length; --i >= 0; ) flux[i].noData();
		sourceFlux.noData();
		integrationTime = 0.0;
	}
		
	@Override
	public void write(String path) throws Exception {
		double jansky = instrument.janskyPerBeam();
		
		DataPoint F = new DataPoint(sourceFlux);
		F.scale(1.0/jansky);
		
		System.out.println("  Note, that the results of the APEX chopped photometry reduction below include");
		System.out.println("  the best estimate of the systematic errors, based on the true scatter of the");
		System.out.println("  chopped photometry measurements. As such, these errors are higher than what");
		System.out.println("  is expected from the nominal NEFD values alone, and reflect the true");
		System.out.println("  uncertainty of the photometry more accurately.");
		System.out.println();
		
		System.out.println("  [" + sourceName + "]");
		System.out.println("  =====================================");
		System.out.print("  Flux  : ");
		
		if(F.value < 1.0)
			System.out.println(Util.getDecimalFormat(F.significance()).format(1000.0 * F.value) 
				+ " +- " + Util.s2.format(1000.0 * F.rms()) + " mJy/beam.");
		else 
			System.out.println(Util.getDecimalFormat(F.significance()).format(F.value) 
				+ " +- " + Util.s2.format(F.rms()) + " Jy/beam.");
		
		System.out.println("  Time  : " + Util.f1.format(integrationTime/Unit.min) + " min.");
		//System.out.println("  NEFD  : " + Util.f1.format(500.0 * F.rms() * Math.sqrt(integrationTime/Unit.s)) + " mJy sqrt(s).");
		System.out.println("  =====================================");
		
	}

	@Override
	public String getSourceName() {
		return sourceName;
	}

	@Override
	public Unit getUnit() {
		// TODO Auto-generated method stub
		return null;
	}

	
}
