/**
 * GetTime
 * This is small Java application that sends a request to an Internet Time Server
 * and displays the fields of the returned NTP packet. This can be used to 
 * verify all fields generated by a time server.
 * <p>
 * It may also be used to learning more about "Network Time Protocol" (NTP) and 
 * working with "User Datagram Protocol" (UDP) is Java.
 * <p>
 * My source code is given freely and WITHOUT Copyright.  You may copy, modify
 * and distribute it as if it were your own code.  However, the NtpMessage.java
 * source code file is copyrighted by Adam Buckley 2004.
  * <p>
 * @author Anthony Mack
 */
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.math.BigInteger;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.NoRouteToHostException;
import java.net.SocketTimeoutException;
import java.net.ConnectException;
import java.net.UnknownHostException;
import java.nio.channels.IllegalBlockingModeException;
import java.text.DecimalFormat;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.plaf.basic.BasicBorders;

public class GetTime extends LablePanel
	{
	//
	// Constants
	//
	//private static final byte[] NTP_IP_ADDRESS 				= new byte[] {64, (byte)183, 56, 58}; //t2.timegps.net
	//private static final int 		NTP_PORT 							= 123;
	private static final int 		TIMEOUT_IN_MILLI_SEC 	= 4000; // 4 second timeout
	private static final Font 	HEX_DUMP_FONT 				= new Font( "Courier New", Font.BOLD, 16 );
	private static final Font 	DISPLAY_FONT 					= new Font( "SansSerif", Font.BOLD, 18 );
	private static final int    frameWidth						= 500;
	private static final int    frameHieght						= 870;
	private static final String[] NTPPacketLabels = { 
			"Leap indicator", "Version", "Mode", "Stratum", "Poll interval", "Precision", "Root delay", "Root dispersion", 
			"Reference identifier", "Reference timestamp", "Originate timestamp", "Receive timestamp", "Transmit timestamp" };
	
	//
	// Data Members
	//
	private JLabel 					line1 					= new JLabel( "Network Time Protocol (NTP) Packet Explorer" );
	private JLabel 					line2 					= new JLabel( "by Anthony Mack" );
	private JLabel 					line3 					= new JLabel( "INTERNET TIME SERVER:" );
	private JLabel 					line4 					= new JLabel( "PORT:" );
	private JLabel 					line5 					= new JLabel( " " );
	private JLabel 					line6 					= new JLabel( "Received NTP Packet (Hex dump)" );
	private JTextField 			urlField 				= new JTextField( "t4.timegps.net" );
	private JTextField 			portField 			= new JTextField( "123  " );
	private JButton  				button 					= new JButton( "Request a NTP packet" );
	private JTextArea  		  hexDump 				= new JTextArea( 3, 36 );
  private String[] 				fieldValue 			= new String[NTPPacketLabels.length];
	private JTextField 			roundTripTime  	= new JTextField( "                                                               " );
	private JTextField 			localClockError = new JTextField( "                                                               " );

	//
	// Constructor
	//
	public GetTime()
		{
		// Build the GUI.
		line1.setFont( DISPLAY_FONT );
		urlField.setPreferredSize( new Dimension(120, 20) );
		urlField.addActionListener( new ActionListener()
			{ public void actionPerformed( ActionEvent e ){ getTimeButtonPushed(); } } );
		portField.addActionListener( new ActionListener()
			{ public void actionPerformed( ActionEvent e ){ getTimeButtonPushed(); } } );
		JPanel  buttonPanel = new JPanel();
		buttonPanel.setBackground( Color.white );
		buttonPanel.add( line3 );
		buttonPanel.add( urlField );
		buttonPanel.add( line4 );
		buttonPanel.add( portField );
		
		button.setPreferredSize( new Dimension(200, 25) );
		button.addActionListener( new ActionListener()
			{ public void actionPerformed( ActionEvent e ){ getTimeButtonPushed(); } } );
		hexDump.setFont( HEX_DUMP_FONT );
		hexDump.setEditable( false );
		hexDump.setBorder( new BasicBorders.FieldBorder(Color.BLUE,Color.BLUE,Color.BLUE,Color.BLUE) );
		roundTripTime.setFont( DISPLAY_FONT );
		roundTripTime.setEditable( false );
		localClockError.setFont( DISPLAY_FONT );
		localClockError.setEditable( false );
		setBackground( Color.WHITE );
		
		AddComponent( line1 );
		AddComponent( line2 );
		AddComponent( buttonPanel );
		AddComponent( button );
		AddComponent( line5 );
		AddComponent( line6 );
		AddComponent( hexDump );
		setupNTPLabels( NTPPacketLabels );
		AddComponent( roundTripTime );
		AddComponent( localClockError );
		// Display the default, out-bound NTP packet on boot.
    refreshDisplay( new NtpMessage()  );
		}

	// Handle "Request a NTP packet" button push.
	private void  getTimeButtonPushed()
		{
    DatagramSocket socket = null;
		try
			{
			socket = new DatagramSocket();
      socket.setSoTimeout( TIMEOUT_IN_MILLI_SEC );
			//InetAddress  NTPipAddress 	= InetAddress.getByAddress( NTP_IP_ADDRESS );
			InetAddress  NTPipAddress 	= InetAddress.getByName( urlField.getText().trim() );
			int					 NTPportNumber  = Integer.parseInt( portField.getText().trim() );
			
      //
			// Open a connection to the Time Server using UDP
      //
      byte[]  				data 		 = new NtpMessage().toByteArray();
      DatagramPacket 	outgoing = new DatagramPacket( data, data.length, NTPipAddress, NTPportNumber );
      
      //
      // Send the request packet.
      //
      long  					sentTime = System.currentTimeMillis();
      socket.send(outgoing);

      //
      // Get NTP Response.
      //
      DatagramPacket  incoming = new DatagramPacket( data, data.length );
      socket.receive( incoming );
      long  	responseTime 				 = System.currentTimeMillis() - sentTime;
      double  destinationTimestamp = NtpMessage.convertFromMillis( System.currentTimeMillis() );
      
      // Validate NTP Response
      byte[]  		NTPbytes = incoming.getData();
      NtpMessage  msg = new NtpMessage( NTPbytes );
      
      //
      // Update GUI
      //
      BigInteger  NTPBigInt 		= new BigInteger( NTPbytes );
      String  		NTPhexString 	= NTPBigInt.toString( 16 ).toUpperCase();
      String  		NTPoutput 		= "";
      for( int i = 0; i < NTPhexString.length(); i++ )
      	{
      	if( (i%32) == 0  &&  i != 0 )
      		NTPoutput +=  "\n";
      	else if( (i%8) == 0  &&  i != 0)
      		NTPoutput +=  " ";
      	NTPoutput +=  NTPhexString.substring( i, i+1 );
      	}
      hexDump.setText( NTPoutput );
      roundTripTime.setText( " Packet round trip time: " + responseTime + " ms " );
      double  localClockOffset = ((msg.receiveTimestamp - msg.originateTimestamp) + (msg.transmitTimestamp - destinationTimestamp)) / 2;

    	String  dir 				= localClockOffset > 0 ? "slow" : "fast";
    	localClockOffset = Math.abs( localClockOffset );
  		roundTripTime.setForeground( Color.BLACK );
  		localClockError.setForeground( Color.BLACK );

      if( localClockOffset > 120.0 )
      	{
      	long  mins = (long) Math.floor( localClockOffset / 60 );
      	long  secs = Math.round( localClockOffset - mins*60 );
      	localClockError.setText( " Your clock is " + mins + " minutes and " + secs + " seconds " + dir + " " );
      	}
      else if( localClockOffset > 2.0 )
      	{
      	double  secs = ( Math.round( localClockOffset * 100.0 ) / 100.0 );
      	localClockError.setText( " Your clock is " + secs + " seconds " + dir + " " );
      	}
      else
      	{
      	long  	millisec 		= Math.round( Math.abs(localClockOffset) * 1000.0 );
      	
    		localClockError.setForeground( Color.BLACK );
      	localClockError.setText( " Your clock is " + millisec + " ms " + dir + " (+/-" + responseTime / 2 + " ms) " );
      	}
      refreshDisplay( msg );
			}
		catch( Exception  error ) 
			{
			handleError( error );
			}
		finally
			{
			if( socket != null )
				socket.close();
			}
		}

	
	// Handle errors.
	private void  handleError( Exception err )
		{
		roundTripTime.setForeground( Color.RED );
		roundTripTime.setText( " Error: " );
		localClockError.setForeground( Color.RED );
		
		if( err instanceof SocketTimeoutException )
			localClockError.setText( " Timeout (wrong host or port?) " );
		else if( err instanceof IllegalBlockingModeException )
			localClockError.setText( " Illegal Blocking Mode " );
		else if( err instanceof SecurityException )
			localClockError.setText( " Access Denied " );
		else if( err instanceof IllegalArgumentException )
			localClockError.setText( " Bad port number " );
		else if( err instanceof NoRouteToHostException )
			localClockError.setText( " No route to host " );
		else if( err instanceof ConnectException )
			localClockError.setText( " Connection refused " );
		else if( err instanceof UnknownHostException )
			localClockError.setText( " Unknown Host " );
		else
			localClockError.setText( " Cannot Connect " );
		}

	/**
	 * Refreshes the display so that all the components show their new values.
	 */
	public void refreshDisplay( NtpMessage  msg )
		{
		String precisionStr = new DecimalFormat( "0.#E0" ).format( Math.pow( 2, msg.precision ) );
		fieldValue[0]  = "" + msg.leapIndicator;
		fieldValue[1]  = "" + msg.version;
		fieldValue[2]  = "" + msg.mode;
		fieldValue[3]  = "" + msg.stratum;
		fieldValue[4]  = "" + msg.pollInterval;
		fieldValue[5]  = msg.precision + " (" + precisionStr + " seconds)";
		fieldValue[6]  = new DecimalFormat( "0.00" ).format( msg.rootDelay * 1000 ) + " ms";
		fieldValue[7]  = new DecimalFormat( "0.00" ).format( msg.rootDispersion * 1000 ) + " ms";
		fieldValue[8]  = NtpMessage.referenceIdentifierToString( msg.referenceIdentifier, msg.stratum, msg.version );
		fieldValue[9]  = NtpMessage.timestampToString( msg.referenceTimestamp );
		fieldValue[10] = NtpMessage.timestampToString( msg.originateTimestamp );
		fieldValue[11] = NtpMessage.timestampToString( msg.receiveTimestamp );
		fieldValue[12] = NtpMessage.timestampToString( msg.transmitTimestamp );
		
		setValues( fieldValue );
		
		// Re-validate incase the components have changes size because of their new values.
		revalidate();
		}

	public static void main( String[] args )
		{
		JFrame frame = new JFrame( "Get Time" );
		frame.getContentPane().add( new GetTime() );

		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
		frame.setSize( frameWidth, frameHieght );
		frame.setVisible( true );
		}
	
	}