import java.awt.*;
import java.io.*;
import java.net.*;
import java.util.*;

/**
 * ClientSocket maintains all communications with the server.  When events
 * are generated through the client's interface, the ClientSocket forms the
 * appropriate messages and sends them off to the server.  The ClientSocket
 * has a ClientReader thread which reads and dispatches incoming messages
 * from the server.
 *
 * @author	Thor Denmark
 * @version	$Revision: 1.10 $	$Date: 1998/05/04 23:02:09 $
 *
 * @exception	IOException		If an IO error occurred
 * @exception	DupUserException	If a user tries to log in twice
 *
 * @see		Socket
 * @see		Thread
 * @see		PrintWriter
 */
public class ClientSocket extends Socket {
  whiteBoard d_whiteBoard;	// pointer back to the ui
  String d_username;		// username associated with this client
  Thread d_reader;		// reader thread for this ClientSocket

  /**
   * Creates the client socket by creating a connection to the server, logging
   * into the server, establishing the username on the server, and creating a
   * reader thread to read incoming messages from the server.
   *
   * @param	wb	Pointer back to the user interface
   * @param	user	Username to log in as
   * @param	host	Hostname of server to log in to
   * @param	port	Port on the server to connect to
   *
   * @exception	IOException		If an IO error occurred
   * @exception	DupUserException	If a user tries to log in twice
   *
   * @see	whiteBoard
   * @see	ClientReader
   * @see	PrintWriter
   * @see	BufferedReader
   * @see	String
   * @see	StringTokenizer
   * @see	Thread
   */
  public ClientSocket(whiteBoard wb, String user, String host, int port)
    throws IOException, DupUserException {
    super(host, port);

    d_whiteBoard = wb;
    d_username = new String(user); // remember our username

    send("server", "login", d_username);

    // Create reader thread
    d_reader = new ClientReader(this);

    // Start the reader thread and give control over to that thread
    d_reader.start();
  }

  /**
   * Converts an array of strings into a comma separated list of strings.
   *
   * @param	array	The array of strings to convert
   * @return	The comma separated string representing the list
   *
   * @see	StringBuffer
   */
  public static String arrayToString(String [] array) {
    StringBuffer buf = new StringBuffer("");
    for(int i=0; i < array.length; ++i) {
      buf.append(array[i]);
      if(i != array.length-1)
	buf.append(',');	
    }
    return(buf.toString());
  }

  /**
   * Converts a comma separated string into an array of strings.
   *
   * @param	str	The comma delimited string to convert
   * @return	The array of strings
   *
   * @see	String
   * @see	StringTokenizer
   */
  public static String [] stringToArray(String str) {
    StringTokenizer t = new StringTokenizer(str, ",");
    String [] array = new String[t.countTokens()];

    for(int i=0; t.hasMoreTokens(); ++i) {
      array[i] = t.nextToken();
    }
    return(array);
  }

  /**
   * Generates a message and sends it to the server given one or more
   * recipients, a command, and zero or more arguments.
   *
   * @param	recipients	A recipient
   * @param	command		The command to send to the server
   * @param	args		An argument
   */
  public void send(String recipient, String command, String arg) {
    doSend(d_username + ":" + recipient + ":" + command + ":" + arg);
  }

  /**
   * Generates a message and sends it to the server given one or more
   * recipients, a command, and zero or more arguments.
   *
   * @param	recipients	A recipient
   * @param	command		The command to send to the server
   * @param	args		A string array list of arguments
   */
  public void send(String recipient, String command, String [] args) {
    doSend(d_username + ":" + recipient + ":" + command + ":" +
	   arrayToString(args));
  }

  /**
   * Generates a message and sends it to the server given one or more
   * recipients, a command, and zero or more arguments.
   *
   * @param	recipients	A string array list of recipients
   * @param	command		The command to send to the server
   * @param	args		An argument
   */
  public void send(String [] recipients, String command, String arg) {
    doSend(d_username + ":" + arrayToString(recipients) + ":" +
	   command + ":" + arg);
  }

  /**
   * Generates a message and sends it to the server given one or more
   * recipients, a command, and zero or more arguments.
   *
   * @param	recipients	A string array list of recipients
   * @param	command		The command to send to the server
   * @param	args		A string array list of arguments
   */
  public void send(String [] recipients, String command, String [] args) {
    doSend(d_username + ":" + arrayToString(recipients) + ":" +
	   command + ":" + arrayToString(args));
  }


  /**
   * Sends a message formed from the send() function to the server.
   *
   * @param	msg		The string to send to the server
   *
   * @see	PrintWriter
   * @see	IOException
   */
  public void doSend(String msg) {

    // THOR -- debugging
    //System.out.println("^^^^^^^^ outgoing message ^^^^^^^^");
    //System.out.println(msg);
    //System.out.println("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");

    try {
      PrintWriter o = new PrintWriter(getOutputStream());
      o.println(msg);
      o.flush();		// flush the output stream
    }
    catch(IOException e) {
      System.err.println("Problem communicating with server: " + e);
    }
  }


  /**
   * Dispatches incoming messages from the server appropriately.  When the
   * server sends a command, this client function first checks to see if the
   * message is addressed to this client.  Assuming that the message is
   * either explicitly addressed to this client, or it is addressed to "ALL",
   * this function will make the appropriate function calls in the user
   * interface based on the incoming message.
   *
   * @param	msg	The incoming message from the server to be parsed
   *
   * @see	String
   * @see	StringTokenizer
   */
  public void dispatch(String msg) {
    StringTokenizer st = new StringTokenizer(msg, ":");
    String from = st.nextToken();
    String to   = st.nextToken();
    String cmd  = st.nextToken();
    String args = new String("");
    if(st.hasMoreTokens())	// some commands do not have arguments
      args = st.nextToken();

    if(cmd.equalsIgnoreCase("DUPHANDLE")) {
      d_whiteBoard.cp.dupUser(args);
    }

    // check to see if the message is addressed to me
    StringTokenizer tot = new StringTokenizer(to, ",");
    while(tot.hasMoreTokens()) {
      String who = tot.nextToken();

      if(who.equalsIgnoreCase(d_username) || who.equalsIgnoreCase("ALL") ||
	 (from.equalsIgnoreCase(d_username) && cmd.equalsIgnoreCase("chat"))) {

	if(cmd.equalsIgnoreCase("chat")) {
	  d_whiteBoard.cp.dispChat("From " + from + " to " + to + " --> " +
				   args);
	}
	else if(cmd.equalsIgnoreCase("userlist")) {
	  d_whiteBoard.cp.setUsers(stringToArray(args));
	}
	else if(cmd.equalsIgnoreCase("line")) {
	  StringTokenizer a = new StringTokenizer(args, ",");
	  
	  d_whiteBoard.dp.drawLine(Integer.parseInt(a.nextToken()),
				   Integer.parseInt(a.nextToken()),
				   Integer.parseInt(a.nextToken()),
				   Integer.parseInt(a.nextToken()),
				   Integer.parseInt(a.nextToken()),
				   new Color(Integer.parseInt(a.nextToken()),
					     Integer.parseInt(a.nextToken()),
					     Integer.parseInt(a.nextToken())));
	}
	else if(cmd.equalsIgnoreCase("rectangle")) {
	  StringTokenizer a = new StringTokenizer(args, ",");
	  
	  d_whiteBoard.dp.drawRect(Integer.parseInt(a.nextToken()),
				   Integer.parseInt(a.nextToken()),
				   Integer.parseInt(a.nextToken()),
				   Integer.parseInt(a.nextToken()),
				   Integer.parseInt(a.nextToken()),
				   new Color(Integer.parseInt(a.nextToken()),
					     Integer.parseInt(a.nextToken()),
					     Integer.parseInt(a.nextToken())));
	}
	else if(cmd.equalsIgnoreCase("circle")) {
	  StringTokenizer a = new StringTokenizer(args, ",");
	  
	  d_whiteBoard.dp.drawCircle(Integer.parseInt(a.nextToken()),
				     Integer.parseInt(a.nextToken()),
				     Integer.parseInt(a.nextToken()),
				     Integer.parseInt(a.nextToken()),
				     Integer.parseInt(a.nextToken()),
				     new Color(Integer.parseInt(a.nextToken()),
					       Integer.parseInt(a.nextToken()),
					       Integer.parseInt(a.nextToken()))
				     );
	}
	else if(cmd.equalsIgnoreCase("raisehand")) {
	  if(args.equalsIgnoreCase("true")) {
	    d_whiteBoard.cp.dispChat("(user " + from +
				     " wishes to speak)");
	  }
	  else {
	    d_whiteBoard.cp.dispChat("(user " + from +
				     " no longer wishes to speak)");
	  }
	}
	else if(cmd.equalsIgnoreCase("clearchatbox")) {
	  d_whiteBoard.cp.eraseChatBox();
	}
	else if(cmd.equalsIgnoreCase("clear")) {
	  d_whiteBoard.dp.erase();
	}
	else if(cmd.equalsIgnoreCase("muteclient")) {
	  if(args.equalsIgnoreCase("false")) {
	    d_whiteBoard.cp.dispChat("(you have been called on by " + from +
				     ")");

	  }
	  System.out.println("\t\tMUTECLIENT RECEIVED WITH ARGS --> " + args);
	}
	else {
	  System.err.println("Unrecognized command from server: '" + cmd +
			     "' -- ignoring.");
	}

	return;
      }
    }
  }
}
