//   Course Number: COSC 650 Data Comm/Networks
//    Program Name: JavaChat
//  Project Number: 2
//         Authors: Brian Hoffman
//                  Mike McKinney
//                  Javier Carrasco
//     Description:  This class creates a thread used by the server to listen 
//                   for and process incoming messages.

import java.net.*;  //package required for socket connection
import java.io.*;   //package required for data streams and stdout
import java.util.*; //package required for Map data structure

public class ThreadedChatServer {
        
    private HashMap roomMap;
    private HashMap clientMap;

    
    /**
     * ------------------------------------------------------------------------
     * This method will return a list of rooms as a string
     *
     * The method first calls the keyset() method of the roomMap
     * which should return a Set of all keys (the keys are the 
     * room names). It then calls iterator() to return an Iterator
     * over the Set.  The iterator is then looped over creating 
     * a comma separated string of the keys (rooms) which is 
     * to the call
     * ------------------------------------------------------------------------
     */ 
    public String getRoomList()
    {
        String roomlist = "";
        Map.Entry roomEntry;
        ChatRoom tempRoom;
        Object rooms[] = roomMap.entrySet().toArray();

        for(int i=0; i<rooms.length; i++)
        {
            roomEntry = (Map.Entry)rooms[i];
            tempRoom =(ChatRoom)roomEntry.getValue();
            if(!tempRoom.isPrivate())
            {
                if(roomlist.equals(""))
                    roomlist = (String)roomEntry.getKey();
                else
                    roomlist = roomlist + "," + (String)roomEntry.getKey();
            }
        }
        return(roomlist);
    }
        
    
    /**
     * ------------------------------------------------------------------------
     * method to add the user userName to room roomName.  Will return false if
     * unable to add to room. Most commonly, the cause is that the room doesnt
     * exist 
     * 
     * The method first checks to see that the room already exists. If so, it
     * will access the ChatRoom object and call its addUser method to insert
     * the user into the room.  It will then get the ChatClient object for the
     * user and send a roomAccept message containing a list of the room members.  
     * Next it calls the broadcast method for the room to send a roomUpdate 
     * message and returns true to signify success
     * ------------------------------------------------------------------------
     */
    public boolean addUserToRoom(String userName, String roomName)
    {
        boolean success;
        
        if(((ChatRoom)roomMap.get(roomName)).isPrivate())
        {
            return(false);
        }
        
        
        if(roomMap.containsKey(roomName))
        {
            success = ((ChatRoom)roomMap.get(roomName)).addUser(userName);
            if(!success)
            {
                ChatMessage m = new ChatMessage("roomDeny", "", roomName, "");
                ((ChatClient)clientMap.get(userName)).sendMessage(m);
                return(false);
            }
            else
                return(true);
        }
        else
            return(false);
    }
    
    /**
     * ------------------------------------------------------------------------
     * removeUserFromRoom
     * ------------------------------------------------------------------------
     */
    public boolean removeUserFromRoom(String userName, String roomName)
    {
        if(roomMap.containsKey(roomName))
        {
            ((ChatRoom)roomMap.get(roomName)).removeUser(userName);
            return(true);
        }
        else
            return(false);
    }
    
    /**
     * ------------------------------------------------------------------------
     *  Method will insert ChatRoom roomObj into roomMap with key  roomName.
     *  Returns false if room already exists in roomMap
     * ------------------------------------------------------------------------
     */
    public boolean insertRoom(String roomName, ChatRoom roomObj)
    {
        if(roomMap.containsKey(roomName))
        {
            ((ChatClient)clientMap.get(roomObj.getOwner())).sendMessage(new 
                 ChatMessage("denyRoom", "", roomName, ""));
            return(false);
        }
        else
        {
            roomMap.put(roomName, roomObj);
            this.clientBroadcast(new ChatMessage("addRoom", "", roomName, roomObj.getOwner()));
            System.out.println("New room broadcast sent");
            return(true);
        }   
    }
    
    /**
     * ------------------------------------------------------------------------
     * getRoom 
     * ------------------------------------------------------------------------
     */
    public ChatRoom getRoom(String roomName)
    {
        if(roomMap.containsKey(roomName))
            return((ChatRoom)roomMap.get(roomName));
        else
            return(null);
    }
    
    /**
     * ------------------------------------------------------------------------
     * bootUser 
     * ------------------------------------------------------------------------
     */    
    public void bootUser(String user, ChatMessage m)
    {
        if(roomMap.containsKey(m.getRoomName()))
        {
            ChatRoom tempRoom = (ChatRoom)roomMap.get(m.getRoomName());
            tempRoom.bootUser(user, m);
        }
    }
    
    /**
     * ------------------------------------------------------------------------
     * cleanUser(String user)
     *
     * This method is used to clean a user from the server. This action is
     * most likely to be performed if a user exits the client software. The
     * client will send a message just prior to closing which will invoke this 
     * method whne received.  I also will invoke it as the exception handling 
     * when the connection to the client is broken.
     *------------------------------------------------------------------------*/
    
    public void cleanUser(String user)
    {
        String tempRoom;
        Object rooms[] = roomMap.keySet().toArray();
        for(int i=0; i<rooms.length; i++)
        {
            tempRoom = (String)rooms[i];
            this.removeUserFromRoom(user,tempRoom);
        }
        this.deleteClient(user);
    }
    
    
    /**
     * ------------------------------------------------------------------------
     * sendChat
     * ------------------------------------------------------------------------
     */
    public boolean sendChat(String roomName, ChatMessage message)
    {
        if(roomMap.containsKey(roomName))
        {
            //this.roomBroadcast(roomName, message);
            this.getRoom(roomName).broadcast(message);
            return(true);
        }
        else
            return(false);
    }
       
    /**
     * ------------------------------------------------------------------------
     * Method will remove the ChatRoom Object associated with
     * the key roomName.  Returns false if the key roomName does
     * not exist.
     * ------------------------------------------------------------------------
     */
    public boolean deleteRoom(String roomName)
    {
        if(roomMap.containsKey(roomName))
        {
            roomMap.remove(roomName);
            return(true);
        }
        else
            return(false);
    }
    
    /**
     * ------------------------------------------------------------------------
     * Returns the ChatClient object that corresponds to userName in
     * the clientMap.  Will return null if the either userName is
     * not a key in the map or if the value for userName is null
     * ------------------------------------------------------------------------
     */
    public ChatClient getClient(String userName)
    {
        if(clientMap.containsKey(userName))
            return((ChatClient)clientMap.get(userName));
        else
            return(null);
    }
    
    /**
     * ------------------------------------------------------------------------
     * Inserts the ChatClient clientObj into the clientMap with key
     * clientName.  Will return false if key clientName already exists.
     * ------------------------------------------------------------------------
     */
    public boolean insertClient(String clientName, ChatClient clientObj)
    {
        if(clientMap.containsKey(clientName))
            return(false);
        else
        {
            clientMap.put(clientName, clientObj);
            return(true);
        }
    }

    /**
     * ------------------------------------------------------------------------
     * Will remove the ChatClient Object associated with the key 
     * clientName.  Returns false if the key roomName does not exist.
     * ------------------------------------------------------------------------
     */
    public boolean deleteClient(String clientName)
    {
        if(clientMap.containsKey(clientName))
        {
            clientMap.remove(clientName);
            return(true);
        }
        else
            return(false);
    }
    
    /**
     * ------------------------------------------------------------------------
     * Private method to send a broadcast to all users in the clientMap.
     * The method will first get an iterator for the entire set of keys.
     * It will then get the ChatClient object for each key and then call
     * the sendMessage method to write to its output stream
     * ------------------------------------------------------------------------
     */
    private void clientBroadcast(ChatMessage brMes)
    {
        String nextClient;
        Iterator clientIter = clientMap.keySet().iterator();
        while(clientIter.hasNext())
        {   
            nextClient = (String)clientIter.next();
            System.out.println("broadcasting: " + nextClient);
            ChatClient tempClient = (ChatClient)clientMap.get(nextClient);
            tempClient.sendMessage(brMes);
        }
    }
  
    /**
     * ------------------------------------------------------------------------
     * roomBroadcast
     * ------------------------------------------------------------------------
     */
   /* private void roomBroadcast(String roomName, ChatMessage message)
    {
        this.getRoom(roomName).broadcast(message);
    }
    */
    
    /**
     * ------------------------------------------------------------------------
     * requestPrivate
     *-------------------------------------------------------------------------
     */
    
    public void requestPrivate(ChatMessage m)
    {
        if(m.getSender().equals(m.getMsg()))
        {
            return;
        }
        
        if(clientMap.containsKey(m.getMsg()))
        {
            ((ChatClient)clientMap.get(m.getMsg())).sendMessage(m);
            //System.out.println("requestPrivate forwarded to " + ((ChatClient)clientMap.get(m.getMsg()).getUser()));
        }
    }
    
    /**
     * ------------------------------------------------------------------------
     * insertPrivRoom
     *
     * This method inserts a private room created and passed as an argument. The
     * room is insert in the roomMap and the participants are broadcast a roomAccept
     * message.
     * -------------------------------------------------------------------------
     */ 
    
    public void insertPrivRoom(String roomName, ChatRoom roomObj)
    {
        if(!(roomMap.containsKey(roomName)))
        {
            roomMap.put(roomName, roomObj);
            ChatMessage privAccept = new ChatMessage("roomAccept", "", roomName, "private," + roomObj.getMembers());
            roomObj.broadcast(privAccept);
        }
    }
            
    /**
     * ------------------------------------------------------------------------
     * denyPrivRoom
     *
     * This method is called to notify that a client denied a request for a 
     * private room.  The method will notify the user that requested by forwarding
     * the message
     * ------------------------------------------------------------------------
     */
    
    public void denyPrivate(ChatMessage m)
    {
        if(clientMap.containsKey(m.getMsg()))
        {
            ((ChatClient)clientMap.get(m.getMsg())).sendMessage(m);
        }
    }
    
    /**
     * ------------------------------------------------------------------------
     * default constructor 
     * ------------------------------------------------------------------------
     */
    public ThreadedChatServer()
    {
        //initialize clients and chatrooms HashMaps
        roomMap = new HashMap();
        clientMap = new HashMap();
        
        try 
	{
            //set the port to be used and max connections
            int port = 6789;
            int sockMax = 100;
            ServerSocket newsrv;
            
            newsrv = new ServerSocket(port, sockMax);
            System.out.println("Waiting for connections");  
            

            
            while(true)
            {
                //accepts new client connection
                Socket clientSocket = newsrv.accept();
     
                System.out.println("Socket created");

                new SocketThread(clientSocket, this).start();
            }
        }
        catch (Exception e)
        {
            System.out.println("Error creating socket");
        }
   }
    
    /**
     * ------------------------------------------------------------------------
     * main - executes application
     * ------------------------------------------------------------------------
     */  
   public static void main( String args[] )
   {
       ThreadedChatServer chatServer = new ThreadedChatServer(); 
   }
}  