//
//  BSCoreDataManager_DataAccessor.m
//  BathyScaphe
//
//  Created by Hori,Masaki on 10/11/03.
//  Copyright 2010 masakih. All rights reserved.
//


#import "DatabaseManager.h"

#import "BSCoreDataManager.h"

#import "BSBoardInformationObject.h"
#import "BSThreadInformationObject.h"
#import "BSBoardThreadItemsObject.h"
#import "BSFavoriteObject.h"
#import "BSBoardHistoryObject.h"

//#import "ThreadTextDownloader.h"
//#import "CMRThreadSignature.h"
//#import "AppDefaults.h"
//#import "CMRDocumentFileManager.h"

//#import "CMRReplyMessenger.h"


@interface BSCoreDataManager (BSCoreDataModelAccessPrivate)
- (void)makeThreadsListsUpdateCursor;
@end

@implementation BSCoreDataManager (BSCoreDataModelAccess)

#pragma mark #### Board Information Accessor ####
- (BSBoardInformationObject *)findBSBoardInformationObjectForHints:(NSDictionary *)hints
{
	NSNumber *boardID = [hints objectForKey:BSBoardInformationObjectHintID];
	NSString *boardURL = [hints objectForKey:BSBoardInformationObjectHintURL];
	NSString *boardName = [hints objectForKey:BSBoardInformationObjectHintName];
	
	NSArray *array = nil;
	NSPredicate *predicate = nil;
	
	
	@try {
		if(boardID) {
			predicate = [NSPredicate predicateWithFormat:@"%K = %@", @"boardID", boardID];
			array = [self fetchDataForEntityName:BSCoreDataModelBoardInformationName
									   predicate:predicate];
			if([array count] != 0) return [array lastObject];
			
			// boardID is primary!
			return nil;
		}
		if(boardURL) {
			predicate = [NSPredicate predicateWithFormat:@"%K = %@", @"boardURL", boardURL];
			array = [self fetchDataForEntityName:BSCoreDataModelBoardInformationName
									   predicate:predicate];
			if([array count] != 0) return [array lastObject];
		}
		if(boardName) {
			// [cd]は旧・mac板（旧・Mac板）対応 Mac OS X 10.5では動かない。
			if(floor(NSAppKitVersionNumber) < 1000) {
				predicate = [NSPredicate predicateWithFormat:@"%K = %@", @"boardName", boardName];
			} else {
				predicate = [NSPredicate predicateWithFormat:@"%K =[cd] %@", @"boardName", boardName];
			}
			
			array = [self fetchDataForEntityName:BSCoreDataModelBoardInformationName
									   predicate:predicate];
			if([array count] != 0) return [array lastObject];
		}
		
		// board history
		if(boardURL) {
			predicate = [NSPredicate predicateWithFormat:@"%K = %@", @"boardURL", boardURL];
			array = [self fetchDataForEntityName:BSCoreDataModelBoardHistoryName
									   predicate:predicate];
			if([array count] != 0) {
				return [[array lastObject] valueForKey:@"board"];
			}
		}
		if(boardName) {
			predicate = [NSPredicate predicateWithFormat:@"%K = %@", @"boardName", boardName];
			array = [self fetchDataForEntityName:BSCoreDataModelBoardHistoryName
									   predicate:predicate];
			if([array count] != 0) {
				return [[array lastObject] valueForKey:@"board"];
			}
		}
	}
	@catch (id ex) {
		if([[ex name] isEqualToString:BSCoreDataManagerFailExuteFetchException]) {
			return nil;
		}
		@throw;
	}
	
	return nil;
}

- (BSBoardInformationObject *)resisterBoardInformationWithName:(NSString *)name URLString:(NSString *)urlString
{
	// check same board already exist
	NSDictionary *hints = [NSDictionary dictionaryWithObject:name forKey:BSBoardInformationObjectHintName];
	id sameName = [self findBSBoardInformationObjectForHints:hints];
	hints = [NSDictionary dictionaryWithObject:urlString forKey:BSBoardInformationObjectHintURL];
	id sameURL = [self findBSBoardInformationObjectForHints:hints];
	if(sameName && sameURL && [sameName isEqual:sameURL]) return sameName;
	
	if(sameURL) {
		[self setName:name toBoard:sameURL];
		return sameURL;
	}
	
	// getting max boardID
	NSManagedObjectContext *context = self.managedObjectContext;
	NSFetchRequest *fetch = [[[NSFetchRequest alloc] init] autorelease];
	NSEntityDescription *entity = [NSEntityDescription entityForName:BSCoreDataModelBoardInformationName
											  inManagedObjectContext:context];
	[fetch setEntity:entity];
	NSSortDescriptor *sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"boardID"
																	ascending:NO] autorelease];
	[fetch setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
	[fetch setFetchLimit:1];
	
	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.";
		}
		NSLog(@"%@", errorString);
		
		return nil;
	}
	
	NSUInteger newID = NSUIntegerMax;
	BSBoardInformationObject *maxBoardIDboard = [array lastObject];
	if(!maxBoardIDboard) {
		newID = 1;
	} else {
		newID = [maxBoardIDboard.boardID unsignedIntegerValue] + 1;
	}
	
	// resister
	BSBoardInformationObject *board = nil;
	board = [NSEntityDescription insertNewObjectForEntityForName:BSCoreDataModelBoardInformationName
										  inManagedObjectContext:self.managedObjectContext];
	if(!board) return nil;
	board.boardID = [NSNumber numberWithUnsignedInteger:newID];
	board.boardName = name;
	board.boardURL = urlString;
	
	[self saveAction:nil];
	
	return board;
}
- (void)setName:(NSString *)name toBoard:(BSBoardInformationObject *)board
{
	if(!board) {
		NSLog(@"board is nil.");
		return;
	}
	if(!name || [name length] == 0) {
		NSLog(@"name is nil or empty.");
		return;
	}
	
	if([name isEqualToString:board.boardName]) return;
	
	// check same history
	NSArray *array = nil;
	@try {
		array = [self fetchDataForEntityName:BSCoreDataModelBoardHistoryName
							 predicateFormat:@"%K = %@ AND %K = %@",
				 @"board", board, @"boardName", name];
	}
	@catch (id ex) {
		if([[ex name] isEqualToString:BSCoreDataManagerFailExuteFetchException]) {
			return;
		}
		@throw;
	}
	
	if([array count] != 0) {
		NSLog(@"Already exist in history.");
		board.boardName = name;
		return;
	}
	
	BSBoardHistoryObject *history = nil;
	history = [NSEntityDescription insertNewObjectForEntityForName:BSCoreDataModelBoardHistoryName
											inManagedObjectContext:self.managedObjectContext];
	if(!history) {
		NSLog(@"Could not insert history.");
		return;
	}
	history.board = board;
	history.boardName = board.boardName;
	
	board.boardName = name;
	
	[self saveAction:nil];
}
- (void)setName:(NSString *)name toBoardID:(NSNumber *)boardID
{
	NSDictionary *hints = [NSDictionary dictionaryWithObject:boardID
													  forKey:BSBoardInformationObjectHintID];
	BSBoardInformationObject *board = [self findBSBoardInformationObjectForHints:hints];
	[self setName:name toBoard:board];
}


- (void)setURLString:(NSString *)urlString toBoard:(BSBoardInformationObject *)board
{
	if(!board) {
		NSLog(@"board is nil.");
		return;
	}
	if(!urlString || [urlString length] == 0) {
		NSLog(@"urlString is nil or empty.");
		return;
	}
	
	if([urlString isEqualToString:board.boardURL]) return;
	
	// check same history
	NSArray *array = nil;
	@try {
		array = [self fetchDataForEntityName:BSCoreDataModelBoardHistoryName
							 predicateFormat:@"%K = %@ AND %K = %@",
				 @"board", board, @"boardURL", urlString];
	}
	@catch (id ex) {
		if([[ex name] isEqualToString:BSCoreDataManagerFailExuteFetchException]) {
			return;
		}
		@throw;
	}
	
	if([array count] != 0) {
		NSLog(@"Already exist in history.");
		board.boardURL = urlString;
		return;
	}
	
	BSBoardHistoryObject *history = nil;
	history = [NSEntityDescription insertNewObjectForEntityForName:BSCoreDataModelBoardHistoryName
											inManagedObjectContext:self.managedObjectContext];
	if(!history) {
		NSLog(@"Could not insert history.");
		return;
	}
	history.board = board;
	history.boardURL = board.boardURL;
	
	board.boardURL = urlString;
	
	[self saveAction:nil];
}
- (void)setURLString:(NSString *)urlString toBoardID:(NSNumber *)boardID
{
	NSDictionary *hints = [NSDictionary dictionaryWithObject:boardID
													  forKey:BSBoardInformationObjectHintID];
	BSBoardInformationObject *board = [self findBSBoardInformationObjectForHints:hints];
	[self setURLString:urlString toBoard:board];
}

- (NSNumber *)boardIDFromBoardURL:(NSString *)urlString
{
	NSDictionary *hints = [NSDictionary dictionaryWithObject:urlString
													  forKey:BSBoardInformationObjectHintURL];
	BSBoardInformationObject *board = [self findBSBoardInformationObjectForHints:hints];
	
	return board.boardID;
}
- (NSString *)urlStringFromBoardID:(NSNumber *)boardID
{
	NSDictionary *hints = [NSDictionary dictionaryWithObject:boardID
													  forKey:BSBoardInformationObjectHintID];
	BSBoardInformationObject *board = [self findBSBoardInformationObjectForHints:hints];
	
	return board.boardURL;
}
- (NSNumber *)boardIDFromBoardName:(NSString *)boardName
{
	NSDictionary *hints = [NSDictionary dictionaryWithObject:boardName
													  forKey:BSBoardInformationObjectHintName];
	BSBoardInformationObject *board = [self findBSBoardInformationObjectForHints:hints];
	
	return board.boardID;
}
- (NSString *)urlStringFromBoardName:(NSString *)boardName
{
	NSDictionary *hints = [NSDictionary dictionaryWithObject:boardName
													  forKey:BSBoardInformationObjectHintName];
	BSBoardInformationObject *board = [self findBSBoardInformationObjectForHints:hints];
	
	return board.boardURL;
}
- (NSString *)boardNameFromBoardID:(NSNumber *)boardID
{
	NSDictionary *hints = [NSDictionary dictionaryWithObject:boardID
													  forKey:BSBoardInformationObjectHintID];
	BSBoardInformationObject *board = [self findBSBoardInformationObjectForHints:hints];
	
	return board.boardName;
}
- (NSString *)boardNameFromBoardURL:(NSString *)urlString
{
	NSDictionary *hints = [NSDictionary dictionaryWithObject:urlString
													  forKey:BSBoardInformationObjectHintURL];
	BSBoardInformationObject *board = [self findBSBoardInformationObjectForHints:hints];
	
	return board.boardName;
}

// for BoardManager-BoardListRepiar
- (void)delaySaveForRemoveBoardInformation
{
	[self saveAction:nil];
}
- (BOOL)removeBoardInformationWithURL:(NSString *)urlString
{
	NSDictionary *hints = [NSDictionary dictionaryWithObject:urlString
													  forKey:BSBoardInformationObjectHintURL];
	BSBoardInformationObject *board = [self findBSBoardInformationObjectForHints:hints];
	if(board) {
		[self.managedObjectContext deleteObject:board];
	}
	
	[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(delaySaveForRemoveBoardInformation) object:nil];
	[self performSelector:@selector(delaySaveForRemoveBoardInformation) withObject:nil afterDelay:0.3];
	return YES;
}

#pragma mark #### Thread Information Accessor ####
- (void)deleteOldSurviveThreadItem:(NSArray *)array
{
//	// remove all object without last one.
//	if([array count] < 2) return;
//	NSManagedObjectContext *moc = [self managedObjectContext];
//	NSUInteger i = 0;
//	for(i = 0; i < [array count] - 1; i++) {
//		[moc deleteObject:[array objectAtIndex:i]];
//	}
}
- (NSArray *)threadInfoWithBoardID:(NSNumber *)boardID
{
	NSArray *array = nil;
	@try {
		array = [self fetchDataForEntityName:BSCoreDataModelSurviveThreadItemsName
							 predicateFormat:@"%K = %@", @"board.boardID", boardID];
	}
	@catch(id ex) {
		if([[ex name] isEqualToString:BSCoreDataManagerFailExuteFetchException]) {
			return [NSArray array];
		}
		@throw;
	}
	if(0 == [array count]) {
		return array;
	}
	[self performSelectorOnMainThread:@selector(deleteOldSurviveThreadItem:) withObject:array waitUntilDone:NO];
	
	id obj = [array lastObject];
	
	NSSet *threadsSet = [obj valueForKeyPath:@"items"];
	return [threadsSet allObjects];
}
- (NSArray *)cachedThreadInfoWithBoardID:(NSNumber *)boardID
{
	NSArray *array = nil;
	@try {
		array = [self fetchDataForEntityName:BSCoreDataModelThreadInformationName
							 predicateFormat:@"%K = %@ AND %K != nil",
				 @"board.boardID", boardID, @"numberOfRead"];
	}
	@catch(id ex) {
		if([[ex name] isEqualToString:BSCoreDataManagerFailExuteFetchException]) {
			return [NSArray array];
		}
		@throw;
	}
	
	return array;
}
- (BSThreadInformationObject *)threadInfoWithIdentifier:(NSString *)threadID boardInfo:(BSBoardInformationObject *)board
{
	NSArray *array = nil;
	@try {
		array = [self fetchDataForEntityName:BSCoreDataModelThreadInformationName
							 predicateFormat:@"%K = %@ AND %K = %@",
				 @"board", board,
				 @"threadID", threadID];
	}
	@catch (id ex) {
		if([[ex name] isEqualToString:BSCoreDataManagerFailExuteFetchException]) {
			return nil;
		}
		@throw;
	}
	
	if([array count] == 0) return nil;
	
	return [array lastObject];
}
- (BSThreadInformationObject *)threadInfoWithIdentifier:(NSString *)threadID boardHints:(NSDictionary *)hints
{
	BSBoardInformationObject *board = [self findBSBoardInformationObjectForHints:hints];
	if(!board) return nil;
	
	return [self threadInfoWithIdentifier:threadID boardInfo:board];
}

- (BSThreadInformationObject *)resisterThreadInformationWithIdentifier:(NSString *)identifier
																  name:(NSString *)name
															 intoBoard:(BSBoardInformationObject *)board
{
	BSThreadInformationObject *thread = nil;
	
	thread = [self threadInfoWithIdentifier:identifier boardInfo:board];
	if(thread) return thread;
	
	thread = [NSEntityDescription insertNewObjectForEntityForName:BSCoreDataModelThreadInformationName
										   inManagedObjectContext:self.managedObjectContext];
	thread.threadID = identifier;
	thread.threadName = name;
	thread.board = board;
	
	// if you send message saveAction:, you must set threadStatus property.
		
	return thread;
}
//- (BSThreadInformationObject *)resisterThreadInformationWithContentsOfFileURL:(NSURL *)fileURL
//{
//	NSString *filepath = [fileURL path];
//	NSDictionary *hoge = [NSDictionary dictionaryWithContentsOfFile:filepath];
//	NSString *datNum, *title, *boardName;
//	NSUInteger count;
//	NSDate *date;
//	CMRThreadUserStatus	*s;
//	id rep;
//	BOOL	isDatOchi;
//	NSUInteger label = 0;
//	
//	datNum = [hoge objectForKey:ThreadPlistIdentifierKey];
//	if (!datNum) return NO;
//	title = [hoge objectForKey:CMRThreadTitleKey];
//	if (!title) return NO;
//	boardName = [hoge objectForKey:ThreadPlistBoardNameKey];
//	if (!boardName) return NO;
//	count = [[hoge objectForKey: ThreadPlistContentsKey] count];
//	
//	rep = [hoge objectForKey:CMRThreadUserStatusKey];
//	s = [CMRThreadUserStatus objectWithPropertyListRepresentation:rep];
//	isDatOchi = (s ? [s isDatOchiThread] : NO);
//	label = (s ? [s label] : 0);
//	
//	date = [hoge objectForKey:CMRThreadModifiedDateKey];
//	
//	NSDictionary *hints = [NSDictionary dictionaryWithObject:boardName forKey:BSBoardInformationObjectHintName];
//	BSBoardInformationObject *board = [self findBSBoardInformationObjectForHints:hints];
//	if(!board) {
//		CMRDocumentFileManager *dfm = [CMRDocumentFileManager defaultManager];
//		NSString *otherBoardName = [dfm boardNameWithLogPath:filepath];
//		hints = [NSDictionary dictionaryWithObject:otherBoardName forKey:BSBoardInformationObjectHintName];
//		board = [self findBSBoardInformationObjectForHints:hints];
//		if(!board) {
//			NSLog(@"Board not resistered.");
//			return NO;
//		}
//	}
//	
//	BSThreadInformationObject *thread = [self resisterThreadInformationWithIdentifier:datNum
//																				 name:title
//																			intoBoard:board];
//	thread.numberOfAll = thread.numberOfRead = [NSNumber numberWithUnsignedInteger:count];
//	thread.modifiedDate = date;
//	thread.threadLabel = [NSNumber numberWithUnsignedInteger:label];
//	thread.isDatOchi = [NSNumber numberWithBool:isDatOchi];
//	
//	[self saveAction:nil];
//	
//	return thread;
//}

- (void)removeThreadInfoWithIdentifier:(NSString *)threadID baordID:(NSNumber *)boardID
{
	NSArray *array = nil;
	@try {
		array = [self fetchDataForEntityName:BSCoreDataModelThreadInformationName
							 predicateFormat:@"%K = %@ AND %K = %@",
				 @"threadID", threadID, @"board.boardID", boardID];
	}
	@catch (id ex) {
		if([[ex name] isEqualToString:BSCoreDataManagerFailExuteFetchException]) {
			return;
		}
		@throw;
	}
	if([array count] == 0) return;
	
	[self.managedObjectContext deleteObject:[array lastObject]];
	[self saveAction:nil];
	
	[self makeThreadsListsUpdateCursor];
}

- (void)setLabel:(NSUInteger)code boardName:(NSString *)boardName threadIdentifier:(NSString *)identifier
{
	NSArray *array = nil;
	@try {
		array = [self fetchDataForEntityName:BSCoreDataModelThreadInformationName
							 predicateFormat:@"%K = %@ AND %K = %@",
					  @"threadID", identifier,
					  @"board.boardName", boardName];
	}
	@catch (id ex) {
		if([[ex name] isEqualToString:BSCoreDataManagerFailExuteFetchException]) {
			return;
		}
		@throw;
	}
	
	if(0 == [array count]) {
		NSLog(@"Could not find thread for boatdName(%@) and threadID(%@)", boardName, identifier);
		return;
	}
	BSThreadInformationObject *thread = [array lastObject];
	thread.threadLabel = [NSNumber numberWithUnsignedInteger:code];
	
	[self saveAction:nil];
}
- (void)setLabel:(NSUInteger)code threadInformation:(id /* BSThreadInformationObject * */)thread
{
	if(![thread isKindOfClass:[BSThreadInformationObject class]]
	   && ![thread isKindOfClass:[BSThreadListItemObject class]]) {
		NSLog(@"thread is not threadObject.");
		return;
	}
	BSThreadInformationObject *aThread = thread;
	aThread.threadLabel = [NSNumber numberWithUnsignedInteger:code];
}
- (void)setIsDatOchi:(BOOL)flag threadInformation:(id /* BSThreadInformationObject * */)thread
{
	if(![thread isKindOfClass:[BSThreadInformationObject class]]
	   && ![thread isKindOfClass:[BSThreadListItemObject class]]) {
		NSLog(@"thread is not threadObject.");
		return;
	}
	
	BSThreadInformationObject *aThread = thread;
	aThread.isDatOchi = [NSNumber numberWithBool:flag];
}
- (void)toggleDatOchiOfThreadInformation:(id /* BSThreadInformationObject * */)thread
{
	if(![thread isKindOfClass:[BSThreadInformationObject class]]
	   && ![thread isKindOfClass:[BSThreadListItemObject class]]) {
		NSLog(@"thread is not threadObject.");
		return;
	}
	
	BSThreadInformationObject *aThread = thread;
	BOOL flag = [aThread.isDatOchi boolValue];
	aThread.isDatOchi = [NSNumber numberWithBool:!flag];
}

//- (NSDictionary *)attributesForThreadsListWithContentsOfFile:(NSString *)inFilePath
//{
//	CMRDocumentFileManager *dfm = [CMRDocumentFileManager defaultManager];
//	NSString *boardName = [dfm boardNameWithLogPath:inFilePath];
//	NSString *threadID = [dfm datIdentifierWithLogPath:inFilePath];
//	
//	if(!boardName) return nil;
//	if(!threadID) return nil;
//	
//	NSDictionary *hints = [NSDictionary dictionaryWithObject:boardName
//													  forKey:BSBoardInformationObjectHintName];
//	
//	BSThreadInformationObject *thread = [self threadInfoWithIdentifier:threadID
//															boardHints:hints];
//	if(!thread) return nil;
//	
//	return thread.attribute;
//}

//- (void)threadTextDownloader:(ThreadTextDownloader *)downloader didUpdateWithContents:(NSDictionary *)userInfo
//{
//	CMRThreadSignature	*signature;
//	
//	UTILAssertKindOfClass(downloader, ThreadTextDownloader);
//	UTILAssertNotNil(userInfo);
//	UTILAssertKindOfClass(userInfo, NSDictionary);
//	
//	signature = [downloader threadSignature];
//	UTILAssertNotNil(signature);
//	
//	NSDate *modDate = [userInfo objectForKey:@"ttd_date"];
//	NSNumber *numCount = [userInfo objectForKey:@"ttd_count"];
//	if (!modDate) {
//		if ([[NSUserDefaults standardUserDefaults] boolForKey:BSUserDebugEnabledKey]) {
//			NSLog(@"** USER DEBUG ** Why? modDate is nil.");
//		}
//	} else {
//		if ([[NSUserDefaults standardUserDefaults] boolForKey:BSUserDebugEnabledKey]) {
//			NSLog(@"** USER DEBUG ** OK. modDate is %@.", modDate);
//		}
//	}
//	
//	NSString *threadID = [signature identifier];
//	NSString *boardName = [signature boardName];
//	
//	NSDictionary *hints = [NSDictionary dictionaryWithObjectsAndKeys:
//						   boardName, BSBoardInformationObjectHintName,
//						   [[downloader boardURL] absoluteString], BSBoardInformationObjectHintURL,
//						   nil];
//	BSBoardInformationObject *board = [self findBSBoardInformationObjectForHints:hints];
//	if(!board) {
//		NSLog(@"Could not find board.");
//		NSLog(@"#### I NEED TO WRITE CODE RESISTER BOARD HERE!! ####");
//		return;
//	}
//	
//	NSArray *array = [self fetchDataForEntityName:BSCoreDataModelThreadInformationName
//								  predicateFormat:@"%K = %@ AND %K = %@",
//					  @"board", board, @"threadID", threadID];
//	BSThreadInformationObject *thread;
//	if(0 != [array count]) {
//		thread = [array lastObject];
//	} else {
//		thread = [self resisterThreadInformationWithIdentifier:threadID
//														  name:[downloader threadTitle]
//													 intoBoard:board];
//		thread.isDatOchi = [NSNumber numberWithBool:[downloader useMaru]];
//	}
//	thread.numberOfAll = numCount;
//	thread.numberOfRead = numCount;
//	thread.modifiedDate = modDate;
//	
//	NSNotification *notification = [NSNotification notificationWithName:BSCoreDataWillUpdateThreadItemNotification
//																 object:self];
//	[[NSNotificationCenter defaultCenter] performSelectorOnMainThread:@selector(postNotification:)
//														   withObject:notification
//														waitUntilDone:YES];
//	if ([CMRPref sortsImmediately]) {
//		[self makeThreadsListsUpdateCursor];
//	} else {
//		NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
//								  threadID, BSCoreDataUserInfoThreadIDKey,
//								  numCount, BSCoreDataUserInfoThreadCountKey,
//								  modDate, BSCoreDataUserInfoThreadModDateKey,
//								  boardName, BSCoreDataUserInfoBoardNameKey,
//								  [NSNumber numberWithBool:[array count] == 0], BSCoreDataUserInfoIsDBInsertedKey,
//								  NULL];
//		
//		NSNotification *notification = [NSNotification notificationWithName:BSCoreDataWantsThreadItemsUpdateNotification
//																	 object:self
//																   userInfo:userInfo];
//		[[NSNotificationCenter defaultCenter] performSelectorOnMainThread:@selector(postNotification:)
//															   withObject:notification waitUntilDone:NO];
//	}
//	
//	[self saveAction:nil];
//}
- (void)makeThreadsListsUpdateCursor
{
	NSNotification *notification = [NSNotification notificationWithName:BSCoreDataDidFinishUpdateDownloadedOrDeletedThreadInfoNotification
																 object:self];
	[[NSNotificationCenter defaultCenter] performSelectorOnMainThread:@selector(postNotification:)
														   withObject:notification
														waitUntilDone:NO];
}

//- (BOOL)rebuildFromFilePath:(NSString *)filepath withBoardID:(NSNumber *)boardID
//{
//	NSDictionary *hoge = [NSDictionary dictionaryWithContentsOfFile:filepath];
//	NSString *datNum, *title;
//	NSUInteger count, numberOfAll;
//	NSDate *date;
//	CMRThreadUserStatus	*s;
//	id rep;
//	BOOL isDatOchi;
//    NSUInteger labelCode;
//	
//	datNum = [hoge objectForKey:ThreadPlistIdentifierKey];
//	if (!datNum) return NO;
//	title = [hoge objectForKey:CMRThreadTitleKey];
//	if (!title) return NO;
//	count = [[hoge objectForKey: ThreadPlistContentsKey] count];
//	
//	rep = [hoge objectForKey:CMRThreadUserStatusKey];
//	s = [CMRThreadUserStatus objectWithPropertyListRepresentation:rep];
//	isDatOchi = (s ? [s isDatOchiThread] : NO);
//    labelCode = (s ? [s label] : 0);
//	date = [hoge objectForKey:CMRThreadModifiedDateKey];
//	
//	NSDictionary *hints = [NSDictionary dictionaryWithObject:boardID
//													  forKey:BSBoardInformationObjectHintID];
//	BSBoardInformationObject *board = [self findBSBoardInformationObjectForHints:hints];
//	BSThreadInformationObject *thread = [self threadInfoWithIdentifier:datNum
//															 boardInfo:board];
//	if(!thread) {
//		thread = [self resisterThreadInformationWithIdentifier:datNum
//														  name:title
//													 intoBoard:board];
//	}
//	
//	numberOfAll = [thread.numberOfAll unsignedIntegerValue];
//	numberOfAll = MAX(numberOfAll, count);
//	
//	thread.numberOfAll = [NSNumber numberWithUnsignedInteger:numberOfAll];
//	thread.numberOfRead = [NSNumber numberWithUnsignedInteger:count];
//	thread.modifiedDate = date;
//	thread.isDatOchi = [NSNumber numberWithBool:isDatOchi];
//	thread.threadLabel = [NSNumber numberWithInteger:labelCode];
//	
//	return YES;
//}

//static NSError *fileContentsErrorObject(NSArray *filepaths)
//{
//    NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:filepaths, DatabaseManagerInvalidFilePathsArrayKey,
//                          NSLocalizedStringFromTable(@"RebuildingErrorAlert", @"DatabaseManager", nil), NSLocalizedDescriptionKey,
//                          NSLocalizedStringFromTable(@"RebuildingErrorMessage", @"DatabaseManager", nil), NSLocalizedRecoverySuggestionErrorKey,
//                          [NSArray arrayWithObjects:NSLocalizedStringFromTable(@"RebuildingErrorOK", @"DatabaseManager", nil),
//                           NSLocalizedStringFromTable(@"RebuildingErrorShowFiles", @"DatabaseManager", nil), nil], NSLocalizedRecoveryOptionsErrorKey,
//                          NULL];
//    return [NSError errorWithDomain:BSBathyScapheErrorDomain code:DatabaseManagerRebuildLogFileContentsError userInfo:dict];
//}
//- (BOOL)rebuildFromLogFolder:(NSString *)folderPath boardID:(NSNumber *)boardID error:(NSError **)error
//{
//	NSDirectoryEnumerator *iter = [[NSFileManager defaultManager] enumeratorAtPath:folderPath];
//	NSString	*fileName, *filePath;
//	NSAutoreleasePool *pool = nil;
//	
//	NSDate *date1 = [NSDate dateWithTimeIntervalSinceNow:0.0];
//	
//	NSMutableArray *invalidFiles = [NSMutableArray array];
//	
//	for(fileName in iter) {
//		pool = [[NSAutoreleasePool alloc] init];
//		if ([[fileName pathExtension] isEqualToString:@"thread"]) {
//			filePath = [folderPath stringByAppendingPathComponent:fileName];
//			if(![self rebuildFromFilePath:filePath withBoardID:boardID]) {
//				[invalidFiles addObject:filePath];
//			}
//		}
//		[pool release];
//		pool = nil;
//	}
//	
//	NSDate *date2 = [NSDate dateWithTimeIntervalSinceNow:0.0];
//	NSLog(@"Work time is %.3f", [date2 timeIntervalSinceDate:date1]);
//	
//	if ((error != NULL) && ([invalidFiles count] > 0)) {
//        *error = fileContentsErrorObject(invalidFiles);
//    }
//	
//	return YES;
//}

// 未使用データを削除する
- (void)deleteUnusedInfomationsOnBoardID:(NSNumber *)boardID
{
	
	NSArray *alivals = [self fetchDataForEntityName:BSCoreDataModelSurviveThreadItemsName
									predicateFormat:@"%K = %@",
													@"board.boardID", boardID];
	if([alivals count] == 0) return;
	
	BSBoardThreadItemsObject *alivalThreads = [alivals lastObject];
	id threads = [alivalThreads.items valueForKey:@"thread"];
	NSArray *array = [self fetchDataForEntityName:BSCoreDataModelThreadInformationName
								  predicateFormat:@"%K = %@ AND %K = NULL AND NOT SELF IN %@",
													@"board.boardID", boardID,
													@"numberOfRead",
													threads];
	
//	UTILDebugWrite1(@"Unused items count is %d", [array count]);
	for(BSThreadInformationObject *thread in array) {
		if([thread.numberOfRead integerValue] == 0) {
			[self.managedObjectContext deleteObject:thread];
		}
	}
}

// データをリセットする
//- (void)cleanUpItemsWhichHasBeenRemoved:(NSArray *)filenames
//{
//#warning CHECK THIS! お気に入り、ラベル等はどうする？
//	for(NSString *path in filenames) {
//		// search baordID and threadID.
//		CMRDocumentFileManager *dfm = [CMRDocumentFileManager defaultManager];
//		NSString *threadID = [dfm datIdentifierWithLogPath:path];
//		NSString *boardName = [dfm boardNameWithLogPath:path];
//		
//		NSDictionary *hints = [NSDictionary dictionaryWithObject:boardName
//														  forKey:BSBoardInformationObjectHintName];
//		BSBoardInformationObject *board = [self findBSBoardInformationObjectForHints:hints];
//		if(!board) continue;
//		
//		// get BSThreadInformationObject
//		BSThreadInformationObject *thread = nil;
//		thread = [self threadInfoWithIdentifier:threadID
//									  boardInfo:board];
//		
//		thread.numberOfRead = nil;
//		thread.modifiedDate = nil;
//	}
//	
//	
//	NSNotification *notification = [NSNotification notificationWithName:BSCoreDataWillDeleteThreadItemsNotification
//																 object:self];
//	[[NSNotificationCenter defaultCenter] performSelectorOnMainThread:@selector(postNotification:)
//														   withObject:notification
//														waitUntilDone:YES];
//    if ([CMRPref sortsImmediately]) {
//        [self makeThreadsListsUpdateCursor];
//	} else {
//        NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
//								  filenames, UserInfoThreadPathsArrayKey,
//								  nil];
//        NSNotification *notification = [NSNotification notificationWithName:BSCoreDataWantsThreadItemsUpdateNotification
//																	 object:self
//																   userInfo:userInfo];
//        [[NSNotificationCenter defaultCenter] performSelectorOnMainThread:@selector(postNotification:)
//															   withObject:notification
//															waitUntilDone:NO];
//    }
//}


#pragma mark #### Favorites Accessor ####
- (NSArray *)favoritesThreadInformation
{
	NSArray *array = nil;
	@try {
		array = [self fetchDataForEntityName:BSCoreDataModelFavoriteName
									predicateFormat:nil];
	}
	@catch(id ex) {
		if([[ex name] isEqualToString:BSCoreDataManagerFailExuteFetchException]) {
			return [NSArray array];
		}
		@throw;
	}
	
	return array;
}
- (NSArray *)sortedFavorites
{
	NSEntityDescription *entry = [NSEntityDescription entityForName:BSCoreDataModelFavoriteName
											 inManagedObjectContext:self.managedObjectContext];
	NSFetchRequest *fetch = [[[NSFetchRequest alloc] init] autorelease];
	[fetch setEntity:entry];
	NSSortDescriptor *sorter = [[[NSSortDescriptor alloc] initWithKey:@"index"
															ascending:YES] autorelease]; 
	[fetch setSortDescriptors:[NSArray arrayWithObject:sorter]];
	
	NSError *error = nil;
	NSArray *array = [self.managedObjectContext executeFetchRequest:fetch error:&error];
	if(!array) {
		if(error) {
			NSLog(@"fail fetch reason -> %@", error);
		}
	}
	
	return array;
}
- (void)packFavIndex
{
	NSArray *array = [self sortedFavorites];
	
	NSUInteger newIndex = 1000;
	for(BSFavoriteObject *fav in array) {
		fav.index = [NSNumber numberWithInteger:newIndex];
		newIndex += 1000;
	}
}

- (BOOL)addFavoritesThreadIdentifier:(NSString *)threadID boardName:(NSString *)boardName
{
	NSDictionary *hints = [NSDictionary dictionaryWithObject:boardName
													  forKey:BSBoardInformationObjectHintName];
	BSThreadInformationObject *thread = [self threadInfoWithIdentifier:threadID
															boardHints:hints];
	if(!thread) {
		// 登録する？
		return NO;
	}
	
	NSArray *array = nil;
	@try {
		array = [self fetchDataForEntityName:BSCoreDataModelFavoriteName
							 predicateFormat:@"%K = %@",
				 @"thread", thread];
	}
	@catch(id ex) {
		if([[ex name] isEqualToString:BSCoreDataManagerFailExuteFetchException]) {
			return NO;
		}
		@throw;
	}
	
	if([array count] != 0) {
		// 既にお気に入りです！
		return NO;
	}
	
	BSFavoriteObject *fav = [NSEntityDescription insertNewObjectForEntityForName:BSCoreDataModelFavoriteName
														  inManagedObjectContext:self.managedObjectContext];
	if(!fav) {
		// 登録出来ません！
		return NO;
	}
	fav.thread = thread;
	fav.index = [NSNumber numberWithLongLong:LLONG_MAX];
	
	[self packFavIndex];
	[self saveAction:nil];
	
	return YES;
}
- (BOOL)removeFavoritesThreadIdentifier:(NSString *)threadID boardName:(NSString *)boardName
{
	NSDictionary *hints = [NSDictionary dictionaryWithObject:boardName
													  forKey:BSBoardInformationObjectHintName];
	BSThreadInformationObject *thread = [self threadInfoWithIdentifier:threadID
															boardHints:hints];
	if(!thread) {
		// 登録する？
		return NO;
	}
	
	NSArray *array = nil;
	@try {
		array = [self fetchDataForEntityName:BSCoreDataModelFavoriteName
							 predicateFormat:@"%K = %@",
				 @"thread", thread];
	}
	@catch(id ex) {
		if([[ex name] isEqualToString:BSCoreDataManagerFailExuteFetchException]) {
			return NO;
		}
		@throw;
	}
	
	if([array count] == 0) {
		// 既にお気に入りじゃありません
		return NO;
	}
	
	[self.managedObjectContext deleteObject:[array lastObject]];
	[self saveAction:nil];
	
	return YES;
}

- (void)moveToFirstFavorites:(NSArray *)favorites
{
	NSArray *array = [self sortedFavorites];
	BSFavoriteObject *first = [array objectAtIndex:0];
	
	NSUInteger step = [first.index unsignedIntegerValue] / ([favorites count] + 1);
	NSUInteger insertPoint = step;
	for(BSFavoriteObject *fav in favorites) {
		fav.index = [NSNumber numberWithUnsignedInteger:insertPoint];
		insertPoint += step;
	}
	
	[self packFavIndex];
	
//	UTILNotifyName(BSCoreDataWillUpdateThreadItemNotification);
}
- (void)moveToLastFavorites:(NSArray *)favorites
{
	NSArray *array = [self sortedFavorites];
	BSFavoriteObject *last = [array lastObject];
	
	NSUInteger lastIndex = [last.index unsignedIntegerValue];
	NSUInteger newIndex = lastIndex + 1000;
	for(BSFavoriteObject *fav in favorites) {
		fav.index = [NSNumber numberWithUnsignedInteger:newIndex];
		newIndex += 1000;
	}
	
//	UTILNotifyName(BSCoreDataWillUpdateThreadItemNotification);
}
- (void)moveFavorites:(NSArray *)favorites afterFavorite:(id)favorite
{
	if([favorites count] == 0) return;
	if([favorites count] >= 1000) {
		NSLog(@"we can not move favorites over thousand.");
		return;
	}
	
	BSFavoriteObject *after = favorite;
	NSArray *array = [self sortedFavorites];
	
	if(!favorite) {
		return [self moveToFirstFavorites:favorites];
	}
	NSUInteger targetIndex = [array indexOfObject:favorite];
	if(targetIndex == [array count] - 1) {
		return [self moveToLastFavorites:favorites];
	}
	
	BSFavoriteObject *before = nil;
	if(targetIndex != 0) {
		before = [array objectAtIndex:targetIndex - 1];
	}
	
	NSUInteger diff = [after.index unsignedIntegerValue] - [before.index unsignedIntegerValue];
	if(diff - 1 < [favorites count]) {
		[self packFavIndex];
		return [self moveFavorites:favorites afterFavorite:favorite];
	}
	
	NSInteger step = diff / ([favorites count] + 1);
	NSInteger insertPoint = [after.index unsignedIntegerValue] + step;
	for(BSFavoriteObject *fav in favorites) {
		fav.index = [NSNumber numberWithUnsignedInteger:insertPoint];
		insertPoint += step;
	}
	
	[self packFavIndex];
	[self saveAction:nil];
	
//	UTILNotifyName(BSCoreDataWillUpdateThreadItemNotification);
	return;
}


#pragma mark #### Reply ####
//- (void)finishWriteMesssage:(NSNotification *)aNotification
//{
//	id obj = [aNotification object];
//	UTILAssertKindOfClass(obj, [CMRReplyMessenger class]);
//	
//	NSString *boardName = [obj boardName];
//	NSString *threadID = [obj datIdentifier];
//	NSDate *writeDate = [obj modifiedDate];
//	
//	NSDictionary *hints = [NSDictionary dictionaryWithObject:boardName forKey:BSBoardInformationObjectHintName];
//	BSThreadInformationObject *threadInfo = [self threadInfoWithIdentifier:threadID
//																boardHints:hints];
//	
//	threadInfo.lastWrittenDate = writeDate;
//}
@end


NSUInteger indexOfIdentifier(NSArray *array, NSString *search)
{
	NSUInteger i;
    NSUInteger count;
	id object;
	id identifier;
	
	count = [array count];
	if (count == 0) {
        return NSNotFound;
	}
	for (i = 0; i < count; i++ ) {
		object = [array objectAtIndex:i];
		identifier = [object identifier];
		if ([search isEqualTo:identifier]) {
			return i;
		}
	}
	
	return NSNotFound;
}

id itemOfTitle(NSArray *array, NSString *searchTitle)
{
	NSString *title;
	NSString *adjustedSearchTitle = [searchTitle stringByAppendingString:@" "];
	
	for(id object in array) {
		//		if ([object isKindOfClass:[BSThreadListItem class]]) NSLog(@"Class OK");
		title = [object threadName];
		NSLog(@"title check: '%@'", title);
		if ([adjustedSearchTitle isEqualToString:title]) {
			return object;
		}
	}
	
	return nil;
}
