// Steve Anichini
// JavaChatServer.java
// Server for JavaChat
import java.net.*;
import java.io.*;
import java.util.*;

// Class to manage information about a connection
class ConnectionInfo 
{
	public Socket socket;
	public String name;
	public PrintStream printStream;
	public int msgSent;
	public int pmsgSent;

	ConnectionInfo(Socket s, String n, PrintStream ps)
	{
		socket = s;
		name = n;
		printStream = ps;
		msgSent = 0;
		pmsgSent = 0;
	}
}

// Stack which is thread-safe

class SynchroStack 
{
	private Stack s=null;

	SynchroStack()
	{
		s = new Stack();
	}

	public synchronized ConnectionInfo pop()
	{
		return (ConnectionInfo) s.pop();
	}

	public synchronized void push(ConnectionInfo o)
	{
		s.push(o);
	}
}

// Class to keep track of all connections
// and allow broadcasts to them

class Broadcast
{
	private Vector connections;

	public Broadcast()
	{
		connections = new Vector();
	}

	synchronized public void sendUsers(ConnectionInfo user)
	{
		for(Enumeration e = connections.elements(); e.hasMoreElements();)
		{
			ConnectionInfo ci = (ConnectionInfo)e.nextElement();

			if(!ci.name.equals(user.name))
			{
				String buf = "AUSR ";

				buf += ci.name;

				user.printStream.println(buf);
			}
		}
	}

	synchronized public void sendTo(String str, String recipient)
	{
		for(Enumeration e = connections.elements(); e.hasMoreElements();)
		{
			ConnectionInfo ci = (ConnectionInfo)e.nextElement();
			
			if(ci.name.equals(recipient))
			{
				ci.printStream.println(str);
				break;
			}
		}
	}
		
	synchronized public void broadcast(String str)
	{
		for (Enumeration e = connections.elements(); e.hasMoreElements();)
		{
			((ConnectionInfo)e.nextElement()).printStream.println(str);
		}
	}

	synchronized public boolean addConnection(ConnectionInfo ci)
	{
		for (Enumeration e = connections.elements(); e.hasMoreElements();)
		{
			if(((ConnectionInfo)e.nextElement()).name.compareTo(ci.name) == 0)
			{
				return false;
			}
		}

		connections.addElement(ci);

		return true;
	}

	synchronized public void removeConnection(ConnectionInfo ci)
	{
		connections.removeElement(ci);
	}

	synchronized public int numberConnections()
	{
		return connections.size();
	}
}

// Thread which handles one client

class ServerThread extends Thread
{
	protected final static int ST_BEGIN = 0;
	protected final static int ST_EXIT = 1;
	protected final static int ST_GETNAME = 2;
	protected final static int ST_GETMSG = 3;
	protected final static int ST_REMCON = 4;
	protected final static int ST_FINISH = 5;

	public void run()
	{
		try
		{
			ConnectionInfo ci = JavaChatServer.s_connections.pop();
			Socket s = ci.socket;
			int state = ST_BEGIN;
			String str = null;

			PrintStream printOut = ci.printStream;
			DataOutputStream dataOut = new DataOutputStream(s.getOutputStream());
			DataInputStream dataIn = new DataInputStream(s.getInputStream());
								
			while(state != ST_FINISH)
			{
				switch (state)
				{
					case ST_BEGIN:
						printOut.println("TEXT Welcome to JavaChat 1.0...");
						printOut.print("TEXT There are ");
						printOut.print(JavaChatServer.broadcast.numberConnections());
						printOut.println(" users.");
						printOut.println("");
						printOut.println("TEXT Please enter a name to use in JavaChat...");
						printOut.println("LABL Enter your name:");
						state = ST_GETNAME;
						break;

					case ST_GETNAME:
						str = dataIn.readLine();
						if(str.compareTo("quit")== 0)
						{
							state = ST_EXIT;
						}
						else
						{
							if(str.startsWith("TEXT"))
							{
								ci.name = str.substring(5);
								
								if(ci.name.indexOf("\"") != -1)
								{
									printOut.println("TEXT Names cannot contain a quote (\") character. Enter a different name...");
								} else if(JavaChatServer.broadcast.addConnection(ci))
								{
									Date d = new Date();
									
									String logMsg = d+" "+(s.getInetAddress().getHostName())+" is "+ci.name+"\r";

									System.out.println(logMsg);

									if(JavaChatServer.logStream != null)
									{
										JavaChatServer.logStream.println(logMsg);
									}
									printOut.print("TEXT Have fun, ");
									printOut.print(ci.name);
									printOut.println(".");

									printOut.println("LABL Enter a message:");

									String buf = new String("AUSR ");
									buf += ci.name;

									JavaChatServer.broadcast.broadcast(buf);

									JavaChatServer.broadcast.sendUsers(ci);

									state = ST_GETMSG;
								}
								else
								{
									printOut.println("TEXT That is not a unique name. Enter a different name...");
								}
							}
						}
						break;

					case ST_GETMSG:
						str = dataIn.readLine();
						if(str.compareTo("quit")==0)
						{
							state = ST_REMCON;
						}
						else
						{
//							System.out.println(str);

							if(str.startsWith("TEXT"))
							{
								String buf= new String("TEXT ");
								String m = str.substring(5);

								buf += ci.name;
								buf += " > ";
								buf += m;
					
								ci.msgSent++;

								JavaChatServer.broadcast.broadcast(buf);
							} else if(str.startsWith("PMSG"))
							{
								int fIndex, lIndex;
								
								fIndex = str.indexOf("\"");
								lIndex = str.indexOf("\"", fIndex+1);
								
								String recp = str.substring(fIndex+1, lIndex);
								String m = str.substring(lIndex+1);
							
								String buf = "TEXT *Private Message* to " +
									recp + " > " + m;
								
								JavaChatServer.broadcast.sendTo(buf, ci.name);
								
								buf = "TEXT *Private Message* from " +
									ci.name + " > " + m;
								

								JavaChatServer.broadcast.sendTo(buf, recp);

								ci.pmsgSent++;

							}
						}
						break;

					case ST_REMCON:
						JavaChatServer.broadcast.removeConnection(ci);
						String buf = new String("DUSR ");

						buf += ci.name;
						JavaChatServer.broadcast.broadcast(buf);

						state = ST_EXIT;
						break;

					case ST_EXIT:
						Date d = new Date();
						String logMsg = 
								d + " Disconnecting " + ci.name + " (" + (s.getInetAddress().getHostName()) 
								+ "), who sent " + (ci.msgSent+ci.pmsgSent) + " messages ( " + ci.pmsgSent + " private)\r";

						System.out.println(logMsg);
						if(JavaChatServer.logStream != null)
						{
							
							JavaChatServer.logStream.println(logMsg);
						}
						s.close();
						state = ST_FINISH;
						break;
				}
			}
		} catch (IOException e)
		{
			e.printStackTrace();
			System.err.println(e.getMessage());
		}
	}
}


// Server which listens for connections
public class JavaChatServer
{
	static public SynchroStack s_connections = null;
	static public Broadcast broadcast = null;
	static public PrintStream logStream = null;

	static public void main(String argv[])
	{
		try
		{
			try
			{
				FileOutputStream logFileStream = new FileOutputStream("javachat.log");

				logStream = new PrintStream(logFileStream);

			} catch (IOException e)
			{
				e.printStackTrace();
				System.err.println(e.getMessage());
				logStream = null;
			}

			if(logStream != null)
			{
				Date d = new Date();

				logStream.println("JavaChat 1.0 Log file\r");
				logStream.println(d + " Server started\r");
			}

			s_connections = new SynchroStack();
			broadcast = new Broadcast();

			ServerSocket sock = new ServerSocket(Integer.parseInt(argv[0]), 8);
			while (true)
			{
				// Grab an incoming connection
				Socket s = sock.accept();

				// Print out network address
				Date d = new Date();
				String logMsg = d + " Connecting "+(s.getInetAddress().getHostName())+"\r";

				System.out.println(logMsg);
				if(logStream != null)
				{
					logStream.println(logMsg);
				}

				ConnectionInfo ci = new ConnectionInfo(s, s.getInetAddress().getHostName(),
														new PrintStream(s.getOutputStream()));

				// Create Thread
				ServerThread st = new ServerThread();

				// push socket onto thread
				s_connections.push(ci);

				// run thread
				st.start();
			}
		} catch (IOException e)
		{
			e.printStackTrace();
			System.err.println(e.getMessage());
		}
	}
}

