// Sample code file: ChatClient.java

// Warning: This code has been marked up for HTML

/*
   Copyright (c) 2000 Novell, Inc.  All Rights Reserved.

   THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND TREATIES. 
   USE AND REDISTRIBUTION OF THIS WORK IS SUBJECT TO THE LICENSE AGREEMENT 
   ACCOMPANYING THE SOFTWARE DEVELOPMENT KIT (SDK) THAT CONTAINS THIS WORK. 
   PURSUANT TO THE SDK LICENSE AGREEMENT, NOVELL HEREBY GRANTS TO DEVELOPER A 
   ROYALTY-FREE, NON-EXCLUSIVE LICENSE TO INCLUDE NOVELL'S SAMPLE CODE IN ITS 
   PRODUCT. NOVELL GRANTS DEVELOPER WORLDWIDE DISTRIBUTION RIGHTS TO MARKET, 
   DISTRIBUTE, OR SELL NOVELL'S SAMPLE CODE AS A COMPONENT OF DEVELOPER'S 
   PRODUCTS. NOVELL SHALL HAVE NO OBLIGATIONS TO DEVELOPER OR DEVELOPER'S 
   CUSTOMERS WITH RESPECT TO THIS CODE. 
*/
 
 
package com.novell.Chat;

//Java imports
import java.awt.* ;
import java.net.* ;
import java.io.* ;
import java.awt.event.* ;
import javax.swing.*;
import java.util.ResourceBundle;

// ConsoleOne imports
import com.novell.application.console.snapin.*;
import com.novell.admin.common.exceptions.*;

/**
 * When a user wishes to establish a connection with either another
 * user or with a chat room, the ChatClient is used.  It will attempt to connect 
 * at the specified IP address and Port number.  Once a connection is made,
 * it starts some threads that handle the communications with the server.
 */
public class ChatClient implements ChatConnection, ComListener
{
    /** 
     * Connection type that is a normal client connection.
     */
    public static final int NORMAL = 0;      
    
    /**
     * Connection type that is a reconnection after a server transfer.
     */
    public static final int RECONNECT = 1;   
    
    /**
     * Connection type that forces the shutdown of a Chat Room
     */
    public static final int FORCE_DOWN = 2;   
    
    /**
     * Connection type that forces the disconnection of a User from a Chat Room
     */
    public static final int FORCE_DIS = 3;    
    
    
    /**
     * Handles all of the TCP/IP Communications.
     */
    Communications communications;
    
    /**
     * The socket used to establish the conection with the server.
     */
    Socket socket;
        
        /**
         * The server's information
         */
        ChatUser serverInfo;
        
        /**
         * This user's information
         */
        ChatUser userInfo;
        
        /** 
         * The Port number of the server machine.
         */
        int svrPort;
                                
        /**
         * Used to communicate information about the chatting to other classes.
         */
        ChatStatusListener listener;    
                
        /**
         * The ChatDialog used with this client.  Provides the user interface for
         * chatting.
         */
        ChatDialog chatDialog;  
                
        /**
         * The parent frame that will be hosting this client.
         */
        Frame parent;
        
        /**
         * True when the user disconnects himself.
         */
        boolean userDisconnected = false;
        
        /**
         * True when the server is being transferred from one user
         * to another.
         */
        boolean serverChanged = false;
        
        
        /**
         * Constructor
         *
         * @param listener The ChatStatusListener that receives the Chat information from this class.
         * @param serverInfo Information about the server the client is connected to.
         * @param chatUser Information about the user using this client.
         * @param svrPort The port number to connect at.
         */      
        public ChatClient(ChatStatusListener listener, ChatUser serverInfo, ChatUser userInfo, int svrPort)
    {
         this.listener = listener;
         this.parent = Chat.getInstance().getShell().getShellFrame();
         this.serverInfo = serverInfo;     
         this.userInfo = userInfo;
         this.svrPort = svrPort;
    }
    
    
    /**
     * ChatConnection Interface Implementation.
     *
     * This key can be used as a unique reference string for this connection.
     *
     * @return A string in the form of IP:Port
     */
    public String getKey()
    {
        if(socket != null)
            return communications.getClientKey();
        
        return null;
    }
    
    /**
     * ChatConnection Interface Implementation.
     *
     * Gets the type of object this client is communicating with:
     * User or Chat Room.
     *
     * @return The ObjectType name.
     */
    public String getType()
    {
        ObjectEntry oe = serverInfo.getObjectEntry();
        if(oe != null)
            return oe.getObjectType().getName();
            
        return null;        
    }
    
    /**
     * ChatConnection Interface Implementation.
     *     
     * @return The ObjectEntry this server is associated with.
     */
    public ObjectEntry getObjectEntry()
    {
        return userInfo.getObjectEntry();   
    }

    /**
     * ChatConnection Interface Implementation.
     *
     * Sends a formatted message from the client to the server.
     *
     * @param message The message being sent.
     */
        public void sendMessage(String message)
        {
            if(communications.isConnected()) 
                communications.sendMessage(userInfo.getName(), message);
        }
        

        /**
         * This method will first attempt to open the socket to the specified IP 
         * and port.  If it fails, it will increment the port by one and then try 
         * again.  The reason for this is that the server my not have been able to 
         * listen at the specifed port due to a port conflict, so the server will 
         * also increment the port by one until it can find a valid port.  The 
         * expectation is that this client will eventually find the port the server was
         * able to open.  It is rare that this will be need, but it's a back up just in case.
         *
         * @param type The type of connection to make.
         */
        public void connect(int type)
        {
            connect(type, null);
        }
         
        /**
         * This method will first attempt to open the socket to the specified IP 
         * and port.  If it fails, it will increment the port by one and then try 
         * again.  The reason for this is that the server my not have been able to 
         * listen at the specifed port due to a port conflict, so the server will 
         * also increment the port by one until it can find a valid port.  The 
         * expectation is that this client will eventually find the port the server was
         * able to open.  It is rare that this will be need, but it's a back up just in case.
         *
         * @param type The type of connection to make.
         * @param key The key previously used by this client.
         *            Only used for RECONNECT and FORCEDOWN types.
         */
        public void connect(int type, String key)
        {
                int tries = 0;  //The number of tries to open the socket.               
                
                while(tries < Chat.PORT_MAX)  //It will stop trying when it has tried PORT_MAX times.
                {
                    try 
                    {               
                        //String ipAddress = serverInfo.getIPAddress();
                        //System.out.println("Connecting at " + ipAddress + ":" + svrPort);
                            
                        //Open the socket.  If it fails, an IOException will be thrown.
                            socket = new Socket(serverInfo.getIPAddress(), svrPort);
                            
                            //This class will now handle the communications.
                            communications = new Communications(this, socket, userInfo.getName(), userInfo.getFullName());
                            
                            //We will now attempt to establish the communications with the server.
                                    
                            if(type == NORMAL)     
                            {
                                if(!communications.estabCom())
                                    Chat.getInstance().errorDialog(Chat.chatRes.getString("Client Error-EstabCom"));                                
                            }
                            else if(type == RECONNECT)
                            {
                                if(!communications.reEstabCom(key))
                                    Chat.getInstance().errorDialog(Chat.chatRes.getString("Client Error-ReEstabCom"));  
                            }
                            else if(type == FORCE_DOWN)  //Simply connects, sends the message, and then closes
                            {
                                if(!communications.forceShutDown())
                                    Chat.getInstance().errorDialog(Chat.chatRes.getString("Client Error-Force Down"));  
                            }
                            else if(type == FORCE_DIS)   //Simply connects, sends the message, and then closes
                            {
                                if(!communications.forceDisconnect(key))
                                    Chat.getInstance().errorDialog(Chat.chatRes.getString("Client Error-Disconnect"));  
                            }
                                    
                            //Stop the loop.
                            break;
                }
                catch(IOException e)
                {
                    //Increment the port, and try again.
                tries++;
                svrPort++;
                        
                if(tries >= Chat.PORT_MAX)
                {
                    JOptionPane.showMessageDialog(Chat.getInstance().getShell().getShellFrame(), 
                        Chat.chatRes.getString("Client failure") + serverInfo.getName() + ".",
                        Chat.chatRes.getString("Chat"), JOptionPane.INFORMATION_MESSAGE);
                        
                    listener.chatStatus(Chat.chatRes.getString("Status-Ready"));
                    return;
                }
                }
            }                                                   
    }

    /**
     * ChatConnection Interface Implementation.
     *
     * Gets the name of the person or chat room this session is with.
     * Used in the title of the ChatDialog.
     *
     * @return The name to place in the title.
     */
    public String getConnectionName()
    {
        return " - " + serverInfo.getName();
    }


        /**
         * ChatConnection Interface Implementation.
         * ComListener Interface Implementation.
     *
         * Called to stop the connection and close the socket.
         */
        public void disconnect()
        {
            userDisconnected = true;
            
            communications.disconnect();
                
                //Close the user's Chat dialog.
                if(chatDialog != null)
        {
            chatDialog.dispose();
            chatDialog = null;
        }
        }
        
        /**
         * ChatConnection Interface Implementation.
         * ComListener Interface Implementation.
     *
     * Shuts down the client.
     * This should be called on shut down.
     */
    public void close()
    {
        disconnect();   
    }

        
        /**
         * Used to test if the connection is valid.  
         *
         * @return True if there is a connection.
         */
        public boolean isConnected()
        {
            if(communications == null)
                return false;
                
            return communications.isConnected();
        }
        
        /**
         * ComListener Interface Implementation.
     *
     * Called when a message is received.
     *
     * @param colorCode The code of the color to use for the name.
     * @param name The name of the user who sent the message.
     * @param message The message received.
     */
    public void messageReceived(int colorCode, String name, String message)
    {
        if(chatDialog == null)
        {
            chatDialog = new ChatDialog(parent, this);
            chatDialog.setVisible(true);
        }        
        chatDialog.displayMessage(Chat.getInstance().getNameColor(colorCode), name, message);
    }
            
    /**
     * ComListener Interface Implementation.
     *
     * Called when a valid connection has been established. 
     * Once this event is raised, you can assume that the connection
     * is valid and has been approved by both sides.
     *
     * @param comm Reference to the Communications object that is handling the
     *             the connection.
     */     
    public void connectionEstablished(Communications comm)
    {
        listener.chatStatus(Chat.chatRes.getString("Status-Chatting"));
            if(chatDialog == null)
            {
                chatDialog = new ChatDialog(parent, this);
                chatDialog.setVisible(true);
            }           
    }
        
    /**
     * ComListener Interface Implementation.
     *
     * Informs the listener of errors that occur in the connection.
     *
     * @param comm Reference to the connection.
     * @param errorCode The error code constant.
     * @param message The error message.
     */
    public void comError(Communications comm, int errorCode, String message)
    {
        if(errorCode == Communications.ERROR_TIMEOUT)
            JOptionPane.showMessageDialog(Chat.getInstance().getShell().getShellFrame(), 
                Chat.chatRes.getString("Message Timeout") + "\n" + message, 
                Chat.chatRes.getString("Chat"), JOptionPane.INFORMATION_MESSAGE);
        else
            Chat.getInstance().errorDialog(Chat.chatRes.getString("ERROR") + " " + errorCode + ":  " + message); 
    }
    
    /**
     * ComListener Interface Implementation.
     *
     * Informs the listener of the current status of the connection.
     *
     * @param comm Reference to the connection.
     * @param message The status message.
     */
    public void comStatus(Communications comm, String message)
    {
        //listener.chatStatus(message);        
    }
    
    /**
     * ComListener Interface Implementation.
     *
     * Requests the listener to verify the connection by checking the 
     * user's full name against the ipAddress.
     *
     * @param fullName The fullName of the user to check.
     * @param ipAddress The supposed IP Address of the user.
     * @return True if it verifies correctly.
     */
    public boolean verifyConnection(String fullName, String ipAddress)
    {
        boolean verified = Chat.getInstance().verifyUser(fullName, ipAddress);
                
        return verified;
    }
    
    /**
     * ComListener Interface Implementation.
     *
     * Asks the listener if it accepts or rejects the connection.
     *
     * @param comm Reference to the connection.
     * @return True if it accepts.
     */
    public int acceptConnection(Communications comm)
    {
        return 0;
    }
    
    /**
     * ComListener Interface Implementation.
     *
     * Called when the given connection is lost.
     *
     * @param comm Reference to the connection.
     */
    public void disconnected(Communications comm)
    {
        if(!serverChanged)
        {
            //If the user didn't disconnect himself, then we'll inform the user of what happened.
            if(!userDisconnected && serverInfo.getObjectEntry().getObjectType().getName().equals(Chat.CHATROOM_TYPE))
                JOptionPane.showMessageDialog(Chat.getInstance().getShell().getShellFrame(), 
                        Chat.chatRes.getString("Client Disconnected"), 
                        Chat.chatRes.getString("Chat"), JOptionPane.INFORMATION_MESSAGE);                            
        
            if(chatDialog != null)
            {
                chatDialog.dispose();
                chatDialog = null;
            }
            listener.disconnected(comm.getClientKey());   
        }        
        
        userDisconnected = false;    
    }
    
    /**
     * ComListener Interface Implementation.
     *
     * This is called when a user enters or leaves a chat room.
     * It is a request to update your user lists.
     *
     * @param comm Reference to the connection.
     */
    public void connectionUpdate(Communications con)
    {
        if(chatDialog == null)
            {
                chatDialog = new ChatDialog(parent, this);
                chatDialog.setVisible(true);
            }
            chatDialog.updateUserList(serverInfo.getObjectEntry());      
    }
    
    /**
     * ComListener Interface Implementation.
     *
     * Raised when a request to transfer the server to this user to received.
     * Used only by Clients.
     *
     * @param startColor The color to start giving to new users for their user name.
     * @return True if successfull.
     */
    public boolean serverTransferRequest(int startColor)
    {
        return Chat.getInstance().chatRoomTransfer(serverInfo.getObjectEntry(), userInfo.getIPAddress(), startColor);        
    }
    
    /**
     * ComListener Interface Implementation.
     *
     * Raised when a client must change which server it is connected to.
     *
     * @param fullName The fullname of the Chat Room.
     */  
    public void changeServer(String fullName)
    {
        ObjectEntry roomOE = null;
        
            serverChanged = true;
            int colorCode = communications.getColorCode();
            String oldKey = getKey();
            
            //Close the old connection
            communications.close();
            
            try
            {
                //Get the room's object
                roomOE = Chat.getInstance().getNDSNamespace().getObjectEntry(fullName);
            }
            catch(SPIException e)
            {
                System.err.println("SPIException in ChatClient.changeServer():  " + e.getMessage());
            }
            
            if(roomOE != null)
            {       
                String name = roomOE.getName();         
            String ip = Chat.getInstance().getAttributeValueAsString(roomOE, Chat.ATTRIBUTE_IP);
            String port = Chat.getInstance().getAttributeValueAsString(roomOE, Chat.ATTRIBUTE_PORT);
            
            svrPort = (new Integer(port)).intValue();
            
            //Create a ChatUser object for this room to hold the information
            serverInfo = new ChatUser(roomOE, name, fullName, ip);
        }
        
        //Connect to the new server
        connect(RECONNECT, oldKey);  
        
        //Set the name color so that it matches the old one.
        communications.setColorCode(colorCode);  
        
        //Change the connection keys in the list of connections.
        Chat.getInstance().swapConnectionKeys(oldKey, getKey(), this);
        
        serverChanged = false;
    }
    
    // ComListener Implementations
    // Unused by this class
    public boolean echoData(String data)
        {
            return false;
        }            
    public String getOwnerName()
    {
        return null;
    }    
    public void disconnect(String key){}
    public void serverTransferResponse(boolean accepted){}    
    public void reConnectionEstablished(Communications comm, String oldKey){}
}