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

import javax.swing.*;
import javax.swing.border.*;
import java.awt.event.*;
import java.awt.*;

public class ChatServer extends JApplet implements ActionListener, Runnable
{
	ServerSocket serverSocket = null;
	Thread runner = null;
	ArrayList users = new ArrayList(); //list of users sorted after ID in alphabetic order.
	boolean connected = false;

	public void init()
	{
	  setUpWindow();
	}

	//
	static int width =300;
	static int height = 200;
	Container pane = null;
	JLabel t = new JLabel("Port", JLabel.LEFT);
	JTextField port = new JTextField("5000", 5);
	JButton connect = new JButton("Connect");
	JTextArea messages = new JTextArea();
	JButton userList = new JButton("users");

	void setUpWindow()
	{
	  setSize(width, height);
	  pane = getContentPane();
	  pane.setLayout(new BorderLayout());
	  JPanel junk = new JPanel();
	  junk.add(t);
	  junk.add(port);
	  junk.add(connect);
	  connect.addActionListener(this);
	  junk.add(userList, BorderLayout.EAST);
	  userList.setBackground(Color.blue);
	  pane.add(junk, BorderLayout.SOUTH);
	  userList.addActionListener(this);
	  pane.add(new JScrollPane(messages), BorderLayout.CENTER);
	  messages.append("*Welcome to ChatServer made by Mads Lenskjær 1-4-2001 (using java 1.3)\n");
	}
	//

	public void start() {}
	public void stop()
	{
	  cleanUp();
	}
	public void actionPerformed(ActionEvent evt)
	{
	  Object s = evt.getSource();
	  if(s instanceof JButton)
	  {
	    JButton b = (JButton)s;
	    if(b == connect)
	    {
	      connected = !connected;
	      if(connected)
	      {
	        try
	        {
	          int myPort = Integer.parseInt(port.getText());
	          if(myPort > 1024)
	          {
	            serverSocket = new ServerSocket(myPort);
	            runner = new Thread(this);
	            runner.start();
	            connect.setText("Disconnect");
	            messages.append("*Server up and running!\n");
	            InetAddress ia = serverSocket.getInetAddress();
	            messages.append("*Your hostName: " + ia.getHostName() + " IP:" + ia.getHostAddress() +
	                            " Port:" + serverSocket.getLocalPort() +"\n");
	          }
	          else
	          {
	            connected = false;
	            messages.append("*The port number must be above 1024!\n");
	          }
   	        }catch(Exception e)
   	        { connected = false;
   	          connect.setText("Connect");
   	          messages.append("*Unable to open socket (try an other port number)\n");
   	          return;
   	        }
	      }
	      else
	        cleanUp();
		}
		else if(b == userList && connected)
	      printUserList();
	  }
	}
	void cleanUp()
	{
	  terminateAll();
	  messages.append("*Stopping server listener thread!\n");
	  if(runner != null)
	    runner.interrupt();
	  runner = null;

	  messages.append("*Closing server socket\n");
	  if(serverSocket != null)
	    try{ serverSocket.close(); }catch(IOException e) {}
	  serverSocket = null;
	  connect.setText("Connect");
	  connected = false;
	  messages.append("*Server has been fully disconnected\n");

	}
	synchronized void terminateAll()
	{
	  int n = users.size();
	  messages.append("*Disconnecting "+ n + " clients\n");

	  for(int i = 0; i < n; i++)
	  { ((ClientServer)users.get(0)).closeConnection();
	    users.remove(0);
	  }
	}
	synchronized boolean removeClient(ClientServer cs)
	{
	  int index = Collections.binarySearch(users, cs);
	  if(index >= 0)
	  { ClientServer css = (ClientServer)users.get(index);
	    users.remove(index);
	    messages.append("*Client named "+css.ID+" has disconnected.\n");
	    return true;
	  }
	  return false;
	}
	synchronized boolean addClient(ClientServer cs)
	{
      int index = Collections.binarySearch(users, cs);
	  if(index < 0)
	  {
	    users.add(-index-1, cs);
	    return true;
	  }
	  else //in the case the user name allready exicist => make it unique
	  {
	    cs.ID += ("" + users.size());
	    System.out.println(cs.ID);
	    index = Collections.binarySearch(users, cs);
	    System.out.println(index);
	    users.add(-index-1,cs);
	    return false;
	  }
	}
	synchronized boolean renameUser(ClientServer cs, String newName)
	{
	  int index = Collections.binarySearch(users, cs);
	  if(index >= 0)
	  {
	    users.remove(index);
	    cs.ID = newName;
	    index = Collections.binarySearch(users, cs);
	    if(index < 0)
	    { users.add(cs);
	      return true;
	    }
	    else
	    {
	      cs.ID += ("" + users.size());
	      System.out.println(cs.ID);
	      index = Collections.binarySearch(users, cs);
	      System.out.println(index);
	      users.add(-index-1,cs);
	      return false;
	    }
	  }
	  return false;
	}
	synchronized void printUserList()
	{
	  int n = users.size();
	  messages.append("Corrently " + n + " clients is logged on =>\n");
	  for(int i = 0; i < n; i++)
	    messages.append((i+1) + ". " + ((ClientServer)users.get(i)).ID);
	}
	synchronized String getUserList()
	{
	  int n = users.size();
	  String names = "";
	  for(int i = 0; i < n; i++)
	    names += ((ClientServer)users.get(i)).ID + " ";
	  return names;
	}
	synchronized void postMessage(String msg)
	{
	  int n = users.size();
	  for(int i = 0; i < n; i++)
	  {
	    ClientServer cs = (ClientServer)users.get(i);
	    cs.addMessage(msg);
	  }
	}
	synchronized boolean personalMessage(String reciever, String msg)
	{
	  ClientServer cs = new ClientServer(reciever);
	  int index = Collections.binarySearch(users, cs);
	  if(index >= 0)
	  {
	    ClientServer recieverCS = (ClientServer)users.get(index);
	    recieverCS.addMessage(msg);
	    return true;
	  }
	  return false;
	}

	//Server listener thread. => accepting new clients; giving them an sender+reciever thread.
	public void run()
	{
	  while(!runner.interrupted())
	  {
	    try
	    {
	      Socket clientSocket = serverSocket.accept();
	      ClientServer cs = new ClientServer(clientSocket, this);

	      //Next the client has to send his name.!
	      BufferedReader br = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
	      String name = br.readLine();
	      cs.ID = name;
	      if(!addClient(cs))
	        cs.addMessage("/ChangeName " + cs.ID); //If the name allready exicist, we change it.

	      messages.append("*New client called "+cs.ID+": "+clientSocket.getInetAddress().toString()+
	                      " "+clientSocket.getPort()+"\n");
	      postMessage("/NewUser " + cs.ID);

	    }catch(IOException e) {}
	  }
	}
}
class ClientServer implements Comparable
{
  //Sender thread.
  class Sender extends Thread
  {
    Sender() { this.start(); }
    void stopSender() { this.interrupt(); }
    public void run()
    {
	  try
	  {
	    String[] msg = null;
	    while(!this.interrupted())
	    {
	      while((msg = getMessages()) == null)
	        ;
	      //print all messages for the client.
	      for(int i = 0; i < msg.length; i++)
	        pw.println(msg[i]);
	    }
	  }catch(Exception e) {}
    }
  }

  //Reciever thread.
  class Reciever extends Thread
  {
    Reciever() { this.start(); }
    void stopReciever() { this.interrupt(); }
    public void run()
    {
      try
      {
        String l = "";
        while(!this.interrupted() && l != null)
        { if((l = br.readLine()) != null)
		  {
	        l = l.trim();
	        if(l.charAt(0) == '/')
	        {
	          StringTokenizer token = new StringTokenizer(l, " ");
	          String command = token.nextToken();

	          //recieving new chat message.
	          if(command.equals("/cast"))
	          {
	            String to = token.nextToken();
	            l = token.nextToken("\0");
	            l = l.trim();
	            //cast message to all in the chat-room
	            if(to.equals("all"))
	              cs.postMessage("/cast " + l);
	            //cast message to the specified person.
	            else
	            {
	              if(cs.personalMessage(to, "/personal " + l))
	                addMessage("/personal "+l);
	              else
	                addMessage("/personal The user ("+to +") you wrote to; is no longer with us!");
	            }
	          }
	          //Change the user name.
	          else if(command.equals("/name"))
	          {
	            String name = token.nextToken();
	            if(!rename(name))
	              cs.personalMessage(ID, "/ChangeName " + ID);
	          }
	          //send the client a userlist.
	          else if(command.equals("/UserList"))
	          {
	            String userList = cs.getUserList();
	            addMessage("/UserList " + userList);
	          }
	        }
	      }
	    }
	  }catch(IOException e) {}

	  finally
	  {
	    s.stopSender();
	    cleanUp();
	    //tell all the other that the client has quit.
	    cs.postMessage("/QuitUser " + ID);
	  }
    }
  }

  Socket clientSocket = null;
  BufferedReader br = null;
  PrintWriter pw = null;
  String ID = "";
  ArrayList messages = new ArrayList();
  ChatServer cs = null;
  Sender s = null;
  Reciever  r = null;

  ClientServer(Socket clientSocket, ChatServer cs)
  {
    this.clientSocket = clientSocket;
    this.cs = cs;
    try
    {
      br = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
      pw = new PrintWriter(clientSocket.getOutputStream(), true);
      ID = clientSocket.getInetAddress().toString() + clientSocket.getPort();
      s = new Sender();
      r = new Reciever();
    }catch(IOException e) {}
  }
  ClientServer(String name)
  {
    this.ID = name;
  }
  synchronized String[] getMessages()
  {
    if(messages.size() == 0)
    {
      try{ this.wait(); }catch(InterruptedException e) {}
    }
    if(messages.size() > 0)
    {
      String[] msg = new String[messages.size()];
      int n = messages.size();
      for(int i = 0; i < n; i++)
      { msg[i] = (String)messages.get(0);
        messages.remove(0);
      }
      return msg;
    }
    return null;
  }
  synchronized void addMessage(String msg)
  {
    messages.add(messages.size(), msg);
    this.notifyAll();
  }
  public int compareTo(java.lang.Object obj)
  {
    ClientServer cs = (ClientServer)obj;
    return ID.compareTo(cs.ID);
  }
  boolean cleanUp()
  {
    return cs.removeClient(this);
  }
  boolean addUp()
  {
    return cs.addClient(this);
  }
  boolean rename(String name)
  {
    return cs.renameUser(this, name);
  }
  public void closeConnection()
  {
    s.stopSender();
    r.stopReciever();
    try{ clientSocket.close(); }catch(IOException e) {}
  }
}