#import "AppDelegate.h"

#import "PPRecipe.h"
#import "SyncHelper.h"
#import "PPExportOperation.h"
#import "PPImportOperation.h"

@interface AppDelegate()

- (void)selectRecipe:(NSManagedObject*)recipe;
- (void)addImage:(NSString*)imagePath toRecipe:(PPRecipe*)recipe;

- (BOOL)updateMetadataForObjects:(NSSet*)updatedObjects 
               andDeletedObjects:(NSSet*)deletedObjects 
                           error:(NSError**)error;

@end

@implementation AppDelegate

#pragma mark Application Delegate methods

- (void)applicationDidFinishLaunching:(NSNotification*)notification
{
  [imageView addObserver:self 
              forKeyPath:@"imagePath" 
                 options:NSKeyValueObservingOptionNew 
                 context:nil];
  
  //START:registerSyncClientOnStartup
  NSError *error = nil;
  syncHelper = [[SyncHelper alloc] initWithDelegate:self error:&error];
  if (!syncHelper) {
    NSLog(@"%@:%s presenting error sync helper failed", [self class], _cmd);
    NSDictionary *ui = [error userInfo];
    for (NSError *suberror in [ui valueForKey:NSDetailedErrorsKey]) {
      NSLog(@"subError: %@", suberror);
    }
    [NSApp presentError:error];
  }
  error = nil;
  //END:registerSyncClientOnStartup
  //START:checkForMetadata
  NSString *path = [self metadataFolder:&error];
  if (!path) {
    NSLog(@"%@:%s Error resolving cache path: %@", [self class], _cmd, error);
    return; 
  }
  if ([[NSFileManager defaultManager] fileExistsAtPath:path]) return;
  
  NSManagedObjectContext *moc = [self managedObjectContext];
  NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
  [request setEntity:[NSEntityDescription entityForName:@"Recipe" 
                                 inManagedObjectContext:moc]];
  
  NSSet *recipes = [NSSet setWithArray:[moc executeFetchRequest:request 
                                                          error:&error]];
  if (error) {
    NSLog(@"%@:%s Error: %@", [self class], _cmd, error);
    return;
  }
  [self updateMetadataForObjects:recipes andDeletedObjects:nil error:&error];
  if (error) {
    NSLog(@"%@:%s Error: %@", [self class], _cmd, error);
    return;
  }
  //END:checkForMetadata  
  
  [recipeIngredientTableView setDelegate:self];
  [recipeIngredientTableView setDoubleAction:@selector(editIngredient:)];
}

//START:applicationOpenFile
- (BOOL)application:(NSApplication*)theApplication 
           openFile:(NSString*)filename
{
  NSDictionary *metadata = [NSDictionary dictionaryWithContentsOfFile:filename];
  NSString *objectIDString = [metadata valueForKey:(id)kPPObjectID];
  NSURL *objectURI = [NSURL URLWithString:objectIDString];
  
  NSPersistentStoreCoordinator *coordinator;
  coordinator = [[self managedObjectContext] persistentStoreCoordinator];
  
  NSManagedObjectID *objectID;
  objectID = [coordinator managedObjectIDForURIRepresentation:objectURI];
  
  NSManagedObject *recipe = [[self managedObjectContext] objectWithID:objectID];
  if (!recipe) return NO;
  
  [self performSelector:@selector(selectRecipe:) 
             withObject:recipe 
             afterDelay:0.01];
  
  return YES;
}
//END:applicationOpenFile

- (NSApplicationTerminateReply)applicationShouldTerminate:(id)sender
{
  NSError *error = nil;
  
  if (!managedObjectContext) return NSTerminateNow;
  if (![managedObjectContext commitEditing]) {
    //Failed to commit editing.
    return NSTerminateCancel;
  }
  if (![managedObjectContext hasChanges]) return NSTerminateNow;
  if (![self save:&error]) {
    int alertReturn = NSRunAlertPanel(nil, @"Could not save changes while quitting. Quit anyway?" , @"Quit anyway", @"Cancel", nil);
    if (alertReturn == NSAlertAlternateReturn) NSTerminateCancel; 
  }
  return NSTerminateNow;
}

#pragma mark Interface Builder Actions
#pragma mark -

- (IBAction)addImage:(id)sender;
{
  PPRecipe *recipe = [[recipeArrayController selectedObjects] lastObject];
  if (!recipe) return;
  
  NSOpenPanel *openPanel = [NSOpenPanel openPanel];
  
  [openPanel setCanChooseDirectories:NO];
  [openPanel setCanCreateDirectories:NO];
  [openPanel setAllowsMultipleSelection:NO];
  
  [openPanel beginSheetForDirectory:nil
                               file:nil
                     modalForWindow:window
                      modalDelegate:self
                     didEndSelector:@selector(addImageSheetDidEnd:returnCode:contextInfo:)
                        contextInfo:recipe];
}

- (void)addImageSheetDidEnd:(NSOpenPanel*)openPanel 
                 returnCode:(NSInteger)returnCode 
                contextInfo:(PPRecipe*)recipe
{
  if (returnCode == NSCancelButton) return;
  NSString *path = [openPanel filename];
  [self addImage:path toRecipe:recipe];
}

- (IBAction)saveAction:(id)sender 
{
  NSError *error = nil;
  if (![self save:&error]) {
    NSLog(@"%@:%s presenting error", [self class], _cmd);
    [[NSApplication sharedApplication] presentError:error];
  }
}

#pragma mark Helper methods
#pragma mark -

- (void)addImage:(NSString*)imagePath toRecipe:(PPRecipe*)recipe
{
  //Build the path we want the file to be at
  NSString *destPath = [self applicationSupportFolder];
  destPath = [destPath stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
  [[NSFileManager defaultManager] copyPath:imagePath toPath:destPath handler:nil];
  [recipe setValue:destPath forKey:@"imagePath"];
}

- (void)observeValueForKeyPath:(NSString*)keyPath 
                      ofObject:(id)object 
                        change:(NSDictionary*)change 
                       context:(void*)context
{
  if (object != imageView) return;
  PPRecipe *recipe = [[recipeArrayController selectedObjects] lastObject];
  if (!recipe) return;
  NSString *path = [imageView imagePath];
  if ([path isEqualToString:[recipe valueForKey:@"imagePath"]]) return;
  [self addImage:path toRecipe:recipe];
}

//START:ExportRecipesFetch
#pragma mark Export methods
#pragma mark -

- (IBAction)exportRecipes:(id)sender;
{
  NSFetchRequest *request = [[NSFetchRequest alloc] init];
  NSManagedObjectContext *moc = [self managedObjectContext];
  [request setEntity:[NSEntityDescription entityForName:@"Recipe"
                                 inManagedObjectContext:moc]];
  NSError *error = nil;
  NSArray *recipes = [moc executeFetchRequest:request
                                        error:&error];
  if (error) {
    [NSApp presentError:error];
    return;
  }
  //END:ExportRecipesFetch
  
  //START:ExportRecipesLoad
  NSMutableArray *exportList = [NSMutableArray array];
  for (NSManagedObject *recipe in recipes) {
    NSMutableDictionary *entry = [NSMutableDictionary dictionary];
    [entry setValue:[NSNumber numberWithBool:NO] 
             forKey:@"selected"];
    [entry setValue:[recipe valueForKey:@"name"] 
             forKey:@"name"];
    [entry setValue:[recipe objectID] 
             forKey:@"objectID"];
    [exportList addObject:entry];
  }
  [exportArrayController setContent:exportList];
  //END:ExportRecipesLoad
  //START:ExportRecipesDisplay
  [NSApp beginSheet:exportSheet
     modalForWindow:window
      modalDelegate:nil
     didEndSelector:NULL 
        contextInfo:nil];
  //END:ExportRecipesDisplay
}

//START:ExportRecipesApproved
- (IBAction)exportApproved:(id)sender;
{
  [exportSheet orderOut:sender];
  [NSApp endSheet:exportSheet];
  
  NSArray *content = [exportArrayController content];
  NSPredicate *pred = [NSPredicate predicateWithFormat:@"selected == YES"];
  NSArray *filtered = [content filteredArrayUsingPredicate:pred];
  NSArray *exportIDs = [filtered valueForKeyPath:@"objectID"];
  
  NSSavePanel *savePanel = [NSSavePanel savePanel];
  [savePanel setExtensionHidden:YES];
  [savePanel setRequiredFileType:@"grx"];
  [savePanel setCanSelectHiddenExtension:NO];
  
  SEL select = @selector(exportSaveDidEnd:returnCode:contextInfo:);
  [savePanel beginSheetForDirectory:nil 
                               file:nil 
                     modalForWindow:window 
                      modalDelegate:self 
                     didEndSelector:select
                        contextInfo:[exportIDs retain]];
  [exportArrayController setContent:nil];
}
//END:ExportRecipesApproved

//START:ExportRecipesKickoff
- (void)exportSaveDidEnd:(NSSavePanel*)savePanel 
              returnCode:(NSInteger)returnCode 
             contextInfo:(NSArray*)exportIDs
{
  if (returnCode == NSCancelButton) {
    [exportIDs release];
    return;
  }
  
  PPExportOperation *op = nil;
  op = [[PPExportOperation alloc] initWithDelegate:self
                                         objectIDs:exportIDs
                                      saveFilePath:[savePanel filename]];
  
  if (!genericOperationQueue) {
    genericOperationQueue = [[NSOperationQueue alloc] init];
  }
  
  [genericOperationQueue addOperation:op];
  [exportIDs release];
  [op release];
}
//END:ExportRecipesKickoff

//START:ExportRecipesCancelled
- (IBAction)exportCancelled:(id)sender;
{
  [exportSheet orderOut:sender];
  [NSApp endSheet:exportSheet];
  [exportArrayController setContent:nil];
}
//END:ExportRecipesCancelled

//START:ImportRecipesOpen
#pragma mark Import methods
#pragma mark -

- (IBAction)importRecipes:(id)sender;
{
  NSOpenPanel *openPanel = [NSOpenPanel openPanel];
  [openPanel setCanChooseDirectories:NO];
  [openPanel setCanCreateDirectories:NO];
  [openPanel setRequiredFileType:@"grx"];
  
  SEL selector = @selector(importSheetDidEnd:returnCode:context:);
  [openPanel beginSheetForDirectory:nil 
                               file:nil 
                     modalForWindow:window 
                      modalDelegate:self 
                     didEndSelector:selector
                        contextInfo:nil];
}
//END:ImportRecipesOpen

//START:ImportRecipesCallBack
- (void)importSheetDidEnd:(NSOpenPanel*)openPanel 
               returnCode:(NSInteger)returnCode 
                  context:(void*)context
{
  if (returnCode == NSCancelButton) return;
  
  PPImportOperation *op = nil;
  op = [[PPImportOperation alloc] initWithDelegate:self
                                          filePath:[openPanel filename]];
  
  if (!genericOperationQueue) {
    genericOperationQueue = [[NSOperationQueue alloc] init];
  }
  
  [genericOperationQueue addOperation:op];
  [op release], op = nil;
}
//END:ImportRecipesCallBack

//START:refreshRecipeList
- (void)refreshRecipeList:(NSArray*)objectIDArray
{
  if (![NSThread isMainThread]) {
    NSLog(@"%@:%s Not on the main thread!", [self class], _cmd);
    return;
  }
  NSManagedObjectContext *moc = [self managedObjectContext];
  
  for (NSManagedObjectID *objectID in objectIDArray) {
    NSManagedObject *object = [moc objectWithID:objectID];
    if (!object) {
      NSLog(@"%@:%s failed to find object %@", [self class], _cmd, objectID);
      continue;
    }
    [moc refreshObject:object mergeChanges:YES];
  }
  [recipeArrayController fetch:self];
}
//END:refreshRecipeList

//START:selectRecipe
- (void)selectRecipe:(NSManagedObject*)recipe;
{
  [recipeArrayController setSelectedObjects:[NSArray arrayWithObject:recipe]];
}
//END:selectRecipe

- (NSString*)metadataFolder:(NSError**)error
{
  //Build the path to the metadata
  NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
  if (!path) { //Unable to find the cache directory
    NSMutableDictionary *errorDict = [NSMutableDictionary dictionary];
    [errorDict setValue:NSLocalizedString(@"Failed to locate caches directory", @"caches directory error description") forKey:NSLocalizedDescriptionKey];
    *error = [NSError errorWithDomain:@"pragprog" code:1000 userInfo:errorDict];
    return nil;
  }
  path = [path stringByAppendingPathComponent:@"Metadata"];
  path = [path stringByAppendingPathComponent:@"GrokkingRecipes"];
  return path;
}

- (NSString*)applicationSupportFolder 
{
  NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
  NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : NSTemporaryDirectory();
  return [basePath stringByAppendingPathComponent:@"GrokkingRecipes"];
}

//START:updateMetadata
- (BOOL)updateMetadataForObjects:(NSSet*)updatedObjects 
               andDeletedObjects:(NSSet*)deletedObjects 
                           error:(NSError**)error
{
  //START:updateMetadataBuildup
  if ((!updatedObjects || ![updatedObjects count]) && 
      (!deletedObjects || ![deletedObjects count])) return YES;
  
  NSString *path = [self metadataFolder:error];
  if (!path) return NO;
  
  BOOL directory = NO;
  
  NSFileManager *fileManager = [NSFileManager defaultManager];
  if (![fileManager fileExistsAtPath:path isDirectory:&directory]) {
    if (![fileManager createDirectoryAtPath:path 
                withIntermediateDirectories:YES 
                                 attributes:nil 
                                      error:error]) {
      return NO;
    }
    directory = YES;
  }
  if (!directory) {
    NSMutableDictionary *errorDict = [NSMutableDictionary dictionary];
    NSString *msg = NSLocalizedString(@"File in place of metadata directory", 
                                      @"metadata directory is a file error description");
    [errorDict setValue:msg forKey:NSLocalizedDescriptionKey];
    *error = [NSError errorWithDomain:@"pragprog" code:1001 userInfo:errorDict];
    return NO;
  }        
  //END:updateMetadataBuildup
  //START:updateMetadataDeletedObjects
  NSString *filePath = nil;
  if (deletedObjects && [deletedObjects count]) {
    for (NSManagedObject *object in deletedObjects) {
      if (![object isKindOfClass:[PPRecipe class]]) continue;
      PPRecipe *recipe = (PPRecipe*)object;
      filePath = [recipe metadataFilename];
      filePath = [path stringByAppendingPathComponent:filePath];
      if (![fileManager fileExistsAtPath:filePath]) continue;
      if (![fileManager removeItemAtPath:filePath error:error]) return NO;
    }
  }
  
  //END:updateMetadataDeletedObjects
  //START:updateMetadataUpdatedObjects
  if (!updatedObjects || ![updatedObjects count]) return YES;
  
  NSNumber *_YES = [NSNumber numberWithBool:YES];
  NSDictionary *attributesDictionary = [NSDictionary 
                                        dictionaryWithObject:_YES
                                        forKey:NSFileExtensionHidden];
  
  for (NSManagedObject *object in updatedObjects) {
    if (![object isKindOfClass:[PPRecipe class]]) continue;
    PPRecipe *recipe = (PPRecipe*)object;
    NSDictionary *metadata = [recipe metadata];
    filePath = [recipe metadataFilename];
    filePath = [path stringByAppendingPathComponent:filePath];
    [metadata writeToFile:filePath atomically:YES];
    [fileManager changeFileAttributes:attributesDictionary atPath:filePath];
  }
  
  return YES;
  //END:updateMetadataUpdatedObjects
}
//END:updateMetadata

#pragma mark Core Data Stack
#pragma mark -

- (NSManagedObjectModel*)managedObjectModel 
{
  if (managedObjectModel) return managedObjectModel;
  
  NSString *path = [[NSBundle mainBundle] pathForResource:@"DataModel"
                                                   ofType:@"momd"];
  if (!path) {
    path = [[NSBundle mainBundle] pathForResource:@"DataModel"
                                           ofType:@"mom"];
  }
  NSAssert(path != nil, @"Unable to find DataModel in main bundle");
  NSURL *url = [NSURL fileURLWithPath:path];
  managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:url];
  return managedObjectModel;
}

//START:progressivelyMigrateURLMethodName
- (BOOL)progressivelyMigrateURL:(NSURL*)sourceStoreURL
                         ofType:(NSString*)type 
                        toModel:(NSManagedObjectModel*)finalModel 
                          error:(NSError**)error
{
  //END:progressivelyMigrateURLMethodName
  //START:progressivelyMigrateURLHappyCheck
  NSFileManager *fileManager = [NSFileManager defaultManager];
  if (![fileManager fileExistsAtPath:[sourceStoreURL path]]) return YES;
  NSDictionary *sourceMetadata = 
  [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:type
                                                             URL:sourceStoreURL
                                                           error:error];
  if (!sourceMetadata) return NO;
  
  if ([finalModel isConfiguration:nil 
      compatibleWithStoreMetadata:sourceMetadata]) {
    *error = nil;
    return YES;
  }
  //END:progressivelyMigrateURLHappyCheck
  //START:progressivelyMigrateURLFindModels
  //Find the source model
  NSManagedObjectModel *sourceModel = [NSManagedObjectModel 
                                       mergedModelFromBundles:nil
                                       forStoreMetadata:sourceMetadata];
  NSAssert(sourceModel != nil, ([NSString stringWithFormat:
                                 @"Failed to find source model\n%@", 
                                 sourceMetadata]));
  
  //Find all of the mom and momd files in the Resources directory
  NSMutableArray *modelPaths = [NSMutableArray array];
  NSArray *momdArray = [[NSBundle mainBundle] pathsForResourcesOfType:@"momd" 
                                                          inDirectory:nil];
  for (NSString *momdPath in momdArray) {
    NSString *resourceSubpath = [momdPath lastPathComponent];
    NSArray *array = [[NSBundle mainBundle] 
                      pathsForResourcesOfType:@"mom" 
                      inDirectory:resourceSubpath];
    [modelPaths addObjectsFromArray:array];
  }
  NSArray* otherModels = [[NSBundle mainBundle] pathsForResourcesOfType:@"mom" 
                                                            inDirectory:nil];
  [modelPaths addObjectsFromArray:otherModels];
  
  if (!modelPaths || ![modelPaths count]) {
    //Throw an error if there are no models
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    [dict setValue:@"No models found in bundle" 
            forKey:NSLocalizedDescriptionKey];
    //Populate the error
    *error = [NSError errorWithDomain:@"Zarra" code:8001 userInfo:dict];
    return NO;
  }
  //END:progressivelyMigrateURLFindModels
  
  //See if we can find a matching destination model
  //START:progressivelyMigrateURLFindMap
  NSMappingModel *mappingModel = nil;
  NSManagedObjectModel *targetModel = nil;
  NSString *modelPath = nil;
  for (modelPath in modelPaths) {
    targetModel = [[NSManagedObjectModel alloc] 
                   initWithContentsOfURL:[NSURL fileURLWithPath:modelPath]];
    mappingModel = [NSMappingModel mappingModelFromBundles:nil 
                                            forSourceModel:sourceModel 
                                          destinationModel:targetModel];
    //If we found a mapping model then proceed
    if (mappingModel) break;
    //Release the target model and keep looking
    [targetModel release], targetModel = nil;
  }
  //We have tested every model, if nil here we failed
  if (!mappingModel) {
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    [dict setValue:@"No models found in bundle" 
            forKey:NSLocalizedDescriptionKey];
    *error = [NSError errorWithDomain:@"Zarra" 
                                 code:8001 
                             userInfo:dict];
    return NO;
  }
  //END:progressivelyMigrateURLFindMap
  //We have a mapping model and a destination model.  Time to migrate
  //START:progressivelyMigrateURLMigrate
  NSMigrationManager *manager = [[NSMigrationManager alloc] 
                                 initWithSourceModel:sourceModel
                                 destinationModel:targetModel];
  
  NSString *modelName = [[modelPath lastPathComponent] 
                         stringByDeletingPathExtension];
  NSString *storeExtension = [[sourceStoreURL path] pathExtension];
  NSString *storePath = [[sourceStoreURL path] stringByDeletingPathExtension];
  //Build a path to write the new store
  storePath = [NSString stringWithFormat:@"%@.%@.%@", storePath, 
               modelName, storeExtension];
  NSURL *destinationStoreURL = [NSURL fileURLWithPath:storePath];
  
  if (![manager migrateStoreFromURL:sourceStoreURL 
                               type:type 
                            options:nil 
                   withMappingModel:mappingModel 
                   toDestinationURL:destinationStoreURL 
                    destinationType:type 
                 destinationOptions:nil 
                              error:error]) {
    return NO;
  }
  //END:progressivelyMigrateURLMigrate
  //Migration was successful, move the files around to preserve the source
  //START:progressivelyMigrateURLMoveAndRecurse
  NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString];
  guid = [guid stringByAppendingPathExtension:modelName];
  guid = [guid stringByAppendingPathExtension:storeExtension];
  NSString *appSupportPath = [storePath stringByDeletingLastPathComponent];
  NSString *backupPath = [appSupportPath stringByAppendingPathComponent:guid];
  
  if (![fileManager moveItemAtPath:[sourceStoreURL path]
                            toPath:backupPath
                             error:error]) {
    //Failed to copy the file
    return NO;
  }
  //Move the destination to the source path
  if (![fileManager moveItemAtPath:storePath
                            toPath:[sourceStoreURL path]
                             error:error]) {
    //Try to back out the source move first, no point in checking it for errors
    [fileManager moveItemAtPath:backupPath
                         toPath:[sourceStoreURL path]
                          error:nil];
    return NO;
  }
  //We may not be at the "current" model yet, so recurse
  return [self progressivelyMigrateURL:sourceStoreURL
                                ofType:type 
                               toModel:finalModel 
                                 error:error];
  //END:progressivelyMigrateURLMoveAndRecurse
}

//START:persistentStoreCoordinator
- (NSPersistentStoreCoordinator*)persistentStoreCoordinator;
{
  if (persistentStoreCoordinator) return persistentStoreCoordinator;
  
  NSFileManager *fileManager = [NSFileManager defaultManager];
  NSString *applicationSupportFolder = [self applicationSupportFolder];
  if ( ![fileManager fileExistsAtPath:applicationSupportFolder
                          isDirectory:NULL] ) {
    [fileManager createDirectoryAtPath:applicationSupportFolder
                            attributes:nil];
  }
  
  NSURL *url = [NSURL fileURLWithPath:[applicationSupportFolder 
                                       stringByAppendingPathComponent:@"GrokkingRecipes.xml"]];
  NSManagedObjectModel *mom = [self managedObjectModel];
  persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
                                initWithManagedObjectModel:mom];
  
  //END:persistentStoreCoordinator
  //START:progressivelyMigrateCall
  NSError *error = nil;
  if (![self progressivelyMigrateURL:url
                              ofType:NSXMLStoreType
                             toModel:mom
                               error:&error]) {
    NSLog(@"%@:%s presenting error progressive migrate failed", [self class], _cmd);
    [[NSApplication sharedApplication] presentError:error];
    return nil;
  }
  //END:progressivelyMigrateCall
  //START:persistentStoreCoordinatorFastSyncStore
  NSPersistentStore *store;
  store = [persistentStoreCoordinator addPersistentStoreWithType:NSXMLStoreType
                                                   configuration:nil
                                                             URL:url
                                                         options:nil
                                                           error:&error];
  if (!store) {
    NSLog(@"%@:%s presenting error no store", [self class], _cmd);
    [[NSApplication sharedApplication] presentError:error];
    return nil;
  }
  
  NSString *fss = @"GrokkingRecipes.fastsyncstore";
  fss = [applicationSupportFolder stringByAppendingPathComponent:fss];
  NSURL *fsdURL = [NSURL fileURLWithPath:fss];
  
  [persistentStoreCoordinator setStoresFastSyncDetailsAtURL:fsdURL
                                         forPersistentStore:store];
  
  return persistentStoreCoordinator;
  //END:persistentStoreCoordinatorFastSyncStore
}

- (NSManagedObjectContext*)managedObjectContext
{
  if (managedObjectContext) return managedObjectContext;
  
  NSPersistentStoreCoordinator *coord = [self persistentStoreCoordinator];
  
  if (!coord) return nil;
  
  managedObjectContext = [[NSManagedObjectContext alloc] init];
  [managedObjectContext setPersistentStoreCoordinator: coord];
  
  NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
  [request setEntity:[NSEntityDescription entityForName:@"Type"
                                 inManagedObjectContext:managedObjectContext]];
  
  NSError *error = nil;
  NSArray *result = [managedObjectContext executeFetchRequest:request 
                                                        error:&error];
  NSAssert(error == nil, [error localizedDescription]);
  if ([result count]) return managedObjectContext;
  
  //The types table has not been populated
  NSArray *types;
  types = [[[NSBundle mainBundle] infoDictionary] valueForKey:@"RecipeTypes"];
  
  for (NSString *type in types) {
    NSManagedObject *object = [NSEntityDescription 
                               insertNewObjectForEntityForName:@"Type" 
                               inManagedObjectContext:managedObjectContext];
    [object setValue:type forKey:@"name"];
  }
  
  return managedObjectContext;
}

//START:metadataSave
- (BOOL)save:(NSError**)error;
{
  NSManagedObjectContext *moc = [self managedObjectContext];
  if (!moc) return YES;
  
  if (![moc hasChanges]) return YES;
  
  //Grab a reference to all of the objects we will need to work with
  NSSet *deleted = [moc deletedObjects];
  NSMutableSet *updated = [NSMutableSet setWithSet:[moc insertedObjects]];
  [updated unionSet:[moc updatedObjects]];
  
  //Save the context
  if (![moc save:error]) {
    return NO;
  }
  //START:PerformSyncOnSave
  NSLog(@"%@:%s Performing sync", [self class], _cmd);
  if (![syncHelper performSync:error]) {
    return NO;
  }
  //END:PerformSyncOnSave
  return [self updateMetadataForObjects:updated 
                      andDeletedObjects:deleted 
                                  error:error];
}
//END:metadataSave

- (IBAction)ingredientSheetSave:(id)sender;
{
  NSUndoManager *undoManager = [[self managedObjectContext] undoManager];
  [undoManager endUndoGrouping];
  [ingredientEditSheet orderOut:sender];
  [NSApp endSheet:ingredientEditSheet];
}

- (IBAction)ingredientSheetCancel:(id)sender;
{
  NSUndoManager *undoManager = [[self managedObjectContext] undoManager];
  [undoManager endUndoGrouping];
  [undoManager undo];
  [ingredientEditSheet orderOut:sender];
  [NSApp endSheet:ingredientEditSheet];
}

- (IBAction)addIngredient:(id)sender;
{
  NSUndoManager *undoManager = [[self managedObjectContext] undoManager];
  [undoManager beginUndoGrouping];
  [undoManager setActionName:@"Add Recipe Ingredient"];
  id ingredient;
  ingredient = [NSEntityDescription 
                insertNewObjectForEntityForName:@"RecipeIngredient" 
                inManagedObjectContext:[self managedObjectContext]];
  id recipe = [[recipeArrayController selectedObjects] lastObject];
  [ingredient setValue:recipe forKey:@"Recipe"];
  NSArray *selected = [NSArray arrayWithObject:ingredient];
  [recipeIngredientArrayController setSelectedObjects:selected];
  [NSApp beginSheet:ingredientEditSheet 
     modalForWindow:window
      modalDelegate:nil 
     didEndSelector:NULL 
        contextInfo:nil];
}

- (IBAction)editIngredient:(id)sender;
{
  NSLog(@"%@:%s entered", [self class], _cmd);
  NSUndoManager *undoManager = [[self managedObjectContext] undoManager];
  [undoManager beginUndoGrouping];
  [undoManager setActionName:@"Edit Recipe Ingredient"];
  [NSApp beginSheet:ingredientEditSheet 
     modalForWindow:window
      modalDelegate:nil 
     didEndSelector:NULL 
        contextInfo:nil];
}

#pragma mark Window Delegate methods
#pragma mark -

- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window
{
  return [[self managedObjectContext] undoManager];
}

#pragma mark Cleanup
#pragma mark -

- (void) dealloc
{
  [managedObjectContext release], managedObjectContext = nil;
  [persistentStoreCoordinator release], persistentStoreCoordinator = nil;
  [managedObjectModel release], managedObjectModel = nil;
  [super dealloc];
}

@end
