package com.ibm.lab.junit.money;

import java.util.*;

/**
 * A MoneyBag defers exchange rate conversions. For example adding 
 * 12 Swiss Francs to 14 US Dollars is represented as a bag 
 * containing the two Monies 12 CHF and 14 USD. Adding another
 * 10 Swiss francs gives a bag with 22 CHF and 14 USD. Due to 
 * the deferred exchange rate conversion we can later value a 
 * MoneyBag with different exchange rates.
 *
 * A MoneyBag is represented as a list of Monies and provides 
 * different constructors to create a MoneyBag. 
 */ 
class MoneyBag implements IMoney {
	private Vector fMonies= new Vector(5);

	private MoneyBag() {
	}
	MoneyBag(Money bag[]) {
		for (int i= 0; i < bag.length; i++) {
			if (!bag[i].isZero())
				appendMoney(bag[i]);
		}
	}
	MoneyBag(Money m1, Money m2) {
		appendMoney(m1);
		appendMoney(m2);
	}
	MoneyBag(Money m, MoneyBag bag) {
		appendMoney(m);
		appendBag(bag);
	}
	MoneyBag(MoneyBag m1, MoneyBag m2) {
		appendBag(m1);
		appendBag(m2);
	}
	public IMoney add(IMoney m) {
		return m.addMoneyBag(this);
	}
	public IMoney addMoney(Money m) {
		return (new MoneyBag(m, this)).simplify();
	}
	public IMoney addMoneyBag(MoneyBag s) {
		return (new MoneyBag(s, this)).simplify();
	}
	private void appendBag(MoneyBag aBag) {
		for (Enumeration e= aBag.fMonies.elements(); e.hasMoreElements(); )
			appendMoney((Money)e.nextElement());
	}
	private void appendMoney(Money aMoney) {
		IMoney old= findMoney(aMoney.currency());
		if (old == null) {
			fMonies.addElement(aMoney);
			return;
		}
		fMonies.removeElement(old);
		IMoney sum= old.add(aMoney);
		if (sum.isZero()) 
			return;
		fMonies.addElement(sum);
	}
	private boolean contains(Money aMoney) {
		Money m= findMoney(aMoney.currency());
		return m.amount() == aMoney.amount();
	}
	public boolean equals(Object anObject) {
		if (isZero())
			if (anObject instanceof IMoney)
				return ((IMoney)anObject).isZero();

		if (anObject instanceof MoneyBag) {
			MoneyBag aMoneyBag= (MoneyBag)anObject;
			if (aMoneyBag.fMonies.size() != fMonies.size())
				return false;

		    for (Enumeration e= fMonies.elements(); e.hasMoreElements(); ) {
		        Money m= (Money) e.nextElement();
				if (!aMoneyBag.contains(m))
					return false;
			}
			return true;
		}
		return false;
	}
	private Money findMoney(String currency) {
		for (Enumeration e= fMonies.elements(); e.hasMoreElements(); ) {
			Money m= (Money) e.nextElement();
			if (m.currency().equals(currency))
				return m;
		}
		return null;
	}
	public int hashCode() {
		int hash= 0;
	    for (Enumeration e= fMonies.elements(); e.hasMoreElements(); ) {
	        Object m= e.nextElement();
			hash^= m.hashCode();
		}
	    return hash;
	}
	public boolean isZero() {
		return fMonies.size() == 0;
	}
	public IMoney multiply(int factor) {
		MoneyBag result= new MoneyBag();
		if (factor != 0) {
			for (Enumeration e= fMonies.elements(); e.hasMoreElements(); ) {
				Money m= (Money) e.nextElement();
				result.appendMoney((Money)m.multiply(factor));
			}
		}
		return result;
	}
	public IMoney negate() {
		MoneyBag result= new MoneyBag();
	    for (Enumeration e= fMonies.elements(); e.hasMoreElements(); ) {
	        Money m= (Money) e.nextElement();
	        result.appendMoney((Money)m.negate());
		}
		return result;
	}
	private IMoney simplify() {
		if (fMonies.size() == 1)
			return (IMoney)fMonies.elements().nextElement();
		return this;
	}
	public IMoney subtract(IMoney m) {
		return add(m.negate());
	}
	public String toString() {
		StringBuffer buffer = new StringBuffer();
		buffer.append("{");
		for (Enumeration e= fMonies.elements(); e.hasMoreElements(); )
		    buffer.append((Money) e.nextElement());
		buffer.append("}");
		return buffer.toString();
	}
}