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


import java.lang.reflect.*;
import java.io.*;
import java.util.*;
import java.text.*;


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

public final class Util {
	public final static DecimalFormat f0 = new DecimalFormat("0");
	public final static DecimalFormat f1 = new DecimalFormat("0.0");
	public final static DecimalFormat f2 = new DecimalFormat("0.00");
	public final static DecimalFormat f3 = new DecimalFormat("0.000");
	public final static DecimalFormat f4 = new DecimalFormat("0.0000");
	public final static DecimalFormat f5 = new DecimalFormat("0.00000");
	public final static DecimalFormat f6 = new DecimalFormat("0.000000");
	public final static DecimalFormat f7 = new DecimalFormat("0.0000000");
	public final static DecimalFormat f8 = new DecimalFormat("0.00000000");
	public final static DecimalFormat f9 = new DecimalFormat("0.000000000");

	public final static DecimalFormat e0 = new DecimalFormat("0E0");
	public final static DecimalFormat e1 = new DecimalFormat("0.0E0");
	public final static DecimalFormat e2 = new DecimalFormat("0.00E0");
	public final static DecimalFormat e3 = new DecimalFormat("0.000E0");
	public final static DecimalFormat e4 = new DecimalFormat("0.0000E0");
	public final static DecimalFormat e5 = new DecimalFormat("0.00000E0");
	public final static DecimalFormat e6 = new DecimalFormat("0.000000E0");
	public final static DecimalFormat e7 = new DecimalFormat("0.0000000E0");
	public final static DecimalFormat e8 = new DecimalFormat("0.00000000E0");
	public final static DecimalFormat e9 = new DecimalFormat("0.000000000E0");

	public final static DecimalFormat d1 = new DecimalFormat("0");
	public final static DecimalFormat d2 = new DecimalFormat("00");
	public final static DecimalFormat d3 = new DecimalFormat("000");
	public final static DecimalFormat d4 = new DecimalFormat("0000");
	public final static DecimalFormat d5 = new DecimalFormat("00000");
	public final static DecimalFormat d6 = new DecimalFormat("000000");
	public final static DecimalFormat d7 = new DecimalFormat("0000000");
	public final static DecimalFormat d8 = new DecimalFormat("00000000");
	public final static DecimalFormat d9 = new DecimalFormat("000000000");
	
	
	public final static DecimalFormat[] e = { e0, e1, e2, e3, e4, e5, e6, e7, e8, e9 },
	f = { f0, f1, f2, f3, f4, f5, f6, f7, f8, f9 },
	d = { null, d1, d2, d3, d4, d5, d6, d7, d8, d9 };

	/**
       Return the standardized angle for a given angle.
       @return an angle between -Pi and Pi.       
	 */
	public final static double standardAngle(double angle) {
		angle = Math.IEEEremainder(angle, 2.0 * Math.PI);
		return angle;
	}

	/**
       Return the time of day for a given time value.
       @return time betweem 0-24h.       
	 */
	public final static double timeOfDay(double time) {
		time = Math.IEEEremainder(time, Unit.day);
		if(time < 0.0) time += 24.0 * Unit.hour;
		return time;
	}

	/**
       Return the equivalent time value for angle. Useful for converting angles to right-ascention or hour-angle
       @return a time.      
       @see angleOfTime
	 */
	public final static double timeOfAngle(double angle) {
		return timeOfDay(angle * Unit.second/Unit.secondAngle);
	}

	/**
       Return the equivalent angle value for a time value. Useful for converting right-ascention or hour-angle to radians.
       @return an angle.
       @see timeOfAngle
	 */
	public final static double angleOfTime(double time) {
		return standardAngle(time * Unit.secondAngle/Unit.second);
	}

	/**
       Construct an angle from degree, arc-minute and arc-second values.
       @param degree
       @param arcminute
       @param arcsecond
       @return an angle.
       @see DMS
	 */    
	public static double DMS(int D, int M, double S) {
		return D*Unit.degree + M*Unit.arcmin + S*Unit.arcsec;
	}

	/**
       Construct an time from hour, minute and second values.
       @param degree
       @param arcminute
       @param arcsecond
       @return an angle.
       @see HMS
	 */ 

	public static double HMS(int H, int M, double S) {
		return H*Unit.hour + M * Unit.minute + S * Unit.second;
	} 

	public static String HMS(double time) { return HMS(time, f[3]); }

	public static String HMS(double time, int precision) { return HMS(time, f[precision]); }

	public static String HMS(double time, DecimalFormat df) {
		if(time < 0.0) return("-" + HMS(-time, df));

		time /= Unit.hour; int hour = (int)time; time -= hour;
		time *= 60.0; int min = (int)time; time -= min;

		return d2.format(hour) + ":" + d2.format(min) + ":" + df.format(60.0 * time);
	}

	public static String shortHMS(double time, DecimalFormat df) {
		if(time < 0.0) return("-" + shortHMS(-time, df));

		time /= Unit.hour; int hour = (int)time; time -= hour;
		time *= 60.0; int min = (int)time; time -= min;

		return (hour > 0 ? d2.format(hour) + ":" : "") + (min > 0 ? d2.format(min) + ":" : "") + df.format(60.0 * time);
	}



	public static String DMS(double angle) { return DMS(angle, f[3]); }

	public static String DMS(double angle, int precision) { return DMS(angle, f[precision]); }

	public static String DMS(double angle, DecimalFormat df) {
		if(angle < 0.0) return("-" + DMS(-angle, df));

		angle /= Unit.degree; int d = (int)angle; angle -= d;
		angle *= 60.0; int m = (int)angle; angle -= m;

		return d2.format(d) + ":" + d2.format(m) + ":" + df.format(60.0 * angle);
	}

	public static String shortDMS(double angle, DecimalFormat df) {
		if(angle < 0.0) return("-" + shortDMS(-angle, df));

		angle /= Unit.degree; int d = (int)angle; angle -= d;
		angle *= 60.0; int m = (int)angle; angle -= m;

		return (d > 0 ? d2.format(d) + ":" : "") + (m > 0 ? d2.format(m) + ":" : "") + df.format(60.0 * angle);
	}


	public static double parseTime(String HMS) {
		StringTokenizer tokens = new StringTokenizer(HMS, " \t\n\r:hmsHMS");

		double time = Integer.parseInt(tokens.nextToken()) * Unit.hour;
		time += Integer.parseInt(tokens.nextToken()) * Unit.min;
		time += Double.parseDouble(tokens.nextToken()) * Unit.second;

		return time;
	}

	public static double parseAngle(String DMS) {
		StringTokenizer tokens = new StringTokenizer(DMS, "- \t\n\r:dmsDMS");

		double angle = Integer.parseInt(tokens.nextToken()) * Unit.degree;
		angle += Integer.parseInt(tokens.nextToken()) * Unit.arcmin;
		angle += Double.parseDouble(tokens.nextToken()) * Unit.arcsec;

		if(DMS.indexOf('-') >= 0) angle *= -1;

		return angle;
	}

	public static boolean parseBoolean(String value) throws NumberFormatException {
		// Yes/No
		if(value.equalsIgnoreCase("y")) return true;
		if(value.equalsIgnoreCase("yes")) return true;
		if(value.equalsIgnoreCase("n")) return false;
		if(value.equalsIgnoreCase("no")) return false;
		// True/False
		if(value.equalsIgnoreCase("t")) return true;
		if(value.equalsIgnoreCase("true")) return true;
		if(value.equalsIgnoreCase("f")) return false;
		if(value.equalsIgnoreCase("false")) return false;
		// On/Off
		if(value.equalsIgnoreCase("on")) return true;
		if(value.equalsIgnoreCase("off")) return false;
		// 1/0
		if(value.equalsIgnoreCase("1")) return true;
		if(value.equalsIgnoreCase("0")) return false;

		throw new NumberFormatException(" ERROR! Illegal Boolean value: " + value);
	}

	static double[] lineCoeff = new double[2];
	public static double[] lineFit(double[] y, double[] w) {
		double s=0.0, sx=0.0, sy=0.0, sxx=0.0, sxy=0.0;

		for(int i=0; i<y.length; i++) {
			s += w[i];
			sx += w[i]*i;
			sy += w[i]*y[i];
			sxx += w[i]*i*i;
			sxy += w[i]*i*y[i];
		}
		double D = s*sxx - sx*sx;

		lineCoeff[0] = (sxx*sy - sx*sxy) / D;
		lineCoeff[1] = (s*sxy - sx*sy) / D;

		return lineCoeff;
	}

	// implement with Properties...
	public static void printContents(Object object) {
		System.out.println("Contents of " + object.getClass().getName() + ":");
		System.out.println("-------------------------------------------------------------");

		Field[] field = object.getClass().getFields();
		for(int i=0; i<field.length; i++) {
			try {
				Object value = field[i].get(object);
				System.out.println("  " + field[i].getName() + " = " + (value == null ? "null" : value.toString())); 
			}
			catch(IllegalAccessException e) {}
		}

		System.out.println("-------------------------------------------------------------");
	}

	/*
    public static double fitsDouble(double value) {
	if(value < 0.0 && value > -1.0) {
	    DecimalFormat df = new DecimalFormat("0.0000000000000E0");
	    return Double.parseDouble(df.format(value));
	}
	else if(value > 0.0 && value < 1.0) {
	    DecimalFormat df = new DecimalFormat("0.00000000000000E0");
	    return Double.parseDouble(df.format(value));
	}
	else return value;
    }

    public static void addLongFitsKey(Header header, String key, String value, String comment) 
	throws FitsException, HeaderCardException {

	Cursor cursor = header.iterator();
	while(cursor.hasNext()) cursor.next();
	addLongFitsKey(cursor, key, value, comment);
    }

    public static String getLongFitsKey(Header header, String key) {
	String value = header.getStringValue(key);

	if(value == null) {
	    value = new String();
	    char ext = 'A';
	    String part;
	    do {
		part = header.getStringValue(key + ext);
		if(part != null) value += part;
		ext++;
	    } 
	    while(part != null);
	}

	return value;
    }


    public static void addLongFitsKey(Cursor cursor, String key, String value, String comment) 
	throws FitsException, HeaderCardException {

	final int size = 65 - comment.length();

	if(value.length() <= size) {
	    cursor.add(new HeaderCard(key, value, comment));
	    return;
	}

	int start = 0;	
	char ext = 'A';

	while(start < value.length()) {
	    int end = start + size;
	    if(end > value.length()) end = value.length();

	    cursor.add(new HeaderCard(key + ext, value.substring(start, end), comment));

	    ext++;
	    start = end;
	}
    }
	 */
	
	public static String getSystemString(String spec) {
		String homes = System.getProperty("user.home");
		
		homes = homes.substring(0, homes.lastIndexOf(File.separator) + 1);
		
		String text = "";
		
		// See if it's a home directory specification starting with '~'...
		if(spec.charAt(0) == '~') {
			int userChars = spec.indexOf(File.separator) - 1;
			if(userChars < 0) userChars = spec.length() - 1;
			if(userChars == 0) text = System.getProperty("user.home");
			else text += homes + spec.substring(1, 1 + userChars);
			spec = spec.substring(userChars + 1);
		}
		
		while(spec.contains("{$")) {
			int from = spec.indexOf("{$");
			int to = spec.indexOf("}");
			if(to < 0) return text + spec;
			
			text += spec.substring(0, from);
			text += System.getenv(spec.substring(from+2, to));
			
			spec = spec.substring(to+1);
		}
		
		text += spec;
		
		return text;
		
	}
	
	public static final double sigmasInFWHM = 2.0*Math.sqrt(2.0*Math.log(2.0));
}


