import java.rmi.*;
import java.rmi.server.*;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;

/**
 * Java Chat - A simple chatroom application - Server Class.
 *
 * This class is the chat server and is responsible for
 * receiving new connections from users, registering the
 * new user in the collection hashtable and forwarding
 * all received messages to all current users, including
 * the sender of the message.
 *
 * @author Sean Handley
 * @version November, 2006
 */
public class ChatServer extends UnicastRemoteObject implements IChatServer
{
    private static final long serialVersionUID = -1979903245133596876L;
    private Hashtable<Integer,String> messages;
    private Integer key;
    private ArrayList<ICallback> clients;

    /**
     * Chat server constructor.
     *
     * @throws java.rmi.RemoteException
     */
    public ChatServer() throws RemoteException
    {
        key = 0;
        messages = new Hashtable<Integer, String>();
        clients = new ArrayList<ICallback>();
        Runtime.getRuntime().addShutdownHook(
            new Thread()
            {
                public void run()
                {
                    exitProcedure();
                }
            }
        );
    }
    /*
     * Close the server gracefully.
     */
    private void exitProcedure()
    {
        logoutAllClients();
    }
    /*
     * Log out all clients currently connected.
     */
    private synchronized void logoutAllClients()
    {
        Iterator it = clients.iterator();
        while(it.hasNext())
        {
            ICallback n = (ICallback)it.next();
            try
            {
                n.forceLogout();
                clients.remove(n);
            }
            catch(NullPointerException e)
            {
                //n might not exist any more...
                //if it doesn't, ignore - it should be removed by disconnect
            }
            catch(Exception e)
            {
                //at this point, just die...
            }
        }
        notify();
    }
    /**
     * Main entry point for the chat server. Sets up the registry and binds
     * the server name.
     * 
     * @param args
     */
    public static void main (String args[])
    {

        try
        {
            //Create a new chat server
            ChatServer cs = new ChatServer();

            //Launch the registry - saves invoking it manually
            java.rmi.registry.LocateRegistry.createRegistry(1099);
            //bind the name into the registry and we're ready to go
            Naming.rebind("CHAT-SERVER", cs);
        }
        catch (java.net.MalformedURLException e)
        {
            System.err.println("Malformed URL for Chat Server name " + e.toString());
        }
        catch (RemoteException e)
        {
            System.err.println("General Communication Error: " + e.toString());
        }
    }
    /**
     * Get the latest message.
     * 
     * @throws java.rmi.RemoteException
     * @return message
     */
    public String getMessage() throws RemoteException
    {
        return messages.get(key -1);
    }
    /**
     * Check to see if a user exists in the clients collection.
     * 
     * @param username
     * @return true if username is found in the clients list, false otherwise
     * @throws java.rmi.RemoteException
     */
    public synchronized boolean checkUser(String username) throws RemoteException
    {
        Iterator it = clients.iterator();
        Boolean found = false;
        while(it.hasNext())
        {
            if(((ICallback)it.next()).getUsername().equalsIgnoreCase(username))
            {
                found = true;
                break;
            }
        }
        notify();
        return found;
    }
    /**
     * Check a username to see if it's in the clients collection. If not,
     * add it to the collection and proceed with communication, returning a zero
     * to indicate the user was not found in the collection. Otherwise, returns
     * a one to indicate the opposite.
     * 
     * @throws java.rmi.RemoteException
     * @param cb the callback interface representing the client
     * @return true if connection was successful, false otherwise
     */
    public synchronized boolean addClient(ICallback cb) throws RemoteException
    {
        Boolean found = checkUser(cb.getUsername());
        notify();
        if(!found)
        {
            clients.add(cb);
            notify();
            return true;
        }
        else
        {
            notify();
            return false;
        }

    }
    /**
     * Removes the supplied callback interface object from the clients list,
     * disconnecting it from future communications.
     * 
     * @param cb the callback interface representing the client
     * @throws java.rmi.RemoteException
     */
    public synchronized void removeClient(ICallback cb) throws RemoteException
    {
        Iterator it = clients.iterator();
        while(it.hasNext())
        {
            if(((ICallback)it.next()).getUsername().equalsIgnoreCase(cb.getUsername()))
            {
                clients.remove(cb);
                break;
            }
        }
        notify();
    }
    /**
     * Announce the presence of a new user by sending a special message.
     * 
     * @throws java.rmi.RemoteException
     * @param username 
     */
    public synchronized void announce(String username) throws RemoteException
    {
        sendMessage("*** " + username + " has just entered the room.");
        notify();
    }
    /**
     * Announce the name change of a user by sending a special message.
     * 
     * @param oldName
     * @param newName
     * @throws java.rmi.RemoteException
     */
    public synchronized void announceNameChange(String oldName, String newName) throws RemoteException
    {
        sendMessage("*** " + oldName + " is now known as " + newName + ".");
        notify();
    }
    /**
     * Indicates that a user has left by sending a special message.
     * 
     * @param username
     * @throws java.rmi.RemoteException
     */
    public synchronized void leave(String username) throws RemoteException
    {
        sendMessage("*** " + username + " has just left the room.");
        notify();
    }
    /**
     * Retrieve a list of usernames from the clients collection.
     * 
     * @return a list of current clients
     * @throws java.rmi.RemoteException
     */
    public ArrayList<String> getUsers() throws RemoteException
    {
        Iterator it = clients.iterator();
        ArrayList<String> clientsList = new ArrayList<String>();
        while(it.hasNext())
        {
            clientsList.add(((ICallback)it.next()).getUsername());
        }
        return clientsList;
    }
    /**
     * Sends the message by adding it to the collection and then
     * invoking the callback method of each client currently
     * in the clients list.
     * 
     * @param msg
     * @throws java.rmi.RemoteException
     */
    public void sendMessage(String msg) throws RemoteException
    {
        //add the message to the collection
        addMessage(msg);
        distributeMessage();
    }
    /*
     * Add the message to the messages collection.
     */
    private synchronized void addMessage(String msg)
    {
        messages.put(key++, msg);
        notify();
    }
    /*
     * Distribute the message to each client in the clients collection.
     */
    private synchronized void distributeMessage()
    {
        Iterator it = clients.iterator();
        while(it.hasNext())
        {
            ICallback n = (ICallback)it.next();
            try
            {
                n.doCallback();
            }
            catch(NullPointerException e)
            {
                //n might not exist any more...
                //if it doesn't, ignore - it should be removed by disconnect
            }
            catch(Exception e)
            {
                System.err.println(e.toString());
            }
        }
        notify();
    }
}