
// Author: Josh Holtrop
// Date: 2008-09-17
// CS656
// Lab Excersize 2

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.*;
import java.io.*;
import java.net.*;

/**
 * This class implements a basic, command-line chat client.
 * It connects to a presence service. The location of the presence
 * service is determined based on command-line parameters that
 * are passed to the ChatClient class when it is started
 */
public class ChatClient
{
    private PresenceService myPServ;
    private String myUserName;
    private Thread myListenThread;
    private ChatClientListener myListener;
    private RegistrationInfo myRegistrationInfo;

    /**
     * This class provides a thread to listen for incoming connections.
     */
    private class ChatClientListener implements Runnable
    {
        ServerSocket mySocket;

        public ChatClientListener()
        {
            try
            {
                /* Just listen on any available TCP port */
                mySocket = new ServerSocket(0);
            }
            catch (Exception e)
            {
                System.err.println("Error creating listener thread!");
                e.printStackTrace();
                System.exit(-1);
            }
        }

        public int getPort()
        {
            return mySocket.getLocalPort();
        }

        public String getHost()
        {
            return mySocket.getInetAddress().getHostName();
        }

        /* called when the thread is first scheduled */
        public void run()
        {
            Socket s;
            /* wait indefinitely for new connections */
            for (;;)
            {
                try
                {
                    s = mySocket.accept();
                    Thread cclt = new Thread(new ChatClientListenerThread(s));
                    cclt.start();
                }
                catch (Exception e)
                {
                }
            }
        }

        /**
         * This class provides a listener for any connections that come in.
         * All it does is print any received chat messages from the remote
         * user to this user's screen.
         */
        public class ChatClientListenerThread implements Runnable
        {
            Socket mySocket;

            public ChatClientListenerThread(Socket s)
            {
                mySocket = s;
            }

            public void run()
            {
                try
                {
                    InputStream is = mySocket.getInputStream();
                    BufferedReader br = new BufferedReader(new InputStreamReader(is));
                    while (!mySocket.isClosed())
                    {
                        /* read and print a message from a peer */
                        String in = br.readLine();
                        if (in == null)
                            break;
                        System.out.println(in);
                        System.out.print("> ");
                    }
                }
                catch (Exception e)
                {
                    return;
                }
            }
        }
    }

    /* construct the main ChatClient object */
    public ChatClient(PresenceService pserv, String userName)
    {
        myPServ = pserv;
        myUserName = userName;
    }

    public void run()
    {
        try
        {
            myListener = new ChatClientListener();
            myRegistrationInfo =
                new RegistrationInfo(myUserName,
                                     InetAddress.getLocalHost().getHostName(),
                                     myListener.getPort(),
                                     true);
            if (!register())
            {
                return;
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
            return;
        }

        /* Start a thread to listen for connections in */
        myListenThread = new Thread(myListener);
        myListenThread.start();

        mainloop();
    }

    /* call the register function in the presence service */
    private boolean register()
    {
        try {
            myPServ.register(myRegistrationInfo);
        } catch (Exception e) {
            System.out.println("Exception caught when registering!");
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /* call the unregister function in the presence service */
    private boolean unregister()
    {
        try {
            myPServ.unregister(myUserName);
        } catch (Exception e) {
            System.out.println("Exception caught when unregistering!");
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /* this is the main wait-for-input-and-respond loop */
    private void mainloop()
    {
        BufferedReader br = new BufferedReader(
                new InputStreamReader(System.in));
        String line;
        try
        {
            for (;;)
            {
                /* print a prompt */
                System.out.print("> ");
                /* read a line of input */
                line = br.readLine();
                if (line == null)
                    break;
                line = line.trim();
                if (line.equals(""))
                    continue;
                /* break the input into the command word, and the rest */
                String command, rest;
                int spaceIndex = line.indexOf(' ');
                if (spaceIndex > 0)
                {
                    command = line.substring(0, spaceIndex);
                    rest = line.substring(spaceIndex + 1);
                    rest = rest.trim();
                }
                else
                {
                    command = line;
                    rest = "";
                }

                if (command.equals("help") || command.equals("?"))
                {
                    System.out.println("Commands:");
                    System.out.println("    friends - view a list of all peers on the system");
                    System.out.println("    talk {username} {message} - send message to username");
                    System.out.println("    broadcast {message} - send message to every available user");
                    System.out.println("    busy - set status to busy/away");
                    System.out.println("    available - set status to busy/away");
                    System.out.println("    exit - disconnect and exit program");
                }
                else if (command.equals("friends"))
                {
                    RegistrationInfo[] friends = myPServ.listRegisteredUsers();
                    System.out.println("Registered Users:");
                    for (RegistrationInfo ri : friends)
                    {
                        System.out.print(ri.getUserName());
                        System.out.print(" [");
                        System.out.print(ri.getStatus() ? "available" : "busy");
                        System.out.println("]");
                    }
                }
                else if (command.equals("talk"))
                {
                    String user = "";
                    int idx;
                    if ((idx = rest.indexOf(' ')) > 0)
                    {
                        user = rest.substring(0, idx);
                        RegistrationInfo ri = myPServ.lookup(user);
                        /* only send a message to available users */
                        if (ri.getStatus())
                        {
                            String message = rest.substring(idx + 1);
                            sendUserMessage(user, message);
                        }
                        else
                        {
                            System.out.println(user + " is busy!");
                        }
                    }
                    else
                    {
                        System.out.println("Syntax: talk {user} {message}");
                    }
                }
                else if (command.equals("broadcast"))
                {
                    RegistrationInfo[] ris = myPServ.listRegisteredUsers();
                    for (RegistrationInfo ri : ris)
                    {
                        /* don't send a message to ourselves */
                        if (!ri.getUserName().equals(myUserName))
                        {
                            /* send message only if user available */
                            if (ri.getStatus())
                            {
                                sendUserMessage(ri.getUserName(), rest);
                            }
                        }
                    }
                }
                else if (command.equals("busy"))
                {
                    if (myRegistrationInfo.getStatus())
                    {
                        myRegistrationInfo = new RegistrationInfo(
                                myRegistrationInfo.getUserName(),
                                myRegistrationInfo.getHost(),
                                myRegistrationInfo.getPort(),
                                false
                                );
                        unregister();
                        register();
                        System.out.println("Status set to busy");
                    }
                }
                else if (command.equals("available"))
                {
                    if (!myRegistrationInfo.getStatus())
                    {
                        myRegistrationInfo = new RegistrationInfo(
                                myRegistrationInfo.getUserName(),
                                myRegistrationInfo.getHost(),
                                myRegistrationInfo.getPort(),
                                true
                                );
                        unregister();
                        register();
                        System.out.println("Status set to available");
                    }
                }
                else if (command.equals("exit"))
                {
                    myListenThread.interrupt();
                    break;
                }
                else
                {
                    System.out.println("Unrecognized Command! Type 'help' or '?' for help!");
                }
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        unregister();
        System.exit(0);
    }

    /* a useful subroutine to break out the task of sending a
     * message to a particular user */
    private void sendUserMessage(String user, String message)
    {
        try
        {
            RegistrationInfo ri = myPServ.lookup(user);
            if (ri != null)
            {
                Socket s = new Socket(InetAddress.getByName(ri.getHost()),
                        ri.getPort());
                OutputStream os = s.getOutputStream();
                message = myUserName + ": " + message;
                os.write(message.getBytes());
                s.close();
            }
        }
        catch (Exception e)
        {
            System.err.println("Error sending message");
            e.printStackTrace();
        }
    }

    /* the main method is invoked when the ChatClient is first started */
    public static void main(String args[])
    {
        String user;
        String host = "localhost";  /* default host */
        int port = 1099;            /* default port */

        if (args.length < 1)
        {
            System.err.println("Usage: ChatClient <user> [host[:port]]");
            return;
        }

        user = args[0];
        user = user.replace(' ', '_');

        if (args.length >= 2)
        {
            StringTokenizer st = new StringTokenizer(args[1], ":");
            if (st.hasMoreTokens())
            {
                host = st.nextToken();
                if (st.hasMoreTokens())
                {
                    port = Integer.parseInt(st.nextToken());
                }
            }
        }

        if (System.getSecurityManager() == null)
        {
            System.setSecurityManager(new SecurityManager());
        }
        try
        {
            Registry registry = LocateRegistry.getRegistry(host, port);
            PresenceService pserv = (PresenceService)
                registry.lookup("PresenceService");
            if (pserv == null)
            {
                System.err.println("Error: Could not connect to presence service");
                return;
            }
            ChatClient cc = new ChatClient(pserv, user);
            cc.run();
        }
        catch (Exception e)
        {
            System.err.println("ChatClient exception:");
            e.printStackTrace();
        }
    }    
}
