//   Course Number: COSC 650 Data Comm/Networks
//    Program Name: MultiChatClient.java
//  Project Number: 2
//         Authors: Brian Hoffman
//                  Mike McKinney
//                  Javier Carrasco
//     Description: This is the main program for executing the client side of 
//                  a Java chat program. It consists of a graphical component 
//                  that diplays the current interface as well as functions that
//                  are used to handle the processing of chat messages. The program
//                  begins by displaying a ChatLoginPanel. This panel establishes
//                  the connection, represented as a ClientConnetion object, and  
//                  sets the username. It also reads a message from the server 
//                  that indicates what chatrooms are available. Using this list,
//                  the program creates a RoomSelectPanel that allows the user to 
//                  create and join rooms as well as exit back to the initial
//                  ChatLoginPanel. Clicking create or join in the RoomSelectPanel
//                  will cause the program to create a ChatroomDialog to deal with
//                  the individual room. The events generated by the user in a
//                  ChatDialog result in method calls back to the MultiChatClient
//                  object which handles all interaction with the server. While
//                  the event-thread is busy with the GUI, the MultiChatClients'
//                  main thread  listens for messages from the server and works 
//                  to process them appropriately by calling methods of the 
//                  RoomSelectPanel and ChatroomDialog. For more information on 
//                  the individual components, the reader is directed to their 
//                  source files.

// Java Core Packages
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.util.Vector;
import java.util.StringTokenizer;
import java.util.HashMap;

// Jave Extension Packages 
import javax.swing.*;
import javax.swing.border.*;

public class MultiChatClient extends JFrame
{ 
    // private class variables
    private ClientConnection connection;
    private boolean normalExit;
    private boolean serverExit;
    private boolean abnormalExit;
    private boolean accidentalClose = false;
    private ChatDialogMap chatDialogMap;
    
    // GUI Components
    private Container container;
    private ChatLoginPanel loginPanel;
    private RoomSelectPanel roomSelectPanel;
    
    /**
     * ------------------------------------------------------------------------
     * default constructor - sets up the GUI
     * ------------------------------------------------------------------------
     */
    public MultiChatClient()
    {
        // title the window 
        super("Java Multiroom Chat Program");
        chatDialogMap = new ChatDialogMap();
        
        // get the content pane and set layout 
        container = getContentPane();
        container.setLayout(new BorderLayout());
        
        // create and add login panel 
        loginPanel = new ChatLoginPanel();
        container.add(loginPanel, BorderLayout.CENTER);
        
        // display the initial GUI
        setSize(300,300);
        setVisible(true);   
    }
    
    /**
     * ------------------------------------------------------------------------
     * runClient - executes the client after the GUI is displayed
     * ------------------------------------------------------------------------
     */
    public void runClient()
    {
        // loop forever, the application only closes when the window does
        while (true)
        {
            // wait for the login panel to get a connection
            while(!loginPanel.success());
            accidentalClose = false;
            connection = loginPanel.getConnection();
                
            //get the connection message with room names and use it to 
            //create and display the room select panel
            normalExit = false;
            serverExit = false;
            abnormalExit = false;
            buildRoomSelectPanel(loginPanel.getConnectMsg());      
            
            // listen for messages and process them 
            processConnection();
            
            // cleanup and print appropriate message, a normal exit results in
            // no messages
            if(serverExit)
            {
                JOptionPane.showMessageDialog(this, "Server ended connection!",
                "Server Closed Connection", JOptionPane.INFORMATION_MESSAGE);
            }
            if (abnormalExit)
            {
                 JOptionPane.showMessageDialog(this, "Connection corrupted or lost!",
                "Connection Lost", JOptionPane.INFORMATION_MESSAGE);
            }
            connection.close();
            cleanupOpenRooms();
                
            // replace the login dialog box             
            restoreLogin();
        }
    }
    
    /**
     * ------------------------------------------------------------------------
     * buildRoomSelectPanel - builds the RoomSelectPanel based on the message
     *                        returned by the LoginPanel.
     * ------------------------------------------------------------------------
     */
    public void buildRoomSelectPanel(ChatMessage connectMsg)
    {
        Vector chatrooms = new Vector();
        StringTokenizer commaRoomList;
        
        // build the string tokenizer and read the chatroom names from the
        // connectMsg and place them into a vector
        commaRoomList = new StringTokenizer(connectMsg.getMsg(), ",");
        
        while(commaRoomList.hasMoreTokens())
        {
           chatrooms.add(commaRoomList.nextToken());
        }
        
        // use the vector to create a new room select panel
        roomSelectPanel = new RoomSelectPanel(this, chatrooms);
        
        // remove the login panel and replace it with the room select panel
        container.remove(loginPanel);
        container.add(roomSelectPanel, BorderLayout.CENTER);
        this.setTitle(connection.getUserName() +"'s Chat Manager");
        container.validate();
    } 
    
    /**
     * ------------------------------------------------------------------------
     * processConnection - listens to messages from the server and calls the 
     *                     appropriate method to deal with each
     * ------------------------------------------------------------------------
     */
    public void processConnection()
    {
        ChatMessage currentMsg;
        String currentType;
        
        try 
        {
            while ( !normalExit && !serverExit && !abnormalExit )
            {

                try // get  message
                {
                    currentMsg = connection.readMessage();
                    currentType = currentMsg.getType();
                }
                catch(InterruptedIOException iio)
                {
                    continue; 
                }
                
                // check type and process
                if (currentType.equals("roomAccept"))
                {
                    createChatDialog(currentMsg);
                }
                else if (currentType.equals("roomDeny"))
                {
                    denyChatDialog(currentMsg); 
                }
                else if (currentType.equals("endRoom"))
                {
                    endChatDialog(currentMsg);
                }
                else if (currentType.equals("requestPrivate"))
                {
                    requestPrivateDialog(currentMsg);
                }
                else if (currentType.equals("denyPrivate"))
                {
                    denyPrivateDialog(currentMsg);
                }
                else if (currentType.equals("denyRoom"))
                {
                    roomRefused(currentMsg);
                }
                else if (currentType.equals("roomOverFlow"))
                {
                    roomOverflow(currentMsg);
                }
                else if (currentType.equals("addRoom"))
                {
                    addChatRoom(currentMsg);
                }
                else if (currentType.equals("particAdd"))
                {
                    addParticipant(currentMsg);
                }
                else if (currentType.equals("leaveRoom"))
                {
                    removeParticipant(currentMsg);
                }
                else if (currentType.equals("bootUser"))
                {
                    bootFromRoom(currentMsg);
                }
                else if (currentType.equals("default"))
                {
                    postMessage(currentMsg);
                }
                else if (currentType.equals("endServer"))
                {
                    setServerExit();
                }
                else
                {
                    // do nothing with invalid message type. You mainly 
                    // get here as the result of a timeout and continue
                }
            }
        }
        
        // handle any other IO exceptions including EOFException by returning
        // after setting the abnormal exit flag
        catch(IOException ioException)
        {
            setAbnormalExit();
            return;
        }
        
        // handle ClassNotFound Exception
        catch (ClassNotFoundException cnf)
        {
            setAbnormalExit();
            return;
        }
    } 
   
    /**
     * ------------------------------------------------------------------------
     * restoreLogin - restores the LoginPanel after the user hits exit on the 
     *                RoomSelectpanel
     * ------------------------------------------------------------------------
     */
    public void restoreLogin()
    {
        loginPanel.reset();
        container.remove(roomSelectPanel);
        container.add(loginPanel, BorderLayout.CENTER);
        container.validate();
        container.repaint();
    } 
    
    /**
     * ------------------------------------------------------------------------
     * getUserName - returns the nickname under which the user logged in 
     * ------------------------------------------------------------------------
     */
    public String getUserName()
    {
        return connection.getUserName();
    } 

     /**
     * ------------------------------------------------------------------------
     * getConnection - returns the current ClientConnection 
     * ------------------------------------------------------------------------
     */
    public ClientConnection getConnection()
    {
        return connection;
    } 
    
    /**
     * ------------------------------------------------------------------------
     * setNormalExit - this method is called by the exit button to tell the client 
     *                 to close the connection and redisplay the login panel. To
     *                 truly exit the program you close the window. This method
     *                 works by setting the normalExit variable which tells the
     *                 runClient method to finish its current message and exit.
     * ------------------------------------------------------------------------
     */
    public void setNormalExit()
    {
        normalExit = true;
    }
    
    /**
     * ------------------------------------------------------------------------
     * setServerExit - this method is called upon receipt of a endServer message 
     *                 so that the program can display an appropriate message, 
     *                 perform cleanup, and restore the ChatLoginPanel
     * ------------------------------------------------------------------------
     */
    public void setServerExit()
    {
        serverExit = true;
    }
    
    /**
     * ------------------------------------------------------------------------
     * setAbnormalExit - this method is called if an unexpected exception 
     *                   occurs and the connection must be closed
     * ------------------------------------------------------------------------
     */
    public void setAbnormalExit()
    {
        abnormalExit = true;
    }
    
    /**
     * ------------------------------------------------------------------------
     * sendMessage - sends the provided message to the server. For a list of 
     *               possible messages, see ChatMessage.java
     * ------------------------------------------------------------------------
     */
    public void sendMessage(ChatMessage msg)
    {
       try
         { connection.sendMessage(msg);}
       catch (IOException ioException)
         { ioException.printStackTrace();}
    } 
    
    /**
     * ------------------------------------------------------------------------
     * cleanupOpenRooms - called when the client is closing, when the 
     *                    connection is accidentally lost, or when the server 
     *                    sends an endServer message
     * ------------------------------------------------------------------------
     */
    public void cleanupOpenRooms()
    {
        chatDialogMap.disposeDialogs();
    } 

    /**
     * ------------------------------------------------------------------------
     * dialogClosing - called by a chat dialog window when it has been closed 
     *                 by the user in order to allow the main program to update 
     *                 the map of open rooms
     * ------------------------------------------------------------------------
     */
    public void dialogClosing(String name)
    {
        
        chatDialogMap.removeRoom(name);
    }
    
    /**
     * ------------------------------------------------------------------------
     * createChatDialog - handle the situation in which the users requeat to 
     *                    join a room has been accepted by opening a new dialog 
     *                    and adding it to the map of open rooms
     * ------------------------------------------------------------------------
     */   
    public void createChatDialog(ChatMessage msg)
    {
        Vector participants = new Vector();
        StringTokenizer commaPartList;
        ChatroomDialog newDialog;
        
        // build the string tokenizer and read the chatroom names from the
        // connectMsg and place them into a vector
        commaPartList = new StringTokenizer(msg.getMsg(), ",");
        
        // read the type
        String type = commaPartList.nextToken();
        
        // read all participants from the message
        while(commaPartList.hasMoreTokens())
        {
           participants.add(commaPartList.nextToken());
        }
        
        // use the vector to create a new room select panel
        newDialog = new ChatroomDialog(this, type, msg.getRoomName(),
                                    (String) participants.get(0), participants);
        
        // add the new open chatroom dialog to the map
        chatDialogMap.addRoom(msg.getRoomName(), newDialog);     
    } 
    
    /**
     * ------------------------------------------------------------------------
     * denyChatDialog - handle the situation in which the user's request to 
     *                  join a chatroom has been denied by popping up a short 
     *                  notification message
     * ------------------------------------------------------------------------
     */  
    public void denyChatDialog(ChatMessage msg) 
    {
        JOptionPane.showMessageDialog(this, "Your request to join " +
            msg.getRoomName() + " has been denied!", "Request Denied",
            JOptionPane.INFORMATION_MESSAGE);
    }
    
    /**
     * ------------------------------------------------------------------------
     * endChatDialog - handle the situation in which the owner of a chatroom 
     *                 has left it causing the room to close
     * ------------------------------------------------------------------------
     */  
    public void endChatDialog(ChatMessage msg)
    { 
        chatDialogMap.getRoom(msg.getRoomName()).dispose();
        roomSelectPanel.removeRoom(msg.getRoomName());
        chatDialogMap.removeRoom(msg.getRoomName());
        
        JOptionPane.showMessageDialog(this, "The owner has left " +
            msg.getRoomName() + " and it has been closed!", 
            "Chatroom Closing", JOptionPane.INFORMATION_MESSAGE);
    } 
    
    /**
     * ------------------------------------------------------------------------
     * requestPrivateDialog - popup a dialog to ask if the user wishes to join
     *                        in a private chatroom with the requestor and then 
     *                        send the appropriate response message
     * ------------------------------------------------------------------------
     */  
    public void requestPrivateDialog(ChatMessage msg)
    {
             int result = JOptionPane.showConfirmDialog(this, msg.getSender() +
                          " has requested a private chatroom", "Private Chatroom Request",
                          JOptionPane.YES_NO_OPTION);
            
            // if accepted send a privateAccept message
            if (result == JOptionPane.YES_OPTION)
            {
                sendMessage( new ChatMessage("acceptPrivate", getUserName(),
                             "", msg.getSender()) );
            }
            // else send a denyPrivate message
            else
            {
                sendMessage( new ChatMessage("denyPrivate", getUserName(),
                             "", msg.getSender()) );
            }
    } 
    
    /**
     * ------------------------------------------------------------------------
     * denyPrivateDialog - inform the user that their request to open a private 
     *                     chatroom with another client has been denied
     * ------------------------------------------------------------------------
     */  
    public void denyPrivateDialog(ChatMessage msg)
    {
        JOptionPane.showMessageDialog(this, "The request for a private chatroom with " +
            msg.getSender() + " has been refused!", "Private Room Denied",
            JOptionPane.INFORMATION_MESSAGE); 
    } 
    
    /**
     * ------------------------------------------------------------------------
     * roomRefused - inform the user that their attempt to create a new chatroom 
     *               has been refused by the server
     * ------------------------------------------------------------------------
     */  
    public void roomRefused(ChatMessage msg)
    {
        JOptionPane.showMessageDialog(this, "Your request to create " + 
            msg.getRoomName() + " has been denied!", "Create Room Denied",
            JOptionPane.INFORMATION_MESSAGE); 
    } 
    
    /**
     * ------------------------------------------------------------------------
     * roomOverflow - inform the user that their attempt to create a new room 
     *                was denied because the server has reached its limit on 
     *                open rooms
     * ------------------------------------------------------------------------
     */
    public void roomOverflow(ChatMessage msg)
    {
        JOptionPane.showMessageDialog(this, "Your request to create" + 
            msg.getRoomName() + " has been denied due to chatroom overflow!",
            "Server Room Overflow", JOptionPane.INFORMATION_MESSAGE);         
    } 
    
    /**
     * ------------------------------------------------------------------------
     * addChatRoom - adds a new chatroom name to the JList presented to the 
     *               user on the roomSelectPanel
     * ------------------------------------------------------------------------
     */  
    public void addChatRoom(ChatMessage msg)
    {
        ChatroomDialog newDialog;
        Vector temp;
        
        roomSelectPanel.addRoom(msg.getRoomName());
        if (msg.getMsg().equals(getUserName()))
        {
            temp = new Vector();
            temp.add(0, getUserName());
            
            // use the vector to create a new room select panel
            newDialog = new ChatroomDialog(this, "public", msg.getRoomName(),
                                           (String) temp.get(0), temp);
        
           // add the new open chatroom dialog to the map
           chatDialogMap.addRoom(msg.getRoomName(), newDialog);     
        }
        return;
    } 
 
    /**
     * ------------------------------------------------------------------------
     * removeFromRoomList - removes a chatroom from the JList on the
     *                      roomSelectPanel
     * ------------------------------------------------------------------------
     */  
    public void removeFromRoomList(String roomName)
    {
        roomSelectPanel.removeRoom(roomName);
    } 
    
    /**
     * ------------------------------------------------------------------------
     * addParticipant - adds a participant to the room indicated in the message
     * ------------------------------------------------------------------------
     */  
    public void addParticipant(ChatMessage msg)
    {
        ChatroomDialog temp = chatDialogMap.getRoom(msg.getRoomName());
        temp.addParticipant(msg.getMsg());
    } 
    
    /**
     * ------------------------------------------------------------------------
     * removeParticipant - removes a participant in the room indicated in the message
     * ------------------------------------------------------------------------
     */  
    public void removeParticipant(ChatMessage msg)
    {
        ChatroomDialog temp = chatDialogMap.getRoom(msg.getRoomName());
        temp.removeParticipant(msg.getSender());
    } 
    
    /**
     * ------------------------------------------------------------------------
     * bootFromRoom - removes a participant in the room indicated in the message
     * ------------------------------------------------------------------------
     */  
    public void bootFromRoom(ChatMessage msg)
    {
        // handle the case where it is me who gets booted
        if (msg.getMsg().equals(getUserName()))
        {      
            chatDialogMap.getRoom(msg.getRoomName()).dispose();
            chatDialogMap.removeRoom(msg.getRoomName()); 
            JOptionPane.showMessageDialog(this, "The owner has booted you from the room " +
                        msg.getRoomName() + " and it has been closed!", 
                       "Chatroom Closing", JOptionPane.INFORMATION_MESSAGE);
        }
        // handle the case where it is a regular user
        else
        {
            ChatroomDialog temp = chatDialogMap.getRoom(msg.getRoomName());
            temp.removeParticipant(msg.getMsg());
        }
    } 
    
    /**
     * ------------------------------------------------------------------------
     *  postMessage - handles the standard case in which a messages has arrived 
     *                and should be displayed in one of the open chat dialog
     *                windows
     * ------------------------------------------------------------------------
     */  
    public void postMessage(ChatMessage msg)
    {
        if (chatDialogMap.roomExists(msg.getRoomName()))
        {
            chatDialogMap.getRoom(msg.getRoomName()).displayMessage(msg);
        }
    } 
    
    /**
     * ------------------------------------------------------------------------
     * main - executes application
     * ------------------------------------------------------------------------
     */   
    public static void main (String args[])
    {
        MultiChatClient application = new MultiChatClient();
        application.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        // run the client
        application.runClient();
    }       
}