// Copyright 1997 The Open Group Research Institute.  All rights reserved.

package krb5.lib;

import krb5.lib.asn1.*;
import java.util.Date;

/**
 * Implementation of the time object for Kerberos
 */
public class KerberosTime implements Cloneable {

	/**
	 * Local time
	 */
	private long kerberosTime; //actually stores local time, not UTC

	/**
	 * Indicates whether it is NOW (default to true)
	 */
	public static final boolean NOW = true;

	//defaults to zero instead of now; use setNow() for current time
	/**
	 * Class constructor
	 */
	public KerberosTime() {
		kerberosTime = 0;
	}

	/**
	 * Class constructor specifying a time
	 *
	 * @param time is of type long
	 */
	public KerberosTime(long time) {
		kerberosTime = time;
	}

	/**
	 * Class constructor specifying whether to initialize it to now
	 *
	 * @param initToNow is of type boolean
	 */
	public KerberosTime(boolean initToNow) {
		if (initToNow) {
			Date temp = new Date();
			setTime(temp);
		}
		else
			kerberosTime = 0;
	}

	/**
	 * Clones a copy of the object
	 *
	 * @return Object is a result
	 * @see java.lang.Object
	 */
	public Object clone() {
		return new KerberosTime(kerberosTime);
	}

	/**
	 * Class constructor specifying the time in string
	 *
	 * @param time is of type String
	 * @exception Asn1Exception an exception
	 * @see java.lang.String
	 * @see krb5.lib.KerberosTime#toKerberosTime
	 * @see krb5.lib.Asn1Exception
	 */
	public KerberosTime(String time) throws Asn1Exception {
		kerberosTime = toKerberosTime(time);
	}
	
	/**
	 * Class constructor specifying encoding reference
	 *
	 * @param ref is of type EncodeRef
	 * @exception Asn1Exception an exception
	 * @see krb5.lib.asn1.EncodeRef
	 * @see krb5.lib.KerberosTime#toKerberosTime
	 * @see krb5.lib.Asn1Exception
	 */
	public KerberosTime(EncodeRef ref) throws Asn1Exception {
		kerberosTime = toKerberosTime(decode.GeneralizedTime(ref));
	}

	/**
	 * Converts time to Kerberos Time
	 *
	 * @return long is a result
	 * @param time is of type String
	 * @exception Asn1Exception an exception
	 * @see java.lang.String
	 * @see krb5.lib.Asn1Exception
	 */
	public static long toKerberosTime(String time) throws Asn1Exception {

		// ASN.1 GeneralizedTime format:

		// "19700101000000Z"
		//  |   | | | | | |
        //  0   4 6 8 | | |
        //           10 | |
		//			   12 |
		//			     14

		if (time.length() != 15)
			throw new Asn1Exception(Krb5.ASN1_BAD_TIMEFORMAT);
		if (time.charAt(14) != 'Z')
			throw new Asn1Exception(Krb5.ASN1_BAD_TIMEFORMAT);
		int year = Integer.parseInt(time.substring(0, 4)) - 1900;
		if (year > 137)
			year = 137;
		Date temp = new Date(
			year,
			Integer.parseInt(time.substring(4, 6)) - 1,
			Integer.parseInt(time.substring(6, 8)),
			Integer.parseInt(time.substring(8, 10)),
			Integer.parseInt(time.substring(10, 12)),
			Integer.parseInt(time.substring(12, 14)));
		//The Date constructor assumes the setting are local relative
		//and converts the time to UTC before storing it.  Since we
		//want the internal representation to correspond to local
		//and not UTC time we subtract the UTC time offset.
		return (temp.getTime() - (temp.getTimezoneOffset() * 60000L));
	}

	//XXX should be moved to krb5.lib.util class
	/**
	 * Pads with 0 
	 *
	 * @return String is a result
	 * @param s is of type String
	 * @param length is of type int
	 * @see java.lang.String
	 */
	public static String zeroPad(String s, int length) {
		StringBuffer temp = new StringBuffer(s);
		while (temp.length() < length)
			temp.insert(0, '0');
		return temp.toString();
	}

	/**
	 * Class constructor specifying a Date
	 *
	 * @param time is of type Date
	 * @see java.util.Date
	 */
	public KerberosTime(Date time) {
		kerberosTime = time.getTime(); //XXX - (time.getTimezoneOffset() * 60000L);
	}

	/**
	 * Converts the object to generalized time string
	 *
	 * @return String is a result
	 * @see java.lang.String
	 */
	public String toGeneralizedTimeString() {
		Date temp = new Date(kerberosTime);
		//Assumes Date is local relative so we need to revert
		//to UTC time before extracting the Date components.
		temp.setTime(temp.getTime() + (temp.getTimezoneOffset() * 60000L));
		return zeroPad(Integer.toString(temp.getYear() + 1900), 4) +
			zeroPad(Integer.toString(temp.getMonth() + 1), 2) +
			zeroPad(Integer.toString(temp.getDate()), 2) +
			zeroPad(Integer.toString(temp.getHours()), 2) +
			zeroPad(Integer.toString(temp.getMinutes()), 2) +
			zeroPad(Integer.toString(temp.getSeconds()), 2) + 'Z';
	}

	/**
	 * Encodes the object to asn1
	 *
	 * @return byte[] is a result
	 * @exception Asn1Exception an exception
	 * @see krb5.lib.Asn1Exception
	 */
	public byte[] asn1Encode() throws Asn1Exception {
		return encode.GeneralizedTime(toGeneralizedTimeString());
	}

	/**
	 * Returns the time
	 *
	 * @return long is a result
	 */
	public long getTime() {
		return kerberosTime;
	}

	/**
	 * Sets the time 
	 *
	 * @param time is of type Date
	 * @see java.util.Date
	 */
	public void setTime(Date time) {
		kerberosTime = time.getTime(); //XXX - (time.getTimezoneOffset() * 60000L);
	}

	/**
	 * Sets the time
	 *
	 * @param time is of type long
	 */
	public void setTime(long time) {
		kerberosTime = time;
	}

	/**
	 * Returns the date
	 *
	 * @return Date is a result
	 * @see java.util.Date
	 */
	public Date toDate() {
		Date temp = new Date(kerberosTime);
		temp.setTime(temp.getTime()); //XXX + (temp.getTimezoneOffset() * 60000L));
		return temp;
	}

	/**
	 * Sets to current time
	 */
	public void setNow() {
		Date temp = new Date();
		setTime(temp);
	}

	/**
	 * Returns in microsecond
	 *
	 * @return int is a result
	 */
	public int getMicroSeconds() {
		Long temp_long = new Long((kerberosTime % 1000L) * 1000L);
		return temp_long.intValue();
	}

	/**
	 * Sets the microseconds
	 *
	 * @param usec is of type int
	 */
	public void setMicroSeconds(int usec) {
		Integer temp_int = new Integer(usec);
		long temp_long = temp_int.longValue() / 1000L;
		kerberosTime = kerberosTime - (kerberosTime % 1000L) + temp_long;
	}

	/**
	 * Sets the microseconds
	 *
	 * @param usec is of type Integer
	 */
	public void setMicroSeconds(Integer usec) {
		if (usec != null) {
			long temp_long = usec.longValue() / 1000L;
			kerberosTime = kerberosTime - (kerberosTime % 1000L) + temp_long;
		}
	}

	/**
	 * Checks to see whether it is within the clock skew specified
	 *
	 * @return boolean is a result
	 * @param clockSkew is of type int
	 */
	public boolean inClockSkew(int clockSkew) {
		KerberosTime now = new KerberosTime(KerberosTime.NOW);
		if (java.lang.Math.abs(kerberosTime - now.kerberosTime) >
			clockSkew * 1000L)
			return false;
		return true;
	}

	/**
	 * Checks to see whether it is within the clock skew
	 *
	 * @return boolean is a result
	 * @see krb5.lib.Config
	 */
	public boolean inClockSkew() {
		return inClockSkew(Config.KDC_ALLOWABLE_CLOCKSKEW);
	}

	/**
	 * Checks the WRT Clock Skew
	 *
	 * @return boolean is a result
	 * @param time is of type KerberosTime
	 * @param clockSkew is of type int
	 * @see krb5.lib.KerberosTime
	 */
	public boolean greaterThanWRTClockSkew(KerberosTime time, int clockSkew) {
		if ((kerberosTime - time.kerberosTime) > clockSkew * 1000L)
			return true;
		return false;
	}

	/**
	 * Checks the WRT Clock Skew
	 *
	 * @return boolean is a result
	 * @param time is of type KerberosTime
	 * @see krb5.lib.KerberosTime
	 */
	public boolean greaterThanWRTClockSkew(KerberosTime time) {
		return greaterThanWRTClockSkew(time, Config.KDC_ALLOWABLE_CLOCKSKEW);
	}

	/**
	 * Compares 2 Kerberos Time
	 *
	 * @return boolean is a result
	 * @param time is of type KerberosTime
	 * @see krb5.lib.KerberosTime
	 */
	public boolean greaterThan(KerberosTime time) {
		return kerberosTime > time.kerberosTime;
	}

	/**
	 * Compares 2 KerberosTime
	 *
	 * @return boolean is a result
	 * @param time is of type KerberosTime
	 * @see krb5.lib.KerberosTime
	 */
	public boolean equals(KerberosTime time) {
		return kerberosTime == time.kerberosTime;
	}

	/**
	 * Checks to see whether the time is 0
	 *
	 * @return boolean is a result
	 */
	public boolean isZero() {
		return kerberosTime == 0;
	}

	/**
	 * Returns seconds
	 *
	 * @return int is a result
	 */
	public int getSeconds() {
		Long temp_long = new Long(kerberosTime / 1000L);
		return temp_long.intValue();
	}

	/**
	 * Sets seconds
	 *
	 * @param sec is of type int
	 */
	public void setSeconds(int sec) {
		Integer temp_int = new Integer(sec);
		kerberosTime = temp_int.longValue() * 1000L;
	}

	//XXX should be removed for size
    /**
	 * Uses for testing
	 *
	 * @param argv is of type String[]
	 * @exception Asn1Exception an exception
	 * @see java.lang.String
	 * @see krb5.lib.Asn1Exception
	 */	
	public static void main(String[] argv) throws Asn1Exception {
		KerberosTime t1 = new KerberosTime();
		System.out.println("t1=" + t1.getTime());
		KerberosTime t2 = new KerberosTime(0);
		System.out.println("t2=" + t2.getTime());
		KerberosTime t3 = new KerberosTime("19700101000000Z");
		System.out.println("t3=" + t3.getTime());
		System.out.println("t1=" + t1.toGeneralizedTimeString());
		System.out.println("t2=" + t2.toGeneralizedTimeString());
		System.out.println("t3=" + t3.toGeneralizedTimeString());
		System.out.println("t3=" + t3.toDate());
	}
}
