package cafe;

public class Cafe {

  public static boolean DEBUG = true;
//public static final int MAX_RUNS = 20;
  public static final double MAX_RUNS = Double.POSITIVE_INFINITY;
  public static boolean noOutput = true;

  private int run;

  public static void main(String[] args) {

    System.out.println("Cafeteria Tray Model");
    int total = 0;
    int numRuns = 1000;
    Cafe x;

    for (int i=0; i<numRuns; i++) {
      x = new Cafe();
      total += x.run;
    }
    System.out.println("Average before death: " + (double)total/(double)numRuns);
  }

  Cafe() {
    Worker A = new Worker(0.50, 0.50);  // create new workers (A=left, D=right)
    Worker B = new Worker(0.50, 0.333);
    Worker C = new Worker(0.50, 0.25);
    Worker D = new Worker(1.00, 0.00);

    A.assignTray(new Tray(0));    // assign empty trays
    B.assignTray(new Tray(0));
    C.assignTray(new Tray(0));
    D.assignTray(new Tray(0));

    do {
      D.assignTray(C.getTray());    // pass the trays to the next worker
      C.assignTray(B.getTray());
      B.assignTray(A.getTray());
      A.assignTray(new Tray());

      showProgressBefore(A,B,C,D);

      A.removeItems();    // workers remove their items
      B.removeItems();
      C.removeItems();
      D.removeItems();

      showProgressAfter(A,B,C,D);

      A.slowdown();   // workers get tired
      B.slowdown();
      C.slowdown();
      D.slowdown();

    } while( ++run < MAX_RUNS && D.getTray().isEmpty() );

    // Display Results
    if (noOutput == false) {
      if ( D.getTray().getNumItems() != 0 ) {
        System.out.println("Overload on run: " + run);
      } else {
        System.out.println("Passed " + MAX_RUNS + " runs without problems");
      }

      System.out.println("System Status:");

      System.out.println(A.toString());   // output status of workers
      System.out.println(B.toString());
      System.out.println(C.toString());
      System.out.println(D.toString());

      System.out.println(A.getTray().toString());   // output status of trays
      System.out.println(B.getTray().toString());
      System.out.println(C.getTray().toString());
      System.out.println(D.getTray().toString());
    }
  }

  private void showProgressAfter(Worker A, Worker B, Worker C, Worker D) {
    if (noOutput == false) {
      System.out.println("New contents: " + A.getTray().getNumItems() + " "
                                          + B.getTray().getNumItems() + " "
                                          + C.getTray().getNumItems() + " "
                                          + D.getTray().getNumItems());
    }
  }

  private void showProgressBefore(Worker A, Worker B, Worker C, Worker D) {
    if (noOutput == false) {
      System.out.println("Total items taken, so far: " + Tray.throughput);
      System.out.println("Current run: " + run);
      System.out.println("Old contents: " + A.getTray().getNumItems() + " "
                                          + B.getTray().getNumItems() + " "
                                          + C.getTray().getNumItems() + " "
                                          + D.getTray().getNumItems());
    }
  }
}

class Worker {
  private static final int MIN_REMOVABLE = 2;
  private static final int MAX_REMOVABLE = 6;
  private static final double slowDownPercentage = 0.05;
  private static int numWorkers;

  private int workerID;

  private int numTraysDone;
  private double efficiency, forgetfulness, droppage;

  private Tray myTray;

  /**
   * Creates a new worker with efficiency between 85% and 100%
   */
  public Worker() {
    efficiency = (15.0*Math.random() + 85.0)/100.0;
    droppage = 0.0;
    workerID = ++numWorkers;
  }

  /**
   * Creates a new worker with efficiency and droppage amount
   */
  public Worker(double forgetfulness, double droppage) {
    this();
    this.droppage = droppage;
    this.forgetfulness = forgetfulness;
  }

  /**
   * Assigns a particular tray to this worker
   */
  public void assignTray(Tray t) {
    myTray = t;
  }

  /**
   * Gets the tray that this worker is using
   */
  public Tray getTray() {
    return myTray;
  }

  /**
   * Removes a certain amount of items from the tray, based on <BR>
   * forgetting, and a maximum number to remove
   */
  public void removeItems() {
    int numToRemove;
    int maxRemoval = maxRemovable();

    if (this.forget()) {
      numToRemove = (int)(droppage * maxRemoval);
      myTray.removeItems(numToRemove);
    } else {
      myTray.removeItems(maxRemoval);
    }
  }

  /**
   * Make the worker perform 5% less efficient
   */
  public void slowdown() {
    efficiency *= (1-slowDownPercentage);
  }

  /**
   * Decides whether or not the worker is slacking off
   */
  private boolean forget() {
    return ( Math.random() > forgetfulness );
  }

  /**
   * Determines based on efficiency, how many items the worker can remove
   */
  private int maxRemovable() {
    return (int)(MIN_REMOVABLE + (efficiency * (MAX_REMOVABLE - MIN_REMOVABLE + 1)));
  }

  /**
   * Forms a user-friendy string representation of a Worker
   */
  public String toString() {
    return ("Worker #" + workerID + " with efficiency of " + ((int)((10000.0*efficiency))/100.0)
         + "% and droppage of " + ((int)((10000.0*droppage))/100.0) + "%");
  }
}

class Tray {
  private static final int LOW_ITEMS = 0;
  private static final int HIGH_ITEMS = 5;
  private static int totalNumTrays;
  public static int throughput;

  private int numItems;
  private int trayID;

  /**
   * Creates a tray with a random number of items in it
   */
  public Tray() {
    numItems = (int)(Math.random()*(HIGH_ITEMS-LOW_ITEMS)) + LOW_ITEMS;
    trayID = ++totalNumTrays;
  }

  /**
   * Creates a tray with a specific number of items in it
   */
  public Tray(int i) {
    this();
    setNumItems(i);
  }

  /**
   * Returns the number of items in a tray
   */
  public int getNumItems() {
    return numItems;
  }

  /**
   * Sets the number of items in a tray
   */
  public void setNumItems(int k) {
    if (k >= 0)
      numItems = k;
    else
      numItems = 0;
  }

  /**
   * Removes a certain number of items from a tray
   */
  public void removeItems(int k) {
    if (k > numItems) {
      numItems = 0;
      throughput += numItems;
    } else {
      numItems -= k;
      throughput += k;
    }
  }

  /**
   * Returns true the tray is empty
   */
  public boolean isEmpty() {
    return (numItems == 0);
  }

  /**
   * Forms a user-friendly representation of a tray object
   */
  public String toString() {
    return("Tray #" + trayID + " has " + getNumItems() + " items on it.");
  }
}
