//********************************************************************
// DataClient.java
//********************************************************************
//
//              A Prototype Remote Data Monitoring System
//
//  DataClient displays data sent by DataServer. At a given time 
//   time interval, DataClient requests data from DataServer. DataServer
//   sends first an integer for the number of data points to be sent 
//   and then sends that many floating point values. 
//
//  DataClient runs both as a standalone app, using DCAppletHolder.java,
//   or in a browser web page. 
//
//  The User Interface displays several items of interest:
//   - The host IP and username are displayed in TextFields and can be altered.   
//   - Stop and Stop buttons can interrupt and restart the connection.
//   - Another TextField displays the status of the connection
//   - a TextArea displays the current data.
//   - One chart also displays the data and another displays a histogram
//     of the values for themonitor channel, e.g. data 3 in a list of 10 
//     data values, which can be selected in another TextField.
//  
//  The thread class DataReader is used to send the requests for data
//   and to read the data. The DataReader consists primarily of a run()
//   that uses functions and properties of the DataClient to obtain and
//   display the data.
//
//  A charting routine obtained from a third party (a demo developed 
//   by Philip Meese for Java Report Magazine) is used to display the 
//   data values for each data event and also to histogram one of the 
//   values chosen.
//   
//
//********************************************************************
package DataMonitor;

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import ThirdParty.BarChart.*;

//********************************************************************

public class DataClient extends Applet implements ActionListener {

    // Properties needed for the connection setup
    Socket server=null;
    BufferedReader net_input_string;
    DataInputStream net_input_data;
    OutputStream net_output;
    DataReader dataReader;

    int ServerPort = 1111; // use 1111 as default

    String host=null;
    String username=null;
    String userport=null;

    boolean connected = false; // flag for connection status
    String msg_line = "";  // String displayed in status TextField
    String data_line = ""; // String displayed in data TextArea


    // UI components
    TextField messageField = null;
    TextField hostField = null;
    TextField usernameField = null;
    TextField monitorChanField = null;
    TextArea dataArea = null;
    Button startButton=null;
    Button stopButton=null;
    DataChart chart = null;
    Histogram hist = null;

    // Data number and a float array to hold the data event
    int numData=0;
    float [] data=null;

    int monitorChan = 0;// Default data channel to histogram

    int TimeUpdate = 2000; // Data update interval

    //----------------------------------------------------------------
    // For standalone use, pass the host (IP address) and username
    // in the constructor.
    public DataClient(String host, String user){
 
      this.host = host;
      this.username = user;

    }
    //----------------------------------------------------------------
    public DataClient(){
    }
    //----------------------------------------------------------------
    // Use init to build the UI including:
    //    host       - input the IP address of the server
    //    username   - login name (DataServer only uses this as a label
    //                 for the connection but a full username/password
    //                 login could be added)
    //    monitor chan-the channel of the data record to use for histogramming
    //    message    - display the connection status messages
    //    dataArea   - show the data
    //    chart      - show value of each channel of current data
    //    hist       - histogram of the monitor channel
    //
    public void init() {
      if( host== null) host = getParameter("host");
      if( username== null) username = getParameter("username");
 
      setLayout( new GridLayout(3,1));

      Panel controlPanel = new Panel();
      controlPanel.setLayout(new BorderLayout());

      Panel p1 = new Panel();
      p1.setLayout( new FlowLayout() );
      p1.add(new Label("Host:"));     
      p1.add(hostField = new TextField(host,16));
      hostField.setEditable(true);
      hostField.setEnabled(true);

      p1.add(new Label("Username:"));
      p1.add(usernameField = new TextField(username,16));
      usernameField.setEditable(true);
 
      controlPanel.add("North",p1);

      Panel p2 = new Panel();
      p2.setLayout( new FlowLayout() );

      startButton = new Button("Start");
      startButton.addActionListener(this);
      p2.add(startButton);

      stopButton = new Button("Stop");
      stopButton.addActionListener(this);
      p2.add(stopButton);

      p2.add(new Label("Monitor Channel:"));
      p2.add(monitorChanField = new TextField(Integer.toString(monitorChan),3));
      monitorChanField.setEditable(true);

      controlPanel.add("Center",p2);

      Panel p3 = new Panel();
      p3.setLayout( new BorderLayout() );
      p3.add("North",messageField = new TextField(45));
      p3.add("South",dataArea = new TextArea(2,45));
  
      controlPanel.add("South",p3);

      add(controlPanel);


      // Use the BarChart class to display all the data channels 
      // for each event and to histogram one of the data channels.
      // BarChart only uses a string of the numbers as input. A better
      // charting tool would use numerical arrays but for a demo this
      // will do well enough.
      int _width= getSize().width/3;
      int _height = getSize().height/3;
      Panel p4 = new Panel();
      p4.setLayout(new BorderLayout());
      chart = new DataChart("5 5 6 7 8",_width,_height-20);
      p4.add("Center",chart._barChart);
      p4.add("South",new Label("Inputs",Label.CENTER));
      p4.add("North",new Label("Plot of each data event"));
     
      add(p4);
           
      Panel p5 = new Panel();
      p5.setLayout(new BorderLayout());
      hist = new Histogram(20,0.0,100.0,_width,_height-50);
      p5.add("North",new Label("Histgram of the Monitor Channel"));
      p5.add("Center",hist.chart._barChart);
      p5.add("South",new Label("Bins 0-100",Label.CENTER));
     
      add(p5);
       
      // Only one button enabled at a time    
      startButton.setEnabled(true);
      stopButton.setEnabled(false);

      // Use an annonymous key listener to watch for the ESC
      // keys to signal to stop data taking.
      addKeyListener( new KeyAdapter() {
        public void keyPressed( KeyEvent e) {
          if( e.getKeyCode() == KeyEvent.VK_ESCAPE ){
            stop();
            System.out.println("Escape key hit");
          }
        }
      } );

    }
    //----------------------------------------------------------------
    public void actionPerformed( ActionEvent e ) {
      if ( e.getActionCommand().equals("Stop") ){
        stop();
      }else {
        start(); 
      }
    }

    //----------------------------------------------------------------
    public void start(){

      if ( connected) stop();

      // Get the current values of the host IP address and
      // and the username
      host = hostField.getText();
      username = usernameField.getText();
  
      // Now try to connect to the DataServer
      try{
        if (connect()) {
            
            // If successful, then reset the histogram, get the
            // channel number to monitor, and start the dataReader
            // thread.
            monitorChan = Integer.parseInt(monitorChanField.getText());
            hist.reset();

            connected = true;
            dataReader = new DataReader(this);
            dataReader.start();
            startButton.setEnabled(false);
            stopButton.setEnabled(true);
        } else {
          msg_line = "* NOT CONNECTED *";
        }
      } catch (IOException e){ 
        msg_line = "* NOT CONNECTED *";
      }
      repaint();
    }
    //----------------------------------------------------------------

    boolean connect() throws IOException {
  
      // Connect to the DataHost using the host IP address
      // and the port at the server location
      server = new Socket(host, ServerPort);

      // Local port number for this socket
      userport = "" + server.getLocalPort();

      // Get the input and output streams from the socket
      InputStream in = server.getInputStream();
      net_input_string = new BufferedReader( 
        new InputStreamReader( in ) ) ;
      net_input_data = new DataInputStream( in );

      net_output = server.getOutputStream();

      if( login() )
          return true;
      else {
          server = null;
          connected = false;
          throw (new IOException());
      }
  
    }

    //----------------------------------------------------------------
    // Here is a homemade login protocol. A password could be
    // easily added.
    boolean login() {

      System.out.println(msg_line="Waiting for login prompt...");
      data_line = "";
      repaint();// Show connection status

      System.out.println((msg_line=read_net_input_line()));
      if( msg_line == null) return false;
      repaint();// Show connection status

      System.out.println("Send username " + username);
      try{
        write_net_output_line(username);
      }catch (IOException e){
        return false;
      }catch (Exception e){
        System.out.println("General exception occurred in sending username!");
        return false;
      }

      System.out.println(msg_line="Waiting for response...");
      repaint();// Show connection status

      System.out.println((msg_line=read_net_input_line()));
      if( msg_line == null) return false;
 
      return true;
    }
    //----------------------------------------------------------------

    void close_server() {
      try{
        server.close();
        connected = false;
      }catch (IOException e)
      {}
    }

    //----------------------------------------------------------------
    // Do all of the steps needed to stop the connection.
    public void stop(){
      if( dataReader != null) {
        // Disconnect and kill the dataReader thread
        close_server();
        dataReader.keepRunning = false;
        dataReader = null;
        connected = false;
  
        // Switch the buttons for restart
        startButton.setEnabled(true);
        stopButton.setEnabled(false);

        // Display the status
        msg_line="Disconnected...";
        repaint();
      }
    }

    //----------------------------------------------------------------
    // Display the message, data, data event and histogram displays
    public void paint(Graphics g){

      messageField.setText(msg_line);
      dataArea.setText(data_line);
      chart.graphIt();
      hist.graphIt();

    }
    //----------------------------------------------------------------
    // The net input stream is wrappped in a DataInputStream 
    // so we can use readLine, readInt and readFloat
    String read_net_input_line(){
      try{
        return net_input_string.readLine();
      }catch (IOException e){
        return null;
      }
    }
    //----------------------------------------------------------------

    int read_net_input_int() throws IOException {
          return net_input_data.readInt();
    }
    //----------------------------------------------------------------

    float read_net_input_float() throws IOException {
          return net_input_data.readFloat();
    }
    //----------------------------------------------------------------
    // The net output is a PrintWriter class which doesn't throw
    // IOException itself. Instead we have to use the PrintWriter
    // checkError() method and throw an exception ourselves if there
    // was an output error.
    void write_net_output_line(String string) throws IOException {
      PrintWriter pout= new PrintWriter( 
        new OutputStreamWriter(net_output, "8859_1"), true );
      pout.println(string);
      if( pout.checkError()) throw (new IOException());
      pout.flush();
      if( pout.checkError()) throw (new IOException());
    }

  }

//********************************************************************
// This thread class used to monitor the connection to the DataServer.
// It periodically requests data from the server and then has DataClient
// update its display.

class DataReader extends Thread
  {
    String net_line = "";
    boolean keepRunning = true;

    DataClient c;

    //----------------------------------------------------------------

    public DataReader(DataClient c) {
      this.c = c;
    }

   //----------------------------------------------------------------

    public void run() {
      String dataString="";
      String histDataString;

      // This loops until either the connection is broken or the
      // stop button or stop key is hit
      while (keepRunning) {

        // Ask the server to send data.
        try{
          c.write_net_output_line("send data");
        }catch (IOException e){
          break;
        }

        // First number sent from server is an integer that gives
        // the number of data values to be sent.
        try{
          c.numData = c.read_net_input_int();
        }catch (IOException e){break;}

        dataString = "Number data pts= " + c.numData + "\n";
        System.out.println(dataString);
        histDataString = "";

        // Create an array to hold the data and then read in the
        // values from the server.
        c.data = new float[c.numData];
        for( int i=0; i < c.numData; i++){
          try{
            c.data[i] = c.read_net_input_float();
            // Pass the data for the monitored channel to the
            // histogram.
            if(i == c.monitorChan) c.hist.setData(c.data[i]);
          }catch (IOException e){break;}

          // BarChart needs data in string form so convert here.
          int j=((int)c.data[i]);
          dataString +=  c.data[i] + " ";
          histDataString += j + " ";
        }
        // Set the data for the TextArea, the event chart.
        c.data_line = dataString + "\n";
        c.chart.setData(histDataString);

        // Now repaint the display.
        c.repaint();

        // Ask for data every TimeUpdate
        try {
          Thread.sleep(c.TimeUpdate);
        } catch (InterruptedException e)
        {}

      }

      c.data_line = dataString + "\n";
      c.msg_line = "disconnected";
      c.repaint();
    }

  }
