// Sample code file: ChatServer.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.io.*;
import java.awt.Color;
import java.net.*;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.util.Hashtable;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.Date;
import java.util.ResourceBundle;
import javax.swing.JOptionPane;

// ConsoleOne imports
import com.novell.application.console.snapin.*;
import com.novell.admin.ns.ValueComponent;

/**
  * Opens a server socket and listens for requests from 
  * clients on a static port. When a connection is received it creates a
  * Communications object to handle further communication with that
  * client.  Whenever a message is received from a client it is relayed 
  * to all other clients that it is connected with.
  */
public class ChatServer implements Runnable, ChatConnection, ComListener
{
    /**
     * The socket used by this server to receive connections.
     */
        ServerSocket serverSocket;
        
        /**
         * Keeps a list of all connected clients.
         */
        Hashtable clientList = new Hashtable();
                
        /**
         * Used to communicate information about the chatting to other classes.
         */
        ChatStatusListener listener;
        
        /**
         * The thread used to listen for connections.
         */
        Thread listenThread = new Thread(this);
                
        /**
         * The port the server is listening at.
         */
        int svrPort;
        
        /**
         * Contains the information about this server.
         */
        ChatUser serverInfo = null;
        
        /**
         * The type of server this is.  User or Chat Room.
         */
        String svrType = null;
        
        /**
         * The ChatDialog used with this server.
         */
        ChatDialog chatDialog;  
        
        /**
         * The name color of the user that controls this server.
         */
        int nameColor = 0;
        
        /**
         * The next name color to issue to a new client.
         */
        int nameColorInc = 0;
        
        /**
         * True when a transfer message is received.
         */
        boolean tranMsgReceived = false;
        
        /**
         * True when the server accepted the transfer.
         */
        boolean serverAcceptedTran = false;
        
        /**
         * True when the server has been changed.
         */
        boolean serverChanged = false;
        
        /**
         * The name of the person this server is connected to for user to user chatting.
         */
        String connectionName = null;
                            
        /**
         * Opens the server socket on the static port and listens for connections.
         * When a new connection is opened it then create a new Communications object.
         *
         * @param listener The ChatStatusListener that receives the Chat information from this class.
         * @param svrPort The port on which this server will listen for connections.
         * @param serverInfo Information about this server.
         * @param startColor The color to start this user at in the ChatDialog.
         */
        public ChatServer(ChatStatusListener listener, int svrPort, ChatUser serverInfo, int startColor)
        {
        this.listener = listener;
        this.serverInfo = serverInfo;
        this.svrPort = svrPort;
        this.nameColorInc = startColor;
        svrType = serverInfo.getObjectEntry().getObjectType().getName();
                
        try
            {         
                //Gets an InetAddress object for the IPAddress for this server.
                InetAddress address = InetAddress.getByName(serverInfo.getIPAddress()); 
                
                int tries = 0;   //Number of times it's tried to open the socket.               

            //Create the socket.
            //If a port collision occurs, an IOException will be thrown.  This loop
            //will then increment the port number by one until either a valid port
            //is found, or until it has tried PORT_MAX times.
                    while(serverSocket == null && tries < Chat.PORT_MAX)
                    {               
                        try
                        {
                                serverSocket = new ServerSocket(this.svrPort, 50, address);     
                                
                                if(serverSocket != null && !getType().equals(Chat.CHATROOM_TYPE))
                                System.out.println("Chat server at " + serverSocket.getInetAddress().toString() + ":" + this.svrPort + ".");
                            }
                            catch (IOException e) 
                        {                           
                                System.err.println(e.getMessage() + " at " + address.toString() + ":" + this.svrPort);                  
                            
                            // Increment the port and then try again.
                            this.svrPort++;   
                            tries++;
                        
                            if(tries >= Chat.PORT_MAX)
                    {
                        Chat.getInstance().errorDialog(MessageFormat.format(Chat.chatRes.getString("Server Error 1"), new Object[]{serverInfo.getName()}));
                        this.svrPort = -1;  //Acts as a flag saying that no socket was opened.
                        return;
                    }
                        }
                    }                   
        } 
                catch (UnknownHostException e) 
                {
                    Chat.getInstance().errorDialog(MessageFormat.format(Chat.chatRes.getString("Server Error 2"), new Object[]{serverInfo.getName(), serverInfo.getIPAddress()}));
                        this.svrPort = -1;
                }
    }
        
    
    /** 
     * Gets the port the server is listening at.
     *
     * @return The port number.
     */
    public int getPort()
    {
        return svrPort;
    }
    
    /**
     * ChatConnection Interface Implementation.
     *
     * @return The type of server this is.
     */
    public String getType()
    {
        return svrType;
    }
    
    /**
     * ChatConnection Interface Implementation.
     *
     * @return The ObjectEntry this server is associated with.
     */
    public ObjectEntry getObjectEntry()
    {
        return serverInfo.getObjectEntry();   
    }
    
    /**
     * ChatConnection Interface Implementation.
     *
     * This key can be used as a unique reference number to this connection.
     *
     * @return A string in the form of IP:Port.  A '*' is appended to the key
     *         as a flag for server's used for user objects.
     */
    public String getKey()
    {   
        if(serverSocket != null)
        {
            String ip = serverSocket.getInetAddress().toString();
            
            int index = ip.indexOf('/');
                if(index != -1)
                    ip = ip.substring(index + 1, ip.length());
            
            if(svrType.equals(Chat.CHATROOM_TYPE))        
                return ip + ":" + serverSocket.getLocalPort();  
            else
                return ip + ":" + serverSocket.getLocalPort() + "*"; 
        }
        
        if(svrType.equals(Chat.CHATROOM_TYPE))        
            return serverInfo.getIPAddress() + ":" + svrPort;  

        return serverInfo.getIPAddress() + ":" + svrPort + "*";
    }
    
    
    /**
     * Starts or stops the server from listening for connections.
     *
     * @param listen When true, it listens, when false, it stops.
     */
    public void listen(boolean listen)
    {
        if(listen)
        {
            if(!listenThread.isAlive())
                listenThread.start();
            else
                listenThread.resume();          
            listener.chatStatus(Chat.chatRes.getString("Status-Ready"));
        }
        else
        {
            listenThread.suspend();
            listener.chatStatus(Chat.chatRes.getString("Status-Disabled"));
        }
    }
    
    /**
     * Sends a transfer request message to the client with the given key.
     *
     * @param key The key of the client to transfer the server to.
     * @return True if successfull.
     */
    public boolean sendTransferMessage(String key)
    {
        //get the client
        Communications client = (Communications)clientList.get(key);
        
        if(client != null)
        {
            serverChanged = true;
            client.sendTransferMessage(nameColorInc);
            
            serverAcceptedTran = false;
            tranMsgReceived = false;
            
            Date curTime = Calendar.getInstance().getTime();
            long start = curTime.getTime();
            long cur = start;
            
            //Waits here until a response is received from the client.
            while(!tranMsgReceived && ((cur - start) < (30 * 1000)))   
                cur = curTime.getTime();
            
            return serverAcceptedTran;
        }
        
        return false;
    }
    
    /**
     * Sends the command to transfer servers to all clients.
     */    
    public void sendTansferCommand()
    {
        Enumeration enum = clientList.elements();
                while (enum.hasMoreElements())
                {
                        Communications client = (Communications)enum.nextElement();
                        
                        //Send the message to this client
            client.sendTransferCommand(serverInfo.getFullName());
        }
        
        serverChanged = true;
    }
    
    
    /**
         * Called when the listenThread is started. Here the server listens for connections.
         * When a connection is made a Communications object is created to monitor the 
         * connection.
         */
    public void run()
    {
        //If the server failed to be created, it will stop the thread from continuing
        if(serverSocket == null)
        {
            Chat.getInstance().errorDialog(MessageFormat.format(Chat.chatRes.getString("null Server Error"), new Object[]{serverInfo.getFullName()}));
            close();
        }
        else
        {
            while(true)
            {
                try 
                {
                    //The method will hold the listenThread until a connection is made.
                                Socket socket = (Socket)serverSocket.accept();
                                                        
                                //This will handle the rest of the communications for this connections.
                                Communications comm = new Communications(this, socket, serverInfo.getName(), serverInfo.getFullName());                                                         
                        } 
                        catch (IOException e)
                        {
                            Chat.getInstance().errorDialog(MessageFormat.format(Chat.chatRes.getString("Socket IO Error"), new Object[]{serverInfo.getFullName()}) + "\n" +
                                                           e.getMessage());
                        }
                    }
                }
    }
    
    
    /**
     * ChatConnection Interface Implementation.
     *
     * Sends a message from this user to all of the listening clients.
     *
     * @param message The message to send.
     */
    public void sendMessage(String message)
        {       
            if(!svrType.equals(Chat.CHATROOM_TYPE))
        {
                if(chatDialog == null)
            {
                chatDialog = new ChatDialog(Chat.getInstance().getShell().getShellFrame(), this);
                chatDialog.setVisible(true);
            }                        
            chatDialog.displayMessage(Chat.getInstance().getNameColor(nameColor), serverInfo.getName(), message);
        }
            
            //Properly formats the message so that the client know what to do with it.
            relayMessage(nameColor, serverInfo.getName(), message);
        }
        
        /**
     * Sends a message from this user to all of the listening clients.
     * This message is meant to be a message informing the other users of the status
     * of the chat room.
     *
     * @param message The message to send.
     */
    public void sendStatusMessage(String message)
        {       
            if(!svrType.equals(Chat.CHATROOM_TYPE))
        {
                if(chatDialog != null)
                chatDialog.displayMessage(Color.black, "", message);
        }
            
            //Properly formats the message so that the client know what to do with it.
            relayMessage(0, "", message);
        }
        
        /**
         * Enumerates over all clients in the client list and relays the messages.
         *
         * @param colorCode The color to print the name of this user in.
         * @param userName The name of the user sending the message.
         * @param message Message as byte array
         */
        public void relayMessage(int colorCode, String userName, String message)
        {                           
                Enumeration enum = clientList.elements();
                while (enum.hasMoreElements())
                {
                        Communications client = (Communications)enum.nextElement();
                        
                        //Send the message to this client
                        client.sendMessage(colorCode, userName, message);
                }
        }
        
        /**
         * ComListener Interface Implementation.
         *
     * Raised when a server should echo a message to the other clients.
     *
     * @param data The message to echo.
     */
        public boolean echoData(String data)
        {                           
                Enumeration enum = clientList.elements();
                while (enum.hasMoreElements())
                {
                        Communications client = (Communications)enum.nextElement();
                        
                        //Send the message to this client
                        client.send(data);
                }
                
                if(svrType.equals(Chat.CHATROOM_TYPE))
                    return true;
                    
        return false;               
        }
                                
        
        /**
         * Sends the update message to all of the connected clients.
         */
        void sendUpdate()
        {
            Enumeration enum = clientList.elements();
                while (enum.hasMoreElements())
                {
                        Communications client = (Communications)enum.nextElement();
                        
                        //Send the message to this client
                        client.sendUpdate();
                }
        }
        
        
        /**
         * Displays the chatDialog is this is not a Chat Room object.
         */
        public void showChatDialog()
        {
            if(!svrType.equals(Chat.CHATROOM_TYPE))
        {
                if(chatDialog == null)
            {
                chatDialog = new ChatDialog(Chat.getInstance().getShell().getShellFrame(), this);
                chatDialog.setVisible(true);
            }    
        }
        }                       
        
        /**
         * ChatConnection Interface Implementation.
         *
     * Gets the name of the person or chat room this session is with.
     *
     * @return The name to place in the title.
     */
    public String getConnectionName()
    {
        return connectionName;
    }
        
        /** 
         * Adds a user to the connection table.
         *
         * @param client The client to add.
         */
        public void addClient(Communications client)
        {
            clientList.put(client.getKey(), client);
        }

        /**
         * Removes the client from the client list.
         *
         * @param key Key used to reference the client. (IP:Port)
         */
        public void removeClient(String key)
        {
                Communications client = (Communications)clientList.remove(key);
                                                
                //When the last client is removed, inform the ChatStatusListener that 
                //no other connections exist.
                if(clientList.size() <= 0)
                {                                      
                    if(chatDialog != null && !svrType.equals(Chat.CHATROOM_TYPE))
            {
                chatDialog.dispose();
                chatDialog = null;
                listener.chatStatus(Chat.chatRes.getString("Status-Ready"));
            }       
                }               
        }       
        
        
        /**
         * Adds a connected user to the ChatRoom object's user list.
         *
         * @param client The Communications object associated with this client to add.
         */
        public void addConnectedUser(Communications client)
        {
            if(svrType.equals(Chat.CHATROOM_TYPE))
            {
                String key = client.getKey();   
                
                Calendar rightNow = Calendar.getInstance();
            String curTime = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(rightNow.getTime());
            
            //formats the data into a single string
                String userInfo = key + "|" + curTime + "|" + client.getRemoteName() + "|" + client.getRemoteFullName();
                
                //Adds it to the list in NDS
                if(Chat.getInstance().setAttributeValue(serverInfo.getObjectEntry(), Chat.ATTRIBUTE_USERLIST, userInfo, true))
                    sendUpdate();  //Inform all clients that a change has taken place on the user list.         
            }
        }
        
        /**
         * Removes the user from the ChatRoom object's user list.
         *
         * @param client The client to remove.
         */
        public void removeConnectedUser(Communications client)
        {
            if(svrType.equals(Chat.CHATROOM_TYPE))
            {
                if(Chat.getInstance().replaceAttributeValue(serverInfo.getObjectEntry(), Chat.ATTRIBUTE_USERLIST, client.getKey(), null))
                sendUpdate(); //Inform all clients that a change has taken place on the user list.
                
            }
        }
        
        /**
         * ChatConnection Interface Implementation.
         * ComListener Interface Implementation.
     *
         * Disconnects the server from all clients.
         */
        public void disconnect()
        {
            Enumeration enum = clientList.keys();
                while(enum.hasMoreElements())
                    disconnect((String)enum.nextElement());
        }
        
        /**
         * ComListener Interface Implementation.
     *
         * Disconnects a specific client.
         * Used by the ChatRoomPageSnapin.
         *
         * @param key Key used to reference the client. (IP:Port)
         */
        public void disconnect(String key)
        {
            Communications client = (Communications)clientList.get(key);
            if(client != null)
            {      
                if(serverChanged)
                {
                    //Remove the user from the client list.
                removeClient(client.getKey());
            }            
            else
            {
                //Sends the disconnect message.
                    client.disconnect();                        
                }
            }
        }
        
        /**
         * ChatConnection Interface Implementation.
         * ComListener Interface Implementation.
     *
     * Closes the ServerSocket.
     * This should be called on shut down.
     */
    public void close()
    {
        serverInfo.setObjectEntry(null);
        
        //Stop listening
        listenThread.stop();
        
        //Disconnects all clients.        
        disconnect();
        
        try
        {
            //Close the socket
            if(serverSocket != null)
            {
                serverSocket.close();
                serverSocket = null;
            }
        }
        catch(IOException e)
        {
            Chat.getInstance().errorDialog(MessageFormat.format(Chat.chatRes.getString("Close Server Error"), new Object[]{serverInfo.getFullName()}) + 
                                            "\n" + e.getMessage());
        }
        
        String key = getKey();
        
        //Informs the ChatStatusListener that this server has been disconnected.
        listener.disconnected(key);
    }
                
        
        /**
         * Used to test if there is a valid connection.  When the client
         * list size is less than zero, then no connections exist.
         *
         * @return True if there is a connection.
         */
        public boolean isConnected()
        {
            if(clientList.size() > 0)
                return true;
            else
                return false;
        }               
        
    /**
     * 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(!svrType.equals(Chat.CHATROOM_TYPE))
        {
            if(chatDialog == null)
            {
                chatDialog = new ChatDialog(Chat.getInstance().getShell().getShellFrame(), 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.
     * @param oldKey The old key of the client connecting.
     */
    public void reConnectionEstablished(Communications comm, String oldKey)
    {
        if(!svrType.equals(Chat.CHATROOM_TYPE))
            listener.chatStatus(Chat.chatRes.getString("Status-Chatting"));
        
        //Add the client to the client list.
        addClient(comm);   
        
        Chat.getInstance().replaceAttributeValue(serverInfo.getObjectEntry(), Chat.ATTRIBUTE_USERLIST, oldKey, comm.getKey());
        sendUpdate();
    }
            
    /**
     * 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)
    {
        //Add the client to the client list.
        addClient(comm);
        
        if(svrType.equals(Chat.CHATROOM_TYPE))
            addConnectedUser(comm);      //Add the user to the chat room's user list.          
        else
        {
            Chat.getInstance().chatStatus(Chat.chatRes.getString("Status-Chatting"));
            showChatDialog();            //Display the user's chat dialog.
        }
                    
        sendStatusMessage(MessageFormat.format(Chat.chatRes.getString("User Joined"), new Object[]{comm.getRemoteName()}));
    }
    
    /**
     * 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 passed = Chat.getInstance().verifyUser(fullName, ipAddress);
        
        return passed;
    }
    
    /**
     * 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)
    {
        //All connections are accepted when it's a chat room object.        
        if(getType().equals(Chat.CHATROOM_TYPE))
        {
            nameColorInc++;
            return nameColorInc;
        }
        //If this server is for a user, the user must accept or reject the offer.
        else if(JOptionPane.showConfirmDialog(Chat.getInstance().getShell().getShellFrame(), 
                MessageFormat.format(Chat.chatRes.getString("Ask to Chat"),new Object[]{comm.getRemoteName()}), 
                "Chat Session Requested", JOptionPane.YES_NO_OPTION, 
                JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION)
        {
            Chat.getInstance().chatStatus(Chat.chatRes.getString("Status-Connect"));
            connectionName = " - " + comm.getRemoteName();
            nameColorInc++;
            return nameColorInc;
        }
        
        return -1;
    }
        
    /**
     * ComListener Interface Implementation.
     *
     * Called when the given connection is lost.
     *
     * @param comm Reference to the connection.
     */
    public void disconnected(Communications comm)
    {
        //Remove the user from the client list.
        removeClient(comm.getKey());
        
        //Remove the user from the chat room's user list.
        removeConnectedUser(comm);       
        
        sendStatusMessage(comm.getRemoteName() + " has left the chat room.");
    }       
    
    /**
     * 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.
     *
     * 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)
    {
        Chat.getInstance().errorDialog("ERROR " + errorCode + ":  " + message); 
    }
    
    /**
     * ComListener Implementation
     *
     * Raised when a response is received to transfer the server.
     * Used only by Servers.
     *
     * @param accepted True if the request was accepted.
     */
    public void serverTransferResponse(boolean accepted)
    {
        serverAcceptedTran = accepted;
        tranMsgReceived = true;    
    }
    
    /**
     * ComListener Interface Implementation.
     *
     * @return The owners name of the Chat Room.
     */
    public String getOwnerName()
    {
        String name = Chat.getInstance().getAttributeValueAsString(serverInfo.getObjectEntry(), Chat.ATTRIBUTE_OWNER);
        String treeName = serverInfo.getObjectEntry().getRoot().getName();
        
        return treeName + "/" + name;
    }
    
    /**
     * ComListener Interface Implementation.
     *
     * Not used
     */
    public boolean serverTransferRequest(int color)
    {
        return false;
    }            
    public void changeServer(String fullName){}      
    public void connectionUpdate(Communications comm){}
}