/*
 *     Copyright: Eugene M. Hutorny (c) 2009, eugene@hutorny.in.ua
 *     License:   Licensed to public under The GNU Lesser General Public License (LGPLv3) 
 *                http://www.opensource.org/licenses/lgpl-3.0.html
 */
package ua.in.hutorny.otj.exer1.accounting;

import java.math.BigDecimal;

import ua.in.hutorny.otj.exer1.money.Currency;
import ua.in.hutorny.otj.exer1.exeptions.NotEnoughFunds;

/**
 * Withdrawing the funds involves three accounts and may require currency conversion
 */
public team abstract class WithdrawCollaboration {

	/**
	 *  Credit account - where the funds are charged from 
	 */
	protected abstract class CreditAccount {
		abstract protected void credit(BigDecimal amount) throws NotEnoughFunds;
		abstract protected Currency currency();		
	}
	
	/**
	 *  Debit account - where the funds send to 
	 */
	protected abstract class DebitAccount {
		abstract protected void debit(BigDecimal amount, Currency currency);		
	}
	/**
	 *  Operational account - where the fee is send to 
	 */
	
	protected abstract class OperationalAccount {
		abstract protected void debit(BigDecimal amount);				
		abstract protected Currency currency();		
	}
	
	/**
	 * A currency converter role is responsible for converting the funds from one currency to another 
	 */
	protected abstract class CurrencyConverter {
		abstract protected BigDecimal convert(Currency from, Currency to, BigDecimal amount);
	}
	
	/**
	 * Fee calculation should be provided by concrete implementation of this collaboration  
	 */
	protected abstract BigDecimal calculateFee(BigDecimal amount);
	
	protected WithdrawCollaboration() {
		super();
	}
	
	/**
	 * Withdrawal routine, parameters are roles described above, plus @param amount, 
	 * which specifies amount of money to be sent.
	 * 1. Calculate a fee for this operation and apply it to this operation by increasing
	 *    value, charged from the credit account
	 * 2. Charge the amount+fee from the credit account
	 * 3. Put the money on the debit account, which is expected to accept any currency
	 * 4. If the currency of the operation differs from the currency of the banks' operational account
	 *    conversion is applied to the fee
	 * 5. The fee is put onto operational account   
	 */
	protected final void withdraw(BigDecimal amount, CreditAccount creditAccount, DebitAccount debitAccount,
			OperationalAccount operationalAccount, CurrencyConverter currencyConvertor) throws NotEnoughFunds {
		BigDecimal fee =  calculateFee(amount);
		creditAccount.credit(amount.add(fee));
		debitAccount.debit(amount,creditAccount.currency());
		if( creditAccount.currency() != operationalAccount.currency())
			fee = currencyConvertor.convert(creditAccount.currency(), operationalAccount.currency(), fee);		
		operationalAccount.debit(fee);		
	}
}
