import java.io.*;
import java.net.*;
import javax.swing.*;
import java.util.*;

/**
   A simple timezone server that can accept time conversion requests from clients.
   @author Sugiharto Widjaja
   @version 09/21/02
*/
public class TimeZoneServer
{
   /**
      Construct the timezone server
      @param area the area panel which will contains any incoming events
   */
   public TimeZoneServer(JTextArea area)
   {
      this.area = area;
   }

   /**
      Enable the server service.
      At the same time, monitor to check for incoming connections is run
      @throws Exception
   */
   public void enableService()
   {
      try
      {
         server = new ServerSocket(PORT_NUMBER);
         inMonitor = new Monitor(this, server);
         inMonitor.start();
      }
      catch(Exception e)
      {
         System.out.println("*** Cannot create a server socket at port " + PORT_NUMBER);
         System.exit(1);
      }
   }

   /**
      Get the text area where all the messages will be appended at
      @return the text area
   */
   public JTextArea getMedia()
   {
      return area;
   }

   // The port number of the server socket
   public static final int PORT_NUMBER = 10000;
   // The server socket
   private ServerSocket server;
   // Monitor to check for incoming connections
   private Monitor inMonitor;
   // The area panel
   private JTextArea area;
}

/**
   The monitor to check for incoming connections
   @author Sugiharto Widjaja
   @version 09/21/02
*/
class Monitor extends Thread
{
   /**
      Construct the monitor
      @param timeServer the timezone server
      @param server the server socket
   */
   public Monitor(TimeZoneServer timeServer, ServerSocket server)
   {
      this.timeServer = timeServer;
      this.server = server;
      area = timeServer.getMedia();
   }

   /**
      This will loop indefinitely to check for incoming connections.
      Upon the connection, each incoming connection will be served
      by one server thread
      @throws IOException
   */
   public void run()
   {
      for(;;)
      {
         try
         {
            incoming = server.accept();
            String clientName = incoming.getInetAddress().getHostAddress();
            area.append("*** Incoming connection from " + clientName + "\n");
            thd = new MultiThreadingServer(this, incoming); /*timeServer.getChecker(),
                                           timeServer.getConverter());*/
            thd.start();
         }
         catch(IOException e)
         {
           area.append("*** Error in accepting client socket\n");
         }
      }
   }

   /**
      Get the area panel
      @return the area panel
   */
   public JTextArea getMedia()
   {
      return area;
   }

   // The timezone server
   private TimeZoneServer timeServer;
   // The server socket
   private ServerSocket server;
   // The incoming socket
   private Socket incoming;
   // The server that will serve exactly one client.
   private MultiThreadingServer thd;
   // The area panel
   private JTextArea area;
}

/**
   This is the main part of this application. Each server will only serve one client.
   Since the Monitor will create one MultiThreadingServer object each time a client
   connect, we will have the number of these objects equals to the number of clients
   currently connected. This class is used to establish a multithreading concept.
   @author Sugiharto Widjaja
   @version 02/21/02
*/
class MultiThreadingServer extends Thread
{
   /**
      Create the server to server one client
      @param server the monitor
      @param incoming the incoming client
   */
   public MultiThreadingServer(Monitor server, Socket incoming)
   {
      this.server = server;
      this.incoming = incoming;
      ip = incoming.getInetAddress().getHostAddress();
   }


   /**
      Run infitely to listen for any incoming requests
      @throws IOException
   */
   public void run()
   {
      try
      {
         in = new BufferedReader(new InputStreamReader(incoming.getInputStream()));
         out = new PrintWriter(incoming.getOutputStream(), true);
         msg = new RequestListener(this);
         msg.start();
      }
      catch(IOException e)
      {
         getServerMedia().append("*** I/O error occured\n");
      }
   }

   /**
      Disconnect from client
      @throws Exception
   */
   public void disconnectFromClient()
   {
      try
      {
         if(incoming != null)
         {
            incoming.close();
         }
      }
      catch(Exception e)
      {
         getServerMedia().append("*** Cannot close client socket\n");
      }
   }

   /**
      Get the required reader to get message from client
      @return the reader
   */
   public BufferedReader getReader()
   {
      return in;
   }

   /**
      Get the required writer to send message to client
      @return the writer
   */
   public PrintWriter getWriter()
   {
      return out;
   }

   /**
      Get the ip address of client
      @return ip address of client
   */
   public String getID()
   {
      return ip;
   }

   /**
      Get the text area
      @return the text area
   */
   public JTextArea getServerMedia()
   {
      return server.getMedia();
   }

   // Monitor for incoming connections
   private Monitor server;
   // Ip address of client
   private String ip;
   // Client socket
   private Socket incoming;
   // Reader to get messages from client
   private BufferedReader in;
   // Writer to send message to client
   private PrintWriter out;
   // Listen to any incoming requests and process them
   private RequestListener msg;
}

/**
   Listener to listen to any incoming messages and to process them
   @author Sugiharto Widjaja
   @version 02/21/02
*/
class RequestListener extends Thread
{
   /**
      Construct the listener
      @param server the multithreading server
   */
   public RequestListener(MultiThreadingServer server)
   {
      this.server = server;
      media = server.getServerMedia();
      checker = new Checker();
      convertor = new TimeConversion();
      in = server.getReader();
      out = server.getWriter();
      ip = server.getID();
   }

   /**
      Run infinitely listening to incoming messages and process them
      @throws Exception
   */
   public void run()
   {
      try
      {
         boolean done = false;
         while(!done)
         {
            String inline = in.readLine();
            if(inline == null)
               done = true;
            else
            {
               if(processRequest(inline.trim()))
                  done = true;
            }
         }
         server.disconnectFromClient();
         media.append("*** Client " + ip + " has disconnected\n");
      }
      catch(Exception e)
      {
         media.append("*** Error in reading incoming messages from client" + ip + "\n");
      }
   }

   /**
      Send a message (e.g. request results, errors) to client
      @param msg the message
   */
   public void sendMessage(String msg)
   {
      if(out != null)
        out.println(msg);
   }

   /**
      Check for incoming messages and process them
      @param text the incoming mesage from client
   */
   private boolean processRequest(String text)
   {
      boolean clientQuit = false;
      StringTokenizer tokenizer = new StringTokenizer(text, ">");
      String opCode = tokenizer.nextToken();
      if(opCode.equals("Q"))
         clientQuit = true;
      else if(opCode.equals("D"))
         media.append("*** Client " + ip + " has diconnected\n");
      else if(opCode.equals("R"))
         processData(tokenizer.nextToken());
      return clientQuit;
   }

   /**
      Process the incoming query, check if there is any error in the query.
      If there is, send an error message to client. Otherwise, perform the
      time conversion and send the result back to client.
      @param data the query from client
   */
   private void processData(String data)
   {
      StringTokenizer token = new StringTokenizer(data, ";");
      String myCity = token.nextToken();
      String date = token.nextToken();
      String time = token.nextToken();
      String destCity = token.nextToken();
      media.append("*** Client " + ip + " query : " + data + "\n");
      if(!checker.checkTheInput(date + ";" + time))
      {
         sendMessage("E>");   // Indicate error in query
         media.append("*** Error in processing Client's query : " + data + "\n");
      }
      else
      {
         String output = convertor.clientRequest(data);
         sendMessage("O>" + output);   // Send the result
         media.append("*** Sending the result to client " + ip + " : " + output + "\n");
      }
   }

   // The multi threading server
   private MultiThreadingServer server;
   // Client IP address
   private String ip;
   // The client socket
   private Socket incoming;
   // The reader to read messages
   private BufferedReader in;
   // The writer to send messages
   private PrintWriter out;
   // Check for the validity of the query
   private Checker checker;
   // The time convertor
   private TimeConversion convertor;
   // The media to display all activities
   private JTextArea media;
}