/*******************************************************************************
 * 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) 2009 Attila Kovacs

package crush.apex;

import crush.*;
import nom.tam.fits.*;
import nom.tam.util.*;

import java.io.*;

import util.*;

public class APEXArraySubscan<InstrumentType extends APEXArray<?>, FrameType extends APEXFrame> 
extends Integration<InstrumentType, FrameType> implements GroundBased {
	/**
	 * 
	 */
	private static final long serialVersionUID = 2929947229904002745L;

	public APEXArraySubscan(APEXArrayScan<InstrumentType, ?> parent) {
		super(parent);
	}
	
	public void markChopped() {
		// Flag pixels that chop on source
		// left and right are the pixel positions, where, if there's a pixel, it will have the source
		// in the left or right beams...
		//System.err.println("on phase is " + integration[i][k].onPhase);
		
		/*
		Vector2D left = new Vector2D(instrument.onPhase == 1 ? 0.0 : 2.0 * scan.chopperThrow, 0.0);
		Vector2D right = new Vector2D(instrument.onPhase == 1 ? -2.0 * scan.chopperThrow : 0.0, 0.0);
		double tolerance = instrument.resolution / 3.0;
		*/
		//markChopped(left, right, tolerance);
	}
	
	@Override
	public int getNodPhase() { return instrument.onPhase == 1 ? 1 : -1; }
	
	@Override
	public void validate() {
		super.validate();	
		if(hasOption("chopped")) markChopped();
	}
	
	
	public synchronized void readData(BinaryTableHDU hdu) throws IOException, FitsException, HeaderCardException {
		final int iData = hdu.findColumn("DATA");
		for(APEXFrame exposure : this) if(exposure != null)
			exposure.parse((float[][]) hdu.getElement(exposure.index, iData));
	}
	
	public void writeData(String fromName, String toName) throws IOException, FitsException, HeaderCardException {
		Fits fits = new Fits(new File(fromName), fromName.endsWith(".gz"));
		BinaryTableHDU hdu = (BinaryTableHDU) fits.getHDU(1);
			
		final int iData = hdu.findColumn("DATA");
		
		for(Frame exposure : this) {
			final Object[] row = hdu.getRow(exposure.index);
			final float[][] data = (float[][]) row[iData];
			for(int c=0; c<data.length; c++) data[c][0] = 0.0F;
			for(Channel channel : instrument) data[channel.dataIndex][0] = exposure.data[channel.index];
			hdu.setRow(exposure.index, row);
		}	
			
		fits.write(new BufferedDataOutputStream(new FileOutputStream(toName)));
	}
	

	public void readDataPar(BinaryTableHDU hdu)  throws IOException, FitsException, HeaderCardException {
		final Header header = hdu.getHeader();	
		final int frames = header.getIntValue("NAXIS2");
		
		clear();
		ensureCapacity(frames);
		
		final APEXArrayScan<InstrumentType, ?> apexScan = (APEXArrayScan<InstrumentType, ?>) scan;
		
		instrument.integrationTime = ((double[]) hdu.getRow(0)[hdu.findColumn("INTEGTIM")])[0] * Unit.s;
		instrument.samplingInterval = instrument.integrationTime;
		// Use the integrationTime to convert to data weights...
		instrument.dataWeights();
		
		System.err.println("   Sampling at " + Util.f3.format(1.0/instrument.samplingInterval) + " Hz.");
	
		final int iMJD = hdu.findColumn("MJD");
		final int iLST = hdu.findColumn("LST");
		
		// In older MBFITS files RA, DEC columns did not exists. Instead the astronomical basis coordinates may be used if the
		// basis system was equatorial.		
		int iRA = hdu.findColumn("RA");
		int iDEC = hdu.findColumn("DEC");
		
		if(iRA < 0 && apexScan.isEquatorial) {
			iRA = hdu.findColumn("BASLONG");
			iDEC = hdu.findColumn("BASLAT");
		}
			
		final int iAZ = hdu.findColumn("AZIMUTH");
		final int iEL = hdu.findColumn("ELEVATIO");
		final int iDX = hdu.findColumn("LONGOFF");
		final int iDY = hdu.findColumn("LATOFF");
		//final int iPA = hdu.findColumn("PARANGLE");
		final int iChop = hdu.findColumn("WOBDISLN");
		final int iPhase = hdu.findColumn("PHASE");
	
		final boolean chopperIncluded = header.getBooleanValue("WOBCOORD");
		
		System.err.println("   " + frames + " frames found (" + 
				Util.f1.format(frames * instrument.samplingInterval / Unit.min) + " minutes).");
			
		if(hasOption("scramble")) System.err.println("   !!! Scrambling position data (noise map only) !!!");
		
		for(int t=0; t<frames; t++) {
			final FrameType exposure = getFrameInstance();
			exposure.index = t;
			
			final Object[] row = hdu.getRow(t);
			exposure.MJD = ((double[])row[iMJD])[0];
			exposure.LST = ((double[])row[iLST])[0];
				
			double az = ((double[])row[iAZ])[0] * Unit.deg;
			double el = ((double[])row[iEL])[0] * Unit.deg;
			
			if(az < -900.0*Unit.deg) az = Double.NaN;
			if(el < -900.0*Unit.deg) el = Double.NaN;
			
			exposure.horizontal = new HorizontalCoordinates(az, el);
			
			exposure.calcParallacticAngle();
			
			exposure.horizontalOffset = new Vector2D(((double[])row[iDX])[0] * Unit.deg, ((double[])row[iDY])[0] * Unit.deg);
			// Make offsets always horizontal...
			if(apexScan.isNativeEquatorial) exposure.toHorizontal(exposure.horizontalOffset);
			
			if(iChop >= 0) exposure.chopperPosition.x = ((double[])row[iChop])[0] * Unit.deg;
			exposure.chopperPhase = ((int[])row[iPhase])[0];			
			exposure.zenithTau = (float) zenithTau;
			
			if(apexScan.isChopped) if(!chopperIncluded) {
				exposure.horizontal.x += exposure.chopperPosition.x / exposure.horizontal.cosLat;
				exposure.horizontalOffset.x += exposure.chopperPosition.x;
			}
			
			// If the RA,DEC coords are readily available, then just use them...
			if(iRA >= 0) {
				double ra = ((double[])row[iRA])[0] * Unit.deg;
				double dec = ((double[])row[iDEC])[0] * Unit.deg;
				
				if(ra < -900.0*Unit.deg) ra = Double.NaN;
				if(dec < -900.0*Unit.deg) dec = Double.NaN;
				
				exposure.equatorial = new EquatorialCoordinates(ra, dec, scan.equatorial.epoch);
			}
			// Otherwise calculate the equatorial coordinates from the horizontal ones...
			else exposure.calcEquatorial();
						
			// Scrambling produces a messed-up map, which is suitable for  studying the noise properties
			// If the scanning is more or less centrally symmetric, the resulting 'noise' map is
			// representative of the non-scrambled map...
			if(hasOption("scramble")) {
				exposure.horizontalOffset.invert();
				exposure.chopperPosition.x *= -1.0;
				exposure.sinPA *= -1.0;
				exposure.cosPA *= -1.0;
			}		
			
			if(exposure.isValid()) add(exposure);
			else add(null);
		}	
	
		final String phase1 = header.getStringValue("PHASE1");	
		final String phase2 = header.getStringValue("PHASE2");
	
		if(phase1 != null) {
			if(phase1.equalsIgnoreCase("WON")) instrument.onPhase = 1;
			else if(phase1.equalsIgnoreCase("WOFF")) instrument.offPhase = 1;
		}
		if(phase2 != null) {
			if(phase2.equalsIgnoreCase("WON")) instrument.onPhase = 2;
			if(phase2.equalsIgnoreCase("WOFF")) instrument.offPhase = 2;
		}
	
		if(apexScan.isChopped) {
			System.err.println("   " + (getNodPhase() > 0 ? "ON-OFF" : "OFF-ON"));
			detectChopper();
		}
		
		
	}

	public void readMonitor(BinaryTableHDU hdu) throws IOException, FitsException, HeaderCardException {}

	@SuppressWarnings("unchecked")
	@Override
	public FrameType getFrameInstance() {
		return (FrameType) new APEXFrame((APEXArrayScan) scan);
	}


	public void fitsRCP() {
		System.err.println("   Using RCP data contained in the FITS.");
		for(APEXPixel pixel : instrument) pixel.position = (Vector2D) pixel.fitsPosition.clone();
	}
}
