import edu.mit.ai.psg.traveler.*;
import edu.mit.ai.psg.ui.outliner.*;
import edu.mit.ai.psg.jexa.*;
import java.lang.reflect.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;

/** 
    A simulation of this scenerio: 
    <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>

    This version has no inner classes, Jeva does not implement inner classes.
    To run, cd to the directory containing this file.  Make sure
    no <tt>.class</tt> files exist in this directory.  Then run
    <pre>
      java edu.mit.ai.psg.traveler.jevaHooks.TraceCallsEvalHook SharedAccountBioNoInner
    </pre>    
**/
public class SharedAccountBioNoInner { 

  public static void main(String[] ignore) {
    final Account account = new Account("MyChecking", 0);
    Thread billee = new BilleeThread("Billee", account);
    Thread lordy = new LordyThread("Lordy", account); 
    System.err.println("yearLength = "+yearLength);
    System.err.println("monthLength = "+monthLength);
    System.err.println("weekLength = "+weekLength);
    System.err.println("yearStart = "+yearStart);
    System.err.println("yearEnd = "+yearEnd);
    billee.start();
    lordy.start();
    try { billee.join(); lordy.join(); } catch (InterruptedException e) {}
    System.out.println(account.getBalance());
    return;
  }
  static void sleepTo(long wakeupTime) {
    long now = System.currentTimeMillis();
    System.err.println(Thread.currentThread().getName()+
		       " sleeping "+(wakeupTime - now)+"ms");
    if (wakeupTime > now) {
      try { Thread.sleep(wakeupTime - now); }
      catch (InterruptedException e) {}
      catch (IllegalArgumentException e) { } // time has past
    } else Thread.yield();
  }
  /** simulated time parameters **/
  static final int yearLength = 24000; // ms; adjust depending on speed
  static final int monthLength = yearLength/12;
  static final int weekLength = yearLength/52;
  static /* final*/ long yearStart = System.currentTimeMillis();
  static /* final*/ long yearEnd = yearStart + yearLength;
}

class BilleeThread extends Thread {
  /** the account into which Billee's salary is deposited **/
  Account account; 
  public BilleeThread(String name, Account account) {
    super(name);
    this.account = account;
  } 
  public void run() {
    System.err.println("Billee weekLength = "+weekLength);
    System.err.println("Billee yearStart = "+yearStart);

    int salary = 300; 
    for (int week = 1; week <= 52; week++) {
      // add a note in this thread's call record
      Trace.doing(new StringRecord("...Sleeping to work..."));
      SharedAccountBioNoInner.sleepTo(yearStart + week * weekLength);
      account.deposit(salary);
    }
  }
  // shared constants
  static final int  weekLength = SharedAccountBioNoInner.weekLength;
  static final long yearStart  = SharedAccountBioNoInner.yearStart;
}

class LordyThread extends Thread {
  /** account from which rent should be withdrawn **/
  Account account;

  LordyThread(String name, Account account) {
    super(name);
    this.account = account;
  }
  /** try to withdaw rent monthly for a year; if overdraft, start a new
      bill collector **/
  public void run() {
    System.err.println("Lordy monthLength = "+monthLength);
    System.err.println("Lordy weekLength = "+weekLength);
    System.err.println("Lordy yearStart = "+yearStart);
    System.err.println("Lordy yearEnd = "+yearEnd);

    int rent = 1000; int penalty = 10; 
    Collection outstandingCollectors =
      Collections.synchronizedCollection(new LinkedList());
    int collectorCount = 0; // count all collectors, not just outstanding ones
    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 CollectorThread("Collector"+(++collectorCount),
			      account, rent + penalty, penalty,
			      outstandingCollectors);
	collector.start();
	Thread.yield();
      }
      // if unpaid lordy can't sleep 
      Trace.doing(new StringRecord
		  (outstandingCollectors.isEmpty()?
		   "...sweet dreams..." : "...fitful naps ..."));
      SharedAccountBioNoInner.sleepTo(yearStart + month * monthLength);
    }
    return;
  }
  // shared constants
  static final int  monthLength = SharedAccountBioNoInner.monthLength;
  static final int  weekLength  = SharedAccountBioNoInner.weekLength;
  static final long yearStart   = SharedAccountBioNoInner.yearStart;
  static final long yearEnd     = SharedAccountBioNoInner.yearEnd;
}

class CollectorThread extends Thread {
  /** account from which bill should be collected **/
  Account account;
  /** amount due so far **/
  int due;
  /** penalty for missing another week (easier to read than interest) **/
  int penalty;
  /** date this collector started work **/
  long startDate = System.currentTimeMillis();
  /** collection of collectors who are still working (haven't succeeded yet)**/
  Collection outstandingCollectors;
  /** Constructor: amountDue should be > 0 **/
  CollectorThread(String name, Account account, int amountDue,
		  int penalty, Collection outstandingCollectors){
    super(name);
    this.account = account;
    this.due = amountDue;
    this.penalty = penalty;
    this.outstandingCollectors = outstandingCollectors;
  }
  /** run:  while it's before yearEnd, repeated try to withdraw amount, then
      sleep until next week. **/
  public void run() { 
    outstandingCollectors.add(this);
    //number of weeks this collectors has tried weekly to collect amount due
    int week = 0;
    while (System.currentTimeMillis() <= yearEnd) { 
      try {
	account.withdraw(due);
	break;
      } catch (IllegalArgumentException overdraft2) { 
	week++;     due += penalty; 
	// add a note in this thread's call record
	Trace.doing(new StringRecord("...snoring..."));
	SharedAccountBioNoInner.sleepTo(startDate + week * weekLength);
      }
    }
    outstandingCollectors.remove(this);
    return;
  }
  /** shared constants **/
  static final long weekLength = SharedAccountBioNoInner.weekLength;
  static final long yearEnd    = SharedAccountBioNoInner.yearEnd;
}

/** A class for a shared account for deposits and withdrawals **/
class Account implements Cloneable {
  /** 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+")"; }
}