import umontreal.iro.lecuyer.simevents.*;
import umontreal.iro.lecuyer.simprocs.*;
import umontreal.iro.lecuyer.rng.*;
import umontreal.iro.lecuyer.probdist.*;
import umontreal.iro.lecuyer.randvar.*;
import umontreal.iro.lecuyer.stat.*;

public class BankProc {
   double   minute = 1.0 / 60.0;
   int      nbServed;           // Number of customers served so far
   double   meanDelay;          // Mean time between arrivals
   SimProcess  nextCust;        // Next customer to arrive
   Resource tellers         = new Resource (0, "The tellers");
   RandomStream  streamArr  = new MRG32k3a(); // Customer's arrivals
   ErlangGen genServ        = new ErlangConvolutionGen
           (new MRG32k3a(), new ErlangDist (2, 1.0/minute));
   RandomStream  streamTeller  = new MRG32k3a(); // Number of tellers
   RandomStream  streamBalk    = new MRG32k3a(); // Balking decisions
   Tally    statServed    = new Tally ("Nb. served per day");
   Tally    avWait        = new Tally ("Average wait per day (hours)");

   class OneDay extends SimProcess {
      public void actions() {
         int nbTellers;         // Number of tellers today.
         nbServed = 0;
         tellers.setCapacity (0);
         tellers.waitList().initStat();
         meanDelay = 2.0 * minute;
         // It is 9:45, start arrival process.
         (nextCust = new Customer()).schedule
           (ExponentialGen.nextDouble (streamArr, 1.0/meanDelay));
         delay (15.0 * minute);
         // Bank opens at 10:00, generate number of tellers.
         double u = streamTeller.nextDouble();
         if (u >= 0.2) nbTellers = 3;
         else if (u < 0.05)  nbTellers = 1;
         else nbTellers = 2;
         tellers.setCapacity (nbTellers);
         delay (1.0);   // It is 11:00, double arrival rate.
         nextCust.reschedule (nextCust.getDelay() / 2.0);
         meanDelay = minute;
         delay (3.0);   // It is 14:00, halve arrival rate.
         nextCust.reschedule (nextCust.getDelay() * 2.0);
         meanDelay = 2.0 * minute;
         delay (1.0);   // It is 15:00, bank closes.
         nextCust.cancel();
      }
   }

   class Customer extends SimProcess {
      public void actions() {
         (nextCust = new Customer()).schedule
            (ExponentialGen.nextDouble (streamArr, 1.0/meanDelay));
         if (!balk()) {
            tellers.request (1);
            delay (genServ.nextDouble());
            tellers.release (1);
            nbServed++;
         }
      }

      private boolean balk() {
         int n = tellers.waitList().size();
         return n > 9 || (n > 5 && 5.0*streamBalk.nextDouble() < n - 5);
      }
   }

   public void simulOneDay() { 
      SimProcess.init();
      new OneDay().schedule (9.75);
      Sim.start();
      statServed.add (nbServed);
      avWait.add (tellers.waitList().statSize().sum());
   }

   public void simulateDays (int numDays) {
      tellers.waitList().setStatCollecting (true);
      for (int i=1; i<=numDays; i++)  simulOneDay();
      System.out.println (statServed.report());
      System.out.println (avWait.report());
   }

   public static void main (String[] args) { 
       new BankProc().simulateDays (100);
   }
}
