import java.util.*;

/** A simulation of this scenario, for illustrating trace biography:
    <blockquote>
       Billee earns 300/week and rents a place at 1000/month.<br>
       The landlord, Lordy, directly debits Billee's account monthly.<br>
       If the rent doesn't come through, Lordy gets anxious,<br>
       and calls on bill collectors who retry weekly.<br>
    </blockquote>

    @author CarlManning, caroma@ai.mit.edu<br>
    Copyright (c) 1999 Massachusetts Institute of Technology
**/

class SharedAccountBio { 
  public static void run(int msPerYear) {
    final Account account = new Account("MyChecking", 0);
    final int yearLength = msPerYear; // e.g., 3000ms
    final int monthLength = yearLength/12;
    final int weekLength = yearLength/52;
    final long yearStart = System.currentTimeMillis();
    final long yearEnd = yearStart + yearLength;

    Thread billee = new Thread("Billee") {
      public void run() {
	int salary = 300; 
	for (int week = 1; week <= 52; week++) {
	  // add a note in this thread's call record
	  sleepTo(yearStart + week * weekLength);
	  account.deposit(salary);
	}
      }};

    Thread lordy = new Thread("Lordy") {
      public void run() {
	final int rent = 1000; final int penalty = 10; 
	final Collection outstandingCollectors =
	  Collections.synchronizedCollection(new LinkedList());
	int collectorCount = 0;
	for (int month = 1; month <= 12; month++) {
	  try { account.withdraw(rent); }
	  catch (IllegalArgumentException overdraft) {

	    // if not paid, create and start bill collector to retry weekly
	    Thread collector = new Thread("Collector"+(++collectorCount)) {
	      int due = rent + penalty;
	      long startDate = System.currentTimeMillis();
	      int week = 0;
	      public void run() { 
		while (System.currentTimeMillis() <= yearEnd) { 
		  try {
		    account.withdraw(due);
		    break;
		  } catch (IllegalArgumentException overdraft2) { 
		    week++;     due += penalty; 
		    sleepTo(startDate + week * weekLength);
		  }
		}
		outstandingCollectors.remove(this);
		return;
	      }}; // end collector

	    outstandingCollectors.add(collector);
	    collector.start(); Thread.yield();
	  }
	  sleepTo(yearStart + month * monthLength);
	}
	return;
      }}; // end lordy

    billee.start();
    lordy.start();
    try { billee.join(); lordy.join(); } catch (InterruptedException e) {}
    System.out.println("Billee's final balance: "+ account.getBalance());
    return;
  }
  static void sleepTo(long wakeupTime) {
    long now = System.currentTimeMillis();
    try { Thread.sleep(wakeupTime - now); }
    catch (InterruptedException e) {}
    catch (IllegalArgumentException e) { } // time has past
  }
  // test
  public static void main(String[] parameters) {
    int parameter =
      Integer.parseInt(parameters.length == 0 ? "12" : parameters[0]);
    run(1000 * parameter);
  }
}


/** A class for a shared account for deposits and withdrawals **/
class Account {  
  /** name **/
  private String myName;
  /** balance should be >= 0 **/
  private int myBalance;
  /** Constructor:  initBalance should be >= 0. **/
  public Account(String name, int initBalance) { 
    myName = name; 
    myBalance = initBalance;
  }
  /** return current balance of account **/
  public synchronized int getBalance() {
    return myBalance;
  }
  /** deposit amount >= 0 **/
  public synchronized void deposit(int amount) {
    myBalance += amount;
  }
  /** withdraw amount >= 0 **/
  public synchronized void withdraw(int amount) {
    if (amount <= myBalance)
      myBalance -= amount;
    else 
      throw new IllegalArgumentException("Overdraft");
  }
  public String toString() {
    return "Account(name="+myName+", balance="+myBalance+")"; }
}
