/*
 *     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.bank;

import java.math.BigDecimal;

import ua.in.hutorny.otj.exer1.Person;
//import base ua.in.hutorny.otj.exer1.Person;
import base ua.in.hutorny.otj.exer1.accounting.Account;
import ua.in.hutorny.otj.exer1.accounting.WithdrawCollaboration;
import ua.in.hutorny.otj.exer1.exeptions.NotEnoughCash;
import ua.in.hutorny.otj.exer1.exeptions.NotEnoughFunds;
import ua.in.hutorny.otj.exer1.money.Cash;
import ua.in.hutorny.otj.exer1.money.Currency;

/**
 * Cash operation in a bank is also a withdraw, but it has some significant differences
 *  - role DebitAccount is played by a person, who has no method debit and take(Cash) should be instead
 *  - sufficient amount of cash should be reserved before operation by acquiring it from the bank 
 */

team class GetCashFromBankCollaboration extends WithdrawCollaboration implements Cashier {

	protected GetCashFromBankCollaboration(Bank bank, Bank.BankAccount account, double interest) {
		super();
		this.bank = bank;
		this.interest = interest;
		this.account = account;
	}

		
	@Override
	protected BigDecimal calculateFee(BigDecimal amount) {
		return new BigDecimal(amount.doubleValue() * interest);
	}

	@SuppressWarnings("bindingconventions")
	protected class CashRecipient extends DebitAccount playedBy Person {

		void takes(Cash cash) -> void takes(Cash cash);

		protected void debit(BigDecimal amount, Currency currency) {
			assert reserved.value() == amount && reserved.currency() == currency; 
			takes(reserved);
		}
	}
	
	protected class CurrencyConverter playedBy Bank {
		BigDecimal convert(Currency from, Currency to, BigDecimal amount) -> 
			BigDecimal internalConversion(Currency from, BigDecimal amount)
			with { from -> from, amount -> amount, result <- result }
	}
		
	protected class CreditAccount playedBy Bank.BankAccount {
		@SuppressWarnings("decapsulation")
		credit -> credit;
		currency -> currency;
	}
	
	protected class OperationalAccount playedBy Account {
		@SuppressWarnings("decapsulation")
		debit -> debit;
		currency -> currency;		
	}
	
	/**
	 * Two lifting occurs in this definition - Bank is lifted as an Operator 
	 * and Account is lifted as an OperationalAccount  
	 */
	protected class Operator playedBy Bank {
		OperationalAccount operationalAccount() -> Account operationalAccount();		
	}
	/*
	 * When ua.in.hutorny.otj.exer1.Person imported with base modifier, the following error occurs:
	 * Person cannot be resolved to a type
	 */
	public void giveCashTo(Person person, double amount) throws NotEnoughFunds, NotEnoughCash {
		BigDecimal value = new BigDecimal(amount);
		reserved = bank.acquireCash(value, account.currency());
		try {
			cashOperation(new BigDecimal(amount), account, person, bank, bank);
		}
		finally {
			if( reserved.value().signum() != 0 ) {
			// Recipient did not take the money
				bank.returnCash(reserved);
			}
		}
	} 
	
	protected void cashOperation(BigDecimal amount, Bank.BankAccount as CreditAccount creditAccount, Person as CashRecipient cashRecipient, 
			Bank as Operator operator, Bank as CurrencyConverter currencyConvertor) throws NotEnoughFunds {		
		super.withdraw(amount, creditAccount, cashRecipient, operator.operationalAccount(), currencyConvertor);
	}
		
	private Cash reserved;
	protected final Bank bank;
	protected final Bank.BankAccount account;
	protected final double interest;
	
}
