//
//  BSSmartBoardUpdateTask.m
//  BathyScaphe
//
//  Created by 堀 昌樹 on 12/07/07.
//  Copyright (c) 2012年 __MyCompanyName__. All rights reserved.
//

#import "BSSmartBoardUpdateTask.h"

#import <CocoaOniguruma/OnigRegexp.h>

#import "AppDefaults.h"
#import "CMRFavoritesManager.h"
#import "BoardManager.h"
#import "CMRHostHandler.h"
#import "CMXTextParser.h"
#import "BSDownloadTask.h"
#import "DatabaseManager.h"

@interface BSSmartBoardUpdateTask ()
@property (assign) BSDBThreadList *targetList;
@property (retain) BoardListItem *item;
@end

@implementation BSSmartBoardUpdateTask
@synthesize targetList = _targetList;
@synthesize item = _item;


+ (id)taskWithThreadList:(BSDBThreadList *)list forceDownload:(BOOL)forceDL rebuild:(BOOL)flag
{
	return [[[[self class] alloc] initWithThreadList:list forceDownload:forceDL rebuild:flag] autorelease];
}
- (id)initWithThreadList:(BSDBThreadList *)list forceDownload:(BOOL)inForceDL rebuild:(BOOL)flag
{
	self = [super init];
	if(self) {
		forceDL = inForceDL;
		_boards = [[NSMutableSet alloc] init];
		_updateData = [[NSMutableDictionary alloc] init];
		self.targetList = list;
		self.item = [list boardListItem];
		if(!([self.item type] & (BoardListFavoritesItem | BoardListSmartBoardItem))) {
			[self autorelease];
			return nil;
		}
		
		[[NSNotificationCenter defaultCenter] addObserver:self
												 selector:@selector(downloadFail:)
													 name:BSDownloadTaskFailDownloadNotification
												   object:nil];
	}
	return self;
}

- (void)dealloc
{
	[[NSNotificationCenter defaultCenter] removeObserver:self];
	
	[_boards release];
	[_updateData release];
	[_item release];
	[_targetThreads release];
	
	[super dealloc];
}

+ (OnigRegexp *)regex
{
	static OnigRegexp *sRegexp = nil;
    if (!sRegexp) {
        sRegexp = [OnigRegexp compile:[NSString stringWithFormat:@"(\\d+)[^,<>]*(?:<>|,).*\\s*(?:\\(|<>|%C)(\\d+)",0xFF08]];
        [sRegexp retain];
    }
    return sRegexp;
}
- (OnigRegexp *)regex
{
	return [[self class] regex];
}

- (void)downloadFail:(NSNotification *)notification
{
	NSError *error = [[notification userInfo] objectForKey:BSDownloadTaskErrorObjectKey];
	[NSApp presentError:error]; 
}

- (void)playFinishSoundIsUpdate:(BOOL)isUpDate
{
	NSSound *finishedSound_ = nil;
	NSString *soundName_ = [CMRPref HEADCheckNewArrivedSound];
	
	if (isUpDate && ![soundName_ isEqualToString:@""]) {
		finishedSound_ = [NSSound soundNamed:soundName_];
	} else {
		soundName_ = [CMRPref HEADCheckNoUpdateSound];
		if (![soundName_ isEqualToString:@""]) {
			finishedSound_ = [NSSound soundNamed:soundName_];
        }
	}
	[finishedSound_ play];
}



- (void)collectTargetBoards
{
	_targetThreads = [[self.item cursorForThreadList] retain];
	for(NSUInteger row = 0, rowCount = [_targetThreads rowCount]; row < rowCount; row++) {
		if(self.isInterrupted) return;
		
		id boardName = [_targetThreads valueForColumn:BoardNameColumn atRow:row];
		if(boardName && ![boardName isKindOfClass:[NSNull class]]) {
			[_boards addObject:boardName];
		}
	}
}

static BOOL isBoardLivedoor(NSURL *boardURL)
{
	const char *host = NULL;
	CMRGetHostCStringFromBoardURL(boardURL, &host);
	return host ? is_jbbs_livedoor(host) : NO;
}
- (void)collectUpdateInformation
{
	NSString *dlFormat = NSLocalizedString(@"Downloding subject.txt of %@", nil);
	
	dispatch_queue_t downloadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
	NSArray *boardNames = [_boards allObjects];
	dispatch_apply([boardNames count], downloadQueue, ^(size_t index) {
		if(self.isInterrupted) return;
		
		NSString *boardName = [boardNames objectAtIndex:index];
		
		self.message = [NSString stringWithFormat:dlFormat, boardName];
		
		NSURL *boardURL = [[BoardManager defaultManager] URLForBoardName:boardName];
		if (!boardURL) {
			NSLog(@"Can NOT create url from bbs named %@.",boardName);
			return;
		}
		NSURL *subjectURL = [NSURL URLWithString:CMRAppSubjectTextFileName relativeToURL:boardURL];
		NSArray *subjectLines = nil;
		BSDownloadTask *dlTask = [[BSDownloadTask alloc] initWithURL:subjectURL];
		{
			[dlTask synchronousDownLoad];
			NSData *subjectData = [dlTask receivedData];
			if(!subjectData || [subjectData length] == 0) return;
			
			CMRHostHandler *handler = [CMRHostHandler hostHandlerForURL:boardURL];
			if (!handler) {
				NSLog(@"Can NOT create host handler from url %@.",boardURL);
				return;
			}
			CFStringEncoding enc = [handler subjectEncoding];
			NSString *subjectString = [CMXTextParser stringWithData:subjectData CFEncoding:enc];
			subjectLines = [subjectString componentsSeparatedByNewline];
		}
		[dlTask release];
		
		BOOL isLivedoor = isBoardLivedoor(boardURL);
		NSMutableArray *dats = [NSMutableArray array];
		
		if(self.isInterrupted) return;
		
		NSMutableDictionary *infos = [NSMutableDictionary dictionary];
		for(NSString *line in subjectLines) {
			if(self.isInterrupted) return;
			
			OnigResult *match = [[self regex] search:line];
			NSString *datString = [match stringAt:1];
			NSString *numString = [match stringAt:2];
			if(!numString) continue;
			
			if(isLivedoor) {
				if([dats containsObject:datString]) continue;
				[dats addObject:datString];
			}
			
			[infos setObject:numString forKey:datString];
		}
		@synchronized(_updateData) {
			[_updateData setObject:infos forKey:boardName];
		}
	});
}

static inline BOOL isNSNullOrNil(id obj)
{
	if(!obj) return YES;
	if([obj isKindOfClass:[NSNull class]]) return YES;
	return NO;
}
static inline id nilIfObjectIsNSNull(id obj)
{
	return (obj == [NSNull null]) ? nil : obj;
}
static inline BOOL isUpdatedThread(NSInteger readCount, NSString *numString, NSInteger currentCount)
{
	return (readCount != 0 && !isNSNullOrNil(numString) && currentCount < [numString integerValue]);
}
- (void)updateDB
{
    BOOL isUpdate = NO;
	
	SQLiteDB *db = [[DatabaseManager defaultManager] databaseForCurrentThread];
	NSString *queryString = [NSString stringWithFormat:@"UPDATE OR IGNORE %@ SET %@ = ?, %@ = ? WHERE %@ = ? AND %@ = ?",
							 ThreadInfoTableName,
							 NumberOfAllColumn,
							 ThreadStatusColumn,// ThreadUpdatedStatus,
							 BoardIDColumn, ThreadIDColumn];
	SQLiteReservedQuery *query = [[SQLiteReservedQuery alloc] initWithQuery:queryString usingSQLiteDB:db];
	
	[db beginTransaction];
	for(NSUInteger index = 0, rowCount = [_targetThreads rowCount]; index < rowCount; index++) {
		if(self.isInterrupted) {
			[db rollbackTransaction];
			return;
		}
		id<SQLiteRow> row = [_targetThreads rowAtIndex:index];
		
		NSInteger status = [nilIfObjectIsNSNull([row valueForColumn:ThreadStatusColumn]) integerValue];
		BOOL isNew = (status & ThreadNewCreatedStatus) == ThreadNewCreatedStatus;
		NSInteger readCount = [nilIfObjectIsNSNull([row valueForColumn:NumberOfReadColumn]) integerValue];
		if(!isNew && readCount == 0) continue;
		NSString *boardName = [row valueForColumn:BoardNameColumn];
		if(isNSNullOrNil(boardName)) continue;
		NSString *datString = [row valueForColumn:ThreadIDColumn];
		if(isNSNullOrNil(datString)) continue;
		NSString *boardID = [row valueForColumn:BoardIDColumn];
		if(isNSNullOrNil(boardID)) continue;
		NSInteger currentCount = [nilIfObjectIsNSNull([row valueForColumn:NumberOfAllColumn]) integerValue];
		NSString *numString = nil;
		
		NSDictionary *infoForBoard = [_updateData objectForKey:boardName];
		numString = [infoForBoard objectForKey:datString];		
		if(isUpdatedThread(readCount, numString, currentCount)) {
			[query cursorWithFormat:"siss", numString, ThreadUpdatedStatus, boardID, datString];
			isUpdate = YES;
		} else if(isNew) {
			[query cursorWithFormat:"iiss", currentCount, ThreadNoCacheStatus, boardID, datString];
//			isUpdate = YES;
		}
		
	}
	[db commitTransaction];
	
	[self playFinishSoundIsUpdate:isUpdate];
}

- (void)excute
{
	if(!forceDL) {
		if(self.isInterrupted) return;
		[self.targetList updateCursor];
		return;
	}
	// 更新チェックの回数上限に達している場合ここでブロックする
	NSError *error = nil;
	if (![[CMRFavoritesManager defaultManager] canHEADCheck:&error]) {
		NSAlert *alert = [NSAlert alertWithError:error];
		[alert runModal];
		return;
	}
	
	// Boardを収集
	self.message = NSLocalizedString(@"Collecting Board informations.", nil);
	[self collectTargetBoards];
	if(self.isInterrupted) return;
	
	// 各Boardのsubject.txtを取得
	self.message = NSLocalizedString(@"Download subject.txt.", nil);
    [self collectUpdateInformation];
	if(self.isInterrupted) return;
	
	// 各subject.txtからDBをupdate
	self.message = NSLocalizedString(@"Update database.", nil);
	[self updateDB];
	
	[[CMRFavoritesManager defaultManager] decrementHEADCheckCount];
	
	if(self.isInterrupted) return;
	[self.targetList updateCursor];
}

@end
