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



import java.text.*;
import java.util.*;

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


// x, y kept in longitude,latitude form
// use RA(), DEC(), setRA() and setDEC(functions) to use RA, DEC coordinates...

public class EquatorialCoordinates extends CelestialCoordinates implements Precessing {
	public CoordinateEpoch epoch;
	
	static CoordinateAxis rightAscentionAxis, declinationAxis, rightAscentionOffsetAxis, declinationOffsetAxis;
	static CoordinateSystem defaultCoordinateSystem, defaultLocalCoordinateSystem;
	
	static {
		defaultCoordinateSystem = new CoordinateSystem("Equatorial Coordinates");
		defaultLocalCoordinateSystem = new CoordinateSystem("Equatorial Offsets");
		
		rightAscentionAxis = new CoordinateAxis("Right Ascention", "RA--");
		rightAscentionAxis.setReverse(true);
		rightAscentionAxis.setFormat(new HourAngleFormat(2));
		rightAscentionAxis.setTickUnits(new double[] { Unit.hourAngle, Unit.minuteAngle, Unit.secondAngle});

		declinationAxis = new CoordinateAxis("Declination", "DEC-");
		declinationAxis.setFormat(new AngleFormat(1));
		declinationAxis.setTickUnits(new double[] { Unit.deg, Unit.arcmin, Unit.arcsec, Unit.mas });

		rightAscentionOffsetAxis = new CoordinateAxis("dRA", rightAscentionAxis.wcsName);
		rightAscentionOffsetAxis.setReverse(true);
	
		declinationOffsetAxis = new CoordinateAxis("dDEC", declinationAxis.wcsName);
		
		defaultCoordinateSystem.add(rightAscentionAxis);
		defaultCoordinateSystem.add(declinationAxis);
		
		defaultLocalCoordinateSystem.add(rightAscentionOffsetAxis);
		defaultLocalCoordinateSystem.add(declinationOffsetAxis);		
	}
	
    public EquatorialCoordinates() {}

	public EquatorialCoordinates(String text) { super(text); }

	public EquatorialCoordinates(double ra, double dec) { super(ra, dec); epoch = new JulianEpoch(2000.0); }

	public EquatorialCoordinates(double ra, double dec, double aEpoch) { super(ra, dec); epoch = aEpoch < 1984.0 ? new BesselianEpoch(aEpoch) : new JulianEpoch(aEpoch); }

	public EquatorialCoordinates(double ra, double dec, String epochSpec) { super(ra, dec); epoch = CoordinateEpoch.forString(epochSpec); }
	
	public EquatorialCoordinates(CelestialCoordinates from) { super(from); }
	
	public void defaults() {
		coordinateSystem = defaultCoordinateSystem;
    	localCoordinateSystem = defaultLocalCoordinateSystem;		
	}

	public boolean equals(Object o) {
		if(!super.equals(o)) return false;
		EquatorialCoordinates coords = (EquatorialCoordinates) o;
		if(!coords.epoch.equals(epoch)) return false;
		return true;		
	}
	
	public void copy(SphericalCoordinates coords) {
		super.copy(coords);
		if(!(coords instanceof EquatorialCoordinates)) return;
		EquatorialCoordinates equatorial = (EquatorialCoordinates) coords;
		epoch = (CoordinateEpoch) equatorial.epoch.clone();
	}
	
	public double RA() { return canonicalLongitude(); }

	public double rightAscension() { return RA(); }

	public double DEC() { return y; }

	public double declination() { return y; }

	public void setRA(double RA) { setCanonicalLongitude(RA); }

	public void setDEC(double DEC) { setCanonicalLatitude(DEC); }
	
	// TODO
	/*
	public double getParallacticAngle(SphericalCoordinates location, double LST) {
		
	}
	*/

	public double getParallacticAngle(HorizontalCoordinates horizontal, GeodesicCoordinates geodesic) {
		double sinPA = horizontal.sinLat * Math.cos(geodesic.latitude()) / cosLat;
		double cosPA = (Math.sin(geodesic.latitude()) - sinLat * horizontal.sinLat)/(cosLat * horizontal.cosLat);	
		return Math.atan2(sinPA, cosPA);
	}

	public HorizontalCoordinates toHorizontal(GeodesicCoordinates geodesic, double LST) {
		HorizontalCoordinates horizontal = new HorizontalCoordinates();
		toHorizontal(this, horizontal, geodesic, LST);
		return horizontal;
	}
	
	public void toHorizontal(HorizontalCoordinates toCoords, GeodesicCoordinates geodesic, double LST) { toHorizontal(this, toCoords, geodesic, LST); }
	
	
	public static void toHorizontalOffset(Vector2D offset, EquatorialCoordinates equatorial, HorizontalCoordinates horizontal, GeodesicCoordinates geodesic) {
		toHorizontalOffset(offset, equatorial.getParallacticAngle(horizontal, geodesic));
	}

	public static void toHorizontalOffset(Vector2D offset, double PA) {
		offset.x *= -1.0;
		offset.rotate(-PA);
	}
	
	
	public static void toHorizontal(EquatorialCoordinates equatorial, HorizontalCoordinates horizontal, GeodesicCoordinates geodesic, double LST) {
		double H = LST * Unit.timeAngle - equatorial.RA();
		double sinPhi = Math.sin(geodesic.latitude());
		double cosPhi = Math.cos(geodesic.latitude());
		horizontal.setLatitude(asin(equatorial.sinLat * sinPhi + equatorial.cosLat * cosPhi * Math.cos(H)));
		double asinA = -Math.sin(H) * equatorial.cosLat;
		double acosA = (equatorial.sinLat - sinPhi * horizontal.sinLat) / cosPhi;
		horizontal.x = Math.atan2(asinA, acosA);
	}
	
	//  B = 1900.0 + (JD - 2415020.31352) / 365.242198781
    //  J = 2000.0 + (JD - 2451545.0) / 365.25
    //  -> B2000.0 = 2451544.5334
    //  J2000.0-B2000.0 = 0.466601900291
    //  2451545.0 JD = 1 January 2000, 11:58:55.816 UT, or 11:59:27.816 TAI
	
	public void precess(CoordinateEpoch newEpoch) {
		precess(newEpoch.getJulianYear(), false);
		epoch = newEpoch;
	}
	
	public void precess(double newJulianEpoch) { precess(newJulianEpoch, true); }
	
	//  Precession from Lederle & Schwan, Astronomy and Astrophysics, 134, 1-6 (1984)
	public void precess(double newJulianEpoch, boolean updateYear) {		
		if(newJulianEpoch == epoch.getJulianYear()) return;
		
		double julianYear = epoch.getJulianYear(); 

		double tau = (julianYear - 2000.0) * Unit.year / Unit.julianCentury;
		double t = (newJulianEpoch - julianYear) * Unit.year / Unit.julianCentury;


		double eta = (2305.6997 + (1.39744 + 0.000060 * tau) * tau 
				+ (0.30201 - 0.000270 * tau + 0.017996 * t) * t) * t * Unit.arcsec;

		double z = (2305.6997 + (1.39744 + 0.000060 * tau) * tau 
				+ (1.09543 + 0.000390 * tau + 0.018326 * t) * t) * t * Unit.arcsec;

		double theta = (2003.8746 - (0.85405 + 0.000370 * tau) * tau
				- (0.42707 + 0.000370 * tau + 0.041803 * t) * t) * t * Unit.arcsec;	

		Matrix P = R3(-z).dot(R2(theta)).dot(R3(-eta));

		double[] v = { cosLat * Math.cos(RA()), cosLat * Math.sin(RA()), Math.sin(DEC()) };

		Matrix l0 = new Matrix(v, Matrix.COLUMN_VECTOR);

		double[] l = P.dot(l0).transpose().value[0];

		double ml = Math.sqrt(l[0] * l[0] + l[1] * l[1]);

		setRA(Math.atan2(l[1], l[0]));
		setDEC(Math.atan2(l[2], ml));

		if(updateYear) epoch.setMJD(JulianEpoch.getMJDForYear(newJulianEpoch));
	}
	
	// TODO precess with proper motion...
	// TODO nutation correction...
	
	public String toString() {
		return super.toString() + " (" + (epoch == null ? "unknown" : epoch.toString()) + ")";	
	}
	
	public String toString(NumberFormat nf) {
		return super.toString(nf) + " " + "(" + (epoch == null ? "unknown" : epoch.toString()) + ")";	
	}
	
	public String toString(DecimalFormat df) {
		return super.toString(df) + " " + "(" + (epoch == null ? "unknown" : epoch.toString()) + ")";	
	}
	

	public void parse(String coords) throws NumberFormatException, IllegalArgumentException {
		StringTokenizer tokens = new StringTokenizer(coords, ",() \t\r\n");
		super.parse(tokens.nextToken() + " " + tokens.nextToken());
		
		try { epoch = tokens.hasMoreTokens() ? CoordinateEpoch.forString(tokens.nextToken()) : null; }
		catch(NumberFormatException e) { epoch = null; }
	}

	
	public void edit(Cursor cursor, String alt) throws HeaderCardException {
		cursor.add(new HeaderCard("RADESYS" + alt, epoch instanceof BesselianEpoch ? "FK4" : "FK5", "The coordinate system used."));
		super.edit(cursor, alt);
		epoch.edit(cursor, alt);
	}
	
	public void parse(Header header, String alt) {
		super.parse(header, alt);
		
		String system = header.getStringValue("RADESYS");
		if(system == null) system = header.getDoubleValue("EQUINOX" + alt) < 1984.0 ? "FK4" : "FK5";
		
		if(system.equalsIgnoreCase("FK4")) epoch = new BesselianEpoch();
		else epoch = new JulianEpoch();
		
		epoch.parse(header, alt);
	}

	private Matrix R1(double phi) {
		double c = Math.cos(phi);
		double s = Math.sin(phi);

		double[][] R = { {  1, 0, 0 }, 
				{  0, c, s },
				{  0,-s, c } };
		return new Matrix(R);
	}

	private Matrix R2(double phi) {
		double c = Math.cos(phi);
		double s = Math.sin(phi);

		double[][] R = { {  c, 0,-s }, 
				{  0, 1, 0 },
				{  s, 0, c } };	
		return new Matrix(R);
	}

	private Matrix R3(double phi) {
		double c = Math.cos(phi);
		double s = Math.sin(phi);

		double[][] R = { {  c, s, 0 }, 
				{ -s, c, 0 },
				{  0, 0, 1 } };	
		return new Matrix(R);		
	}

	@Override
	public EquatorialCoordinates getEquatorialPole() { return equatorialPole; }

	@Override
	public double getZeroLongitude() { return 0.0; }
	
	private static EquatorialCoordinates equatorialPole = new EquatorialCoordinates(0.0, quarterTurn);

	public CoordinateEpoch getEpoch() { return epoch; }
}
