import Utilities.*;
import Synchronization.*;

class RendezvousRequestReply extends MyObject {

   private double[] a = null, b = null, c = null;
   private final int N = 5;
   private String fromWhom = null;

   public RendezvousRequestReply(String fromWhom) {
      super("RendezvousRequestReply");
      this.fromWhom = fromWhom;
      a = new double[N]; b = new double[N]; c = new double[N];
      for (int i = 0; i < N; i++) {
         a[i] = random(-N, N); b[i] = random(-N, N);
      }
   }

   public void doRequest() {
      nap(1+(int)random(1000*N));  // simulate some computation time
      for (int i = 0; i < N; i++) c[i] = a[i] + b[i];
   }

   public String toString() {
      String value = "\nfromWhom=" + fromWhom;
      value += "\na=";
      for (int i = 0; i < N; i++) value += " " + a[i];
      value += "\nb=";
      for (int i = 0; i < N; i++) value += " " + b[i];
      value += "\nc=";
      for (int i = 0; i < N; i++) value += " " + c[i];
      return value;
   }

   public String fromWhom() { return fromWhom; }
}

class Client extends MyObject implements Runnable {

   private int id = -1;
   private int napTime = 0;
   private EstablishRendezvous er = null;

   public Client(int id, int napTime, EstablishRendezvous er) {
      super("client " + id);
      this.id = id;
      this.napTime = napTime;
      this.er = er;
      System.out.println(getName() + " is alive, napTime=" + napTime);
      new Thread(this).start();
   }

   public void run() {
      int napping;
      RendezvousRequestReply rrr;
      while (true) {
         napping = 1 + (int)random(napTime);
         nap(napping);
         rrr = new RendezvousRequestReply(getName());
         System.out.println("age()=" + age() + ", " + getName()
            + " wants to rendezvous");
/*
 * In the following statement, we could discard the return value since the
 * server made its changes inside rrr and we are in the same JVM as the server
 * so the object reference we get back is to the same actual object we sent.
 * BUT if we ever change the program to the remote case where the client and
 * server are in different JVMs, then we would have to change the following
 * statement.  To avoid that future potential bug, we will store the return
 * value back into rrr, which works for both local and remote.
 */
         rrr = (RendezvousRequestReply)
            er.clientToServer().clientMakeRequestAwaitReply(rrr);
         System.out.println("age()=" + age() + ", " + getName()
            + " after rendezvous" + rrr);
      }
   }
}

class ServerThread extends MyObject implements Runnable {

   private Rendezvous r = null;

   public ServerThread(Rendezvous r) {
      super("ServerThread");
      this.r = r;
      new Thread(this).start();
   }

   public void run() {
      RendezvousRequestReply rrr
         = (RendezvousRequestReply) r.serverGetRequest();
      System.out.println("age()=" + age()
         + ", a ServerThread got a request from " + rrr.fromWhom());
      rrr.doRequest();
      System.out.println("age()=" + age()
         + ", a ServerThread finished the request from " + rrr.fromWhom());
      r.serverMakeReply(rrr);
   }
}

class MultiThreadedServer extends MyObject implements Runnable {

   private EstablishRendezvous er = null;
   private boolean threadedServer = false;

   public MultiThreadedServer(EstablishRendezvous er,
         boolean threadedServer) {
      super("MultiThreadedServer");
      this.er = er;
      this.threadedServer = threadedServer;
      System.out.println(getName() + " is alive");
      new Thread(this).start();
   }

   public void run() {
      while (true) {
         Rendezvous r = er.serverToClient();
         if (threadedServer) {
            System.out.println("age()=" + age() + ", Server got a request");
            // spawn a new thread to handle the request asynchronously
            new ServerThread(r);
         } else {  // do it here and now before handling any more clients
            RendezvousRequestReply rrr
               = (RendezvousRequestReply) r.serverGetRequest();
            System.out.println("age()=" + age()
               + ", Server got a request from " + rrr.fromWhom());
            rrr.doRequest();
            System.out.println("age()=" + age()
               + ", Server finished the request from " + rrr.fromWhom());
            r.serverMakeReply(rrr);
         }
      }
   }
}

class ClientServer extends MyObject {

   public static void main(String[] args) {

      // parse command line options, if any, to override defaults
      GetOpt go = new GetOpt(args, "Utc:n:R:");
      go.optErr = true;
      String usage = "Usage: -t -c numClients -n napTime -R runTime";
      int ch = -1;
      boolean threadedServer = false;
      int numClients = 3;
      int napTime = 4;
      int runTime = 20;      // seconds
      while ((ch = go.getopt()) != go.optEOF) {
         if      ((char)ch == 'U') {
            System.out.println(usage);  System.exit(0);
         }
         else if ((char)ch == 't') threadedServer = true;
         else if ((char)ch == 'c')
            numClients = go.processArg(go.optArgGet(), numClients);
         else if ((char)ch == 'n')
            napTime = go.processArg(go.optArgGet(), napTime);
         else if ((char)ch == 'R')
            runTime = go.processArg(go.optArgGet(), runTime);
         else {
            System.err.println(usage);  System.exit(1);
         }
      }
      System.out.println("ClientServer: numClients=" + numClients
         + ", napTime=" + napTime + ", runTime=" + runTime);
      if (threadedServer)
         System.out.println
            ("Each client request will be handled in its own server thread");

      // create the server
      EstablishRendezvous er = new EstablishRendezvous();
      MultiThreadedServer s = new MultiThreadedServer(er, threadedServer);

      // create the Clients
      for (int i = 0; i < numClients; i++) new Client(i, 1000*napTime, er);

      // let the Clients run for a while
      nap(runTime*1000);
      System.out.println("age()=" + age()
         + ", time to stop the threads and exit");
      System.exit(0);
   }
}

/* ............... Example compile and run(s)

D:\>javac clsv.java

D:\>java ClientServer -c4 -R15
ClientServer: numClients=4, napTime=4, runTime=15
MultiThreadedServer is alive
client 0 is alive, napTime=4000
client 1 is alive, napTime=4000
client 2 is alive, napTime=4000
client 3 is alive, napTime=4000
age()=170, client 0 wants to rendezvous
age()=280, Server got a request from client 0
age()=830, Server finished the request from client 0
age()=830, client 0 after rendezvous
fromWhom=client 0
a= 0.176308 -3.65113 1.90081 -0.351813 -4.85627
b= 2.97069 4.57178 3.25911 -4.1121 -3.60055
c= 3.147 0.920648 5.15992 -4.46391 -8.45682
age()=880, client 1 wants to rendezvous
age()=880, Server got a request from client 1
age()=1260, client 3 wants to rendezvous
age()=1980, client 0 wants to rendezvous
age()=2030, client 2 wants to rendezvous
age()=5710, Server finished the request from client 1
age()=5710, client 1 after rendezvous
fromWhom=client 1
a= 2.20636 0.22274 -4.80148 3.46159 -4.07722
b= -0.395222 -4.86496 0.661616 -0.734338 1.17361
c= 1.81114 -4.64222 -4.13986 2.72725 -2.90362
age()=5770, Server got a request from client 3
age()=8290, client 1 wants to rendezvous
age()=9610, Server finished the request from client 3
age()=9610, client 3 after rendezvous
fromWhom=client 3
a= -2.47209 -2.53815 1.53533 -3.71705 0.608039
b= -4.5575 -3.35972 -4.22435 1.70403 1.71254
c= -7.02959 -5.89787 -2.68902 -2.01302 2.32058
age()=9610, Server got a request from client 0
age()=11040, client 3 wants to rendezvous
age()=12140, Server finished the request from client 0
age()=12140, client 0 after rendezvous
fromWhom=client 0
a= 0.972876 -2.60852 -0.115246 -3.27382 3.06074
b= 1.83023 1.62499 2.28122 2.3162 -0.993425
c= 2.80311 -0.983532 2.16597 -0.957624 2.06732
age()=12190, Server got a request from client 2
age()=13240, client 0 wants to rendezvous
age()=14940, Server finished the request from client 2
age()=14940, client 2 after rendezvous
fromWhom=client 2
a= -4.45033 4.96301 1.15257 0.786161 -2.53553
b= 4.45445 3.62493 1.94064 2.83647 0.678649
c= 0.00411965 8.58794 3.09321 3.62263 -1.85688
age()=14940, Server got a request from client 1
age()=15160, time to stop the threads and exit

D:\>java ClientServer -t -c4 -R10
ClientServer: numClients=4, napTime=4, runTime=10
Each client request will be handled in its own server thread
MultiThreadedServer is alive
client 0 is alive, napTime=4000
client 1 is alive, napTime=4000
client 2 is alive, napTime=4000
client 3 is alive, napTime=4000
age()=930, client 3 wants to rendezvous
age()=930, Server got a request
age()=930, a ServerThread got a request from client 3
age()=1700, client 2 wants to rendezvous
age()=1700, Server got a request
age()=1700, a ServerThread got a request from client 2
age()=2800, client 0 wants to rendezvous
age()=2800, Server got a request
age()=2800, a ServerThread got a request from client 0
age()=3460, a ServerThread finished the request from client 2
age()=3460, client 2 after rendezvous
fromWhom=client 2
a= 3.47112 3.35398 -4.82645 -1.94492 -4.87518
b= -3.00398 0.173401 -2.38435 2.06435 4.58692
c= 0.467137 3.52738 -7.2108 0.11943 -0.288263
age()=3620, client 1 wants to rendezvous
age()=3620, Server got a request
age()=3620, a ServerThread got a request from client 1
age()=4280, a ServerThread finished the request from client 3
age()=4280, client 3 after rendezvous
fromWhom=client 3
a= -4.61478 -4.49431 0.426164 3.21181 0.756048
b= -4.54147 3.99372 -4.00696 2.89895 0.649383
c= -9.15625 -0.500589 -3.5808 6.11076 1.40543
age()=6210, client 2 wants to rendezvous
age()=6210, Server got a request
age()=6210, a ServerThread got a request from client 2
age()=6590, a ServerThread finished the request from client 2
age()=6590, client 2 after rendezvous
fromWhom=client 2
a= 4.87003 -4.63748 4.69299 -3.58343 2.96334
b= 2.32726 -3.63721 0.914748 -2.68447 1.90788
c= 7.19729 -8.27468 5.60774 -6.2679 4.87121
age()=7250, a ServerThread finished the request from client 0
age()=7250, client 0 after rendezvous
fromWhom=client 0
a= -1.36603 4.02213 2.92174 4.54016 -3.53111
b= -1.18313 -0.0211466 4.63714 -2.46519 -3.89137
c= -2.54917 4.00099 7.55889 2.07497 -7.42248
age()=8020, client 3 wants to rendezvous
age()=8020, Server got a request
age()=8020, a ServerThread got a request from client 3
age()=8020, client 2 wants to rendezvous
age()=8020, Server got a request
age()=8020, a ServerThread got a request from client 2
age()=8400, a ServerThread finished the request from client 1
age()=8400, client 1 after rendezvous
fromWhom=client 1
a= -0.738761 -2.9057 3.12267 -1.12499 -1.47475
b= 1.55269 1.43304 -0.516861 3.31844 -0.544111
c= 0.813931 -1.47266 2.60581 2.19345 -2.01886
age()=9010, a ServerThread finished the request from client 2
age()=9010, client 2 after rendezvous
fromWhom=client 2
a= -0.0128668 4.16441 -2.20455 -1.08956 -1.67223
b= -2.4696 3.96719 -0.51026 -2.8545 3.19811
c= -2.48247 8.13161 -2.71481 -3.94405 1.52588
age()=10050, time to stop the threads and exit
                                            ... end of example run(s)  */
