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



class TelemetryReader implements Runnable {

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

  public TelemetryReader(DoradoGui 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;
    }

    System.out.println("TelemetryReader " + _idNo + " : create thread");
    _readerThread = new Thread(this, "readerThread");
    _readerThread.start();
  }

  public void run() {

    System.out.println("TelemetryReader.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.updateTelemetry(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
      }
    }
    System.out.println("TelemetryReader.run() is terminating...");
  }

  // 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 _pendingLabel;

  public JButton button;

  Component create(String label) {
    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++;
	update();
      }
    });
    pane.add(button);
    pane.add(Box.createRigidArea(new Dimension(10, 10)));
    _pendingLabel = new JLabel("#Pending replies: xxxx");
    _pendingLabel.setAlignmentY(Component.TOP_ALIGNMENT);
    pane.add(_pendingLabel);
    _nPendingReplies = 0;
    update();
    return 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 DoradoGui 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;

  // 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 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;
  private TelemetryReader _telemetryReader;
  private String _telemetryFilename;
  private int _readerNo = 0;

  DoradoGui _guiInstance;

  public DoradoGui(String telemetryFilename) {
    _dateFormat = DateFormat.getDateTimeInstance();

    _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);

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


  public Component createTelemetry() {

    JPanel parentPane = new JPanel();
    parentPane.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.add(_requestStatusButton.create("Request"));

    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.add(_requestNavButton.create("Request"));


    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.add(_requestCtdButton.create("Request"));

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

    return parentPane;
  };

  public Component createCommands() {

    JButton abortButton = new JButton("Abort Mission");

    abortButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
	System.out.println("Abort!");
      }
    });  // End of inner class

    JPanel pane = new JPanel();

    pane.setBorder(BorderFactory.createEmptyBorder(30, 30, 10, 30));

    pane.setLayout(new BoxLayout(pane, BoxLayout.X_AXIS));
    pane.add(abortButton);
    return pane;
  };

  
  public Component createContents() {
    JPanel pane = new JPanel();
    pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));
    Component content;
    content = createTelemetry();
    pane.add(content);
    content = createCommands();
    pane.add(content);
    return pane;
  }

  synchronized void updateTelemetry(String packetBuffer) {

    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);
      return;
    }

    TelemetryPacket packet;
    Date utc;

    if (typeCode == MISSIONSTATE) {

      try {
	packet = new StatusPacket(packetBuffer);

	packet.nextToken();  // Skip packet type

	//	long utcSec = Integer.parseInt(packet.nextToken().trim());

	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() + "                      ");
	// _missionTime.setText(packet.nextToken() + "ppppppaaaaaaaaaadddd");

	switch (Integer.parseInt(packet.nextToken().trim())) {
	case INITIAL_STATE:
	  _missionState.setText("Initial");
	  break;
	case NORMAL_STATE:
	  _missionState.setText("Normal");
	  break;
	case ABORTING_STATE:
	  _missionState.setText("Aborting");
	  break;
	case SHUTTINGDOWN_STATE:
	  _missionState.setText("Shutting down");
	  break;
	case 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 == NAVIGATION) {

      try {
	packet = new NavigationPacket(packetBuffer);

	packet.nextToken();  // Skip packet type

	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 == CTD) {

      try {
	packet = new CtdPacket(packetBuffer);

	packet.nextToken();  // Skip packet type

	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);
    // }

  }



  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.
    DoradoGui app = new DoradoGui(telemetryFilename);
    Component contents;
    contents = app.createContents();
    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 TelemetryPacket {

  // These must match field widths in C++ header file
  static final int typeFieldLength = 1;
  static final int timeTagFieldLength = 10;

  String _buffer;
  Vector _fields;
  int _currentField;
  int _startIndex;

  public TelemetryPacket(String buffer) {
    _fields = new Vector();
    _buffer = buffer;
    addField(typeFieldLength);
    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("TelemetryPacket::advance() - at end of packet");
      return;
    }
  }

  // Return token in next field
  public synchronized String nextToken() {

    if (_currentField >= _fields.size()) {
      // Should throw some kinda exception here...
      System.out.println("TelemetryPacket::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.substring(_startIndex, _startIndex + width);
    advance();
    return token;
  }
}


class NavigationPacket extends TelemetryPacket {

  // 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 TelemetryPacket {

  // 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 CtdPacket extends TelemetryPacket {

  // 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);
  }
}

