import java.net.*;
import java.io.*;
import java.util.*;

public class JChatServer
{
   private ServerSocket serverSocket;
   int clientPort;
   ArrayList clients;
   ArrayList nicks;


   public void start() throws IOException
   {
      clients = new ArrayList();
      nicks = new ArrayList();
      Socket client;
      serverSocket = new ServerSocket(7777);
      getServerInfo();

      // Ciclo infinito in ascolto di eventuali Client
      while (true)
      {
         client = startListening();
         clients.add(client);
         getClientInfo(client);
         startListeningSingleClient(client);			
      }
   }


   private void startListeningSingleClient(Socket client)
   {
      Thread t = new Thread (new ParallelServer(client));
      t.start();
   }


   public class ParallelServer implements Runnable
   {
      Socket client;
      BufferedReader is;
      DataOutputStream os;

      public ParallelServer(Socket client)
      {
         this.client = client;
         try
         {
            // Stream di byte utilizzato per la comunicazione via socket
            is = new BufferedReader(new InputStreamReader(client.getInputStream()));
            os = new DataOutputStream(client.getOutputStream());
         }
         catch (Exception ex)
         {
         }
      }

      public void run() 
      {
         try
         {
            while (true)
            {
               sendMessage(receiveMessage(is), is, os, client);
            }
         }
         catch (Exception ex)
         {
         }
      }
   }

   public void getServerInfo()
   {
      // Informazioni sul Server in ascolto
      InetAddress indirizzo = serverSocket.getInetAddress();
      String server = indirizzo.getHostAddress();
      int port = serverSocket.getLocalPort();
      System.out.println("In ascolto Server: " + server + " porta: " + port);
   }


   public void getClientInfo(Socket client)
   {
      // Informazioni sul Client che ha effettuato la chiamata
      InetAddress address = client.getInetAddress();
      String clientInfo = address.getHostName();
      clientPort = client.getPort();
      System.out.println("In chiamata Client: " + clientInfo + " porta: "
	   + clientPort);	
   }


   public Socket startListening() throws IOException
   {
      System.out.println("In attesa di chiamate dai Client... ");
      return serverSocket.accept();		
   }


   public String receiveMessage(BufferedReader is) throws IOException
   {
      String strReceived = is.readLine();

      if(strReceived.startsWith("~"))
      {
         // Controlla se il nick è gia' presente
         if (nicks.contains(strReceived.substring(1)))
         {
            strReceived = "NOGOODUSER";
         }
         else
            nicks.add(strReceived.substring(1));
      }
      else if(strReceived.startsWith("^"))
      {
         nicks.remove(strReceived.substring(1));
      }

      System.out.println("Il Client ha inviato: " + strReceived);
      return strReceived ;
   }


   public void sendMessage(String recMsg, BufferedReader is, DataOutputStream os,
       Socket client ) throws IOException
   {
      int idxToRemove = -1;
      for(Iterator all = clients.iterator(); all.hasNext();)
      {			
         Socket cl = (Socket)all.next();			
         if(recMsg.startsWith("~"))
            addNewUser(recMsg, cl, client);
         else if(recMsg.startsWith("^"))
            idxToRemove = removeUser(recMsg, idxToRemove, cl, client);
         else
         {
            if(recMsg.equals("NOGOODUSER") && cl.equals(client))
            {
               new DataOutputStream(cl.getOutputStream()).writeBytes(recMsg + "\n");
               idxToRemove = removeUser(recMsg, idxToRemove, cl, client);
            }
            else
               broadcastMessage(recMsg, cl);
         }
      }
      // Se ci sono client da rimuovere, tale rimozione viene effettuata
      // solo al termine del ciclo per evitare problemi sugli indici.
      if (idxToRemove > -1)
         clients.remove(idxToRemove);
   }


   private void addNewUser(String recMsg, Socket cl, Socket client) throws IOException
   {
      if (cl.equals(client))
      {
         StringBuffer response = new StringBuffer();
         for (int i = 0; i < nicks.size(); i++)
         {
            response.append(nicks.get(i));
            response.append("~");
         }

         String strResponse = "OKNEWUSER" + response.toString();
         System.out.println(strResponse);
         new DataOutputStream(cl.getOutputStream()).writeBytes(strResponse + "\n");
      }
      else
      {
         String strName = recMsg.substring(1);
         new DataOutputStream(cl.getOutputStream()).writeBytes("NEWUSER" + strName +
            "\n");					
      }		
   }


   private int removeUser(String recMsg, int idxToRemove, Socket cl, Socket client)
      throws IOException
   {
      if (cl.equals(client))
      {
         close(cl.getInputStream(), cl.getOutputStream(), cl);
         idxToRemove = clients.indexOf(cl);
      }
      else
      {
         String strName = recMsg.substring(1);
         new DataOutputStream(cl.getOutputStream()).writeBytes("REMOVEUSER" + 
            strName + "\n");
      }
      return idxToRemove;
   }


   private void broadcastMessage(String recMsg, Socket cl) throws IOException
   {
      // Il messaggio "NOGOODUSER" deve essere inviato
      // solo ad uno specifico  client (gestito nella sendMessage).
      if(! recMsg.equals("NOGOODUSER"))
         new DataOutputStream(cl.getOutputStream()).writeBytes(recMsg + "\n");
   }


   public void close(InputStream is, OutputStream os, Socket client)
      throws IOException
   {
      System.out.println("Chiamata close");
      // chiusura della comunicazione con il Client
      os.close();
      is.close();
      System.out.println("Chiusura chiamata Client: " +
         client.getInetAddress().getHostName() + "su porta: " + clientPort);
      client.close();		
   }	
}