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

public class ChatServer implements Runnable {
/*
  Versioning:
    1.0  -- first version, I guess
    1.01 -- first version with a version :) also sends who's on before you log in.
    1.02 -- added "last MESSAGES (10)  messages"
    1.03 -- attempted a patch to keep it from crashing on an early exit
    1.04 -- changed timestamp to something shorter
*/

  private static final String VERSION = "1.04";
  private static final int MESSAGES = 10;
  private static int port=7791;
  private static int chatcount=0;
  private static Hashtable threads;
  private static SimpleDateFormat tsformat;
  private DataOutputStream oos;
  private DataInputStream ois;
  private String myname;
  private String lastpost;
  private static Vector lastmessages=new Vector();

  public String toString() {
    return myname+" "+lastpost;
  }

  public ChatServer(DataInputStream in, DataOutputStream out) {
    oos=out; ois=in;
    new Thread(this).start();
  }

  private void postMessage(String s) {
    try {
      oos.writeUTF(s);
      oos.flush();
    } catch (Exception e) {
      System.out.println(this+": Unable to send message "+s+".");
    }
  }

  private static void broadcastMessage(String s, boolean time) {
    if (time) s=time()+s;
    System.out.println(s);
    lastmessages.addElement(s);
    if (lastmessages.size() > MESSAGES) lastmessages.removeElementAt(0);
    Enumeration e=threads.keys();
    ChatServer tempcs=null;
    while (e.hasMoreElements()) {
      try {
        tempcs=(ChatServer)threads.get((String)e.nextElement());
        tempcs.postMessage(s);
      } catch (Exception ex) {
        System.out.println("Unable to send message "+s+" to "+tempcs.myname+".");
        System.out.println(" -- "+ex);
      }
    }
  }

  public void run() {
    boolean good=true;
    String error=null;
    try {
      String s;
      oos.writeUTF("Server version: "+VERSION+"\n"+who(""));
      while (myname == null) {
        s=ois.readUTF();
        if (s.equals("/exit")) return;
        if (threads.get(s) != null) {
          oos.writeUTF("That name is in use, please try another."); oos.flush();
        } else if (s.charAt(0)==' '||s.charAt(0)=='-'||s.charAt(0)=='\t') {
          oos.writeUTF("Names may not begin with a -, or whitespace.");
        } else if (s.indexOf('=') != -1) {
          oos.writeUTF("Names may not contain an '='."); oos.flush();
        } else {
          myname=s;
          oos.writeUTF("ok"); oos.flush();
          logon(s);
        }
      }
      while (good) {
        s=ois.readUTF();
	    lastpost=time();
        if (s.equals("/who")) {
          oos.writeUTF(who(myname)); oos.flush();
        } else if (s.equals("/exit")) {
          good=false;
        } else if (s.equals("/help")) {
          oos.writeUTF(help()); oos.flush();
        } else if (s.equals("/beep")) {
          broadcastMessage("/beep",false);
          broadcastMessage(myname+" beeped.", true);
        } else if (s.equals("/time")) {
          oos.writeUTF("Current time: "+time()); oos.flush();
        } else if (s.startsWith("/w ")) {
          int boundary=s.indexOf('=');
          if (boundary < 4) {
            oos.writeUTF("you have to whisper *to* somebody!"); oos.flush();
          } else {
          String name=s.substring(3,boundary);
          String message=s.substring(boundary+1,s.length());
          ChatServer response=null;
          name=name.trim();  message=message.trim();
          Enumeration e=threads.keys();
          while (e.hasMoreElements()) {
            ChatServer tempcs=(ChatServer)threads.get((String)e.nextElement());
            if (tempcs.myname.startsWith(name) 
                && (response == null 
                    || tempcs.myname.length() < response.myname.length())) 
              response=tempcs;
          }
          if (response == null) {
            oos.writeUTF("Comrade not found: "+name); oos.flush();
          } else {
            response.postMessage(myname+" whispers: "+message);
            oos.writeUTF("Message sent to: "+response.myname);
          }
          }
        } else if (s.charAt(0) == '/') {
          oos.writeUTF("Unknown command: "+s); oos.flush();
        } else broadcastMessage(s,true);
      }
    } catch (EOFException eofe) {
      good=false;
      error = "someone knocked on the door but didn't come in: "+eofe.toString();
    } catch (Exception ex) {
      System.out.println(myname+" error in run: "+ex);
      good=false;
      error = "network error: "+ex.toString();
      if (myname != null) error = myname+" had a "+error;
    }
    if (myname != null) {
      logout(myname);
    }
    if (error != null) {
      broadcastMessage("-- "+error,true);
    }
  }

  private void logout(String name) {
    threads.remove(name);
    broadcastMessage("-- "+name+" has logged off.",true);
    chatcount--;
  }

  private void logon(String name) {
    threads.put(name,this);
    broadcastMessage("-- "+name+" has logged on.",true);
    lastpost=time();
    chatcount++;
  }

  private static String lastMessages() {
    StringBuffer messages=new StringBuffer();
    for (int i=0;i<lastmessages.size();i++) {
      messages.append((String)lastmessages.elementAt(i)).append('\n');
    }
    return messages.toString();
  }

  private static String time() {
    return tsformat.format(new Date());
  }

  private static String help() {
    return "--commands:\n  /help: this list\n  /who: list of logins and idles\n  /time: current PST time\n  /beep: beeps everyone\n  /w [name]=[message]: whispers message to name\n  /clear: clears the viewport\n  /exit: the graceful way to leave\n";
  }

  private static String who(String me) {
    Enumeration e=threads.keys();
    StringBuffer who=new StringBuffer();
    String tempname;
    if (me.equals("")) {
      if (chatcount < 1) {
        who.append("There is currently nobody in the chatroom.\nDo come in.\n");
      } else {
        who.append("currently in the chatroom: [last post]\n");
        while (e.hasMoreElements()) {
          tempname=(String)e.nextElement();
          if (!tempname.equals(me))
            who.append("   ").append(threads.get(tempname).toString()).append("\n");
        }
        who.append("===\n");
      }
      who.append("\nThe last "+MESSAGES+" messages:\n");
      who.append("------------------------------\n");
      who.append(lastMessages());
      who.append("------------------------------\n");
    } else {
      who.append("--currently in: [last post]\n");
      if (chatcount <2) {
        who.append("  Nobody Home.\n");
      } else {
        while (e.hasMoreElements()) {
          tempname=(String)e.nextElement();
          if (!tempname.equals(me))
            who.append("   ").append(threads.get(tempname).toString()).append("\n");
        }
        who.append("===\n");
      }
    }
    return who.toString();
  }

  public static void main(String[] args) {
    Socket s=null;
    ServerSocket ss=null;
    if (args.length > 1) {
      System.out.println("Proper usage: java ChatServer [port]");
      System.exit(-1);
    }
    if (args.length == 1) try {
      port=Integer.parseInt(args[0]);
    } catch (Exception e) {
      System.out.println("Proper usage: java ChatServer [port]");
      System.exit(-1);
    }
    try {
      System.out.println("Attempting to open port "+port);
      ss=new ServerSocket(port);
      System.out.println("Chat server opened on port "+port);
    } catch (Exception e) {
      System.out.println("Unable to open port: "+port);
      System.exit(-1);
    }
    threads=new Hashtable();
    tsformat=new SimpleDateFormat("[MM/dd HH:mm zz] ");
    String tempname;
    DataInputStream tempois;
    DataOutputStream tempoos;
    ChatServer cs;
    while (true) {
      try {
        s=ss.accept();
        s.setKeepAlive(true);
        tempois=new DataInputStream(s.getInputStream());
        tempoos=new DataOutputStream(s.getOutputStream());
        cs=new ChatServer(tempois,tempoos);
      } catch (Exception e) {
        System.out.println(" Unexpected error: "+e);
      }
    }
  }
}

