#import "MoneyBag.h"

#import "Money.h"

@interface MoneyBag (Privates)
- (id)init;
- (void)appendBag:(MoneyBag *)aBag;
- (void)appendMoney:(Money *)aMoney;
- (BOOL)contains:(Money *)aMoney;
- (Money *)findMoney:(NSString *)currency;
- (id<IMoney>)simplify;
@end

@implementation MoneyBag

- (id)init {
    self = [super init];
    fMonies = [[NSMutableArray alloc] init];
    return self;
}

- (id)initWithMoneyArray:(NSArray *)array {
    NSEnumerator *e;
    Money *each;
    
    self = [self init];
    
    e = [array objectEnumerator];
    while ((each = [e nextObject]) != nil) {
        if (![each isZero])
            [self appendMoney:each];
    }
    return self;
}

- (id)initWithMoney:(Money *)money1 money:(Money *)money2 {
    self = [self init];
    [self appendMoney:money1];
    [self appendMoney:money2];
    return self;
}

- (id)initWithMoney:(Money *)money bag:(MoneyBag *)bag {
    self = [self init];
    [self appendMoney:money];
    [self appendBag:bag];
    return self;
}

- (id)initWithMoneyBag:(MoneyBag *)bag1 bag:(MoneyBag *)bag2 {
    self = [self init];
    [self appendBag:bag1];
    [self appendBag:bag2];
    return self;
}

- (void)dealloc {
    [fMonies release];
    [super dealloc];
}

- (id<IMoney>)add:(id<IMoney>)money {
    return [money addMoneyBag:self];
}

- (id<IMoney>)addMoney:(Money *)money {
    return [[[[MoneyBag alloc] initWithMoney:money bag:self] autorelease] simplify];
}

- (id<IMoney>)addMoneyBag:(MoneyBag *)bag {
    return [[[[MoneyBag alloc] initWithMoneyBag:bag bag:self] autorelease] simplify];
}

- (void)appendBag:(MoneyBag *)aBag {
    NSEnumerator *e = [aBag->fMonies objectEnumerator];
    Money *each;
    while ((each = [e nextObject]) != nil) {
        [self appendMoney:each];
    }
}

- (void)appendMoney:(Money *)aMoney {
    id<IMoney> old;
    id<IMoney> sum;
    
    old = [self findMoney:[aMoney currency]];
    if (old == nil) {
        [fMonies addObject:aMoney];
        return;
    }
    [fMonies removeObject:old];
    sum = [old add:aMoney];
    if ([sum isZero])
        return;
    [fMonies addObject:sum];
}

- (BOOL)contains:(Money *)aMoney {
    Money *m = [self findMoney:[aMoney currency]];
    return [m amount] == [aMoney amount];
}

- (BOOL)isEqual:(id)anObject {
    if ([self isZero])
        if ([anObject conformsToProtocol:@protocol(IMoney)])
            return [anObject isZero];
    
    if ([anObject isMemberOfClass:[MoneyBag class]]) {
        MoneyBag *aMoneyBag;
        NSEnumerator *e;
        Money *m;
        
        aMoneyBag = (MoneyBag *)anObject;
        if ([aMoneyBag->fMonies count] != [fMonies count])
            return NO;
        
        e = [fMonies objectEnumerator];
        while ((m = [e nextObject]) != nil) {
            if (![aMoneyBag contains:m])
                return NO;
        }
        return YES;
    }
    return NO;
}

- (Money *)findMoney:(NSString *)currency {
    NSEnumerator *e = [fMonies objectEnumerator];
    Money *m;
    while ((m = [e nextObject]) != nil) {
        if ([[m currency] isEqualToString:currency])
            return m;
    }
    return nil;
}

- (BOOL)isZero {
    return [fMonies count] == 0;
}

- (id<IMoney>)multiply:(int)factor {
    MoneyBag *result = [[[MoneyBag alloc] init] autorelease];
    if (factor != 0) {
        NSEnumerator *e = [fMonies objectEnumerator];
        Money *m;
        while ((m = [e nextObject]) != nil) {
            [result appendMoney:[m multiply:factor]];
        }
    }
    return result;
}

- (id<IMoney>)negate {
    MoneyBag *result = [[[MoneyBag alloc] init] autorelease];
    NSEnumerator *e = [fMonies objectEnumerator];
    Money *m;
    while ((m = [e nextObject]) != nil) {
        [result appendMoney:[m negate]];
    }
    return result;
}

- (id<IMoney>)simplify {
    if ([fMonies count] == 1)
        return [[fMonies objectEnumerator] nextObject];
    return self;
}

- (id<IMoney>)subtract:(id<IMoney>)money {
    return [self add:[money negate]];
}

- (NSString *)description {
    NSMutableString *buffer = [NSMutableString string];
    NSEnumerator *e = [fMonies objectEnumerator];
    Money *m;
    [buffer appendString:@"{"];
    while ((m = [e nextObject]) != nil)
        [buffer appendString:[m description]];
    [buffer appendString:@"}"];
    return buffer;
}

@end
