// Simulation of more workers and items!
// Revision record:
// Draft by DIS 2/27/2001
// Revised by Swamy 3/11/2001
// Some additional comments by DIS 3/11/2001

public class p4sp01 {
    public static int run=1; // initial number of cycles of the simulation
    
    // Welcome the user and start the simulation:
       public static void main(String[] args) {
	   System.out.print("Welcome to BELT! Enter \"p\" to activate pausing: ");
	   TokenReader in = new TokenReader(System.in);
	   runSimulation(in,in.readString()); // flag for pausing
       } //method main
    
    // Run all runs of the simulation:
       public static void runSimulation(TokenReader in, String pause) {
	   // Initialize stuff
  	      Worker w1=new Worker(1); Worker w2=new Worker(2);
	      Worker w3=new Worker(3); Worker w4=new Worker(4);
	      Belt b=new Belt(new Tray(),new Tray(),new Tray(),new Tray());
	      
	   // Main loop to perform simulation runs
	      while (b.getLastTrayItems()==0)
		  {
		      System.out.println("Current run: "+ run++ ); // post incr!
		      b.Shift_and_Refill();
		      b.printContents("Old");
		      b.extractContents(w1,w2,w3,w4);
		      b.printContents("New");
		      decreaseEff(w1,w2,w3,w4);
		      b.report();
		      if (pause.equals("p")) in.waitUntilEnter(); // pause
		  }
	      
	   // Report results:
	      System.out.println("The Workers are overloaded!");
	      System.out.println("Leftovers: "+ b.getLastTrayItems() );

    } // method runSimulation
    
    // Decrease worker efficiency for next run:
       public static void decreaseEff(Worker w1, Worker w2, Worker w3, Worker w4) {
	   w1.changeEfficiency();
	   w2.changeEfficiency();
	   w3.changeEfficiency();
	   w4.changeEfficiency();
       }

} // Class Simulation


// Class to contain irritating random() code. Not the best place, 
// to store them necessarily:
class MyMath {
    // An error term to account for precision in double arithmetic
       public static double error=1e-5;

    // Return a random integer in range [low,high]:
       public static int random(int low, int high) {
	   return (int)(Math.random()*(high-low+1) + low);
       }

    // Return a random double in range [low,high]:
       public static double random(double low, double high) {
	   // Cannot say Math.random()*(high-low+1) since 1 is no longer the
	   // "smallest" double value - causes trouble for eg. if high=1, low=0.85
	      double a=Math.random()*(high-low+error)+low;
	   
	   // Check if a>high - could be because error>0
	      if (a>high) a=high;
	      return a;
       }

} // class MyMath


// Class Belt has Trays
class Belt {
    private int totalItems; // total amount of items extracted from all Trays
    private Tray tray1;
    private Tray tray2;
    private Tray tray3;
    private Tray tray4;
    
    // Create new Belt with four Trays:
       Belt(Tray t1, Tray t2, Tray t3, Tray t4) {
	   tray1 = t1; tray2 = t2; tray3 = t3; tray4 = t4;
       }
    
    // Print contents of all trays:
       public void printContents(String s) {
	   System.out.print(s+" contents: ");
	   System.out.print(tray1.getItems()+" ");
	   System.out.print(tray2.getItems()+" ");
	   System.out.print(tray3.getItems()+" ");
	   System.out.print(tray4.getItems());
	   System.out.println();
       }
    
    // Workers extract items from the trays in front of them
    // increment totalItems and update leftovers
        public void extractContents(Worker w1, Worker w2, Worker w3, Worker w4) {
	    // each worker takes items from CURRENT tray in front of worker:
	       totalItems += w1.extractItems(tray1)+w2.extractItems(tray2) + 
		   w3.extractItems(tray3)+w4.extractItems(tray4);
	}
    
    // Shift Trays to the right and refill first tray
       public void Shift_and_Refill() {
	   tray4 = tray3;
	   tray3 = tray2;
	   tray2 = tray1;
	   tray1 = new Tray();  // give the first tray new items
	   tray1.fillTray();
       }
    
    // Report findings:
       public void report() {
	   System.out.println("Total items taken, so far: "+totalItems);
       }
    
    // Retrieve items on last tray:
       public int getLastTrayItems() { 
	   return tray4.getItems(); 
       }
    
} // Class Belt


class Worker {
    
    // Setup variables representing worker capacity, 
    // efficiency, efficiency loss:
       static private final int LOW_ITEM  = 2; 
       static private final int HIGH_ITEM = 6; 
       static private final double HIGH_EFF  = 1.0;  
       static private final double LOW_EFF   = 0.85;
       static private final double FACTOR    = 0.95;  
    
    // Stuff specific to each individual worker
       private int position;         //  position in the line, from L to R
       private double efficiency;    //  eff initially ranges 85% to 100%
    
    // Construct a new Worker and choose a random efficiency:
       public Worker(int position) {
	   setEfficiency(); this.position = position;
       }
    
    // Set a random efficiency:
       private void setEfficiency() { 
	   efficiency = MyMath.random(LOW_EFF,HIGH_EFF);
       }
    
    // Reduce current Worker's efficiency:
       public void changeEfficiency() { efficiency *= FACTOR; }
    
    // Extract items from input Tray and update Tray's amount of items:
       public int extractItems(Tray tr) {
	   
	   // Amount of items current Worker is *able* to take:
	      int items = (int)(efficiency*MyMath.random(LOW_ITEM,HIGH_ITEM) );
	   
	   // Worker may choose to skip some boxes.
	   // Reduce items depending on position and willingness to skip items:
	      switch(position) {
	      case 1: items -= (items*MyMath.random(0,1))/2; break;
	      case 2: items -= (items*MyMath.random(0,1))/3; break;
	      case 3: items -= (items*MyMath.random(0,1))/4; break;
	      case 4: break; 
	      default: System.out.println("Unknown worker"+position);System.exit(0);
	      }
	      
	   // Can remove at most the no. of items on the tray
	   // So if items > # of items on tray empty the tray
	      items=Math.min(items,tr.getItems()); 
	      tr.removeItems(items);
	      
	   // Return # of extracted items(possibly 0)
	      return items;
       }

} // Class Worker


class Tray { 
    static private final int MIN = 0; // minimum amount of items stored on a Tray
    static private final int MAX = 5; // maximum amount of items stored on a Tray    
    private int items;                // amount of items on current Tray
    
    // Construct an empty Tray
       public Tray() { }
    
    // Return the number of items on current Tray
       public int getItems() { return items; }
    
    // Reduce the number of items on current Tray by r
    // If r>no. of items currently on tray set items to 0
       public void removeItems(int r) { items=items-r; if (items<0) items=0; }
    
    // Store a random amount of items on the current Tray
       public void fillTray() { items = MyMath.random(MIN,MAX); }
    
    // Return a Boolean value depending on whether or not current Tray is empty
       public boolean isEmpty() { return (items==0); }

} // Class Tray


