import java.io.*;
import java.text.*;

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


public class SharcHeader implements Cloneable {
    SharcScan scan;
    
    // All angles are in radians, including RA
    float RA=0.0F, DEC=0.0F, AZ = 0.0F, EL = 0.0F, AZE = 0.0F, ELE = 0.0F; 
    float RAO = 0.0F, DECO = 0.0F, AZO = 0.0F, ELO = 0.0F, chopOffset;
    float LST = 0.0F, PA = 0.0F;
    int ticks, flags;
    double UT = 0.0, startUT = 0.0, endUT = 0.0;

    double dspTime;
    int frameNumber;

    static boolean hasExtraTimingInfo = false;

    // matrix for Position angle
    public double cosA, sinA, cosPA, sinPA, cosEL;  

    // the Pointing Center wrt to the rotationCenter in RADEC coords
    private Vector2D RC;

    // Various Calculated terms
    public Vector2D radec0, altaz0;

    // A faster implementation for coordinate calculation
    public double dRA0, dDEC0, dAZ0, dEL0;
    private double dRAdx, dDECdx, dRAdy, dDECdy;
    private double dAZdx, dELdx, dAZdy, dELdy;

    public SharcHeader() { init(); }

    public SharcHeader(SharcScan setscan) { scan = setscan; init(); }
    
    public void init() {}

    public Object clone() {
	try { return super.clone(); }
	catch(CloneNotSupportedException e) { e.printStackTrace(); }
	return null;
    }

    public double getPositionAngle() {
	return PA + (scan.isNasmyth ?  EL : scan.rotatorAngle) - scan.rotatorZeroAngle;
    }

    // Calculate some objects for faster pixel RADEC position determination
    public void calc() {	
	// M(PA) Calculate entries of parallactic angle rotation matrix
	cosPA = Math.cos(PA);
	sinPA = Math.sin(PA);

	// M(alpha) Calculate entries of position angle rotation matrix
	double alpha = getPositionAngle();

	cosA = Math.cos(alpha);
	sinA = Math.sin(alpha);

	// cos terms for conversions between global and local coordinates
	cosEL = Math.cos(EL);

	// The nominal position vectors
	radec0 = new Vector2D(scan.RA, scan.DEC);
	altaz0 = new Vector2D(AZ, EL);
		
	// Calculate the RADEC offset of the rotation center to
	// the pointing center.
	// Assume pointed at zero rotation
	RC = getArrayOffset(scan.rotationCenter, scan.pointingCenter);
	RC.x *= -scan.pixelXSize;
	RC.y *= scan.pixelYSize;	
	altaz2RADEC(RC);

	// Calculate the RADec offset position of the corner pixel (0,0)
	// Start with the Aggregated Telescope Offsets
	Vector2D dradec0 = new Vector2D(scan.RAO + RAO, scan.DECO + DECO);
	dradec0.add(altaz2RADEC(new Vector2D(scan.AZO + AZO + (scan.chopped ? chopOffset : 0.0F) + AZE * Math.cos(EL), 
					     scan.ELO + ELO + ELE)));

	// Correct for Array Rotation 
	dradec0.add(RC);
	dradec0.add(array2RADEC(getArrayOffset(new Vector2D(0.0,0.0), scan.rotationCenter)));

	// The differencials in the column and row directions
	Vector2D coldradec = array2RADEC(new Vector2D(1.0, 0.0));
	Vector2D rowdradec = array2RADEC(new Vector2D(0.0, 1.0)); 

	dRA0 = dradec0.x; dDEC0 = dradec0.y;
	dRAdx = coldradec.x; dDECdx = coldradec.y;
	dRAdy = rowdradec.x; dDECdy = rowdradec.y;

	// Calculate the AltAZ offset position for the corner pixel (0,0)
	Vector2D daltaz0 = radec2ALTAZ(new Vector2D(dradec0));
	Vector2D coldaltaz = radec2ALTAZ(new Vector2D(coldradec));
	Vector2D rowdaltaz = radec2ALTAZ(new Vector2D(rowdradec));

	dAZ0 = daltaz0.x; dEL0 = daltaz0.y;
	dAZdx = coldaltaz.x; dELdx = coldaltaz.y;
	dAZdy = rowdaltaz.x; dELdy = rowdaltaz.y;
	
    }

    // position angle rotation using pre-calculated matrix
    // x coordinate is inverted before rotation to correspond 
    // to ALTAZ coordinates at zero rotation angle
    // x coordinate is inverted after rotation to account
    // for inverted RA coordinate
    private Vector2D array2RADEC(Vector2D array) {
	// First scale to angular size vector
	array.x *= -scan.pixelXSize;
	array.y *= scan.pixelYSize;

	double newx = array.x * cosA - array.y * sinA;
	array.y     = array.x * sinA + array.y * cosA;
	array.x = -newx;

	return array;	
    }

    // parallactic angle rotation using pre-calculated matrix
    // x coordinate is inverted after rotation to account
    // for inverted RA coordinate
    private Vector2D altaz2RADEC(Vector2D altaz) {
	double newx = altaz.x * cosPA - altaz.y * sinPA;
	altaz.y     = altaz.x * sinPA + altaz.y * cosPA;
	altaz.x = -newx;
	return altaz;
    }

    private Vector2D radec2ALTAZ(Vector2D radec) {
	double newx = -radec.x * cosPA + radec.y * sinPA;
	radec.y = radec.x * sinPA + radec.y * cosPA;
	radec.x = newx;
	return radec;
    }
    
    // Array coordinates align to RADEC coordinates at zero position angle
    // At zero rotation column number goes opposite to AZ, row number along EL   
    public Vector2D getArrayOffset(Vector2D position, Vector2D reference) {
	return new Vector2D(position.x - reference.x, position.y - reference.y);
    }

    // For speediness the same vector is recalculated again and again
    // Need to keep methods accessing results synchronized
    private Vector2D bolOffset = new Vector2D();
    public synchronized Vector2D getArrayOffset(Bolometer pixel, Vector2D reference) {
	Vector2D position = pixel.position;
	bolOffset.x = pixel.position.x - reference.x;
	bolOffset.y = pixel.position.y - reference.y;
	return bolOffset;
    }

    // For speediness the same vector is recalculated again and again
    // Need to keep methods accessing results synchronized
    private Vector2D daltaz = new Vector2D();
    public synchronized Vector2D getdAZdEL(Bolometer pixel) {
	double dx = pixel.position.x;
	double dy = pixel.position.y;

	daltaz.x = dAZ0 + dAZdx * dx + dAZdy * dy;
	daltaz.y = dEL0 + dELdx * dx + dELdy * dy;
	
     	return daltaz;	
    }	

    // For speediness the same vector is recalculated again and again
    // Need to keep methods accessing results synchronized
    private Vector2D dradec = new Vector2D();
    public synchronized Vector2D getdRAdDEC(Bolometer pixel) {
	double dx = pixel.position.x;
	double dy = pixel.position.y;

	dradec.x = dRA0 + dRAdx * dx + dRAdy * dy;
	dradec.y = dDEC0 + dDECdx * dx + dDECdy * dy;

	return dradec;
    }


    public synchronized Vector2D getRADEC(Bolometer pixel) {
	Vector2D radec = getdRAdDEC(pixel);
	// convert dRA to absolute RA difference
	radec.x /= scan.cosDEC;
	radec.add(radec0);
	return radec;
    }

    public synchronized Vector2D getALTAZ(Bolometer pixel) {
	Vector2D altaz = getdAZdEL(pixel);
	altaz.x /= cosEL;
	altaz.add(altaz0);
	return altaz;
    }

    public synchronized Vector2D getTrackingALTAZ(Bolometer pixel) {
	Vector2D altaz = getdAZdEL(pixel);
	if(scan.chopped) altaz.x -= chopOffset;
	altaz.x /= cosEL;
	altaz.add(altaz0);
	return altaz;
    }

    public static void findColumns(BinaryTableHDU hdu) {
	iRA = hdu.findColumn("RA");
	iDEC = hdu.findColumn("DEC");
	iAZ = hdu.findColumn("AZ");
	iEL = hdu.findColumn("EL");
	iAZE = hdu.findColumn("AZ_ERROR");
	iELE = hdu.findColumn("EL_ERROR");
	iPA = hdu.findColumn("Parallactic Angle");
	iUT = hdu.findColumn("UT");
	iLST = hdu.findColumn("LST");
	iDT = hdu.findColumn("Detector Time");
	iSN = hdu.findColumn("Sequence Number");
	iRAO = hdu.findColumn("RAO");
	iDECO = hdu.findColumn("DECO");
	iAZO = hdu.findColumn("AZO");
	iELO = hdu.findColumn("ELO");
	iFlag = hdu.findColumn("Celestial");
	iCHO = hdu.findColumn("CHOP_OFFSET");
	
	hasExtraTimingInfo = iDT >= 0 && iSN >= 0; 
	hasColumnInfo = true;
    }

    public void validate() {
	if(valid) return;
	
	UT = (startUT + endUT) / 2.0;

	RA /= samples; DEC /= samples; AZ /= samples; EL /= samples;
	LST /= samples; PA /= samples; dspTime /= samples;
	AZE /= samples; ELE /= samples;
	AZO /= samples; ELO /= samples; RAO /= samples; DECO /= samples;
	chopOffset /= samples;

	valid = true;
    }

    public void add(SharcHeader header) throws IllegalStateException {
	valid = false;

	if( header.startUT < startUT) throw new IllegalStateException("Non consecutive frame.");
	if( header.startUT - endUT > scan.samplingInterval ) throw new IllegalStateException("Gap found in data");

	// Check for chopper speed
	if(Math.abs(header.chopOffset - startChopOffset) > scan.chopTolerance) throw new IllegalStateException("Chopper Smear");

	final int n = header.samples;

	RA += n * header.RA; DEC += n * header.DEC;
	AZ += n * header.AZ; EL += n * header.EL;
	AZE += n * header.AZE; ELE += n * header.ELE;
	PA += n * header.PA; LST += n * header.LST;
	
	RAO += n * header.RAO; DECO += n * header.DECO;
	AZO += n * header.AZO; ELO += n * header.ELO;

	chopOffset += n * header.chopOffset;

	endUT = header.endUT;

	samples += n;
    }


    private double startChopOffset = 0.0;
    public void parse(Object[] row) {
	valid = false;

	try { UT = ((double[]) row[iUT])[0] * Unit.HOUR; }
	catch(ClassCastException e) { UT = ((float[])row[iUT])[0] * Unit.HOUR; }

	startUT = UT;
	endUT = startUT + scan.samplingInterval;

	RA = ((float[])row[iRA])[0]*Unit.HOUR*15.0F;
	DEC = ((float[])row[iDEC])[0]*Unit.DEGREE;
	AZ = ((float[])row[iAZ])[0]*Unit.DEGREE;
	EL = ((float[])row[iEL])[0]*Unit.DEGREE;
	AZE = ((float[])row[iAZE])[0]*Unit.ArcSECOND;
	ELE = ((float[])row[iELE])[0]*Unit.ArcSECOND;
	PA = ((float[])row[iPA])[0]*Unit.DEGREE;
	
	LST = ((float[])row[iLST])[0]*Unit.HOUR;
	
	if(hasExtraTimingInfo) {
	    dspTime = ((double[])row[iDT])[0] * Unit.SECOND;
	    frameNumber = ((int[])row[iSN])[0];
	}

	// Add in the sweeping offsets
	RAO = ((float[])row[iRAO])[0]*Unit.ArcSECOND;
	DECO = ((float[])row[iDECO])[0]*Unit.ArcSECOND;
	AZO = ((float[])row[iAZO])[0]*Unit.ArcSECOND;
	ELO = ((float[])row[iELO])[0]*Unit.ArcSECOND;
	
	chopOffset = ((float[])row[iCHO])[0]*Unit.ArcSECOND;
	startChopOffset = chopOffset; 

	for(int bit=0; bit<32; bit++) flags |= ((boolean[])row[iFlag+bit])[0] ? 1<<bit : 0;
	
	samples = 1;
	valid = true;
    }

    protected int samples = 0;
    private boolean valid = false;

    private static boolean hasColumnInfo = false;
    private static int iRA, iDEC, iAZ, iEL, iAZE, iELE, iPA, iUT, iLST, iDT, iSN, iRAO, iDECO, iAZO, iELO, iFlag, iCHO; 

    // Pixel scales from A. Kovacs (Apr 2003) 
    final static float pixelXSize = 4.89F * Unit.ArcSECOND;
    final static float pixelYSize = 4.82F * Unit.ArcSECOND;
    final static float pixelSize = (pixelXSize + pixelYSize) / 2.0F;
    final static float pixelArea = pixelXSize * pixelYSize;

    // Pixel scales from Dowell (31 Aug 2004)
    final static float nasmythPixelXSize = 4.49F * Unit.ArcSECOND;
    final static float nasmythPixelYSize = 4.74F * Unit.ArcSECOND;

    // The signal level at the detector stage that corresponds to unity change in emissivity.
    // Determined from Jan 2004 sky dip.
    final static float I0 = 3.04E-3F * Unit.VOLT;
    
    // the flags
    static final int CELESTIAL = 1<<0;
    static final int TRANSITED = 1<<1;
    static final int SCANNING = 1<<2;
    static final int ACQUIRED = 1<<3;
    static final int TRACKING = 1<<4;

    static final int CHOPPING = 1<<8;
    static final int ON_BEAM = 1<<9;

    static final int EQUATORIAL = 1<<15;

    static final int NOD_LEFT = 1<<22;
    static final int NOD_RIGHT = 1<<23; 
	
    
}




