package server;

import api.*;

import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;
import java.util.*;

/**
 *
 * @author Peter Cappello
 */
public final class ServerImpl extends UnicastRemoteObject implements Server
{
    private Map<Client, Client> clientProxies = Collections.synchronizedMap( new HashMap<Client,Client>() );

    /**
     * Chat Server
     * @throws RemoteException when unable to open sockets to listen for
     * remotely invoked methods
     */
    ServerImpl() throws RemoteException {}

    /**
     * Used to instantiate a chat Server
     * @param args unused
     * @throws Exception if, for any reason, construction or registration fail
     */
    public static void main(String args[]) throws Exception
    {
        System.setSecurityManager( new RMISecurityManager() );
        ServerImpl server = new ServerImpl();
        Registry registry = LocateRegistry.createRegistry( Server.PORT );
        registry.bind( Server.SERVICE_NAME, server);
        System.out.println("Server ready.");
    }

    public void login( Client client, String name )
    {
        assert client != null;
        assert name != null;
        
        Client clientProxy = new ClientProxy( client, name, this );
        clientProxies.put( client, clientProxy );
        Message message = new Message( name, "Signed on.");
        update( message );

        assert clientProxies.get( client ) == clientProxy;
    }

    public void logout( Client client )
    {
        assert client != null;

        Client clientProxy = clientProxies.remove( client );
        if ( null != clientProxy )
        {
            String clientName = null;
            try
            {
                clientName = clientProxy.getClientName();
            }
            catch ( RemoteException ignore ) {}
            Message message = new Message( clientName, "Signed off.");
            update( message );
        }

        assert clientProxies.get( client ) == null;
    }

    public List<String> list()
    {
        List<String> clientNameList = new LinkedList<String>();

        Collection<Client> clientProxyCollection = clientProxies.values();
        for ( Client clientProxy : clientProxyCollection )
        {
            String clientName = null;
            try
            {
                clientName = clientProxy.getClientName();
            }
            catch ( RemoteException ignore ) {}
            clientNameList.add( clientName );
        }
        return clientNameList;
    }

    synchronized public void update( Message message )
    {
        assert message != null;

        Collection<Client> clientProxyCollection = clientProxies.values();
        for ( Client clientProxy : clientProxyCollection )
        {
            try
            {
                clientProxy.update( message );
            }
            catch ( RemoteException ignore ) {} // handled by clientProxy
        }
    }

    //________________________
    // Only for unit testing
    //________________________ß
    Map<Client, Client> getClientProxies() { return clientProxies; }


    /**
     * The Thread that invokes Remote methods on the Client - one per Client
     */
    private class ClientProxy extends Thread implements Client
    {
        private Client client;
        private String clientName;
        private Server server;
        private Q<Message> q = new Q<Message>();

        /**
         *
         * @param client
         * @param clientName
         * @param server
         */
        public ClientProxy( Client client, String clientName, Server server )
        {
            assert client != null;
            assert clientName != null;
            assert server != null;

            this.client = client;
            this.clientName = clientName;
            this.server = server;
            start();
        }

        public void update( Message message )
        {
            assert message != null;
            q.add( message );
        }

        /**
         * When its queue of messages is nonempty, it updates
         * its corresponding Remote client with the message.
         */
        @Override
        public void run()
        {
            while ( true )
            {
                try
                {
                    client.update( q.remove() );
                }
                catch (RemoteException exception )
                {
                    try
                    {
                        server.logout( this );
                        return; // let this thread die
                    }
                    catch ( RemoteException ignore ) {} // local method
                }
            }
        }

        public String getClientName() { return clientName; }
    }
}