//
//  KMDocument.m
//  BathyScaphe
//
//  Created by 堀 昌樹 on 11/12/08.
//  Copyright (c) 2011年 __MyCompanyName__. All rights reserved.
//

#import "KMDocument.h"

#import "KMLogDocumentWindowController.h"

#import "BoardManager.h"
#import "CMRThreadAttributes.h"
#import "CMRThreadSignature.h"
#import "ThreadTextDownloader.h"
#import "CMRThreadFileLoadingTask.h"
#import "CMRThreadPlistComposer.h"
#import "CMRThreadMessageBuffer.h"
#import "CMRThreadMessage.h"
#import "CMRThreadDictReader.h"
#import "CMR2chDATReader.h"

#import "CMRTaskManager.h"
#import "CMRHistoryManager.h"
#import "CMRTrashbox.h"

#import "BSMessageSampleRegistrant.h"
#import "BSSpamJudge.h"
#import "BSAsciiArtDetector.h"

#import "AppDefaults.h"
#import <SGAppKit/NSWorkspace-SGExtensions.h>


NSString *KMDocumentDidChangeNotification = @"KMDocumentDidChangeNotification";

NSString *KMDocumentDidChangeMessageNotification = @"KMDocumentDidChangeMessageNotification";
NSString *KMDocumentChangedMessageIndexKey = @"KMDocumentChangedMessageIndexKey";



NSString *const CMRAbstractThreadDocumentDidToggleDatOchiNotification = @"CMRAbstractThreadDocumentDidToggleDatOchiNotification";
NSString *const CMRAbstractThreadDocumentDidToggleLabelNotification = @"CMRAbstractThreadDocumentDidToggleLabelNotification";



@interface KMDocument() <BSMessageSampleRegistrantDelegate>
@property (readwrite, retain) CMRThreadMessageBuffer *messageBuffer;
@property (readwrite) BOOL messagesEdited;

@property (readonly) CMRThreadSignature *threadSignature;
@property (readonly) BSMessageSampleRegistrant *registrant;

- (void)loadContent:(NSURL *)fileURL;
- (BOOL)synchronize;
- (NSArray *)observeKeysForThreadAttributes;
@end

@interface KMDocument(CMRThreadLayout_Methods_Private)
- (void)addMessagesFromBuffer:(CMRThreadMessageBuffer *)otherBuffer;
@end


@implementation KMDocument
@synthesize threadAttr = _threadAttr;
@synthesize messageBuffer = _messageBuffer;
@synthesize messagesEdited = _messagesEdited;
@synthesize registrant = _registrant;

void registerObserver(KMDocument *document, id target, NSArray *targetKeyPaths)
{
	for(NSString *keyPath in targetKeyPaths) {
		[target addObserver:document
				 forKeyPath:keyPath
					options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
					context:target];
	}
}
void unregisterObserver(KMDocument *document, id target, NSArray *targetKeyPaths)
{
	for(NSString *keyPath in targetKeyPaths) {
		[target removeObserver:document
					forKeyPath:keyPath];
	}
}

- (id)init
{
    self = [super init];
    if (self) {
        // Add your subclass-specific initialization here.
        // If an error occurs here, return nil.
		
		self.messageBuffer = [[[CMRThreadMessageBuffer alloc] init] autorelease];
		
		NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
		[nc addObserver:self
			   selector:@selector(threadMessageDidChangeAttribute:)
				   name:CMRThreadMessageDidChangeAttributeNotification
				 object:nil];
		[nc addObserver:self
			   selector:@selector(trashDidPerformNotification:)
				   name:CMRTrashboxDidPerformNotification
				 object:[CMRTrashbox trash]];
    }
    return self;
}
- (void)dealloc
{
	NSArray *keys = [self observeKeysForThreadAttributes];
	unregisterObserver(self, _threadAttr, keys);
	[_threadAttr release];
	[_messageBuffer release];
	[_registrant release];
	
	[[NSNotificationCenter defaultCenter] removeObserver:self];
	
	[super dealloc];
}

/*
- (void)windowControllerDidLoadNib:(NSWindowController *)aController
{
    [super windowControllerDidLoadNib:aController];
    // Add any code here that needs to be executed once the windowController has loaded the document's window.
}
*/
- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError
{
    /*
     Insert code here to write your document to data of the specified type. If outError != NULL, ensure that you create and set an appropriate error when returning nil.
    You can also choose to override -fileWrapperOfType:error:, -writeToURL:ofType:error:, or -writeToURL:ofType:forSaveOperation:originalContentsURL:error: instead.
    */
    if (outError) {
        *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:NULL];
    }
    return nil;
}
- (BOOL)readFromURL:(NSURL *)url ofType:(NSString *)typeName error:(NSError **)outError
{
	// do nothig.
	return YES;
}
- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError
{
    /*
    Insert code here to read your document from the given data of the specified type. If outError != NULL, ensure that you create and set an appropriate error when returning NO.
    You can also choose to override -readFromFileWrapper:ofType:error: or -readFromURL:ofType:error: instead.
    If you override either of these, you should also override -isEntireFileLoaded to return NO if the contents are lazily loaded.
    */
    if (outError) {
        *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:NULL];
    }
    return YES;
}

//+ (BOOL)autosavesInPlace
//{
//    return YES;
//}
- (void)close
{
	[self synchronize];
	[super close];
}

// properties
+ (NSSet *)keyPathsForValuesAffectingThreadTitle
{
	return [NSSet setWithObject:@"threadAttr.threadTitle"];
}

- (NSString *)path
{
	return self.threadAttr.path;
}
- (NSString *)boardName
{
	return self.threadAttr.boardName;
}
- (NSString *)threadTitle
{
	return self.threadAttr.threadTitle;
}
- (NSString *)bbsIdentifier
{
	return self.threadAttr.bbsIdentifier;
}
- (NSString *)datIdentifier
{
	return self.threadAttr.datIdentifier;
}
- (NSURL *)boardURL
{
	return self.threadAttr.boardURL;
}
- (NSURL *)threadURL
{
	return self.threadAttr.threadURL;
}

- (NSDictionary *)threadDictionary
{
	NSMutableDictionary		*dict_;
	CMRThreadAttributes		*attributes_;
	
	attributes_ = self.threadAttr;
	if (!attributes_) return nil;
	
	dict_ = [NSMutableDictionary dictionary];
	[dict_ setNoneNil:[attributes_ threadTitle] forKey:CMRThreadTitleKey];
	[dict_ setNoneNil:[attributes_ path] forKey:CMRThreadLogFilepathKey];
	[dict_ setNoneNil:[attributes_ datIdentifier] forKey:ThreadPlistIdentifierKey];
	[dict_ setNoneNil:[attributes_ boardName] forKey:ThreadPlistBoardNameKey];
	
	return dict_;
}
- (CMRThreadSignature *)threadSignature
{
	CMRThreadSignature *sig = self.threadAttr.threadSignature;
	if(sig) return sig;
	
	return [CMRThreadSignature threadSignatureFromFilepath:[[self fileURL] path]];
}
- (void)setMessageBuffer:(CMRThreadMessageBuffer *)newBaffer
{
	@synchronized(self) {
		[_messageBuffer release];
		_messageBuffer = [newBaffer retain];
	}
}
- (CMRThreadMessageBuffer *)messageBuffer
{
	CMRThreadMessageBuffer *result = nil;
	@synchronized(self) {
		result = [[_messageBuffer retain] autorelease];		
	}
	return result;
}
- (void)setThreadAttr:(CMRThreadAttributes *)newAttr
{
	@synchronized(self) {
		NSArray *keys = [self observeKeysForThreadAttributes];
		unregisterObserver(self, _threadAttr, keys);
		
		[_threadAttr release];
		_threadAttr = [newAttr retain];
		
		registerObserver(self, _threadAttr, keys);
	}
	
	NSString *title_ = self.threadTitle;
	if(!title_) return;
	id identifier = self.threadAttr.threadSignature;
	if(!identifier) return;
	[[CMRHistoryManager defaultManager] addItemWithTitle:title_ type:CMRHistoryThreadEntryType object:identifier];
}
- (CMRThreadAttributes *)threadAttr
{
	CMRThreadAttributes *result = nil;
	@synchronized(self) {
		result = [[_threadAttr retain] autorelease];		
	}
	return result;
}
- (BOOL)isAAThread
{
    return [self.threadAttr isAAThread];
}

- (void)setIsAAThread:(BOOL)flag
{
    if ([self isAAThread] == flag) return;
	
    [self.threadAttr setIsAAThread:flag];
}

- (BOOL)isDatOchiThread
{
    return [self.threadAttr isDatOchiThread];
}

- (void)setIsDatOchiThread:(BOOL)flag
{
    if ([self isDatOchiThread] == flag) return;
    
    [self.threadAttr setIsDatOchiThread:flag];
//    NSDictionary *foo = [NSDictionary dictionaryWithObject:[self path] forKey:@"path"];
//    UTILNotifyInfo(CMRAbstractThreadDocumentDidToggleDatOchiNotification, foo);
}
- (void)setLabelValue:(NSUInteger)label
{
	if(self.threadAttr.label == label) return;
	
	[self.threadAttr setLabel:label];
	
	// CMRThreadAttributesが直接変更される可能性があるためKVOで処理する。
	//	NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[self.threadAttr path], @"path",
	//							  [NSNumber numberWithUnsignedInteger:label], @"code", NULL];
	//    UTILNotifyInfo(CMRAbstractThreadDocumentDidToggleLabelNotification, userInfo);
}
- (BSMessageSampleRegistrant *)registrant
{
	if(!_registrant) {
		_registrant = [[BSMessageSampleRegistrant alloc] initWithThreadSignature:self.threadAttr.threadSignature];
	}
	return _registrant;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (context == _threadAttr) {
		if([keyPath isEqualToString:@"label"]) {
			id new = [change valueForKey:NSKeyValueChangeNewKey];
			NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
									  [self path], @"path",
									  new, @"code",
									  NULL];
			UTILNotifyInfo(CMRAbstractThreadDocumentDidToggleLabelNotification, userInfo);
		} else if([keyPath isEqualToString:@"isDatOchiThread"]) {
			id new = [change objectForKey:NSKeyValueChangeNewKey];
			id old = [change objectForKey:NSKeyValueChangeOldKey];
			if(new && old && [new isEqual:old]) return;
			
			NSDictionary *foo = [NSDictionary dictionaryWithObject:[self path] forKey:@"path"];
			UTILNotifyInfo(CMRAbstractThreadDocumentDidToggleDatOchiNotification, foo);
		} else if([keyPath isEqualToString:@"isAAThread"]) {
			id new = [change valueForKey:NSKeyValueChangeNewKey];
			[self.messageBuffer changeAllMessageAttributes:[new boolValue] flags:CMRAsciiArtMask];
		} else if([keyPath isEqualToString:@"threadTitle"]) {
			NSString *title_ = self.threadTitle;
			UTILAssertNotNil(title_);
			
			id identifier = self.threadAttr.threadSignature;
			UTILAssertNotNil(identifier);
			
			[[CMRHistoryManager defaultManager] addItemWithTitle:title_ type:CMRHistoryThreadEntryType object:identifier];
		}
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

- (NSArray *)observeKeysForThreadAttributes
{
	static NSArray *keys = nil;
	if(keys) return keys;
	
	keys = [[NSArray arrayWithObjects:@"label", @"isDatOchiThread", @"isAAThread", @"threadTitle", nil] retain];
	return keys;
}

- (NSArray *)shuldInsertKeys
{
	static NSArray *keys = nil;
	if(keys) return keys;
	
	keys = [[NSArray alloc] initWithObjects:
			CMRThreadWindowFrameKey,
			CMRThreadLastReadedIndexKey,
			CMRThreadVisibleRangeKey,
			CMRThreadUserStatusKey,
			ThreadPlistBoardNameKey,
			CMRThreadLogFilepathKey,
			ThreadPlistIdentifierKey,
			CMRThreadTitleKey,
			CMRThreadCreatedDateKey,
			CMRThreadModifiedDateKey,
			CMRThreadNumberOfMessagesKey,
			ThreadPlistLengthKey,
			nil];
	return keys;
}
- (void)loadContentOfFile:(NSString *)logFilepath
{
	NSDictionary *thread = [NSDictionary dictionaryWithContentsOfFile:logFilepath];
	if(!thread) return;
	
	NSMutableDictionary *dict = [NSMutableDictionary dictionary];
	for(NSString *key in thread) {
		if([[self shuldInsertKeys] containsObject:key]) {
			[dict setObject:[thread objectForKey:key] forKey:key];
		}
	}
	if(!self.threadAttr) {
		self.threadAttr = [[[CMRThreadAttributes alloc] initWithDictionary:dict] autorelease];
	} else {
		[self.threadAttr addEntriesFromDictionary:dict];
	}
	
	if([self.messageBuffer count] == 0) {
		CMRThreadMessageBuffer *buffer_ = [[CMRThreadMessageBuffer alloc] init];
		CMRThreadDictReader *reader_ = [CMRThreadDictReader readerWithContents:thread];
		[reader_ setNextMessageIndex:0];
		[reader_ composeWithComposer:buffer_];
		[self addMessagesFromBuffer:buffer_];
		[buffer_ release];
		self.messagesEdited = YES;
	}
	
	[[NSNotificationCenter defaultCenter] postNotificationName:KMDocumentDidChangeNotification
														object:self];
}
- (void)downloadContent
{
	CMRDownloader			*downloader;
	downloader = [ThreadTextDownloader downloaderWithIdentifier:self.threadSignature
													threadTitle:self.threadTitle
													  nextIndex:0];
	if(!downloader) return;
	[[NSNotificationCenter defaultCenter] addObserver:self
											 selector:@selector(threadFileDownloadDidFinishNotification:)
												 name:ThreadTextDownloaderDidFinishLoadingNotification
											   object:downloader];
	[[CMRTaskManager defaultManager] addTask:downloader];
	[downloader loadInBackground];
}
- (void)loadContent:(NSURL *)fileURL
{
	NSString *filepath = [fileURL path];
	
	SGFileRef			*fileRef_;
	NSString			*actualPath_;
	
	fileRef_ = [SGFileRef fileRefWithPath:filepath];
	actualPath_ = [fileRef_ pathContentResolvingLinkIfNeeded];
	
	// 
	// ファイル参照は存在しないファイルには作られない
	// 
	if(actualPath_ && [self.messageBuffer count] == 0) {
		[self loadContentOfFile:actualPath_];
	}
	
	[self downloadContent];
}

#pragma mark --- File Saving ---
- (void)setLastViewingIndex:(NSUInteger)index
{
	[self.threadAttr setLastIndex:index];
}
- (NSUInteger)lastViewingIndex
{
	return [self.threadAttr lastIndex];
}
- (void)setSavedWindowFrame:(NSRect)frame
{
	[self.threadAttr setWindowFrame:frame];
}
- (NSRect)savedWindowFrame
{
	return [self.threadAttr windowFrame];
}
- (BOOL)synchronize
{
	NSString				*filepath_ = [self path];
	NSMutableDictionary		*mdict_;
	BOOL					attrEdited_, mesEdited_;
	
//	[self saveWindowFrame];
//	[self saveLastIndex];
	
	attrEdited_ = [self.threadAttr needsToUpdateLogFile];
	mesEdited_ = self.isMessagesEdited;
	if (!attrEdited_ && !mesEdited_) {
//		UTIL_DEBUG_WRITE(@"Not need to synchronize");
		return YES;
	}
	
	mdict_ = [NSMutableDictionary dictionaryWithContentsOfFile:filepath_];
	if (!mdict_) return NO;
	
	if (attrEdited_) {
		[self.threadAttr writeAttributes:mdict_];
		[self.threadAttr setNeedsToUpdateLogFile:NO];
	}
	
	if (mesEdited_) {
		NSMutableArray			*newArray_;
		CMRThreadPlistComposer	*composer_;
		
		newArray_ = [[NSMutableArray alloc] init];
		composer_ = [[CMRThreadPlistComposer alloc] initWithThreadsArray:newArray_];
//		UTIL_DEBUG_WRITE1(@"compose messages count=%lu", (unsigned long)[mBuffer_ count]);
		
		for(CMRThreadMessage *m in self.messageBuffer.messages) {
			[composer_ composeThreadMessage:m];
		}
		
		[mdict_ setObject:newArray_ forKey:ThreadPlistContentsKey];
		
		[composer_ release];
		[newArray_ release];
		
		self.messagesEdited = NO;
	}
	if ([CMRPref saveThreadDocAsBinaryPlist]) {
		NSData *data_;
		data_ = [NSPropertyListSerialization dataFromPropertyList:mdict_ format:NSPropertyListBinaryFormat_v1_0 errorDescription:NULL];
		
		if (!data_) return NO;
		return [data_ writeToFile:filepath_ atomically:YES];
	} else {
		return [mdict_ writeToFile:filepath_ atomically:YES];
	}
}

- (void)threadFileDownloadDidFinishNotification:(id)no
{
	[[NSNotificationCenter defaultCenter] removeObserver:self
													name:ThreadTextDownloaderDidFinishLoadingNotification
												  object:[no object]];
	NSDictionary *info = [no userInfo];
	if(!self.threadAttr) {
		CMRThreadAttributes *at = [[[CMRThreadAttributes alloc] initWithDictionary:[info objectForKey:CMRDownloaderUserInfoAdditionalInfoKey]] autorelease];
		NSMutableDictionary *dict = [NSMutableDictionary dictionary];
		[dict setObject:[[no object] threadTitle] forKey:CMRThreadTitleKey];
		[dict setObject:[[self threadSignature] boardName] forKey:ThreadPlistBoardNameKey];
		[dict setObject:[[self threadSignature] identifier] forKey:ThreadPlistIdentifierKey];
		
		[at addEntriesFromDictionary:dict];
		self.threadAttr = at;
	} else {
		[self.threadAttr addEntriesFromDictionary:[info objectForKey:CMRDownloaderUserInfoAdditionalInfoKey]];
	}
	
	[self.threadAttr setNeedsToUpdateLogFile:YES];
	
	id dat = [info objectForKey:ThreadPlistContentsKey];
	CMR2chDATReader *reader_ = [CMR2chDATReader readerWithContents:dat];
	
	CMRThreadMessageBuffer *buffer_ = [[CMRThreadMessageBuffer alloc] init];
	[reader_ setNextMessageIndex:[self.messageBuffer count]];
	[reader_ composeWithComposer:buffer_];
	if([buffer_ count] != 0) {
		[self addMessagesFromBuffer:buffer_];
		self.messagesEdited = YES;
		
		[[NSNotificationCenter defaultCenter] postNotificationName:KMDocumentDidChangeNotification
															object:self];
	}
	[buffer_ release];
	
	[self synchronize];
}

- (void)setLabelOfThread:(NSUInteger)label toggle:(BOOL)shouldToggle
{
	[self doesNotRecognizeSelector:_cmd];
//    BOOL    isAssign = YES;
//    NSUInteger currentLabel = [self.threadAttr label];
//    if (currentLabel == label) {
//        if (!shouldToggle) {
//            return;
//        }
//        [self.threadAttr setLabel:0];
//        isAssign = NO;
//    } else {
//        [self.threadAttr setLabel:label];
//    }
//    NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[self.threadAttr path], @"path",
//							  [NSNumber numberWithUnsignedInteger:(isAssign ? label : 0)], @"code", NULL];
//    UTILNotifyInfo(CMRAbstractThreadDocumentDidToggleLabelNotification, userInfo);
}

- (BOOL)registrant:(BSMessageSampleRegistrant *)aRegistrant shouldRegardNameAsDefaultNanashi:(NSString *)name;
{
	BoardManager *bm = [BoardManager defaultManager];
	NSArray *noNames = [bm defaultNoNameArrayForBoard:self.boardName];
	return [noNames containsObject:name];
}
- (NSUInteger)registrant:(BSMessageSampleRegistrant *)aRegistrant numberOfMessagesWithIDString:(NSString *)idString
{
    CMRThreadMessage *message;
    NSUInteger count = 0;
    for(message in self.messages) {
        NSString *idOfMessage = [message IDString];
        if (idOfMessage && [idOfMessage isEqualToString:idString]) {
            count++;
        }
    }
    return count;
}

@end


@implementation KMDocument (Filters)
// AA Filter
- (IBAction)runAsciiArtDetector:(id)sender
{
	CMRThreadSignature		*threadID;
	
	threadID = [self.threadAttr threadSignature];
	if (!threadID) {
		return;
	}
	[[BSAsciiArtDetector sharedInstance] runDetectorWithMessages:self.messageBuffer with:threadID];
}

// Spam Filter
- (IBAction)runSpamFilter:(id)sender
{
	CMRThreadSignature		*threadID;
	
	threadID = [self.threadAttr threadSignature];
	if (!threadID) {
		return;
	}
	
    BSSpamJudge *judge = [[[BSSpamJudge alloc] initWithThreadSignature:threadID] autorelease];
    [judge judgeMessages:self.messageBuffer];
}
- (void)registerSpam:(CMRThreadMessage *)message
{
	[self.registrant setDelegate:self];
	[self.registrant registerMessage:message];
}
- (void)unregisterSpam:(CMRThreadMessage *)message
{
	[self.registrant unregisterMessage:message];
}
@end


#import "BSThreadInfoPanelController.h"
#import "BSLabelMenuItemView.h"

@implementation KMDocument(Actions)

- (IBAction)reload:(id)sender
{
	[self loadContent:self.fileURL];
}
- (IBAction)reloadThread:(id)sender
{
	[self reload:sender];
}

- (IBAction)retrieve:(id)sender
{
	self.messageBuffer = [[[CMRThreadMessageBuffer alloc] init] autorelease];
	self.messagesEdited = YES;
	
	[[NSNotificationCenter defaultCenter] postNotificationName:KMDocumentDidChangeNotification
														object:self];
	[[NSData data] writeToFile:self.path options:NSAtomicWrite error:NULL];
	[self downloadContent];
}

- (IBAction)openInBrowser:(id)sender
{
    NSURL *url = [CMRThreadAttributes threadURLWithDefaultParameterFromDictionary:[self.threadAttr dictionaryRepresentation]];
    [[NSWorkspace sharedWorkspace] openURL:url inBackground:[CMRPref openInBg]];
}

- (IBAction)toggleAAThread:(id)sender
{
	[self setIsAAThread: ![self isAAThread]];
}
- (IBAction)toggleDatOchiThread:(id)sender
{
	[self setIsDatOchiThread: ![self isDatOchiThread]];
}

- (IBAction)copyThreadAttributes:(id)sender
{
	NSMutableString	*tmp;
	NSURL			*url_ = nil;
	NSPasteboard	*pboard_ = [NSPasteboard generalPasteboard];
	NSArray			*types_;
	
	tmp = SGTemporaryString();
	
	[CMRThreadAttributes fillBuffer:tmp withThreadInfoForCopying:[NSArray arrayWithObject:self.threadDictionary]];
	url_ = [CMRThreadAttributes threadURLFromDictionary:self.threadDictionary];
	
	types_ = [NSArray arrayWithObjects:NSURLPboardType, NSStringPboardType, nil];
	[pboard_ declareTypes:types_ owner:nil];
	
	[url_ writeToPasteboard:pboard_];
	[pboard_ setString:tmp forType:NSStringPboardType];
	
	[tmp deleteCharactersInRange:[tmp range]];
}

- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
	SEL action = [menuItem action];
	if(action == @selector(toggleAAThread:)) {
		[menuItem setState:[self isAAThread] ? NSOnState : NSOffState];
	} else if(action == @selector(toggleDatOchiThread:)) {
		[menuItem setState:[self isDatOchiThread] ? NSOnState : NSOffState];
	}
	
	return YES;
}

- (void)trashDidPerformNotification:(NSNotification *)notification
{
	NSArray		*files_;
	NSNumber	*err_;
	
	UTILAssertNotificationName(notification, CMRTrashboxDidPerformNotification);
	UTILAssertNotificationObject(notification, [CMRTrashbox trash]);
	
	err_ = [[notification userInfo] objectForKey:kAppTrashUserInfoStatusKey];
	if (!err_) return;
	UTILAssertKindOfClass(err_, NSNumber);
	if ([err_ integerValue] != noErr) return;
	
	files_ = [[notification userInfo] objectForKey:kAppTrashUserInfoFilesKey];
	UTILAssertKindOfClass(files_, NSArray);
	if (![files_ containsObject:[self path]]) return;
	
	// Could not use fist emunerator
	NSWindowController *wc = nil;
	while( (wc = [[self windowControllers] lastObject]) ) {
		[self removeWindowController:wc];
	}
//	[self close];
}
@end

@implementation KMDocument(CMRThreadLayout_Methods)
- (void)addMessagesFromBuffer:(CMRThreadMessageBuffer *)otherBuffer
{
	if (!otherBuffer) {
		return;
	}
	@synchronized(self.messageBuffer) {
		[self willChangeValueForKey:@"messages"];
		[self willChangeValueForKey:@"numberOfReadedMessages"];
		[self.messageBuffer addMessagesFromBuffer:otherBuffer];
		[self didChangeValueForKey:@"numberOfReadedMessages"];
		[self didChangeValueForKey:@"messages"];
		
		for(CMRThreadMessage *m in [otherBuffer messages]) {
			[m setPostsAttributeChangedNotifications:YES];
		}
		
	}
}
- (void)threadMessageDidChangeAttribute:(NSNotification *)theNotification
{
	CMRThreadMessage	*message;
	NSUInteger			mIndex;
	
	UTILAssertNotificationName(
							   theNotification,
							   CMRThreadMessageDidChangeAttributeNotification);
	
	message = [theNotification object];
	if ((mIndex = [[self messageBuffer] indexOfMessage:message]) != NSNotFound) {
		NSDictionary *info = [NSDictionary dictionaryWithObject:[NSNumber numberWithInteger:mIndex]
														 forKey:KMDocumentChangedMessageIndexKey];
		[[NSNotificationCenter defaultCenter] postNotificationName:KMDocumentDidChangeMessageNotification
															object:self
														  userInfo:info];
		[self setMessagesEdited:YES];
	}
}

- (void)changeAllMessageAttributes:(BOOL)onOffFlag flags:(UInt32)mask
{
	[[self messageBuffer] changeAllMessageAttributes:onOffFlag flags:mask];
}

- (NSUInteger)numberOfMessageAttributes:(UInt32)mask
{
	NSUInteger			count_ = 0;
	
	for(CMRThreadMessage *m in [self.messageBuffer messages]) {
		if (mask & [m flags]) {
			count_++;
        }
	}
	return count_;
}
- (NSArray *)messages
{
	return [self.messageBuffer messages];
}
- (CMRThreadMessage *)messageAtIndex:(NSUInteger)index
{
	return [self.messageBuffer messageAtIndex:index];
}
- (NSArray *)messagesAtIndexes:(NSIndexSet *)indexes
{
	return [self.messageBuffer messagesAtIndexes:indexes];
}
- (NSUInteger)numberOfReadedMessages
{
	return [self.messageBuffer count];
}

@end