
package timesyn;

import java.math.*;
import java.net.*;
import java.util.*;

public class TimeSyn {

    final static int TIMESERVER_PORT = 123;
    final static int TIMEOUT = 3000;

    static public double getGlobalTime(String timeserver, int localPort) throws Exception {

        double time = -1.0;
        DatagramSocket socket = null;
        InetAddress timeServer = null;

            socket = new DatagramSocket(localPort);
            timeServer = InetAddress.getByName(timeserver);

            socket.setSoTimeout(TIMEOUT);

            Calendar ref = Calendar.getInstance();
	    ref.set(1900, 1, 1, 0, 0, 0);
	    Date ref_date = ref.getTime();
	    long ref_mil = -ref_date.getTime();

	    long ref_sec = ref_mil / 1000;
	    long ref_fac = ref_mil % 1000;

	    long curr_mil = System.currentTimeMillis();
	    long cur_sec = curr_mil / 1000;
	    long cur_fac = curr_mil % 1000;

	    long actual_sec = ref_sec + cur_sec;
	    long actual_fac = ref_fac + cur_fac;

	    if (actual_fac > 1000) {
	        actual_fac -= 1000;
	        actual_sec += 1;
	    }

	    int int_sec = (int)actual_sec;
	    double dbl_fac = (double)actual_fac / 1000;

	    //System.out.println("Faction real: " + dbl_fac);

	    int int_fac = 0;
	    int mask = 0x80000000;

	    for (int i = 0; i < 32; i++) {
	        dbl_fac *= 2;
	        if (dbl_fac >= 1) {
		    dbl_fac -= 1;
		    int_fac = int_fac | mask;
	        }
		mask = mask >>> 1;
	    }

	    int firstbyte = 0x23;

	    byte[] packet = new byte[48];

	    packet[0] = ((new Integer(firstbyte)).byteValue());

	    packet[40] = (new Integer(int_sec >>> 24)).byteValue();
	    packet[41] = (new Integer(int_sec >>> 16)).byteValue();
	    packet[42] = (new Integer(int_sec >>> 8)).byteValue();
	    packet[43] = (new Integer(int_sec)).byteValue();

	    packet[44] = (new Integer(int_fac >>> 24)).byteValue();
	    packet[45] = (new Integer(int_fac >>> 16)).byteValue();
	    packet[46] = (new Integer(int_fac >>> 8)).byteValue();
	    packet[47] = (new Integer(int_fac)).byteValue();

            DatagramPacket send_pac = new DatagramPacket(packet, packet.length,
                                                        timeServer, TIMESERVER_PORT);
	    socket.send(send_pac);

	    packet = null;
	    packet = send_pac.getData();

	    //System.out.println("Request sent");
	    //printBuffer(packet);
	    //System.out.println();

	    //--------------- Received from Time Server
	    byte[] recv_buf = new byte[48];
	    DatagramPacket recv_pac = new DatagramPacket(recv_buf, recv_buf.length);

	    socket.receive(recv_pac);
            if (recv_pac.getLength() == 0)
                return -1.0;

	    //-------------- find out current receive time
	    curr_mil = System.currentTimeMillis();
	    cur_sec = curr_mil / 1000;
	    cur_fac = curr_mil % 1000;

	    actual_sec = ref_sec + cur_sec;
	    actual_fac = ref_fac + cur_fac;

	    if (actual_fac > 1000) {
	        actual_fac -= 1000;
	        actual_sec += 1;
	    }

    	    //System.out.println("Reply received");
	    recv_buf = recv_pac.getData();
	    //printBuffer(recv_buf);
	    //System.out.println();

	    byte[] tmpCur = new byte[8];
	    tmpCur[4] = (new Integer((int)actual_sec >>> 24)).byteValue();
	    tmpCur[5] = (new Integer((int)actual_sec >>> 16)).byteValue();
	    tmpCur[6] = (new Integer((int)actual_sec >>> 8)).byteValue();
	    tmpCur[7] = (new Integer((int)actual_sec)).byteValue();
	    tmpCur[0] = 0;
	    tmpCur[1] = 0;
	    tmpCur[2] = 0;
	    tmpCur[3] = 0;


	    //--------------- Calculate Offset
	    double org_sec = byteToInt(packet, 40).doubleValue() + byteToDouble(packet, 44);
	    double dest_sec = (new BigInteger(tmpCur)).doubleValue() + (double)actual_fac / 1000;

	    double rev_sec = byteToInt(recv_buf, 32).doubleValue() + byteToDouble(recv_buf, 36);
	    double tran_sec = byteToInt(recv_buf, 40).doubleValue() + byteToDouble(recv_buf, 44);

            double delay = (dest_sec - org_sec) - (rev_sec - tran_sec);

            time = tran_sec + delay / 2;


        if (socket != null)
            socket.close();

        return time;

    }

    static private void printBuffer(byte[] buf) {
	for (int i = 0; i < buf.length; i+=4) {
	    int val =  ( ( ((new Byte(buf[i])).intValue() & 0x000000FF) << 24 ) |
			 ( ((new Byte(buf[i+1])).intValue() & 0x000000FF) << 16) |
			 ( ((new Byte(buf[i+2])).intValue() & 0x000000FF) << 8) |
			 ( (new Byte(buf[i+3])).intValue() & 0x000000FF)  );
	    System.out.println(Integer.toBinaryString(val));
	}
	System.out.println();

    }

    static private BigInteger byteToInt(byte[] buf, int i) {

	byte[] tmp = new byte[8];
        tmp[4] = buf[i];
	tmp[5] = buf[i + 1];
	tmp[6] = buf[i + 2];
	tmp[7] = buf[i + 3];
	tmp[0] = 0;
	tmp[1] = 0;
	tmp[2] = 0;
	tmp[3] = 0;

	return (new BigInteger(tmp));

    }

    static private double byteToDouble(byte[] buf, int i) {
	int mask = 0x80000000;
	int val = byteToInt(buf, i).intValue();
	double rtnVal = 0;

	for (int j = 0; j < 32; j++) {
	    if ( (val & mask) != 0)
		rtnVal += Math.pow(2, -j-1);

	    mask = mask >>> 1;
	}

	return rtnVal;


    }

}