//********************************************************************
// server.java
//********************************************************************
//
//              A Prototype Remote Data Monitoring System
//                with FallingObject Simulation Data
//
//    DataServer provides data to DataClients. A DataClient sends
//  a request for data from the Server. DataServer responds by first
//  sending an integer for the number of data points and then sends 
//  the floating point values.
//
//    DataServer runs standalone. The server is meant to be run at
//  the same time that a separate data acquisition program is running.
//  The DAQ program would be programmed to periodically update a file
//  with the data of interest. This server would then read this file 
//  and send the data to the DataClients.
//
//    An instance of the thread class DataSender is assigned to each 
//  DataClient socket. It monitors the socket for requests from the
//  DataClient and then reads the data and sends it to the DataClient.
//  The DataSender consists primarily of a run() that uses functions 
//  and properties of the DataServer.
//    
//    This version of DataServer also holds an instance of the 
//  FallingObject applet (Lecture 11B) in a frame. From the G_Detector
//  instance that measures the rate of free fall, simulated data
//  is created and saved in a histgram. This histogram data is passed
//  to the DataClient when it requests it.
//
//********************************************************************
package DataMonitor;

import java.awt.*; 
import java.awt.event.*;
import java.io.*;
import java.net.*;
import FOExperiment.*;

//********************************************************************

public class DataServer extends Frame implements ActionListener{
  ServerSocket server_socket;

  String user[];
  BufferedReader net_input[];
  OutputStream net_output[];

  int DataServerPort = 1111;// Default port number

  DataSender senders[];
  static int client_counter = 0;
  static int maxUsers = 10;
  static int NumDataVals=10;

  static FallingObject FOApplet = null;
  static DataServer dataServeAppletHolder = null;

//----------------------------------------------------------------

  public static void main(String args[]){

    dataServeAppletHolder = new DataServer();

    FOApplet = new FallingObject();

    dataServeAppletHolder.add("Center", FOApplet);

    FOApplet.init();
    FOApplet.start();

    // Use an adapter to close the window
    WindowListener listener = new WindowAdapter() {
      public void windowClosing(WindowEvent e){
        FOApplet.stop();
        dataServeAppletHolder.dispose();
        System.exit(0);
      }
    };

    dataServeAppletHolder.addWindowListener(listener);
    
    dataServeAppletHolder.setSize(525,550);
    dataServeAppletHolder.pack();
    dataServeAppletHolder.show();

    dataServeAppletHolder.startServing();
  }
//----------------------------------------------------------------

  public DataServer(){
    // Set up frame
    setTitle("DataServer & FallingObject Applet");
    Menu m = new Menu("File");
    MenuItem mi = new MenuItem( "Quit" );
    mi.addActionListener( this );
    m.add(mi);
    MenuBar mb = new MenuBar();
    mb.add(m);
    setMenuBar(mb);

  }

//----------------------------------------------------------------
  public void startServing(){
    // Now setup server stuff
    user = new String[maxUsers];
    net_input = new BufferedReader[maxUsers];
    net_output = new OutputStream[maxUsers];
    senders = new DataSender[maxUsers];

    // The server_socket is used to make connections to
    // DataClients at this port number
    try{
      server_socket = new ServerSocket(DataServerPort);
    }catch (IOException e)
      {
        System.out.println("Error in server socket");
        return;
      }

    System.out.println("Waiting for users...");
    
    // Loop here to grab clients
    while (true){
      try {
        // accept() only returns when a connection has
        // been made.
        Socket socket = server_socket.accept();
        // Do the setup this socket and then loop
        // back around to wait for the next DataClient.
        service_request(socket);
      }
      catch (IOException e){
        System.out.println("Exception: <" + e + ">");
        break;
      }
    }

  }
//----------------------------------------------------------------
  public void actionPerformed( ActionEvent e ) {
    if ( e.getActionCommand().equals("Quit") ){
      FOApplet.stop();
      dispose();
      System.exit(0);
    }
  }
//----------------------------------------------------------------
// Set up the connection to the DataClient. This means getting the
// IO streams, carrying out the login prototcol, and then starting
// a DataSender thread to monitor the DataClient.
//
// The code is a bit messy because we check both reads and writes
// for errors in case the connection breaks down.
//
// The reads catch their own IOExceptions and return a null, while
// string writes use a PrintStream that doesn't throw IOException. So
// we use the checkError() method and throw it ourselves.
//
  public void service_request(Socket socket) {
    InputStream input;
    OutputStream output;

    try{
      input = socket.getInputStream();
      output = socket.getOutputStream();
    }
    catch (IOException e){
      System.out.println("Unable to get input/output streams");
        return;
    }

    if (client_counter >= maxUsers ) {
      try{
        write_net_output_line(
            new PrintStream(output),
           "Sorry, We've reached maximum of " + maxUsers + " clients");
        return;
      } catch (IOException e){
        System.out.println("Connection fails during login");
        return;
      }
    }

    // Keep an array of input streams for each client. Use
    // DataInputStream wrapper so we can use the readLine() methods.
    net_input[client_counter] = new BufferedReader(new InputStreamReader(input));
    net_output[client_counter] = output;

    // Do a simple login protocol. Note a password check could
    // be added here.
    try{
      write_net_output_line(net_output[client_counter], "Username: ");
    } catch (IOException e){
      System.out.println("Connection fails during login");
        return;
    }

    user[client_counter] = read_net_input_line(net_input[client_counter]);
    if( user[client_counter] == null ) {
      System.out.println("Connection fails during login");
      return;
    } 
 
    try{
      write_net_output_line(net_output[client_counter], "Login successful");
    } catch (IOException e){
      System.out.println("Connection fails during login");
        return;
    }

    System.out.println(user[client_counter] + " Connected! ");
    System.out.println(socket.toString());

    // The login is successful so now spin off the DataSender to 
    // service this client.
    senders[client_counter] = new DataSender(this, client_counter);
    senders[client_counter].start();
    System.out.println("DataReader " + client_counter +" started");
       
    client_counter++;
  }
//----------------------------------------------------------------
// Whenever one client disconnects we need to reset the array to
// remove the empty slot.
  public void clientLogoff(int id) {
    net_input[id] = null;
    net_output[id] = null;
    senders[id] = null;
    for( int i=id; i<(client_counter-1); i++){
      net_input[i] = net_input[i+1];
      net_output[i] = net_output[i+1];
      senders[i] = senders[i+1];
      senders[i].index = i;
      user[i] = user[i+1];
    }
    --client_counter;
  }
//----------------------------------------------------------------

  String read_net_input_line(BufferedReader input) {
    try{
      return input.readLine();
    }catch (IOException e){
      return null;
    }
  }

//----------------------------------------------------------------
// Output is wrapped with a PrintWriter, which doesn't throw 
// IOException. So we use the checkError() method and throw it 
// ourselves.
  void write_net_output_line(OutputStream output, String string) 
                  throws IOException {
    PrintWriter pout = new PrintWriter( 
      new OutputStreamWriter(output, "8859_1"), true );
    pout.println(string);
    if( pout.checkError()) throw (new IOException());
    pout.flush();
    if( pout.checkError()) throw (new IOException());
  }
//----------------------------------------------------------------
  void write_net_output_int(DataOutputStream output, int i)
        throws IOException {
    output.writeInt(i);
    output.flush();
  }
//----------------------------------------------------------------
  void write_net_output_float(DataOutputStream output,float f)
     throws IOException {
    output.writeFloat(f);
    output.flush();
  }
}
