/*
 * IPInspector
 *
 * Copyright (C) 2002, Jonathan Sevy <jsevy@mcs.drexel.edu>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */
 
package airportipinspector;


import java.util.*;
import java.net.*;
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.plaf.*;
import java.awt.event.*;
import java.io.*;


import airport.*;





public class IPInspector extends JFrame
							implements ActionListener, Runnable
{	
	
	
	JButton newPreferencesButton, discoverDevicesButton;
	JTextField airportInternalIPField;
	AirportInfoTextField airportExternalIPField, airportNameField, airportLocationField;
	JPanel hostPanel;
	
	MenuBar theMenubar;
	Menu fileMenu;
	MenuItem quitItem, aboutItem, savePreferencesItem;
	
	JTextArea messagesArea;
	JTextField statusArea;
	JTextAreaWriter messagesAreaWriter;
	
	Thread inspectionThread;
	
	IPInspectorAirportInfo airportInfo;

	Preferences preferences;
	boolean preferencesSaved = true;
	
	InetAddress airportInternalIPAddress;
	
	private Vector changeListeners;
	AirportInfoChangeEmailer changeEmailer;
	
	
	
	// WindowCloseAdapter to catch window close-box closings
	private class WindowCloseAdapter extends WindowAdapter
	{ 
		public void windowClosing(WindowEvent e)
		{
		    System.exit(0);
		}
	}
		
		
	
	public IPInspector()
	{
		    	
    	airportInfo = new IPInspectorAirportInfo();
    	changeListeners = new Vector();
    	
    	// create thread, but don't start it
    	inspectionThread = new Thread(this);
    		    
    	setUpDisplay();
    	
    	// create Writer interface into messages area for use by mailer thread
    	messagesAreaWriter = new JTextAreaWriter(messagesArea);
    	
    	this.pack();
			
        // tweak app size to make it a little larger than necessary, to address the
        // "shrunken textfields" problem arising from the layout manager packing stuff
        // a little too tightly.
        Dimension dim = this.getSize();
        dim.height += 20;
        dim.width += 20;
        this.setSize(dim);

        this.show();
        
		// set up preferences
		// read any settings that have previously been saved
		readSettings();
		
		// put up dialog
		if (getPreferences() == false)
		{
		    messagesArea.setText("Problem with supplied information; set options again");
		}
		
		
	}
	
	
	
	public void addChangeListener(AirportInfoChangeListener changeListener)
	{
	    if (!changeListeners.contains(changeListener))
	        changeListeners.add(changeListener);
	}
	
	
	
	public void removeChangeListener(AirportInfoChangeListener changeListener)
	{
	    if (changeListeners.contains(changeListener))
	        changeListeners.remove(changeListener);
	}
	
	
	
	private void setUpDisplay()
	{
		
		
		
		// set fonts to smaller-than-normal size, for compaction!
		UIManager manager = new UIManager();
		FontUIResource appFont = new FontUIResource("SansSerif", Font.PLAIN, 10);
		UIDefaults defaults = manager.getLookAndFeelDefaults();
		Enumeration keys = defaults.keys();
		
		while (keys.hasMoreElements())
		{
			String nextKey = (String)(keys.nextElement());
			if ((nextKey.indexOf("font") > -1) || (nextKey.indexOf("Font") > -1))
			{
				manager.put(nextKey, appFont);
			}
		}
		
		
		
		// add WindowCloseAdapter to catch window close-box closings
		addWindowListener(new WindowCloseAdapter());

		
		
		this.setTitle("AirPort IP Inspector");
		
		this.getRootPane().setBorder(new BevelBorder(BevelBorder.RAISED));
		
		theMenubar = new MenuBar();
		this.setMenuBar(theMenubar);
		fileMenu = new Menu("File");
		
		
		aboutItem = new MenuItem("About...");
		aboutItem.setActionCommand("about");
		aboutItem.addActionListener(this);
		fileMenu.add(aboutItem);
		
		savePreferencesItem = new MenuItem("Save settings");
		savePreferencesItem.setActionCommand("save preferences");
		savePreferencesItem.addActionListener(this);
		fileMenu.add(savePreferencesItem);
				
		fileMenu.addSeparator();

		quitItem = new MenuItem("Quit");
		quitItem.setShortcut(new MenuShortcut('q'));
		quitItem.setActionCommand("quit");
		quitItem.addActionListener(this);
		fileMenu.add(quitItem);
		
		theMenubar.add(fileMenu);
		
		
		JLabel airportInternalIPLabel = new JLabel("Base station LAN address:");
		airportInternalIPField = new JTextField(10);
		airportInternalIPField.setText("10.0.1.1");
		airportInternalIPField.setEditable(false);
				
		JLabel airportExternalIPLabel = new JLabel("Base station WAN address:");
		airportExternalIPField = new AirportInfoTextField((AirportInfoRecord)airportInfo.get("waIP"));
		airportExternalIPField.setEditable(false);
		
		JLabel airportNameLabel = new JLabel("Base station name:");
		airportNameField = new AirportInfoTextField((AirportInfoRecord)airportInfo.get("syNm"));
		airportNameField.setEditable(false);

		JLabel airportLocationLabel = new JLabel("Base station location:");
		airportLocationField = new AirportInfoTextField((AirportInfoRecord)airportInfo.get("syLo"));
		airportLocationField.setEditable(false);

		
		newPreferencesButton = new JButton("New settings");
		newPreferencesButton.setActionCommand("new preferences");
		newPreferencesButton.addActionListener(this);
		
		discoverDevicesButton = new JButton("Discover devices");
		discoverDevicesButton.setActionCommand("discover devices");
		discoverDevicesButton.addActionListener(this);
		
		
		
		
		
		// set params for layout manager
		GridBagLayout  theLayout = new GridBagLayout();
		GridBagConstraints c = new GridBagConstraints();
		
		c.gridwidth = 1;
		c.gridheight = 1;
		c.fill = GridBagConstraints.NONE;
		c.ipadx = 0;
		c.ipady = 0;
		Insets theMargin = new Insets(2,2,2,2);
		c.insets = theMargin;
		c.anchor = GridBagConstraints.CENTER;
		c.weightx = .5;
		c.weighty = .5;
		
		
		JPanel buttonPanel = new JPanel();
		buttonPanel.setLayout(theLayout);
		
		c.gridx = 1;
		c.gridy = 1;
		theLayout.setConstraints(newPreferencesButton, c);
		buttonPanel.add(newPreferencesButton);
		
		c.gridx = 2;
		c.gridy = 1;
		theLayout.setConstraints(discoverDevicesButton, c);
		buttonPanel.add(discoverDevicesButton);
		
		
		
		hostPanel = new JPanel();
		hostPanel.setLayout(theLayout);
		
		c.gridx = 1;
		c.gridy = 1;
		theLayout.setConstraints(airportInternalIPLabel, c);
		hostPanel.add(airportInternalIPLabel);
		
		c.gridx = 2;
		c.gridy = 1;
		theLayout.setConstraints(airportInternalIPField, c);
		hostPanel.add(airportInternalIPField);
		
		c.gridx = 1;
		c.gridy = 2;
		theLayout.setConstraints(airportExternalIPLabel, c);
		hostPanel.add(airportExternalIPLabel);
		
		c.gridx = 2;
		c.gridy = 2;
		theLayout.setConstraints(airportExternalIPField, c);
		hostPanel.add(airportExternalIPField);
		
		c.gridx = 1;
		c.gridy = 3;
		theLayout.setConstraints(airportNameLabel, c);
		hostPanel.add(airportNameLabel);
		
		c.gridx = 2;
		c.gridy = 3;
		theLayout.setConstraints(airportNameField, c);
		hostPanel.add(airportNameField);
		
		c.gridx = 1;
		c.gridy = 4;
		theLayout.setConstraints(airportLocationLabel, c);
		hostPanel.add(airportLocationLabel);
		
		c.gridx = 2;
		c.gridy = 4;
		theLayout.setConstraints(airportLocationField, c);
		hostPanel.add(airportLocationField);
		
		
		
		JPanel messagesPanel = new JPanel();
		messagesPanel.setLayout(theLayout);
		
		statusArea = new JTextField(50);
		messagesArea = new JTextArea(6,50);
		JScrollPane messagesScroll = new JScrollPane(messagesArea);
		
		c.gridx = 1;
		c.gridy = 1;
		JLabel messagesLabel = new JLabel("Messages:");
		theLayout.setConstraints(messagesLabel, c);
		messagesPanel.add(messagesLabel);
		
		c.gridx = 1;
		c.gridy = 2;
		theLayout.setConstraints(statusArea, c);
		messagesPanel.add(statusArea);
		
		c.gridx = 1;
		c.gridy = 3;
		theLayout.setConstraints(messagesScroll, c);
		messagesPanel.add(messagesScroll);
		
		
		
		
		this.getContentPane().setLayout(theLayout);
		
		c.gridx = 1;
		c.gridy = 1;
		theLayout.setConstraints(hostPanel, c);
		this.getContentPane().add(hostPanel);
		
		c.gridx = 1;
		c.gridy = 2;
		theLayout.setConstraints(buttonPanel, c);
		this.getContentPane().add(buttonPanel);
		
		c.gridx = 1;
		c.gridy = 4;
		theLayout.setConstraints(messagesPanel, c);
		this.getContentPane().add(messagesPanel);
		
		c.gridx = 1;
		c.gridy = 5;
		JLabel authorLabel = new JLabel(" Version 1.0        J. Sevy, February 2003 ");
		authorLabel.setFont(new Font("SansSerif", Font.ITALIC, 8));
		theLayout.setConstraints(authorLabel, c);
		this.getContentPane().add(authorLabel);
		
		
		
	}
	
	
	
	
	public void actionPerformed(ActionEvent theEvent)
	// respond to button pushes, menu selections
	{
		String command = theEvent.getActionCommand();
		
	
		if (command.equals("quit"))
		{
			if (preferencesSaved == false)
			{
			    // put up dialog to ask if settings should be saved
			    if (JOptionPane.showConfirmDialog(this, "Save current settings?","Save settings?",JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION)
			    {
			        saveSettings();
			    }
			}
			
			System.exit(0);
		}
		
		
		
		if (command == "about")
		{
			AboutDialog aboutDialog = new AboutDialog(this);
		}
		
		
		
		if (command.equals("discover devices"))
		{
			AirportDiscoverer discoverer = new AirportDiscoverer();
		}	
		
		
		
		if (command == "new preferences")
		{
			getPreferences();
		}
		
		
		if (command.equals("save preferences"))
		{
			saveSettings();
		}
		
	}
	
	
	
	private boolean getPreferences()
	{
	    
	    PreferencesDialog theDialog = new PreferencesDialog(this, preferences);
			
        if (!theDialog.isCancelled())
        {
        	
        	// stop thread if needed
        	if (inspectionThread.isAlive())
        	{
        	    inspectionThread.interrupt();
        	}
        	
        	// get preferences
        	Preferences newPreferences = theDialog.getPreferences();
        	
        	
        	try
    		{
    			
    			if (!newPreferences.equals(preferences))
    		        preferencesSaved = false;
    		        
    		    airportInternalIPAddress = InetAddress.getByName(newPreferences.ipAddress);
    			airportInternalIPField.setText(newPreferences.ipAddress);
    			
    			// remove current action object
                this.removeChangeListener(changeEmailer);
                
                // create and add new action object
                changeEmailer = new AirportInfoChangeEmailer(newPreferences.emailAddress, newPreferences.smtpHost, messagesAreaWriter);
    	        changeListeners.add(changeEmailer);
    	        
    			preferences = newPreferences;
    			
    			inspectionThread = new Thread(this);
    		    inspectionThread.start();
    		    
    		    return true;
    		
    		}
    		catch(UnknownHostException e)
    		{
    			JOptionPane.showMessageDialog(this, "Unknown host name supplied.");
    		}
    		catch(Exception e)
    		{
    			JOptionPane.showMessageDialog(this, "Error setting new preferences: " + e);
    		}
	        
        }
        
        return false;
	}
	
	
	
	
	private IPInspectorAirportInfo getInfo()
	{
	    
	    try
		{
			
			IPInspectorAirportInfo newInfo;
	    
	        byte[] requestPayload = airportInfo.getRequestBytes();
			
			Airport2ProtocolMessage requestMessage = new Airport2ProtocolMessage(Airport2ProtocolMessage.READ, preferences.password, requestPayload, requestPayload.length);
			
			// open socket
			int port = 5009;
			
			Socket configSocket = new Socket(airportInternalIPAddress, port);
			
			// set socket timeout to 5 seconds
			configSocket.setSoTimeout(5000);
				
			DataInputStream inputStream = new DataInputStream(configSocket.getInputStream());
			
			OutputStream outputStream = configSocket.getOutputStream();
			
			outputStream.write(requestMessage.getBytes());
			
			
			/*
			System.out.println("Request header bytes:");
			System.out.println(printHexBytes(requestMessage.getBytes()));
			*/
			
			outputStream.write(requestPayload);
			
			
			// get and process response
			byte[] responseHeader = new byte[128];
			
			inputStream.readFully(responseHeader);
			//int numBytes = inputStream.read(responseHeader);
			
			// create temp buffer to hold retrieved config; 4k should suffice...
			byte[] responsePayload = new byte[4096];
			
			// read info
			
			try
			{
				inputStream.readFully(responsePayload);
			}
			catch (EOFException e)
			{
				// this is expected; we _should_ reach the end of the stream before filling the buffer
			}
			
			
			
			/*
			numBytes = 0;
			int newNumBytes = 0;
			while(  ((newNumBytes = inputStream.read(responsePayload, numBytes, responsePayload.length - numBytes)) >= 0 )
					&&
					(numBytes < responsePayload.length)
				 )
			{
				System.out.println("Read " + newNumBytes + " bytes.");
				numBytes += newNumBytes;
			}
			*/
			
			
			//System.out.println("done.");
			//System.out.println("Read " + numBytes + " bytes total.");
			
			inputStream.close();
			outputStream.close();
			
			configSocket.close();
			
			
			// check to see if we retrieved valid data from the base station
			try
			{
				statusArea.setText("Information retrieved (" + (new Date()).toString() + ").");
			    
			    return new IPInspectorAirportInfo(responsePayload);	
                
			}
			catch(IllegalArgumentException ex)
			{
				// thrown by AirportInfo constructor if no data retrieved
				statusArea.setText("Error retrieving information (check password)");
			}
			
		}
		catch(SocketException e)
		{
			statusArea.setText("Error retrieving information: " + e + ".");
		}
		catch(IOException e)
		{
			statusArea.setText("Error retrieving information: timed out waiting for response.");
		}
		catch (Exception e)
		{
			statusArea.setText("Error retrieving information: " + e.getMessage() + ".");
		}
		
		// if get here, didn't get valid stuff; return a blank structure
		return new IPInspectorAirportInfo();
		
	}
	
	
	
	
	private void refreshInfoDisplay(IPInspectorAirportInfo newInfo)
	{
		// update displayed info
		airportExternalIPField.setAirportInfoRecord(newInfo.get("waIP"));
		airportNameField.setAirportInfoRecord(newInfo.get("syNm"));
		airportLocationField.setAirportInfoRecord(newInfo.get("syLo"));
		
	}
	
	
	
	private String hexByte(byte b)
	{
		int pos = b;
		if (pos < 0)
			pos += 256;
		String returnString = new String();
		returnString += Integer.toHexString(pos/16);
		returnString += Integer.toHexString(pos%16);
		return returnString;
	}
	
	
	
	private String printHexBytes(byte[] bytes)
	{
		String returnString = new String();
		
		for(int i = 0; i < bytes.length; i++)
		{
			returnString += hexByte(bytes[i]) + " ";
			
			if (((i+1)%16) == 0)
				returnString += "\n";
				
		}
		
		return returnString;
		
	}
	
	
	/*
	private static boolean arraysEqual(byte[] a, byte[] b)
	{
		if (a.length != b.length)
		{
			return false;
		}
		else
		{
			for (int i = 0; i < a.length; i++)
			{
				if (a[i] != b[i])
					return false;
			}
		}
		
		return true;
	}
	
	
	
	private static byte[] maskBytes(byte[] inBytes, byte[] mask)
	{
		byte[] maskedBytes = new byte[inBytes.length];
		
		for (int i = 0; i < inBytes.length; i++)
		{
			maskedBytes[i] = (byte)(inBytes[i] & mask[i % inBytes.length]);
		}
		
		return maskedBytes;
	}
	*/
	
	
	
	private void invokeChangeListeners(IPInspectorAirportInfo oldInfo, IPInspectorAirportInfo newInfo)
	{
	    // just call the processInfoChange method of each listener
	    for (int i = 0; i < changeListeners.size(); i++)
	    {
	        ((AirportInfoChangeListener)changeListeners.elementAt(i)).processInfoChange(oldInfo, newInfo);
	    }
	}
	
	
	
	public void run()
	{
		
		IPInspectorAirportInfo newInfo;
		
		while(!Thread.currentThread().isInterrupted())
		{
		    
		    // retrieve current info from base station
			newInfo = getInfo();
			
			// now refresh display
			refreshInfoDisplay(newInfo);
			
			// see if info has changed; if so, take action
			if (!newInfo.get("waIP").toString().equals(airportInfo.get("waIP").toString()))
			{
			    invokeChangeListeners(airportInfo, newInfo);
			}
			
			airportInfo = newInfo;
			
			try
			{
				// sleep for inspection interval seconds
				Thread.currentThread().sleep(1000 * preferences.queryInterval);
			}
			catch(InterruptedException e)
			{
				// don't bother informing of interruption
				Thread.currentThread().interrupt();
			}
			
		
		}
		
		
	}
	
	
	
	private void saveSettings()
	{
	    // save into file IpInspector.ini
	    ObjectOutputStream outStream;
	    
	    try
	    {
    	    outStream = new ObjectOutputStream(new FileOutputStream("IPInspector.ini"));
    	    outStream.writeObject(preferences);
    	    outStream.close();
    	    preferencesSaved = true;
	    }
	    catch (Exception e)
	    {
	        // oh well...
	        messagesArea.setText("Couldn't write settings: " + e.toString());
	        preferencesSaved = false;
	    }
	    
	}
	
	
	
	private void readSettings()
	{
	    // read from file IpInspector.ini
	    
	    try
	    {
	        ObjectInputStream inStream = new ObjectInputStream(new FileInputStream("IPInspector.ini"));
	        preferences = (Preferences)inStream.readObject();
	        inStream.close();
	        preferencesSaved = true;
	    }
	    catch (Exception e)
	    {
	        // couldn't read; just return empty settings
	        messagesArea.setText("Couldn't read settings: " + e.toString());
	        preferences = new Preferences();
	        preferencesSaved = false;
	    }
	    
	}
	
	
	
	public static void main(String args[]) 
	{
		try
		{
			
			IPInspector theApp = new IPInspector();
			
		}
		catch (Exception e)
		{}
	}
	

}