//
//  BSCoreDataManager.m
//  BathyScaphe
//
//  Created by Hori,Masaki on 10/10/12.
//  Copyright 2010 masakih. All rights reserved.
//

#import "BSCoreDataManager.h"

//#import "CMRReplyMessenger.h"
#import "SQLiteDB.h"

#import "BSDCAppDelegate.h"


@implementation BSCoreDataManager
NSString *const BSCoreDataModelBoardInformationName = @"BoardInformation";
NSString *const BSCoreDataModelThreadInformationName = @"ThreadInformation";
NSString *const BSCoreDataModelBoardHistoryName = @"BoardHistory";
NSString *const BSCoreDataModelThreadItemName = @"ThreadItem";
NSString *const BSCoreDataModelFavoriteName = @"Favorite";
NSString *const BSCoreDataModelSurviveThreadItemsName = @"SurviveThreadItems";


NSString *const BSBoardInformationObjectHintID = @"BSBoardInformationObjectHintID";
NSString *const BSBoardInformationObjectHintURL = @"BSBoardInformationObjectHintURL";
NSString *const BSBoardInformationObjectHintName = @"BSBoardInformationObjectHintName";

NSString *const BSCoreDataManagerFailExuteFetchException = @"BSCoreDataManagerFailExuteFetchException";


NSString *const BSCoreDataDidFinishUpdateDownloadedOrDeletedThreadInfoNotification = @"BSCoreDataDidFinishUpdateDownloadedOrDeletedThreadInfoNotification";
NSString *const BSCoreDataWillUpdateThreadItemNotification = @"BSCoreDataWillUpdateThreadItemNotification";
NSString *const BSCoreDataWillDeleteThreadItemsNotification = @"BSCoreDataWillDeleteThreadItemsNotification";


NSString *const BSCoreDataWantsThreadItemsUpdateNotification = @"BSCoreDataWantsThreadItemsUpdateNotification";
NSString *const BSCoreDataUserInfoBoardNameKey = @"BoardName";
NSString *const BSCoreDataUserInfoThreadIDKey = @"Identifier";
NSString *const BSCoreDataUserInfoThreadCountKey = @"Count";
NSString *const BSCoreDataUserInfoThreadModDateKey = @"ModDate";
NSString *const BSCoreDataUserInfoThreadPathsArrayKey = @"Files";
NSString *const BSCoreDataUserInfoUpdateTypeKey = @"UpdateType";
NSString *const BSCoreDataUserInfoIsDBInsertedKey = @"IsInsert";
NSString *const BSCoreDataUserInfoThreadStatusKey = @"ThreadStatus";

static BSCoreDataManager *defaultManager;

@synthesize managedObjectContext;


- (void)deleteOldSurviveItems
{
	NSArray *array = nil;
	@try {
		array = [self fetchDataForEntityName:BSCoreDataModelSurviveThreadItemsName predicate:nil];
	}
	@catch(id ex) {
		if([[ex name] isEqualToString:BSCoreDataManagerFailExuteFetchException]) {
			return;
		}
		@throw;
	}
	if(0 == [array count]) {
		return;
	}
	
	NSManagedObjectContext *moc = [self managedObjectContext];
	for(id obj in array) {
		[moc deleteObject:obj];
	}
	[self saveAction:nil];
}
+ (BSCoreDataManager *)defaultManager
{
	if(defaultManager) return defaultManager;
	
	defaultManager = [[[self class] alloc] init];
	[defaultManager deleteOldSurviveItems];
	
	return defaultManager;
}
+ (BSCoreDataManager *)oneTimeEditor
{
	BSCoreDataManager *result = [[[self alloc] init] autorelease];
	result->oneTimeEditor = YES;
	
	return result;
}

- (id)init
{
	[super init];
	
	NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
	[nc addObserver:self selector:@selector(applicationWillTerminate:) name:NSApplicationWillTerminateNotification object:NSApp];
//	[nc addObserver:self selector:@selector(finishWriteMesssage:) name:CMRReplyMessengerDidFinishPostingNotification object:nil];
	
	return self;
}

/**
 Creates, retains, and returns the managed object model for the application 
 by merging all of the models found in the application bundle.
 */

- (NSManagedObjectModel *)managedObjectModel
{
	if(managedObjectModel != nil) {
		return managedObjectModel;
	}
	
	managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];    
	return managedObjectModel;
}

- (NSURL *)storeURL
{
//	NSString *storePath = [[CMRFileManager defaultManager] supportFilepathWithName:@"BathyScaphe.qdb"
//																  resolvingFileRef:nil];
//	
//	NSURL *url = [NSURL fileURLWithPath:storePath];
//	
//	return url;
	
	NSString *fpath = [BSDCAppDelegate bathyScapheSupportFolderPath];
	return [NSURL fileURLWithPath:[fpath stringByAppendingPathComponent:@"BathyScaphe.qdb"]];
}

/**
 Returns the persistent store coordinator for the application.  This 
 implementation will create and return a coordinator, having added the 
 store for the application to it.  (The folder for the store is created, 
 if necessary.)
 */

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
	if(persistentStoreCoordinator != nil) {
		return persistentStoreCoordinator;
	}
	
	NSError *error;
    
	NSURL *url = [self storeURL];
	
	persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
	if(!persistentStoreCoordinator) {
		NSLog(@"Could not create store coordinator");
		exit(-1);
	}
	
	NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
							 [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
							 nil];
	
	if(![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:options error:&error]){
		[[NSApplication sharedApplication] presentError:error];
		NSLog(@"Error -> %@", error);// localizedDescription]);
	}
	
	return persistentStoreCoordinator;
}


/**
 Returns the managed object context for the application (which is already
 bound to the persistent store coordinator for the application.) 
 */

- (NSManagedObjectContext *)managedObjectContext
{
	if(oneTimeEditor) {
		if(managedObjectContext != nil) {
			return managedObjectContext;
		}
		
		NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
		if(coordinator != nil) {
			managedObjectContext = [[NSManagedObjectContext alloc] init];
			[managedObjectContext setPersistentStoreCoordinator: coordinator];
			[managedObjectContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
			[managedObjectContext setUndoManager:nil];
			
			[[NSNotificationCenter defaultCenter] addObserver:[[self class] defaultManager]
													 selector:@selector(anotherContextDidSave:)
														 name:NSManagedObjectContextDidSaveNotification
													   object:managedObjectContext];
		}
		return managedObjectContext;
	}
	
	if(![NSThread isMainThread]) {
		id moc = [[[NSThread currentThread] threadDictionary] objectForKey:@"BSCoreDataManagerManagedObjectContext"];
		if(moc) return moc;
		
		moc = [[[NSManagedObjectContext alloc] init] autorelease];
		[moc setPersistentStoreCoordinator:[self persistentStoreCoordinator]];
		[[[NSThread currentThread] threadDictionary] setObject:moc
														forKey:@"BSCoreDataManagerManagedObjectContext"];
		
		[[NSNotificationCenter defaultCenter] addObserver:self
												 selector:@selector(anotherContextDidSave:)
													 name:NSManagedObjectContextDidSaveNotification
												   object:moc];
		
		[moc setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
		[moc setUndoManager:nil];
		
		return moc;
	}
	
	if(managedObjectContext != nil) {
		return managedObjectContext;
	}
	
	NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
	if(coordinator != nil) {
		managedObjectContext = [[NSManagedObjectContext alloc] init];
		[managedObjectContext setPersistentStoreCoordinator: coordinator];
		[managedObjectContext setUndoManager:nil];
	}
	
	return managedObjectContext;
}


/**
 Performs the save action for the application, which is to send the save:
 message to the application's managed object context.  Any encountered errors
 are presented to the user.
 */

- (IBAction)saveAction:(id)sender
{
	NSError *error = nil;
	if([self.managedObjectContext commitEditing]) {
		if([self.managedObjectContext hasChanges] && ![self.managedObjectContext save:&error]) {
			[[NSApplication sharedApplication] presentError:error];
		}
		if(![NSThread isMainThread]) {
			[self.managedObjectContext reset];
//			NSLog(@"Reset on Thread(%p).", [NSThread currentThread]);
		}
	}
}


/**
 Implementation of the applicationShouldTerminate: method, used here to
 handle the saving of changes in the application managed object context
 before the application terminates.
 */

//- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
- (void)applicationWillTerminate:(id)notification
{
	NSError *error;
	NSUInteger reply = NSTerminateNow;
	
	if(managedObjectContext != nil) {
		if([managedObjectContext commitEditing]) {
			if([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
				
                // This error handling simply presents error information in a panel with an 
                // "Ok" button, which does not include any attempt at error recovery (meaning, 
                // attempting to fix the error.)  As a result, this implementation will 
                // present the information to the user and then follow up with a panel asking 
                // if the user wishes to "Quit Anyway", without saving the changes.
				
                // Typically, this process should be altered to include application-specific 
                // recovery steps.  
				
				BOOL errorResult = [[NSApplication sharedApplication] presentError:error];
				
				if(errorResult == YES) {
					reply = NSTerminateCancel;
				} else {
					
					NSInteger alertReturn = NSRunAlertPanel(nil, @"Could not save changes while quitting. Quit anyway?" , @"Quit anyway", @"Cancel", nil);
					if(alertReturn == NSAlertAlternateReturn) {
						reply = NSTerminateCancel;	
					}
				}
			}
		} else {
			reply = NSTerminateCancel;
		}
	}
	
	return;// reply;
}


/**
 Implementation of dealloc, to release the retained variables.
 */

- (void)dealloc
{
	NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
	if(oneTimeEditor) {
		[self saveAction:nil];
		[nc removeObserver:[[self class] defaultManager] name:nil object:managedObjectContext];
	}
	
	[nc removeObserver:self name:nil object:managedObjectContext];
	[nc removeObserver:self name:nil object:NSApp];
//	[nc removeObserver:self name:CMRReplyMessengerDidFinishPostingNotification object:nil];
	
	[managedObjectContext release], managedObjectContext = nil;
	[persistentStoreCoordinator release], persistentStoreCoordinator = nil;
	[managedObjectModel release], managedObjectModel = nil;
	[super dealloc];
}


- (void)anotherContextDidSave:(NSNotification *)notification
{
	[managedObjectContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:)
										   withObject:notification
										waitUntilDone:YES];
}

- (void)refresh
{
	if([NSThread isMainThread]) {
		[self saveAction:nil];
//		[managedObjectContext release];
//		managedObjectContext = nil;
		[self.managedObjectContext reset];
//		NSLog(@"Reset on Main Thread.");
		
		return;
	}
	
	[[[NSThread currentThread] threadDictionary] removeObjectForKey:@"BSCoreDataManagerManagedObjectContext"];
//	[self.managedObjectContext reset];
//	NSLog(@"Reset on Not Main Thread(%p).", [NSThread currentThread]);
}


- (NSArray *)fetchDataForEntityName:(NSString *)entityName sortDescriptors:(NSArray *)sortDescriptors predicate:(NSPredicate *)predicate;
{
	NSManagedObjectContext *context = self.managedObjectContext;
	NSFetchRequest *fetch = [[[NSFetchRequest alloc] init] autorelease];
	NSEntityDescription *entity = [NSEntityDescription entityForName:entityName
											  inManagedObjectContext:context];
	
	[fetch setEntity:entity];
	[fetch setPredicate:predicate];
	if(sortDescriptors) {
		[fetch setSortDescriptors:sortDescriptors];
	}
	
	NSError *error = nil;
	NSArray *array = [context executeFetchRequest:fetch
											error:&error];
	if(!array) {
		NSString *errorString = nil;
		if(error) {
			errorString = [NSString stringWithFormat:@"Can not execute request: %@", error];
		} else {
			errorString = @"Can not execute request.";
		}
		[NSException raise:BSCoreDataManagerFailExuteFetchException format:@"%@", errorString];
	}
	
//	NSLog(@"Fetch on Thread(%p).", [NSThread currentThread]);
	
	return array;
}
- (NSArray *)fetchDataForEntityName:(NSString *)entityName sortDescriptors:(NSArray *)sortDescriptors predicateFormat:(NSString *)format, ...
{
	NSPredicate *predicate = nil;
	
	if(format) {
		va_list ap;
		va_start(ap, format);
		predicate = [NSPredicate predicateWithFormat:format arguments:ap];
		va_end(ap);
	}
	
	return [self fetchDataForEntityName:entityName sortDescriptors:sortDescriptors predicate:predicate];
}
- (NSArray *)fetchDataForEntityName:(NSString *)entityName predicate:(NSPredicate *)predicate
{
	return [self fetchDataForEntityName:entityName sortDescriptors:nil predicate:predicate];
}
- (NSArray *)fetchDataForEntityName:(NSString *)entityName predicateFormat:(NSString *)format, ...
{
	NSPredicate *predicate = nil;
	
	if(format) {
		va_list ap;
		va_start(ap, format);
		predicate = [NSPredicate predicateWithFormat:format arguments:ap];
		va_end(ap);
	}
	
	return [self fetchDataForEntityName:entityName sortDescriptors:nil predicate:predicate];
}

- (void)doVacuum
{
	SQLiteDB *db;
	NSURL *storeURL = [self storeURL];
	db = [[[SQLiteDB alloc] initWithDatabasePath:[storeURL path]] autorelease];
	
	[db performQuery:@"VACUUM;"];
}

@end


@implementation NSString(BSThreadListUpdateTaskAddition)
- (NSComparisonResult)numericCompare:(NSString *)string
{
	return [self compare:string options:NSNumericSearch];
}
@end


@implementation NSNumber(BSThreadListUpdateTaskAddition)
- (NSComparisonResult)numericCompare:(id)obj
{
	return [self compare:obj];
}
@end


@implementation NSDate(BSThreadListUpdateTaskAddition)
- (NSComparisonResult)numericCompare:(id)obj
{
	return [self compare:obj];
}
@end

