// file: $ISIP_IFC/class/java/ConnectionManager/ConnectionManager.java
// version: $Id: ConnectionManager.java 10231 2005-09-13 14:29:38Z stanley $

// import necessary java libraries
//
import java.awt.*;
import java.awt.image.*;
import javax.swing.*;
import java.awt.event.*;
import java.util.*;
import java.net.*; 

/**
 * Uses a DemoNetworkHandler to connect to the specified network
 * and then processes network messages.
 */
public class ConnectionManager extends JFrame implements Runnable,
							 DemoEventProcessable,
							 DemoNetworkListener,
							 DemoProtocol {
    
    //-------------------------------------------------------------------------
    //
    // public constants
    //
    //-------------------------------------------------------------------------

    /**
     * The name represents this client.
     */
    public static String player_name_d;

    /**
     * Default delay time (40 milliseconds).
     */
    public static int DELAY_TIME = 1000/25;

    //------------------------------------------------------------------------
    //
    // protected data
    //
    //-----------------------------------------------------------------------

    /**
     * <code> Thread </code> for this client.
     */
    protected Thread thread_d;

    /**
     * Process events
     */
    protected DemoEventProcessor event_processor_d; 

    /**
     * Network handler for this client.
     */
    protected DemoNetworkHandler network_handler_d;

    /**
     * The player represents the client.
     */
    protected DemoPlayer player_d;

    /**
     * Default IP address for the Control Server.
     */
    protected String process_manager_ip_d ;

    /**
     * Default port number of Control Server.
     */ 
    protected int process_manager_port_d;

    /**
     * IP address for the client to run.
     */
    protected static InetAddress local_ip_address_d;

    /**
     * Port number of the Hub. Retrieved from the server.
     */
    protected int hub_port_d = HUB_PORT_UNDEFINED;

    /**
     * Port number used by Galaxy Communicator Hub to connect to this
     * client
     *
     * This number is used by the galaxy hub to listen also on server side
     */ 
    protected int client_port_d ;

    /**
     * A boolean value indicate the mode of the UI: client or server
     */
    protected boolean server_mode_d = true;

    /**
     * A string value that stores the type of demo that is running.
     */
    protected String demo_type_d;

    /**
     * A boolean value to indicate if we are connected
     */
    protected boolean connected_d = false;

    /**
     * A string value to popup to the user in case something bad happens
     */
    protected String error_str_d = "Unable to connect to network or " +
	"server is down!";

    protected final static int HUB_PORT_UNDEFINED = -1;

    //-------------------------------------------------------------------------
    //
    // public methods
    //
    //-------------------------------------------------------------------------
      
    /**
     * Initializes the <code> DemoEventProcessor </code> and <code>
     * DemoNetworkHandler </code> using default parameters values.
     */
    public ConnectionManager() {
	// Just make the object
	// an empty constructor
    }
    

    /**
     * Initializes the <code> DemoEventProcessor </code> and <code>
     * DemoNetworkHandler </code> using the specified parameter
     * values.
     *
     * @param client_port_a port number of this client
     * @param process_manager_port_a port number the control server
     * are listening server
     * @param process_manager_ip_a an IP address for the control
     * @param server_mode_a a boolean value indicating the mode (true:
     * server; false: client)
     */
    public ConnectionManager(int client_port_a, 
			     int process_manager_port_a, 
			     String process_manager_ip_a,
			     boolean server_mode_a, String demo_type_a) {
	
	System.out.println("A connection manager has been created to handle " +
			   "a [" + demo_type_a + "] demo. Proccess manager " +
			   "ip[" + process_manager_ip_a + "] and port[" + 
			   process_manager_port_a + "]");
	demo_type_d = demo_type_a;
	process_manager_ip_d = process_manager_ip_a;
        process_manager_port_d = process_manager_port_a;
	client_port_d = client_port_a;
	server_mode_d = server_mode_a;

	event_processor_d = new DemoEventProcessor(this);
	network_handler_d = new DemoNetworkHandler();
        network_handler_d.init(process_manager_ip_d, 
			       process_manager_port_d, 
			       this);
	connected_d = true;
    }

    /**
     * Initializes the <code> DemoEventProcessor </code> and <code>
     * DemoNetworkHandler </code> using the specified parameter
     * values.
     *
     * @param client_port_a port number of this client
     * @param process_manager_port_a port number the control server
     * are listening server
     * @param process_manager_ip_a an IP address for the control
     * @param server_mode_a a boolean value indicating the mode (true:
     * server; false: client)
     *
     * @return a boolean value indictating status.
     */
    public boolean init(int client_port_a, 
			int process_manager_port_a, 
			String process_manager_ip_a,
			boolean server_mode_a, String demo_type_a) {

	System.out.println("A connection manager has been init'd to handle " +
			   "a [" + demo_type_a + "] demo. Proccess manager " +
			   "ip[" + process_manager_ip_a + "] and port[" + 
			   process_manager_port_a + "]");

	demo_type_d = demo_type_a;
	process_manager_ip_d = process_manager_ip_a;
        process_manager_port_d = process_manager_port_a;
	client_port_d = client_port_a;
	server_mode_d = server_mode_a;
	event_processor_d = new DemoEventProcessor(this);

	// Create one of these guys and test to make sure it worked ok
	//
	network_handler_d = new DemoNetworkHandler();
        boolean returnval = network_handler_d.init(process_manager_ip_d, 
						   process_manager_port_d, 
						   this);
	if (returnval = false) {
	    return false;
	}
	connected_d = true;

	return true;
    }

    //-------------------------------------------------------------------------
    //
    // class-specific public methods:
    //  set methods
    //
    //-------------------------------------------------------------------------
    
    /**
     * Set the thread for this client.
     *
     * @param thread_a a thread object.
     *
     * @return a boolean value indicating status.
     */
    public boolean setThread(Thread thread_a) {

	thread_d = thread_a;

	// exit gracefully
	//
	return true;
    }


    //-------------------------------------------------------------------------
    //
    // class-specific public methods:
    //  get methods
    //
    //-------------------------------------------------------------------------
    
    /**
     * Get the thread for this client.
     *
     * @return a thread object of the current client.
     */
    public Thread getThread() {
	return thread_d;
    }

    /**
     * Get the hub port of the server. 
     *
     * @return the port number of the hub running on the server
     */
    public synchronized int getHubPort() {
	while (hub_port_d == HUB_PORT_UNDEFINED && connected_d == true) {
	    try {
		wait(100);
	    }
	    catch (Exception e) {
		System.out.println(" wait satement throw a exception");
	    }
	}
	return hub_port_d;
    }

    /**
     * Get the error message for this client.
     *
     * @return a String object with an error message.
     */
    public String getDisconnectErrorMessage() {
	return error_str_d;
    }

    /**
     * This is the threaded part of the class.  This method is
     * automatically called after calling <code>start()</code> on the
     * <code> loop </code> member data.  It processes the received
     * network events, sleeps for 40 milliseconds, and then starts
     * over processing events again.  
     */
    public void run() {

	// local variables
	//
        long startTime;
	long waitTime;
	long elapsedTime;
        Thread thisThread = Thread.currentThread();

        while (thread_d == thisThread) {

            startTime = System.currentTimeMillis();
            
            // process received events
	    //
            event_processor_d.processEventList();
   
            //  handle frame rate
	    //
            elapsedTime = System.currentTimeMillis() - startTime;
            waitTime = Math.max(DELAY_TIME - elapsedTime, 5);
            
            try {
                Thread.sleep(waitTime); 
            }
	    catch (InterruptedException e) {
	    }
	}
	System.out.println("Connection Manager Thread ended");
	dispose();
    }
    
    /**
     * This method is required by the <code> DemoEventProcessable
     * </code> interface.  The <code> networkMessageReceived() </code>
     * method of this class adds the events to the <code>
     * DemoEventProcessor </code>'s event list.  The <code>
     * DemoEventProcessor </code> will remove the events and send them
     * to its associated <code> DemoEventProcessable </code> handler
     * (this class) by calling the <code> handleEvent() </code> method
     * of the handler.
     *
     * @param event_a the network event sent by the <code>
     * DemoEventProcessor </code>.
     *
     * @return a boolean value indicating status.
     */
    public boolean handleEvent(AWTEvent event_a) {
	
        switch(event_a.getID()) {
	    
	case DemoNetworkEvent.NETWORK_MESSAGE_RECEIVED:
            
	    System.out.println("Network Message Received:" 
			       + ((DemoNetworkEvent)event_a).getMessage());
	    handleNetworkMessage(((DemoNetworkEvent)event_a).getMessage());
	    break;
	    
	case DemoNetworkEvent.NETWORK_DISCONNECTED:
	    System.out.println("Network Disconnected");
	    exitProgram();
	    break;
	    
	default:
	    System.out.println("Unknown event given to Connection Manager" + 
			       "handleEvent()");
	    System.out.println("Event is " + event_a.getID());
	    break;
	}
	
	// exit gracefully
	//
	return true;
    }
    
    /**
     * This method is where the specific network events are processed.
     * The <code> handleEvent() </code> method sends all <code>
     * DemoNetworkEvents </code> except for
     * <code>NETWORK_DISCONNECTED</code> to this method.
     *
     * @param message_a the <code> String </code> message describing a
     * network event.
     *
     * @return a boolean value indicating status.
     */
    public boolean handleNetworkMessage(String message_a) {

	// extract message type
	//
        StringTokenizer st = new StringTokenizer(message_a, "|");
        int type = -10;
	try {
	    type = Integer.parseInt(st.nextToken());
	}
	catch (Exception ex) {
	    System.out.println("ConnnectManager-handleNetworkMessage(): " +
			       "unable to parse integer. Value of message " +
			       "is [" + message_a + "]");
	    ex.printStackTrace();
	    return false;
	}
        
	// branch according to different types
	//
        switch (type) {

	case MSG_INIT_PLAYER:
	    
	    player_d = new DemoPlayer(Integer.parseInt(st.nextToken()));
	    
	    if (server_mode_d) {
		player_name_d = "true";
	    }
	    else {
		player_name_d = "false";
	    }

	     if (demo_type_d == "SPEECH_ANALYSIS") {
		  network_handler_d.sendMessage(DEMO_SPEECH_ANALYSIS + "|" + player_name_d);
	     }

	     else if (demo_type_d == "SPEECH_RECOGNITION") {
		 network_handler_d.sendMessage(DEMO_SPEECH_RECOGNITION + "|" + player_name_d);
	     }
	     
	     else if (demo_type_d == "DIALOG_NAVIGATION") {
		 
		 network_handler_d.sendMessage(DEMO_DIALOG_NAVIGATION +
					       "|" + player_name_d);
	     }
	     
	     else if (demo_type_d == "SPEAKER_VERIFICATION") {
		 
		 network_handler_d.sendMessage(DEMO_SPEAKER_VERIFICATION +
					       "|" + player_name_d);
	     }
	     
	     else {
		 System.out.println("Unknown demo type given to Connection " +
				    "Manager handleNetworkMessage()");
		 System.out.println("Type is " + demo_type_d);
	     }
	     
	     network_handler_d.sendMessage(MSG_SET_NAME + "|" + player_name_d);
	     
	     try {
		 local_ip_address_d = InetAddress.getLocalHost(); 
		 network_handler_d.sendMessage(MSG_CLIENT_IP_ADDRESS + "|" + 
					       local_ip_address_d.getHostAddress());
		 network_handler_d.sendMessage(MSG_CLIENT_PORT + "|" + 
					       client_port_d);
		 
		 // set the ip address
		 //
		 player_d.setIPAddress(local_ip_address_d.getHostAddress());
		 
		 // set port number
		 //
		 player_d.setPortNumber(client_port_d);
	     }
	     catch (UnknownHostException uhe) {
		System.out.println("Error - unable to resolve localhost" + 
				   uhe); 
	    }

	    try {
		thread_d.sleep(1000);
	    }
	    catch(InterruptedException e) {
	    }
	    
	    network_handler_d.sendMessage(MSG_START_DEMO + 
					  "|" + player_name_d);
	    System.out.println("Finished sending MSG_START_DEMO signal");
	    break;
	    
	case MSG_SET_NAME:
	    player_d.setName(st.nextToken());
	    System.out.println("Player's name set to[" + player_d.getName() +
			       "]");
	    break;
	    
	case MSG_SET_HUB_PORT:
	    try {
		hub_port_d = Integer.parseInt(st.nextToken());
	    }
	    catch (Exception e) {
		System.out.println("Client failed to retrive the Hub port" +
				   ", so demo needs to be restarted!");
		exitProgram();
		disconnect();
	    }
	    break;		   
	    
	case MSG_CLIENT_IP_ADDRESS:
	    System.out.println("Client already knows it's own IP address. " +
			       "So why send it from server->client?");
	    break;
	
	case MSG_CLIENT_PORT:
	    System.out.println("Client already knows it's own port address. " +
			       "So why send it from server->client?");
	    break;

	case MSG_SERVER_FAILURE:
	    System.out.println("Server had a problem! Pop up a box and " +
			       "exit demo");
 	    System.err.println("Server had a problem! Pop up a box and " +
			       "exit demo");

	    // Setup saying there is no longer a connection
	    //
	    exitProgram();

	    // Find the error message and set as ConnectionManager error msg.
	    //
	    String error = st.nextToken();
	    if (error == null)
		error = "None Given";

	    error_str_d = "Server reported failure of [" +
		error + "]. Exiting client";

	    dispose();
	    break;

	default:
	    System.out.println("Unknown message given to Connection " +
			       "Manager: handleNetworkMessage()");
	    System.out.println("Message is[" + message_a + "] and type is [" +
			       type + "]");
	    break;
	}
	// exit gracefully
	//
	return true;
    }
    
    /**
     * Adds the received network event to the event processor's
     * event list.
     *
     * @param event_a the received network event.
     *
     * @return a boolean value indicating status.
     */
    public boolean networkMessageReceived(DemoNetworkEvent event_a) {

        event_processor_d.addEvent(event_a);

	// exit gracefully
	//
	return true;
    }
    
    /**
     * Adds the received <code> NETWORK_DISCONNECTED </code> event to
     * the event processor's event list.
     *
     * @param event_a the received network event.
     *
     * @return a boolean value indicating status.
     */
    public boolean networkDisconnected(DemoNetworkEvent event_a) {

	// This is about to go ahead and disconnect this so short-cut it
	// a bit and set connected to false
	//
	connected_d = false;
        event_processor_d.addEvent(event_a);

	// exit gracefully
	//
	return true;
    }
    
    /**
     * Sets the <code> Thread </code> named <code> loop </code> equal
     * to <code> null </code>.  This will end the while loop in the
     * <code> run() </code> method.
     *
     * @return a boolean value indicating status.
     */
    public boolean exitProgram() {

        thread_d = null;
	connected_d = false;
	
	// exit gracefully
	//
	return true;
    }

    /**
     * Returns the value of whether the Connection Manager is still connected
     * to the server side.
     *
     * @return a boolean value indicating status.
     */
    public boolean isStillConnected() {
	return connected_d;
    }

    /**
     * Sends a network message using the associated <code>
     * DemoNetworkHandler </code>.
     *
     * @param server_name_a the name of the server to add to the server list
     * @param server_pid_a the PID of the server  
     *
     * @return a boolean value indicating status.
     */
    public boolean addServerList(String server_name_a, int server_pid_a) {
	System.out.println("in ConnectionManager.addServerList() with " +
			   "server[" + server_name_a + "] pid[" +
			   server_pid_a + "]");
	network_handler_d.sendMessage(MSG_SET_SERVER + "|" +
				      server_name_a + "|" + 
				      server_pid_a);
	// exit gracefully
	//
	return true;
    }

    /**
     * Disconnect the network connection.
     *
     * @return a boolean value indicating status.
     */
    public boolean disconnect() {
	return network_handler_d.disconnect();
    }
}
