/*
 * ATM Example system - file Transaction.java   
 *
 * copyright (c) 2001 - Russell C. Bjork
 *
 */
 
package atm.transaction;
import atm.ATM;
import atm.Session;
import atm.physical.*;
import banking.Balances;
import banking.Card;
import banking.Message;
import banking.Status;
import banking.Receipt;

/** Abstract base class for classes representing the various kinds of
 *  transaction the ATM can perform
 */
public abstract class Transaction
{
    /** Constructor
     *
     *  @param atm the ATM used to communicate with customer
     *  @param session the session in which this transaction is being performed
     *  @param card the customer's card
     *  @param pin the PIN entered by the customer
     */
     
    protected Transaction(ATM atm, Session session, Card card, int pin)
    {
        this.atm = atm;
        this.session = session;
        this.card = card;
        this.pin = pin;
        this.serialNumber = nextSerialNumber ++;
        this.balances = new Balances();
        
        state = GETTING_SPECIFICS_STATE;
    }
         
    /** Create a transaction of an appropriate type by asking the customer
     *  what type of transaction is desired and then returning a newly-created
     *  member of the appropriate subclass
     *
     *  @param atm the ATM used to communicate with customer
     *  @param session the session in which this transaction is being performed
     *  @param card the customer's card
     *  @param pin the PIN entered by the customer
     *  @return a newly created Transaction object of the appropriate type
     *  @exception CustomerConsole.Cancelled if the customer presses cancel instead
     *         of choosing a transaction type
     */
    public static Transaction makeTransaction(ATM atm, Session session,
                                              Card card, int pin)
                                throws CustomerConsole.Cancelled              
    {
        int choice = atm.getCustomerConsole().readMenuChoice(
                "Please choose transaction type", TRANSACTION_TYPES_MENU);
                
        switch(choice)
        {
            case 0:
            
                return new Withdrawal(atm, session, card, pin);
                
            case 1:
            
                return new Deposit(atm, session, card, pin);
                
            case 2:
            
                return new Transfer(atm, session, card, pin);
                
            case 3:
            
                return new Inquiry(atm, session, card, pin);
                
            default:
            
                return null;    // To keep compiler happy - should not happen!
        }
    }
    
    /** Peform a transaction.  This method depends on the three abstract methods
     *  that follow to perform the operations unique to each type of transaction
     *  in the appropriate way.
     *
     *  @return true if customer indicates a desire to do another transaction;
     *          false if customer does not desire to do another transaction
     *  @exception CardRetained if card was retained due to too many invalid PIN's
     */
    public boolean performTransaction() throws CardRetained
    {
        String doAnotherMessage = "";
        Status status = null;
        Receipt receipt = null;
        
        while (true)    // Terminates by return in ASKING_DO_ANOTHER_STATE or exception
        {
            switch(state)
            {
                case GETTING_SPECIFICS_STATE:
                
                    try
                    {           
                        message = getSpecificsFromCustomer();
                        atm.getCustomerConsole().display("");
                        state = SENDING_TO_BANK_STATE;
                    }
                    catch(CustomerConsole.Cancelled e)
                    {
                        doAnotherMessage = "Last transaction was cancelled";
                        state = ASKING_DO_ANOTHER_STATE;
                    }
                    
                    break;
                    
                case SENDING_TO_BANK_STATE:
                                
                    status = atm.getNetworkToBank().sendMessage(message, balances);
                
                    if (status.isInvalidPIN())
                        state = INVALID_PIN_STATE;
                    else if (status.isSuccess())
                        state = COMPLETING_TRANSACTION_STATE;
                    else
                    {
                        doAnotherMessage = status.getMessage();
                        state = ASKING_DO_ANOTHER_STATE;
                    }
                    
                    break;
                
                case INVALID_PIN_STATE:
                
                    try
                    {
                        status = performInvalidPINExtension();
                    
                        // If customer repeatedly enters invalid PIN's, a
                        // CardRetained exception is thrown, and this method
                        // terminates
                        
                        if (status.isSuccess())
                            state = COMPLETING_TRANSACTION_STATE;
                        else
                        {
                            doAnotherMessage = status.getMessage();
                            state = ASKING_DO_ANOTHER_STATE;
                        }
                    }
                    catch(CustomerConsole.Cancelled e)
                    {
                        doAnotherMessage = "Last transaction was cancelled";
                        state = ASKING_DO_ANOTHER_STATE;
                    }

                    break;
                        
                case COMPLETING_TRANSACTION_STATE:

                    try
                    {
                        receipt = completeTransaction();
                        state = PRINTING_RECEIPT_STATE;
                    }
                    catch(CustomerConsole.Cancelled e)
                    {
                        doAnotherMessage = "Last transaction was cancelled";
                        state = ASKING_DO_ANOTHER_STATE;
                    }
                    
                    break;
                    
                case PRINTING_RECEIPT_STATE:
                
                    atm.getReceiptPrinter().printReceipt(receipt);
                    state = ASKING_DO_ANOTHER_STATE;
                    
                    break;
                    
                case ASKING_DO_ANOTHER_STATE:
                
                    if (doAnotherMessage.length() > 0)
                        doAnotherMessage += "\n";
                        
                    try
                    {
                        String [] yesNoMenu = { "Yes", "No" };

                        boolean doAgain = atm.getCustomerConsole().readMenuChoice(
                            doAnotherMessage + 
                            "Would you like to do another transaction?",
                            yesNoMenu) == 0;
                        return doAgain;
                    }
                    catch(CustomerConsole.Cancelled e)
                    {
                        return false;
                    }
            }
        }
    }
        
    
    /** Perform the Invalid PIN Extension - reset session pin to new value if successful
     *
     *  @return status code returned by bank from most recent re-submission
     *          of transaction
     *  @exception CustomerConsole.Cancelled if customer presses the CANCEL key
     *             instead of re-entering PIN
     *  @exception CardRetained if card was retained due to too many invalid PIN's
     */
    public Status performInvalidPINExtension() throws CustomerConsole.Cancelled,
                                                      CardRetained
    {
        Status status = null;
        for (int i = 0; i < 3; i ++)
        {
            pin = atm.getCustomerConsole().readPIN(
                "PIN was incorrect\nPlease re-enter your PIN\n" +
                "Then press ENTER");
            atm.getCustomerConsole().display("");
            
            message.setPIN(pin);
            status = atm.getNetworkToBank().sendMessage(message, balances);
            if (! status.isInvalidPIN())
            {
                session.setPIN(pin);
                return status;
            }
        }
        
        atm.getCardReader().retainCard();
        atm.getCustomerConsole().display(
            "Your card has been retained\nPlease contact the bank.");
        try
        {
            Thread.sleep(5000);
        }
        catch(InterruptedException e)
        { }
        atm.getCustomerConsole().display("");
                
        throw new CardRetained();
    }
    

    /** Get serial number of this transaction
     *
     *  @return serial number
     */
    public int getSerialNumber()
    {
        return serialNumber;
    }
    
    /** Get specifics for the transaction from the customer - each
     *  subclass must implement this appropriately.
     *
     *  @return message to bank for initiating this transaction
     *  @exception CustomerConsole.Cancelled if customer cancelled this transaction
     */
    protected abstract Message getSpecificsFromCustomer() throws CustomerConsole.Cancelled;
    
    /** Complete an approved transaction  - each subclass must implement
     *  this appropriately.
     *
     *  @return receipt to be printed for this transaction
     *  @exception CustomerConsole.Cancelled if customer cancelled this transaction
     */
    protected abstract Receipt completeTransaction() throws CustomerConsole.Cancelled;
    
    
    // Local class representing card retained exception
   
    
    /** Exception that is thrown when the customer's card is retained due to too
     *  many invalid PIN entries
     */
    public static class CardRetained extends Exception
    {
        /** Constructor
         */
        public CardRetained()
        {
            super("Card retained due to too many invalid PINs");
        }
    }
    
    
    // Instance variables


    /** ATM to use for communication with the customer
     */
    protected ATM atm;
    
    /** Session in which this transaction is being performed
     */
    protected Session session;
    
    /** Customer card for the session this transaction is part of
     */
    protected Card card;
    
    /** PIN entered or re-entered by customer
     */
    protected int pin;
    
    /** Serial number of this transaction
     */
    protected int serialNumber;
    
    /** Message to bank describing this transaction
     */
    protected Message message;
    
    /** Used to return account balances from the bank
     */
    protected Balances balances;
    
    /** List of available transaction types to display as a menu
     */
    private static final String [] TRANSACTION_TYPES_MENU = 
        { "Withdrawal", "Deposit", "Transfer", "Balance Inquiry" };
        
    /** Next serial number - used to assign a unique serial number to
     *  each transaction
     */
    private static int nextSerialNumber = 1;
    
    /** The current state of the transaction
     */
    private int state;
    
    // Possible values for state
    
    /** Getting specifics of the transaction from customer
     */
    private static final int GETTING_SPECIFICS_STATE = 1;
    
    /** Sending transaction to bank
     */
    private static final int SENDING_TO_BANK_STATE = 2;
    
    /** Performing invalid PIN extension
     */
    private static final int INVALID_PIN_STATE = 3;
    
    /** Completing transaction
     */
    private static final int COMPLETING_TRANSACTION_STATE = 4;
    
    /** Printing receipt
     */
    private static final int PRINTING_RECEIPT_STATE = 5;
    
    /** Asking if customer wants to do another transaction
     */
    private static final int ASKING_DO_ANOTHER_STATE = 6;
}
