#import <Foundation/Foundation.h>

@interface NSDictionary (MergingExtensions)
+ (NSDictionary *) dictionaryByMergingDictionaries: (NSArray *) dictionaries;
@end

@implementation NSDictionary (MergingExtensions)
+ (NSDictionary *) dictionaryByMergingDictionaries: (NSArray *) dictionaries;
{
    NSMutableDictionary *result;
    NSMutableSet *keysToResolve;
    NSEnumerator *keyEnum, *dictEnum;
    NSString *key;
    NSDictionary *dict;
    Class dictClass = [NSDictionary class];
    
    result = [NSMutableDictionary dictionary];
    keysToResolve = [NSMutableSet set];

    /* Merge unconflicting keys, find all conflicting paths, and store mergable paths. */
    dictEnum = [dictionaries objectEnumerator];
    while ((dict = [dictEnum nextObject])) {
        keyEnum = [dict keyEnumerator];
        while ((key = [keyEnum nextObject])) {
            NSObject *value, *otherValue;
            NSMutableArray *contenders;

            value = [dict objectForKey: key];

            /* Check for conflicts */
            if ((otherValue = [result objectForKey: key])) {

                /* Check for previous conflict */
                if ([keysToResolve member: key]) {
                    if (![value isKindOfClass: dictClass]) {
                        NSLog(@"Cannot merge key '%@' key since it "
                              @"has a non-dictionary value",
                              key);
                        return nil;
                    }

                    contenders = (NSMutableArray *) otherValue;
                    contenders = [result objectForKey: key];
                    [contenders addObject: value];
                    [keysToResolve addObject: key];
                } else {
                    /* This is a new conflict */
                    if (![value isKindOfClass: dictClass] ||
                        ![otherValue isKindOfClass: dictClass]) {
                        /* Check if the two definitions are equivalent */
                        if ([value isEqual: otherValue]) {
                            /* skip the new definition and consider them merged */
                            continue;
                        }
                        
                        NSLog(@"Cannot merge keys '%@' since at least "
                              @"one is not a dictionary",
                              key);
                        return nil;
                    }

                    contenders = [[NSMutableArray alloc] initWithObjects:
                                  value, otherValue, nil];
                    [result setObject: contenders forKey: key];
                    [contenders release];
                    [keysToResolve addObject: key];
                }
            } else {
                /* No conflict, include the key in the result */
                [result setObject: value forKey: key];
            }
        }
    }

    /* Now, recursively merge any keys that need it */
    keyEnum = [keysToResolve objectEnumerator];
    while ((key = [keyEnum nextObject])) {
        NSArray *contenders;
        NSDictionary *subResult;
        
        contenders = [result objectForKey: key];
        subResult = [self dictionaryByMergingDictionaries: contenders];
        if (!subResult) {
            NSLog(@"Failed to resolve contention among definitions for the key '%@'",
                  key);
            return nil;
        }

        [result setObject: subResult forKey: key];
    }

    /* Whew! */
    return result;
}

@end


int main (int argc, const char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    NSMutableArray *dictionaries;
    NSDictionary *outputDictionary;
    NSString *outputDictionaryPath, *plistString;
    int argIndex;
    
    dictionaries = [[NSMutableArray alloc] init];
    
    argIndex = 0;
    while (argIndex++ < argc - 3) {
        NSString *dictionaryPath;
        NSDictionary *dictionary;

        dictionaryPath = [[NSString alloc] initWithCString: argv[argIndex]];

        // Load the plist into a string first, and use propertyList to get the dictionary,
        // since that provides useful debugging information in the exception.

        plistString = [NSString stringWithContentsOfFile:dictionaryPath];

        NS_DURING {
            dictionary = [plistString propertyList];
        }
        NS_HANDLER {
            NSLog(@"An error occurred while parsing %@ as a property list.\n", dictionaryPath);
            NSLog(@"%@: %@\n", [localException name], [localException reason]);
            exit(1);
        }
        NS_ENDHANDLER

        if (!dictionary) {
            NSLog(@"Cannot open or read '%@' as a property list", dictionaryPath);
            exit(1);
        } else
            [dictionaries addObject: dictionary];
    }

    if (strcmp("-o", argv[argIndex])) {
        fprintf(stderr,
                "usage: %s [dictionaryPath1 ... dictionaryPathN] -o outputDictionaryPath\n",
                argv[0]);
        exit(1);
    }

    argIndex++;
    outputDictionaryPath = [[NSString alloc] initWithCString: argv[argIndex]];
    if (!(outputDictionary = [NSDictionary dictionaryByMergingDictionaries: dictionaries])) {
        NSLog(@"Unable to merge dictionaries!");
        exit(1);
    }

    if (![outputDictionary writeToFile: outputDictionaryPath atomically: YES]) {
        NSLog(@"Unable to store dictionary at path '%@'", outputDictionaryPath);
        exit(1);
    }
    
    [pool release];
    exit(0);       // insure the process exit status is 0
    return 0;      // ...and make main fit the ANSI spec.
}
