//
//  KMLogView.m
//  KMLogView
//
//  Created by 堀 昌樹 on 12/05/08.
//  Copyright (c) 2012年 __MyCompanyName__. All rights reserved.
//

#import "KMLogView.h"

#include "KMRangeArray.h"


@interface KMLogView ()
- (void)clearCache;
- (void)buildContents;
@end

@implementation KMLogView
{
	NSMutableArray *rows;
	NSMutableDictionary *rangeOfRows;
	
	KMRangeArray *rangeArray;
	
	NSInteger _clickedRow;
	
	dispatch_queue_t composeQueue;
	NSMutableAttributedString *contentBuffer;
}
@synthesize datasource = _datasource;
@synthesize delegate = _delegate;

- (id)initWithFrame:(NSRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code here.
		
    }
    
    return self;
}
- (void)dealloc
{
	self.delegate = nil;
	self.datasource = nil;
	[rangeOfRows release];
	[rows release];
	[rangeArray release];
	[contentBuffer release];
	
	dispatch_release(composeQueue);
	
	[super dealloc];
}

- (void)awakeFromNib
{
	rangeOfRows = [[NSMutableDictionary alloc] init];
	rangeArray = [[KMRangeArray alloc] init];
	contentBuffer = [[NSMutableAttributedString alloc] init];
}

- (void)setRange:(NSRange)range toIndex:(NSInteger)index
{
//	@synchronized(rangeOfRows) {
//		[rangeOfRows setObject:[NSValue valueWithRange:range]
//						forKey:[NSNumber numberWithInteger:index]];
//	}
	@synchronized(rangeArray) {
		[rangeArray appendRangeWithLength:range.length];
	}
}
- (NSRange)rangeAtIndex:(NSInteger)index
{
	id rangeVal = nil;
	@synchronized(rangeOfRows) {
		rangeVal = [rangeOfRows objectForKey:[NSNumber numberWithInteger:index]];
	}
	return [rangeVal rangeValue];
}

- (void)reloadData
{
	[self clearCache];
	NSTextStorage *ts = [self textStorage];
	[ts deleteCharactersInRange:NSMakeRange(0, [ts length])];
	[self buildContents];
}
- (NSInteger)numberOfRow
{
	if(_datasource) return [_datasource numberOfRowsInLogView:self];
	return 0;
}
- (NSRange)rangeOfRow:(NSUInteger)row
{
	return [self rangeAtIndex:row];
}

- (NSInteger)clickedRow
{
	return _clickedRow;
}

- (NSRect)rectOfRow:(NSInteger)row
{
	NSRange range = [self rangeOfRow:row];
	return [[self layoutManager] boundingRectForGlyphRange:range inTextContainer:[self textContainer]];
}
- (NSRange)rowsInRect:(NSRect)rect
{
	NSRange result = {NSNotFound, 0};
	NSRange glyphRange = [[self layoutManager] glyphRangeForBoundingRect:rect
														 inTextContainer:[self textContainer]];
	NSInteger numberOfRow = [self numberOfRow];
	BOOL hit = NO;
	for(NSInteger i = 0; i < numberOfRow; i++) {
		if(NSIntersectionRange([self rangeOfRow:i], glyphRange).location != NSNotFound) {
			if(hit) {
				result.length++;
			} else {
				result.location = i;
				result.length = 1;
				hit = YES;
			}
		} else if(hit) {
			break;
		}
	}
	return result;
}
- (NSInteger)rowAtPoint:(NSPoint)point
{
	NSUInteger index = [[self layoutManager] glyphIndexForPoint:point inTextContainer:[self textContainer]];
	
	// TODO: change to binay search
	NSInteger numberOfRow = [self numberOfRow];
	for(NSInteger i = 0; i < numberOfRow; i++) {
		if(NSLocationInRange(index, [self rangeOfRow:i])) return i;
	}
	return NSNotFound;
}


- (void)scrollRowToVisible:(NSInteger)row
{
	[self scrollRectToVisible:[self rectOfRow:row]];
}

- (void)clearCache
{
	[rows removeAllObjects];
	@synchronized(rangeOfRows) {
		[rangeOfRows removeAllObjects];
	}
}

- (void)applyCache:(id)dummy
{
	@synchronized(contentBuffer) {
		[[self textStorage] appendAttributedString:contentBuffer];
		[contentBuffer deleteAll];
	}
}
- (void)applyImmediateIfNeeded:(id)dummy
{
	[[self class] cancelPreviousPerformRequestsWithTarget:self
												 selector:@selector(applyCache:)
												   object:contentBuffer];
	NSUInteger length = 0;
	@synchronized(contentBuffer) {
		length = [contentBuffer length];
	}
	if(length > 1024 * 16) {
		[self applyCache:nil];
	} else {
		[self performSelector:@selector(applyCache:) withObject:contentBuffer afterDelay:0.5];
	}
}
- (void)pushContent:(NSAttributedString *)string
{
	@synchronized(contentBuffer) {
		[contentBuffer appendAttributedString:string];
	}
	[self performSelectorOnMainThread:@selector(applyImmediateIfNeeded:) withObject:nil waitUntilDone:NO];
}
- (void)buildContents
{
	if(!_datasource
	   || ![_datasource respondsToSelector:@selector(numberOfRowsInLogView:)]
	   || ![_datasource respondsToSelector:@selector(logView:attributedStringForRow:)]) {
		return;
	}
	
	if(!composeQueue) {
		composeQueue = dispatch_queue_create("com.masakih.composing", DISPATCH_QUEUE_SERIAL);
	}
	
	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^ {
		__block NSUInteger length = 0;
		dispatch_apply([self numberOfRow], composeQueue, ^(size_t i) {
			NSAttributedString *content = [_datasource logView:self attributedStringForRow:i];
			
			[self pushContent:content];
			[self setRange:NSMakeRange(length, [content length]) toIndex:i];
			length += [content length];
		});
	});
}

- (void)showRanges:(id)sender
{
	NSDictionary *dict = nil;
	@synchronized(rangeOfRows) {
		dict = [rangeOfRows copy];
	}
	[dict autorelease];
	
	NSArray *keys = [dict allKeys];
	keys = [keys sortedArrayUsingSelector:@selector(compare:)];
	for(id key in keys) {
		fprintf(stderr, "%s = \"%s\";\n",
				[[key description] UTF8String],
				[[[dict objectForKey:key] description] UTF8String]);
	}
	fprintf(stderr, "\n\n\n");
	fprintf(stderr, "%s\n", [[rangeArray description] UTF8String]);
}

@end
