// Collection classes needed.
import java.util.*;
import java.io.*;

        /** Simple definition of an bank account class.
         *  All amounts are stored in long amounts of pennies.
         *  @author David J. Barnes (d.j.barnes@ukc.ac.uk)
         *  @version Version 2.0 (14th Apr 1999)<br>
         *  @see SimpleBank
         */

public class PennyBankAccount {
        /** Create an account belonging to <i>bank</i>.
         *  The account has the given <i>name</i> and <i>accountNumber</i>.
         */
    PennyBankAccount(SimpleBank bank,String name,long accountNumber){
        this.bank = bank;
        this.name = name;
        this.accountNumber = accountNumber;
    }

        /** Return how much is in the account.
         */
    public long getBalance(){
        return balance;
    }

        /** Credit the given amount.
         *  <i>amount</i> which must be greater than zero.
         */
    public void credit(long amount){
        try{
            addTransaction(new Credit(amount));
            setBalance(getBalance()+amount);
        }
        catch(Exception e){
            getWriter().println(e.getMessage());
        }
    }

        /** Debit the given amount.
         *  <i>amount</i> which must be greater than zero.
         */
    public void debit(long amount){
        try{
            addTransaction(new Debit(amount));
            setBalance(getBalance()-amount);
        }
        catch(Exception e){
            getWriter().println(e.getMessage());
        }
    }

        /** Print a statement to an associated writer.
         *  The statement includes the account name and number
         *  followed by a list of credit and debit transactions.
         *  The current balance is shown at the end.
         *  @see #getWriter()
         *  @see #setWriter(PrintWriter)
         */
    public void printStatement(){
        PrintWriter writer = getWriter();
        writer.println("Statement for: "+getName()+
                                ", Account number: "+getNumber());
        // A header is needed. The format of this depends upon
        // the field widths used to print Credit and Debit transactions.
        final String creditHeader = "Credit", debitHeader = "Debit";
        String combinedHeader;
        if(Credit.creditFormatWidth < Debit.debitFormatWidth){
            // creditHeader comes first.
            combinedHeader = StringFormatter.rightAdjust(creditHeader,
                                                    Credit.creditFormatWidth)+
                             StringFormatter.rightAdjust(debitHeader,
                                Debit.debitFormatWidth-Credit.creditFormatWidth);
        }
        else{
            // debitHeader comes first.
            combinedHeader = StringFormatter.rightAdjust(debitHeader,
                                                    Debit.debitFormatWidth)+
                             StringFormatter.rightAdjust(creditHeader,
                                Credit.creditFormatWidth-Debit.debitFormatWidth);
        }
        writer.println(combinedHeader);

        // Now print each transaction, one per line.
        Collection statement = getTransactions();
        Iterator items = statement.iterator();
        while(items.hasNext()){
            Transaction t = (Transaction) items.next();
            writer.println(t);
        }
        writer.println();
        writer.println("Current balance: "+
                                Transaction.amountAsString(getBalance()));
        writer.println("=====");
        writer.println();
    }

        /** Return the account number.
         */
    public long getNumber(){
        return accountNumber;
    }

        /** Return the account name.
         */
    public String getName(){
        return name;
    }

        /** Return a formatted version of the account name and number.
         */
    public String toString(){
        final int nameWidth = 20, numberWidth = 10;
        return StringFormatter.leftAdjust(getName(),nameWidth)+
               " A/C#: "+
               StringFormatter.rightAdjust(""+getNumber(),numberWidth);
    }

        /** Get the writer to be used for reporting.
         *  @see #printStatement()
         */
    public PrintWriter getWriter(){
        return writer;
    }

        /** Set the writer to be used for reporting.
         *  @see #printStatement()
         */
    public void setWriter(PrintWriter w){
        writer = w;
    }

        /** Return the overdraft limit on the account.
         */
    public long getOverdraftLimit(){
        return overdraftLimit;
    }

        /** Mutator for balance. All external modification
         * of the balance must be via credit and debit.
         */
    protected void setBalance(long newBalance){
        if(newBalance >= getOverdraftLimit()){
            balance = newBalance;
        }
        else{
            getWriter().println("Attempt to set balance to: "+newBalance+
                " which is below the overdraft limit of: "+overdraftLimit);
        }
    }

        /** Add the given transaction to the account's list of
         *  transactions.
         *  @see #printStatement()
         */
    protected void addTransaction(Transaction t){
        getTransactions().add(t);
    }

        /** Return a collection of the account's transactions.
         *  @see java.util.Collection
         */
    protected Collection getTransactions(){
        return transactions;
    }

    // How much is currently in the account, in cents.
    private long balance;
    // A balance must not fall below overdraftLimit.
    private final long overdraftLimit = -20000;
    // The account number of this instance.
    private final long accountNumber;
    // The name of this account.
    private String name;
    // The bank to which we belong.
    private final SimpleBank bank;

    // Keep track of all transactions on the account.
    private LinkedList transactions = new LinkedList();
    // Where reports are printed to. Standard output by default.
    private PrintWriter writer = new PrintWriter(System.out);
}
