// import javax.swing.*;          // This is the final package name.
import com.sun.java.swing.*;      // This is the JDK 1.1 Swing beta package
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.text.*;
import java.io.*;


class TelemetryReader implements Runnable {

  private AcousticGui _gui;
  private BufferedReader _reader;
  private Thread _readerThread = null;
  private int _idNo;

  public TelemetryReader(AcousticGui gui, String filename, int idNo) {
    super();
    _gui = gui;
    _idNo = idNo;

    try {
      _reader = 
	new BufferedReader(
			   new InputStreamReader(
						 new FileInputStream(filename)));
    }
    catch (FileNotFoundException e) {
      System.out.println("Couldn't open telemetry file \"" + filename + "\"");
      return;
    }

    _readerThread = new Thread(this, "readerThread");
    _readerThread.start();
  }

  public void run() {

    // Create a packet buffer
    StringBuffer buf = new StringBuffer(512);

    while (_readerThread != null) {

      try {
	while (_reader.ready()) {
	  char c = (char )_reader.read();
	  if (c != '\n')
	    // Still in packet; append character to packet buf
	    buf.append(c);
	  else {
	    // Finished packet; update telemetry gui
	    if (buf.length() > 0) {
	      _gui.telemetryPage.update(buf.toString());
	      // Re-initialize packet buf
	      buf.setLength(0);
	    }
	  }
	}
      }
      catch (Exception e) {
	// Exception handling here
      }
      // No characters available; sleep for a bit before trying again
      try {
	_readerThread.sleep(500);
      }
      catch (InterruptedException e) {
	// Exception handling here
      }
    }
  }

  // Cause the run() method to terminate
  public void stop() {
    System.out.println("TelemetryReader.stop() - idno=" + _idNo);
    _readerThread = null;
  }

};


class CommandButton {

  private int _nPendingReplies = 0;
  private JLabel _seqNoLabel;
  private JLabel _pendingLabel;
  protected JButton _button;
  private int _commandType = AcousticGui.NAVIGATION;
  private AcousticGui _app;
  private int _sequenceNo = 0;

  public JButton button;

  CommandButton(JPanel parent, String label, AcousticGui app,
		int commandType) {
    _app = app;
    _commandType = commandType;
    JPanel pane = new JPanel();
    pane.setLayout(new BoxLayout(pane, BoxLayout.X_AXIS));
    _button = new JButton(label);
    _button.setAlignmentY(Component.TOP_ALIGNMENT);
    _button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
	_nPendingReplies++;

	// Sequence number cycles between 1 and 9
	if (_sequenceNo < 9) 
	  _sequenceNo++;
	else
	  _sequenceNo = 1;

	CommandPacket packet = 
	  new CommandPacket(_commandType, _sequenceNo, new Date());

	packet.print();

	// Write command file
	try {

	  System.out.println("Selected transponder: " + 
			     _app.commsPage.selectedTransponder());

	  FileOutputStream output = new FileOutputStream("command.out");
	  output.write(packet.buffer().getBytes());
	  output.write('\n');
	  output.close();
	}
	catch (IOException exception) {
	  System.out.println("TelemetryRequestButton eventListener");
	  System.out.println(exception.toString());
	}
	update();
      }
    });

    pane.add(_button);
    pane.add(Box.createRigidArea(new Dimension(10, 10)));

    JPanel subPane = new JPanel();
    subPane.setLayout(new GridLayout(1, 0));
    
    _seqNoLabel = new JLabel("Seq #: ");
    _seqNoLabel.setVisible(false);

    _pendingLabel = new JLabel("#Pending replies: xxxx");
    subPane.add(_seqNoLabel);
    subPane.add(_pendingLabel);

    _nPendingReplies = 0;
    update();
    pane.add(subPane);
    parent.add(pane);
  };

  void acknowledgeReply() {

    if (_nPendingReplies > 0) 
      _nPendingReplies--;

    update();
  }

  void update() {
    if (_nPendingReplies > 0)
      _pendingLabel.setVisible(true);
    else
      _pendingLabel.setVisible(false);

    _pendingLabel.setText("#Pending replies: " + _nPendingReplies);
  }
}



public class AcousticGui extends JFrame {

  // These "enumerated values" must match those in AcousticMessage.h (C++)
  static final int NAVIGATION = 0;
  static final int MISSIONSTATE = 1;
  static final int CTD = 2;
  static final int ABORT = 3;

  // These "enumerated values" must match those in LayeredControlIF.h (C++)
  static final int INITIAL_STATE = 0;
  static final int NORMAL_STATE = 1;
  static final int ABORTING_STATE = 2;
  static final int SHUTTINGDOWN_STATE = 3;
  static final int DONE_STATE = 4;

  static final String TELEMETRY_FILE_NAME = "telemetry.dat";

  private TelemetryReader _telemetryReader;
  private String _telemetryFilename;
  private int _readerNo = 0;


  public TelemetryPage telemetryPage;
  public CommsPage commsPage;
  public CommandPage commandPage;

  public Component contents;
  AcousticGui _guiInstance;

  public AcousticGui(String telemetryFilename) {

    _guiInstance = this;
    JMenuBar menuBar;
    JMenu menu;
    JMenuItem menuItem;

    // Create the menu bar
    menuBar = new JMenuBar();
    setJMenuBar(menuBar);

    menu = new JMenu("File");
    menuBar.add(menu);

    menuItem = new JMenuItem("Open telemetry");
    menuItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
	System.out.println("Open telemetry file");
      }
    });  // End of inner class

    menu.add(menuItem);

    menuItem = new JMenuItem("Refresh telemetry");
    menuItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
	System.out.println("Restart telemetry reader on file" + 
			   _telemetryFilename);

	_telemetryReader.stop();
	_telemetryReader = new TelemetryReader(_guiInstance, 
					       _telemetryFilename,
					       _readerNo++);
      }
    });  // End of inner class

    menu.add(menuItem);

    menuItem = new JMenuItem("Exit");
    menuItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
	System.exit(0);
      }
    });  // End of inner class

    menu.add(menuItem);

    contents = createContents();

    _telemetryFilename = telemetryFilename;
    _telemetryReader = new TelemetryReader(this, _telemetryFilename,
					   _readerNo++);
  }

  // Create telemetry page
  public Component createTelemetry() {
    JPanel pane = new JPanel();
    telemetryPage = new TelemetryPage(pane, this);
    return pane;
  }


  // Create Comms page
  public Component createComms() {
    JPanel pane = new JPanel();
    commsPage = new CommsPage(pane);
    return pane;
  }

  // Create commands page
  public Component createCommands() {

    JPanel pane = new JPanel();
    commandPage = new CommandPage(pane, this);
    return pane;
  };

  // Create GUI contents
  private Component createContents() {
    JPanel pane = new JPanel();
    pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));

    JTabbedPane tabbedPane = new JTabbedPane();
    tabbedPane.addTab("Telemetry", createTelemetry());
    tabbedPane.addTab("Comms", createComms());
    tabbedPane.addTab("Control", createCommands());
    pane.add(tabbedPane);

    return pane;
  }



  public static void main(String[] args) {
    try {
      UIManager.setLookAndFeel(
			       UIManager.getCrossPlatformLookAndFeelClassName());
    } catch (Exception e) { }

    String telemetryFilename;

    if (args.length == 1) 
      telemetryFilename = args[0];
    else
      telemetryFilename = TELEMETRY_FILE_NAME;

    //Create the top-level container and add contents to it.
    AcousticGui app = new AcousticGui(telemetryFilename);
    Component contents;
    contents = app.contents;
    app.getContentPane().add(contents, BorderLayout.NORTH);

    //Finish setting up the frame, and show it.
    app.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
	System.exit(0);
      }
    });
    app.pack();
    app.setVisible(true);
  }
}


class TelemetryPage {

  private AcousticGui _app;
  private JLabel _navUtc;
  private JLabel _latLon;
  private JLabel _heading;
  private JLabel _depth;
  private JLabel _altitude;
  private JLabel _statusUtc;
  private JLabel _missionTime;
  private JLabel _missionState;
  private JLabel _behavior;
  private JLabel _battery;
  private JLabel _speed;
  private JLabel _pitch;
  private JLabel _ctdUtc;
  private JLabel _conductivity;
  private JLabel _temperature;
  private DateFormat _dateFormat;
  private CommandButton _requestNavButton;
  private CommandButton _requestStatusButton;
  private CommandButton _requestCtdButton;

  public TelemetryPage (JPanel parent, AcousticGui app) {

    _app = app;

    // Create DateFormat for later use
    _dateFormat = DateFormat.getDateTimeInstance();

    JPanel telemetryPane = new JPanel();
    telemetryPane.setLayout(new GridLayout(0, 1));

    JPanel pane1;
    JPanel nameColumn;
    JPanel valueColumn;
    JLabel label;

    JPanel statusPane = new JPanel();
    statusPane.setBorder(BorderFactory.createTitledBorder("Status"));
    statusPane.setLayout(new BoxLayout(statusPane, BoxLayout.Y_AXIS));
    pane1 = new JPanel();
    // pane1.setLayout(new BoxLayout(pane1, BoxLayout.X_AXIS));
    pane1.setLayout(new GridLayout(1, 0));

    // Create first column (data names)
    nameColumn = new JPanel();
    nameColumn.setLayout(new BoxLayout(nameColumn, BoxLayout.Y_AXIS));
    nameColumn.setBorder(BorderFactory.createTitledBorder("nameColumn"));
    nameColumn.setBorder(BorderFactory.createEmptyBorder(30, //top
							 30, //left
							 10, //bottom
							 10) //right
			 );

    label = new JLabel("UTC: ");
    nameColumn.add(label);
    label = new JLabel("Mission time (sec): ");
    nameColumn.add(label);
    label = new JLabel("Mission state: ");
    nameColumn.add(label);
    label = new JLabel("Behavior: ");
    nameColumn.add(label);
    label = new JLabel("Battery: ");
    nameColumn.add(label);

    pane1.add(nameColumn);

    // Create second column (data values)
    valueColumn = new JPanel();
    valueColumn.setLayout(new BoxLayout(valueColumn, BoxLayout.Y_AXIS));
    valueColumn.setBorder(BorderFactory.createEmptyBorder(30, //top
							  30, //left
							  10, //bottom
							  10) //right
			  );

    _statusUtc = new JLabel("status utc");
    valueColumn.add(_statusUtc);
    _missionTime = new JLabel("missionTime                ");
    valueColumn.add(_missionTime);
    _missionState = new JLabel("missionState");
    valueColumn.add(_missionState);
    _behavior = new JLabel("behaviorName");
    valueColumn.add(_behavior);
    _battery = new JLabel("battery");
    valueColumn.add(_battery);

    pane1.add(valueColumn);

    statusPane.add(pane1);

    _requestStatusButton = 
      new CommandButton(statusPane, "Request", _app, 
			AcousticGui.MISSIONSTATE);

    JPanel navigationPane = new JPanel();

    navigationPane.setBorder(BorderFactory.createTitledBorder("Navigation"));

    navigationPane.setLayout(new BoxLayout(navigationPane, BoxLayout.Y_AXIS));
    pane1 = new JPanel();
    pane1.setLayout(new GridLayout(1, 0));

    // Create first column (data names)
    nameColumn = new JPanel();
    nameColumn.setLayout(new BoxLayout(nameColumn, BoxLayout.Y_AXIS));
    nameColumn.setBorder(BorderFactory.createEmptyBorder(30, //top
							 30, //left
							 10, //bottom
							 10) //right
			 );

    label = new JLabel("UTC: ");
    nameColumn.add(label);
    label = new JLabel("Lat/lon: ");
    nameColumn.add(label);
    label = new JLabel("Depth (m): ");
    nameColumn.add(label);
    label = new JLabel("Altitude (m): ");
    nameColumn.add(label);
    label = new JLabel("Heading (deg): ");
    nameColumn.add(label);
    label = new JLabel("Speed (m/s): ");
    nameColumn.add(label);
    label = new JLabel("Pitch (deg): ");
    nameColumn.add(label);

    pane1.add(nameColumn);

    // Create second column (data values)
    valueColumn = new JPanel();
    valueColumn.setLayout(new BoxLayout(valueColumn, BoxLayout.Y_AXIS));
    valueColumn.setBorder(BorderFactory.createEmptyBorder(30, //top
							  30, //left
							  10, //bottom
							  10) //right
			  );

    _navUtc = new JLabel("nav utc");
    valueColumn.add(_navUtc);
    _latLon = new JLabel("lat/lon");
    valueColumn.add(_latLon);
    _depth = new JLabel("depth");
    valueColumn.add(_depth);
    _altitude = new JLabel("altitude");
    valueColumn.add(_altitude);
    _heading = new JLabel("heading");
    valueColumn.add(_heading);
    _speed = new JLabel("speed");
    valueColumn.add(_speed);
    _pitch = new JLabel("pitch");
    valueColumn.add(_pitch);

    pane1.add(valueColumn);

    navigationPane.add(pane1);

    _requestNavButton = 
      new CommandButton(navigationPane, "Request", _app, 
			AcousticGui.NAVIGATION);

    JPanel ctdPane = new JPanel();
    ctdPane.setBorder(BorderFactory.createTitledBorder("CTD"));
    ctdPane.setLayout(new BoxLayout(ctdPane, BoxLayout.Y_AXIS));
    pane1 = new JPanel();
    pane1.setLayout(new GridLayout(1, 0));

    // Create first column (data names)
    nameColumn = new JPanel();
    nameColumn.setLayout(new BoxLayout(nameColumn, BoxLayout.Y_AXIS));
    nameColumn.setBorder(BorderFactory.createEmptyBorder(30, //top
							 30, //left
							 10, //bottom
							 10) //right
			 );

    label = new JLabel("UTC: ");
    nameColumn.add(label);
    label = new JLabel("Conductivity: ");
    nameColumn.add(label);
    label = new JLabel("Temperature: ");
    nameColumn.add(label);

    pane1.add(nameColumn);

    // Create second column (data values)
    valueColumn = new JPanel();
    valueColumn.setLayout(new BoxLayout(valueColumn, BoxLayout.Y_AXIS));
    valueColumn.setBorder(BorderFactory.createEmptyBorder(30, //top
							  30, //left
							  10, //bottom
							  10) //right
			  );

    _ctdUtc = new JLabel("CTD utc");
    valueColumn.add(_ctdUtc);
    _conductivity = new JLabel("conductivity                ");
    valueColumn.add(_conductivity);
    _temperature = new JLabel("temperature");
    valueColumn.add(_temperature);

    pane1.add(valueColumn);

    ctdPane.add(pane1);

    _requestCtdButton = 
      new CommandButton(ctdPane, "Request", _app, AcousticGui.CTD);

    // Add status and navigation panes to parent
    telemetryPane.add(statusPane);
    telemetryPane.add(navigationPane);
    telemetryPane.add(ctdPane);

    parent.add(telemetryPane);
  }

  synchronized void update(String recordBuffer) {

    AcousticRecord record = new AcousticRecord(recordBuffer);

    // Update comms information
    _app.commsPage.update(record);

    if (!record.containsAuvPacket()) {
      System.out.println("No AUV packet contained in " + record.string());
      return;
    }

    String packetBuffer;

    try {
      packetBuffer = record.packetString();
    }
    catch (Exception e) {
      System.out.println("Caught exception trying to extract packet from " +
			 record.string());

      System.out.println(e.toString());
      return;
    }

    NumberFormat formatter = NumberFormat.getNumberInstance();
    double value;

    
    int typeCode;

    try {
      // Get type code for packet
      typeCode = Integer.parseInt(packetBuffer.substring(0, 1));
    }
    catch (Exception e) {
      System.out.println("updateTelemetry() - couldn't parse type from:" 
                         + packetBuffer);

      System.out.println(e.toString());
      return;
    }

    AcousticPacket packet;
    Date utc;

    if (typeCode == AcousticGui.MISSIONSTATE) {

      try {
	packet = new StatusPacket(packetBuffer);

	packet.nextToken();  // Skip packet type
	packet.nextToken();  // Skip sequence number

	utc = 
	  new Date((long )Integer.parseInt(packet.nextToken().trim()) * 1000);

	_statusUtc.setText(_dateFormat.format(utc));


	// Have to pad for some reason to get proper sizing???
	_missionTime.setText(packet.nextToken() + "                      ");

	switch (Integer.parseInt(packet.nextToken().trim())) {
	case AcousticGui.INITIAL_STATE:
	  _missionState.setText("Initial");
	  break;
	case AcousticGui.NORMAL_STATE:
	  _missionState.setText("Normal");
	  break;
	case AcousticGui.ABORTING_STATE:
	  _missionState.setText("Aborting");
	  break;
	case AcousticGui.SHUTTINGDOWN_STATE:
	  _missionState.setText("Shutting down");
	  break;
	case AcousticGui.DONE_STATE:
	  _missionState.setText("Done");
	  break;
	default:
	  _missionState.setText("???");
	}

	// Behavior index
	String index = packet.nextToken().trim();
      
	_behavior.setText(packet.nextToken().trim() + " (#" + index + ")");

	int current = Integer.parseInt(packet.nextToken().trim());
	double voltage = Integer.parseInt(packet.nextToken().trim()) / 10.;
	formatter.setMinimumFractionDigits(1);
	formatter.setMaximumFractionDigits(1);
	_battery.setText(formatter.format(current) + " mA, " + 
			 formatter.format(voltage) + " V");


	// Acknowledge this packet as a "reply" to a status request. 
	// This is a bit hokey, as we don't really know if this packet
	// results from an explicit request.
	_requestStatusButton.acknowledgeReply();
      }
      catch (Exception e) {
	System.out.println("Exception while processing StatusPacket");
	System.out.println(e.toString());
      }
    }

    else if (typeCode == AcousticGui.NAVIGATION) {

      try {
	packet = new NavigationPacket(packetBuffer);

	packet.nextToken();  // Skip packet type
	packet.nextToken();  // Skip sequence number

	utc = 
	  new Date((long )Integer.parseInt(packet.nextToken().trim()) * 1000);

	_navUtc.setText(_dateFormat.format(utc));

	double latitude = Integer.parseInt(packet.nextToken().trim()) / 10000.;
	double longitude = Integer.parseInt(packet.nextToken().trim()) / 10000.;
	formatter.setMinimumFractionDigits(4);
	formatter.setMaximumFractionDigits(4);

	_latLon.setText(formatter.format(latitude) + ", " + 
			formatter.format(longitude));

	_depth.setText(packet.nextToken());
	_altitude.setText(packet.nextToken());

	formatter.setMaximumFractionDigits(1);
	formatter.setMinimumFractionDigits(1);

	value = Integer.parseInt(packet.nextToken().trim()) / 10.;
	_heading.setText(formatter.format(value));

	value = Integer.parseInt(packet.nextToken().trim()) / 10.;
	_speed.setText(formatter.format(value));

	value = Integer.parseInt(packet.nextToken().trim()) / 10.;
	_pitch.setText(formatter.format(value));

	// Acknowledge this packet as a "reply" to a navigation request. 
	// This is a bit hokey, as we don't really know if this packet
	// results from an explicit request.
	_requestNavButton.acknowledgeReply();
      }
      catch (Exception e) {
	System.out.println("Exception while processing NavigationPacket");
	System.out.println(e.toString());
      }
    }
    else if (typeCode == AcousticGui.CTD) {

      try {
	packet = new CtdPacket(packetBuffer);

	packet.nextToken();  // Skip packet type
	packet.nextToken();  // Skip sequence number

	utc = 
	  new Date((long )Integer.parseInt(packet.nextToken().trim()) * 1000);

	_ctdUtc.setText(_dateFormat.format(utc));
        value = Integer.parseInt(packet.nextToken().trim()) / 1000.;
	formatter.setMaximumFractionDigits(4);
	formatter.setMinimumFractionDigits(4);
        _conductivity.setText(formatter.format(value));

	value = Integer.parseInt(packet.nextToken().trim()) / 100.;
	formatter.setMaximumFractionDigits(2);
	formatter.setMinimumFractionDigits(2);
	_temperature.setText(formatter.format(value));

	_requestCtdButton.acknowledgeReply();
      }
      catch (Exception e) {
	System.out.println("Exception while processing CtdPacket");
	System.out.println(e.toString());
      }
    }      
    else {
      System.out.println("Unknown telemetry packet code: " + packetBuffer);

    }

    // if (packet.hasMoreTokens()) {
    //   System.out.println("Too many tokens in packet? " + packetBuffer);
    // }

  }


}


class CommandPage {

  CommandPage(JPanel parent, AcousticGui app) {

    new CommandButton(parent, "Abort Mission", app, 
		      AcousticGui.ABORT);
  }
}


class CommsPage {

  private ButtonGroup _transponderSelect;
  Vector _transponders;
  JTextArea _packets;

  // Transponder signal info and selection button
  static class Transponder {

    private JProgressBar _signal;
    static final double _MaxSignal = 99999.;
    private String _name;
    private boolean selected = false;
    private JRadioButton _select;
    private JLabel _metaData1;
    private JLabel _metaData2;

    Transponder(String name, JPanel parent, ButtonGroup selections, 
		boolean select) {
      _name = name;
      JPanel pane = new JPanel();
      pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));
      pane.setBorder(BorderFactory.createTitledBorder("Transponder " + name + ":"));

      _metaData1 = new JLabel("");
      pane.add(_metaData1);
      _metaData2 = new JLabel("");
      pane.add(_metaData2);

      JPanel subPane = new JPanel();

      subPane.add(new JLabel("Power:"));
      _signal = new JProgressBar();
      _signal.setMinimum(0);
      _signal.setMaximum(convertSignal(_MaxSignal));
      subPane.add(_signal);
      _select = new JRadioButton("Use", select);
      selected = select;
      
      _select.addItemListener(new ItemListener() {
	public void itemStateChanged(ItemEvent event) {
	  if (_select.isSelected()) 
	    selected = true;
	  else
	    selected = false;
	}
      }); 

      // Add to ButtonGroup
      selections.add(_select);
      subPane.add(_select);
      pane.add(subPane);
      parent.add(pane);
    }

    public String name() {
      return _name;
    }

    // Update display
    public void update(AcousticRecord.MetaData data) {
      _metaData1.setText("Rcvd at: " + data.time + ",  mooring " + 
			data.receiver + ", transp " + data.transmitter);

      _metaData2.setText("Rcv power: " + data.receivedPower);

      _signal.setValue(convertSignal(data.receivedPower));
    }
    
    // Convert floating point signal strength to integer value for display
    // in progress bar
    private int convertSignal(double signal) {
      // Just return for now...
      return (int )signal;
    }
  }


  public CommsPage(JPanel parent) {
    
    _transponderSelect = new ButtonGroup();
    _transponders = new Vector();

    JPanel pane = new JPanel();
    pane.setLayout(new GridLayout(0, 1));

    _transponders.addElement(new Transponder("M1", pane, _transponderSelect,
					     true));

    _transponders.addElement(new Transponder("M2", pane, _transponderSelect,
					     false));

    _transponders.addElement(new Transponder("M3", pane, _transponderSelect,
					     false));

    _transponders.addElement(new Transponder("M4", pane, _transponderSelect, 
					     false));

    JPanel subPane = new JPanel();


    subPane.setBorder(BorderFactory.createTitledBorder("Rcvd Packets"));

    _packets = new JTextArea(5, 30);
    JScrollPane scrollPane = new JScrollPane(_packets);

    subPane.add(scrollPane);

    pane.add(subPane);
    parent.add(pane);
  }

  public void update(AcousticRecord record) {

    // Update packet display
    _packets.append(record.string() + "\n");

    if (!record.containsAuvPacket())
      return;

    AcousticRecord.MetaData data = new AcousticRecord.MetaData();

    try {
      record.metaData(data);
    }
    catch (Exception e) {
      System.out.println("Error extracting metadata from " + record.string());
      System.out.println(e.toString());
      return;
    }

    // Get receiver name
    String name = "M" + Integer.toString(data.receiver);

    // Get signal strength
    double strength = data.receivedPower;

    // Find transponder and update it
    for (int i = 0; i < _transponders.size(); i++) {

      Transponder transponder = 
	(Transponder )_transponders.elementAt(i);

      if (name.compareTo(transponder.name()) == 0) {
	// Found transponder
	transponder.update(data);
	break;
      }
    }
  }


  public String selectedTransponder() {
    for (int i = 0; i < _transponders.size(); i++) {

      Transponder transponder = 
	(Transponder )_transponders.elementAt(i);

      if (transponder.selected)
	return transponder.name();
    }
    return "";
  }
}


/*
AcousticRecord consists of a header, followed by the actual AcousticPacket
*/
class AcousticRecord {
  static final int _AuvTransmitter = 66;
  static final String _ValidPrefix = "RX";
  static final char _PacketDelimiter = '"';
  String _buffer;

  static class MetaData {
    String time;
    int receiver;
    int transmitter;
    int receivedPower;
  };

  AcousticRecord(String record) {
    _buffer = record;

  }


  // Extract transponder #, received power, etc.
  void metaData(MetaData data) throws Exception {

    if (!_buffer.substring(0, _ValidPrefix.length()).equals(_ValidPrefix)) {
      // Doesn't contain a packet
      throw new Exception("Record does not contain packet");    
    }

    int nTokens = 0;
    StringTokenizer stream = new StringTokenizer(_buffer, " ");
    boolean done = false;
    while (stream.hasMoreTokens() && !done) {

      String token = stream.nextToken();

      switch (nTokens) {

      case 0:
	// "RX", "TO", etc
	break;

      case 1:
	// Transponder number
	data.receiver = Integer.parseInt(token);
	break;

      case 2:
	// Date/time
	data.time = token;
	break;

      case 3:
	// Received power
	data.receivedPower = Integer.parseInt(token);
	break;

      case 4:
	// Transmitter id
	data.transmitter = Integer.parseInt(token);
	done = true;
	break;
      }

      nTokens++;
    }
  }

  // Return string representation
  String string() {
    return _buffer;
  }

  // Return true if record contains acoustic packet data
  boolean containsPacket() {
    if (_buffer.substring(0, _ValidPrefix.length()).equals(_ValidPrefix)) 
      return true;
    else
      return false;
  }


  boolean containsAuvPacket() {
    if (!containsPacket())
      return false;

    MetaData data = new MetaData();
    try {
      metaData(data);
    }
    catch (Exception e) {
      return false;
    }
    if (data.transmitter == _AuvTransmitter)
      return true;
    else
      return false;    
  }

  // Return acoustic packet string
  String packetString() throws Exception {

    int startIndex;
    int endIndex;

    if ((startIndex = _buffer.indexOf(_PacketDelimiter)) == -1) {
      throw new Exception("Start of packet not found in record");
    }
    if ((endIndex = _buffer.indexOf(_PacketDelimiter, startIndex + 1)) 
	== -1) {
      throw new Exception("End of packet not found in record");
    }

    return _buffer.substring(startIndex+1, endIndex-1);
  }

  
}



/*
The following AcousticPacket class and subclasses should probably
be in a separate package.
*/


class AcousticPacket {


  // Number of bytes in acoustic packet
  public static final int _Nbytes = 50;

  // These must match field widths in C++ header file
  static final int _TypeFieldLength = 1;
  static final int _SequenceNoFieldLength = 1;
  static final int _TimeTagFieldLength = 10;

  StringBuffer _buffer;
  Vector _fields;
  int _currentField;
  int _startIndex;

  public AcousticPacket(String buffer) {
    _fields = new Vector();
    _buffer = new StringBuffer(buffer);
    addField(_TypeFieldLength);
    addField(_SequenceNoFieldLength);
    addField(_TimeTagFieldLength);
    reset();
  }

  // Add a field
  void addField(int fieldWidth) {
    _fields.addElement(new Integer(fieldWidth));
  }

  // Restart tokenizing
  public void reset() {
    _currentField = 0;
    _startIndex = 0;
  }

  public int nFields() {
    return _fields.size();
  }

  void advance() {
    if (_currentField < _fields.size()) {
      Integer w = (Integer )_fields.elementAt(_currentField);
      int width = w.intValue();
      _startIndex += width;
      _currentField++;
    }
    else {
      // Throw some exception here?
      System.out.println("AcousticPacket::advance() - at end of packet");
      return;
    }
  }

  // Return token in current field, then advance current field
  public synchronized String nextToken() {

    if (_currentField >= _fields.size()) {
      // Should throw some kinda exception here...
      System.out.println("AcousticPacket::nextToken() - at end of packet");
      return "";
    }

    // Get width of current field
    Integer w = (Integer )_fields.elementAt(_currentField);
    int width = w.intValue();

    // Extract substring of specified width 
    String token = 
      _buffer.toString().substring(_startIndex, _startIndex + width);

    advance();
    return token;
  }

  // Set token in next field, then advance field
  public void setNextToken(String token) {

    // Get width of current field
    Integer w = (Integer )_fields.elementAt(_currentField);
    int width = w.intValue();

    if (token.length() > width) {
      // Throw some exception here
      return;
    }
    _buffer.insert(_startIndex, token);

    advance();
  }

  public String buffer() {
    return _buffer.toString();
  }

  public void print() {
    System.out.println(_buffer);
  }
}



class CtdPacket extends AcousticPacket {

  // These must match field widths in C++ header file
  static final int _ConductivityFieldWidth = 7;
  static final int _TemperatureFieldWidth = 4;
  static final int _DepthFieldWidth = 4;
  
  public CtdPacket(String buffer) {
    super(buffer);
    addField(_ConductivityFieldWidth);
    addField(_TemperatureFieldWidth);
    addField(_DepthFieldWidth);
  }
}


class NavigationPacket extends AcousticPacket {

  // These must match field widths in C++ header file
  static final int _LatitudeFieldWidth = 7;
  static final int _LongitudeFieldWidth = 8;
  static final int _DepthFieldWidth = 4;
  static final int _AltitudeFieldWidth = 4;
  static final int _HeadingFieldWidth = 5;
  static final int _SpeedFieldWidth = 3;
  static final int _PitchFieldWidth = 4;

  public NavigationPacket(String buffer) {
    super(buffer);
    addField(_LatitudeFieldWidth);
    addField(_LongitudeFieldWidth);
    addField(_DepthFieldWidth);
    addField(_AltitudeFieldWidth);
    addField(_HeadingFieldWidth);
    addField(_SpeedFieldWidth);
    addField(_PitchFieldWidth);
  }
}


class StatusPacket extends AcousticPacket {

  // These must match field widths in C++ header file
  static final int _MissionTimeFieldWidth = 6;
  static final int _MissionStateFieldWidth = 2;
  static final int _BehaviorIndexFieldWidth = 3;
  static final int _BehaviorFieldWidth = 15;
  static final int _BatteryCurrentFieldWidth = 5;
  static final int _BatteryVoltageFieldWidth = 5;
  
  public StatusPacket(String buffer) {
    super(buffer);
    addField(_MissionTimeFieldWidth);
    addField(_MissionStateFieldWidth);
    addField(_BehaviorIndexFieldWidth);
    addField(_BehaviorFieldWidth);
    addField(_BatteryCurrentFieldWidth);
    addField(_BatteryVoltageFieldWidth);
  }
}


class CommandPacket extends AcousticPacket {

  public CommandPacket(int commandType, int sequenceNo, Date utc) {

    super(new String(" "));

    // Initialize to all blanks
    _buffer.setLength(_Nbytes);
    for (int i = 0; i < _Nbytes; i++)
      _buffer.setCharAt(i, ' ');

    // Set command type
    setNextToken(Integer.toString(commandType));

    // Set command sequence
    setNextToken(Integer.toString(sequenceNo));

    // Time-tag
    setNextToken(Long.toString(utc.getTime() / 1000));
  }
}




