//
//  PWTreeController.m
//  PWTreeControllerDemo
//
//  Created by Frank Illenberger on 04.03.06.
//  Copyright 2006 ProjectWizards, Melle, Germany. 
//

#import "PWTreeController.h"
#import "PWTreeController-Private.h"
#import "PWTreeControllerNode.h"
#import "PWOutlineView.h"
#import "PWOutlineView.h"
#import "NSOutlineView-PWExtensions.h"
#import "PWObservationInfo.h"

#define PWSortAttributesObservingContext (void *)@"PWSortAttributesObservingContext"
#define PWNewRootArray (void *)@"NewRootArray"

@implementation PWTreeController

CFHashCode directHashCallBack(const void *value)
{
	return (CFHashCode)value;
}

- (void)setup
{
	isAutosorting = YES;
	static CFDictionaryKeyCallBacks keyCallbacks   	 =  { .version = 0, .retain = NULL, .release = NULL, .copyDescription = NULL, .equal = NULL, .hash = directHashCallBack };
	static CFDictionaryValueCallBacks valueCallbacks =  { .version = 0, .retain = NULL, .release = NULL, .copyDescription = NULL, .equal = NULL };
	nodesForObjects = CFDictionaryCreateMutable(nil, 0, &keyCallbacks, &valueCallbacks);
	attributeObservers = [[NSMutableArray alloc] init];
	[self setRootKeyPath:@"rootObjects"];
	[self setChildrenKeyPath:@"children"];
	isSetup = YES;
}

- (id)init
{
	[super init];
	if(!isSetup)
		[self setup];
	return self;
}

- (void)awakeFromNib
{
	if(!isSetup)
		[self setup];
	[outlineView _buildConfigurationMenu];
}

- (int)outlineView:(NSOutlineView *)ov numberOfChildrenOfItem:(id)item
{
	if(filterPredicate)
		return !item ? [filteredRootNodes count] : [[item filteredSubNodes] count];
	else
		return !item ? [[self rootNodes] count] : [[item subNodes] count];
}

- (BOOL)outlineView:(NSOutlineView *)ov isItemExpandable:(id)item
{
	if (filterPredicate)
		return [[item filteredSubNodes] count];
	else
		return ![item isLeaf];
}

- (void) delete: (id) sender
{
	[rootSource delete: sender];
}

- (void) ungroup: (id) sender
{
	[rootSource ungroup: sender];
}

- (void) group: (id) sender
{
	[rootSource group: sender];
}

- (void) addChild: (id) sender
{
	[rootSource addChildToOutline: sender];
}

- (void) addSibling: (id) sender
{
	[rootSource addSiblingToOutline: sender];
}

- (id)outlineView:(NSOutlineView *)ov child:(int)index ofItem:(id)item
{
	if(filterPredicate)
		return !item ? [filteredRootNodes objectAtIndex:index] : [[item filteredSubNodes] objectAtIndex:index];
	else
		return !item ? [[self rootNodes] objectAtIndex:index] : [[item subNodes] objectAtIndex:index];
}

- (id)outlineView:(NSOutlineView *)ov objectValueForTableColumn:(NSTableColumn *)column byItem:(id)item
{
    if ([[column bindingKeyPath] isEqualToString:@"info"]) return nil;
    if ([[column bindingKeyPath] isEqualToString:@"note"]) return nil;
	return [[item representedObject] valueForKeyPath:[column bindingKeyPath]];
}

- (void)outlineView:(NSOutlineView *)ov setObjectValue:(id)value forTableColumn:(NSTableColumn *)column byItem:(id)item
{
	id obj = [item representedObject];
	NSString *keyPath = [column bindingKeyPath];
	NSError *error = nil;
    if ([[column identifier] isEqualToString:@"note"]) return;
    if ([[column identifier] isEqualToString:@"info"]) return;
	if ([[column identifier] isEqualToString:@"context"] || [[column identifier] isEqualToString:@"project"])
	{
        id cell=[column dataCell];
        value=[[cell itemAtIndex: [value intValue]] representedObject];
	}
	if(![obj validateValue:&value forKeyPath:keyPath error:&error])
		[ov presentError:error modalForWindow:[ov window] delegate:nil didPresentSelector:0 contextInfo:nil];
	else
		[obj setValue:value forKeyPath:keyPath];
}

- (void)outlineView:(NSOutlineView *)ov sortDescriptorsDidChange:(NSArray *)oldDescriptors
{
	[self setSortDescriptors:[ov sortDescriptors]];
	[ov reloadData];
}

- (NSMutableArray *)createNodesFromObservedObjectsArray:(NSArray *)objects parentNode:(PWTreeControllerNode *)parent
{
	NSMutableArray *nodes = [NSMutableArray array];
	
	NSArray *descs = [self sortDescriptorsForSorting];
	NSArray *sortedObjects = descs ? [objects sortedArrayUsingDescriptors:descs] : objects;
	int count = [sortedObjects count];
	for(int index=0; index<count; index++)
	{
		id obj = [sortedObjects objectAtIndex:index];
		PWTreeControllerNode *node = [[PWTreeControllerNode alloc] initWithTreeController:self observedObject:obj parentNode:parent];
		[nodes addObject:node];
		[node release];
	}
	return nodes;
}

- (void)willChangeSubNodesOfNode:(PWTreeControllerNode *)node
{
	[outlineView saveState];
}

- (void)didChangeSubNodesOfNode:(PWTreeControllerNode *)node
{
	[self performFiltering];
	if(filterPredicate || [outlineView levelForItem:node]==0)
		[outlineView reloadData];
	else
		[outlineView reloadItem:node reloadChildren:YES];
}

- (void)willChangeLeafStateOfNode:(PWTreeControllerNode *)node
{
	[outlineView saveState];
}

- (void)didChangeLeafStateOfNode:(PWTreeControllerNode *)node
{
	[self performFiltering];
	if([outlineView levelForItem:node]==0)
		[outlineView reloadData];
	else
		[outlineView reloadItem:node reloadChildren:YES];
}

- (id) selection
{
	NSArray * result=[self selectedObjects];
	if (![result count]) return NSNoSelectionMarker;
	if ([result count] > 1) return NSMultipleValuesMarker;
	return NSNotApplicableMarker;
	//return result;
}

- (NSArray *)selectedObjects
{
	NSMutableArray *selection = [NSMutableArray array];
	if(outlineView)
	{
		NSIndexSet *selectedRows = [outlineView selectedRowIndexes];
		int row = [selectedRows firstIndex];
		while(row!=NSNotFound)
		{
			id item = [outlineView itemAtRow:row];
			if(item)
				[selection addObject:[item representedObject]];
			row = [selectedRows indexGreaterThanIndex:row];
		}
	}
	return selection;
}

- (PWTreeControllerNode *)nodeForObservedObject:(id)object
{
	return (PWTreeControllerNode *)CFDictionaryGetValue(nodesForObjects, object);
}

- (void)setSelectedObjects:(NSArray *)objects
{
	int count = [objects count];
	NSMutableIndexSet *rows = [NSMutableIndexSet indexSet];
	for(int index=0; index<count; index++)
	{
		PWTreeControllerNode *node = [self nodeForObservedObject:[objects objectAtIndex:index]];
		if(node)
		{
			int row = [outlineView rowForItem:node];
			if(row!=-1)
				[rows addIndex:row];
		}
	}
	[outlineView selectRowIndexes:rows byExtendingSelection:NO];
}

- (void)resort
{
	[((NSMutableArray *) [self rootNodes]) sortUsingDescriptors:[self observedObjectsSortDescriptors]];
	int count = [rootNodes count];
	for(int index=0; index<count; index++)
		[[rootNodes objectAtIndex:index] setNeedsResort:YES];	

	[outlineView reloadData];
}

- (void)autosort
{
	if(isAutosorting && pendingAutosort)
	{
		pendingAutosort = NO;
		[self resort];
	}
}

- (NSArray *)rootNodes
{
	if (!rootNodes && isSetup) [self rootObjectsArray]; // force calc if necessary
	return rootNodes;
}

- (void)dealloc
{
	[rootNodes makeObjectsPerformSelector:@selector(treeControllerWillDealloc)];
	[rootNodes release];
	[filterPredicate release];
	[sortDescriptors release];
	[sortDescriptorKeys release];
	[observedObjectsSortDescriptors release];
	[rootObjectsArray release];
	[rootObjectsSet release];
	[outlineView release];
	CFRelease(nodesForObjects);
	[attributeObservers autorelease];
	[childrenKeyPath release];
	[isLeafKeyPath release];
	[super dealloc];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
	if(context == PWSortAttributesObservingContext && !pendingAutosort)
	{
		pendingAutosort = YES;
		if(isAutosorting)
			[self performSelector:@selector(autosort) withObject:nil afterDelay:0.0];	
		
	}
	if(context == PWNewRootArray)
	{
		//printf("noticed root change\n");
		[rootObjectsArray release];
		rootObjectsArray=nil;
		[self rootObjectsArray]; 
	}
	
}

- (void)setSortDescriptors:(NSArray *)descriptors
{
	NSSortDescriptor *desc;
	NSEnumerator *e = [sortDescriptors objectEnumerator];
	while (desc = [e nextObject]) 
		[self removeObserver:self forKeyPath:[@"arrangedObjects." stringByAppendingString:[desc key]]];
	
	[descriptors retain];
	[sortDescriptors release];
	sortDescriptors = descriptors;
	[observedObjectsSortDescriptors release];
	observedObjectsSortDescriptors = nil;
	
	[sortDescriptorKeys release];
	sortDescriptorKeys = [[NSSet alloc] initWithArray:[sortDescriptors valueForKey:@"key"]];

	e = [sortDescriptors objectEnumerator];
	while (desc = [e nextObject]) 
		[self addObserver:self forKeyPath:[@"arrangedObjects." stringByAppendingString:[desc key]] options:0 context:PWSortAttributesObservingContext];
	[self resort];
}


- (NSArray *)sortDescriptors
{
	return sortDescriptors;
}

- (NSArray *)sortDescriptorsForSorting
{
	return sortDescriptors;
}

- (NSArray *)observedObjectsSortDescriptors
{
	if(!observedObjectsSortDescriptors)
	{
		NSMutableArray *descriptors = [[NSMutableArray alloc] init];
		NSArray *sortDescs = [self sortDescriptorsForSorting];
		int count = [sortDescs count];
		for(int index=0; index<count; index++)
		{
			NSSortDescriptor *desc = [sortDescs objectAtIndex:index];
			NSString *key = [@"representedObject." stringByAppendingString:[desc key]];
			NSSortDescriptor *observedDesc = [[NSSortDescriptor alloc] initWithKey:key ascending:[desc ascending]];
			[descriptors addObject:observedDesc];
			[observedDesc release];
		}
		observedObjectsSortDescriptors = descriptors;
	}
	return observedObjectsSortDescriptors;
}

- (void)setChildrenKeyPath:(NSString *)keyPath
{
	if(keyPath && childrenKeyPath && ![keyPath isEqual:childrenKeyPath])
		[[self rootNodes] makeObjectsPerformSelector:@selector(prune)];
	[keyPath retain];
	[childrenKeyPath release];
	childrenKeyPath = keyPath;
}

- (NSString *)childrenKeyPath
{
	return childrenKeyPath;
}

- (void)setRootKeyPath:(NSString *)keyPath
{
	[keyPath retain];
	[rootKeyPath release];
	rootKeyPath = keyPath;
	if (keyPath && rootSource) 
	{
		[rootSource addObserver:self forKeyPath:rootKeyPath options:NSKeyValueObservingOptionNew context: PWNewRootArray];
		[self rootObjectsArray];
	}
}

- (NSString *)rootKeyPath
{
	return rootKeyPath;
}

- (void)setIsLeafKeyPath:(NSString *)keyPath
{
	NSString *oldPath = isLeafKeyPath;
	
	[keyPath retain];
	isLeafKeyPath = keyPath;


	if(![isLeafKeyPath isEqual:oldPath])
	{
		[[self rootNodes] makeObjectsPerformSelector:@selector(treeControllerDidChangeLeafKeyPath:) withObject:oldPath];
		[outlineView reloadData];
	}
	[oldPath release];
}

- (NSString *)isLeafKeyPath
{
	return isLeafKeyPath;
}

- (void)setRootObjectsArray:(NSArray *)array
{
	array = [NSMutableArray arrayWithArray:array];
	BOOL changed = ![array isEqual:rootObjectsArray];
	[array retain];
	[rootObjectsArray release];
	rootObjectsArray = array;
	if(array)
	{
		[rootObjectsSet release];
		rootObjectsSet = nil;
	}
	if(changed)
	{
		[outlineView saveState];
		[rootNodes release];
		rootNodes = [[self createNodesFromObservedObjectsArray:array parentNode:nil] retain];
		if(filterPredicate)
			[self performFiltering];
		[outlineView reloadData];
	}
}

- (NSArray *)rootObjectsArray
{
	if (!rootObjectsArray && rootKeyPath && rootSource)
	{
		[self setRootObjectsArray: [rootSource valueForKey: rootKeyPath]];
	}
	return rootObjectsArray;
}

- (void)setRootObjectsSet:(NSSet *)set
{
	set = [NSMutableSet setWithSet:set];
	BOOL changed = ![set isEqual:rootObjectsSet];
	[set retain];
	[rootObjectsSet release];
	rootObjectsSet = set;
	if(set)
	{
		[rootObjectsArray release];
		rootObjectsArray = nil;
	}
	if(changed)
	{
		[outlineView saveState];
		[rootNodes release];
		rootNodes = [[self createNodesFromObservedObjectsArray:[set allObjects] parentNode:nil] retain];
		if(filterPredicate)
			[self performFiltering];
		[outlineView reloadData];
	}
}

- (NSSet *)rootObjectsSet
{
	return rootObjectsSet;
}

- (void)setOutlineView:(PWOutlineView *)ov
{
	[ov retain];
	[outlineView release];
	outlineView = ov;
	[outlineView createAttributeObservers];
}

- (PWOutlineView *)outlineView
{
	return outlineView;
}

- (void)setIsAutosorting:(BOOL)autosort
{
	isAutosorting = autosort;
	if(isAutosorting && pendingAutosort)
		[self performSelector:@selector(autosort) withObject:nil afterDelay:0.0];	
}

- (BOOL)isAutosorting
{
	return isAutosorting;
}

- (void)recursivelyAddFilteredObject:(id)object path:(NSMutableArray *)path
{
	if([filterPredicate evaluateWithObject:object])
	{
		[filteredObjects addObject:object];
		[filteredObjectsAndAncestors addObject:object];
		[filteredObjectsAndAncestors addObjectsFromArray:path];
	}
	if(!isLeafKeyPath || ![[object valueForKeyPath:isLeafKeyPath] boolValue])
	{
		id children = [object valueForKeyPath:childrenKeyPath];
		if([children count])
		{
			[path addObject:object];
			NSEnumerator *e = [children objectEnumerator];
			id child;
			while (child = [e nextObject]) 
				[self recursivelyAddFilteredObject:child path:path];
			[path removeLastObject];
		}
	}
}

- (void)performFiltering
{
	[filteredObjects release];
	filteredObjects = nil;
	[filteredObjectsAndAncestors release];
	filteredObjectsAndAncestors = nil;
	[filteredRootNodes release];
	filteredRootNodes = nil;
	
	if(filterPredicate)
	{
		filteredObjects = [[NSMutableSet alloc] init];
		filteredObjectsAndAncestors = [[NSMutableSet alloc] init];
		NSMutableArray *path = [NSMutableArray array];
		NSEnumerator *e = [[self rootObjectsArray] ? (id)rootObjectsArray : (id)rootObjectsSet objectEnumerator];
		id obj;
		while (obj = [e nextObject]) 
			[self recursivelyAddFilteredObject:obj path:path];
		NSMutableArray *roots = [NSMutableArray array];
		int count = [[self rootNodes] count];
		for(int index=0; index<count; index++)
		{
			PWTreeControllerNode *rootNode = [rootNodes objectAtIndex:index];
			if([filteredObjectsAndAncestors containsObject:[rootNode representedObject]])
				[roots addObject:rootNode];
		}
		filteredRootNodes = [roots retain];
		[rootNodes makeObjectsPerformSelector:@selector(createAllSubNodes)];
	}
}

- (NSArray *)filteredRootNodes
{
	return filteredRootNodes;
}

- (NSArray *)arrangedRootNodes
{
	return filteredRootNodes ? filteredRootNodes : [self rootNodes];
}

- (void)resetIsExpandedFilteredKeyPathValues
{
	NSString *keyPath = [outlineView isExpandedFilteredKeyPath];
	if(outlineView && keyPath)
	{
		id obj;
		NSNumber *yesValue = [NSNumber numberWithBool:YES];
		NSEnumerator *e = [filteredObjectsAndAncestors objectEnumerator];
		while (obj = [e nextObject]) 
			[obj setValue:yesValue forKey:keyPath];
	}
}

- (void)setFilterPredicate:(NSPredicate *)pred
{
	NSArray *uncover = !pred && filterPredicate ? [self selectedObjects] : nil;
	[pred retain];
	[filterPredicate release];
	filterPredicate = (id) pred;

	[self performFiltering];
	
	[[self rootNodes] makeObjectsPerformSelector:@selector(flushFilteredSubNodes)];
	[outlineView reloadData];
	if(uncover)
	{
		[outlineView uncoverOriginalItems:uncover];
		[self setSelectedObjects:uncover];
	}
	if(pred)
		[self resetIsExpandedFilteredKeyPathValues];
}

- (NSPredicate *)filterPredicate
{
	return (NSPredicate *) filterPredicate;
}

- (void)rearrangeObjects
{
	[[self rootNodes] makeObjectsPerformSelector:@selector(prune)];
	[outlineView reloadData];
}

- (PWObservationInfo *)observationInfoForObserver:(id)observer keyPath:(NSString *)keyPath
{
	int count = [attributeObservers count];
	for(int index=0; index<count; index++)
	{
		PWObservationInfo *info = [attributeObservers objectAtIndex:index];
		if(info->observer == observer && [info->keyPath isEqual:keyPath])
			return info;
	}
	return nil;
}

- (void)addObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
{
	if([keyPath hasPrefix:@"arrangedObjects."])
	{
		NSString *subPath = [keyPath substringFromIndex:16];		
		PWObservationInfo *info = [[[PWObservationInfo alloc] initWithObserver:anObserver keyPath:subPath options:options context:context] autorelease];
		if(![attributeObservers containsObject:info])
		{
			[attributeObservers addObject:info];
			NSArray *observationArray = [NSArray arrayWithObject:info];
			int count = [[self rootNodes] count];
			for(int index=0; index<count; index++)
				[[rootNodes objectAtIndex:index] addAttributeObservers:observationArray recurse:YES];
		}
	}
	else
		[super addObserver:anObserver forKeyPath:keyPath options:options context:context];
}

- (void)removeObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath
{
	if([keyPath hasPrefix:@"arrangedObjects."])
	{
		NSString *subPath = [keyPath substringFromIndex:16];
		PWObservationInfo *info = [self observationInfoForObserver:anObserver keyPath:subPath];
		if(info)
		{
			NSArray *observationArray = [NSArray arrayWithObject:info];
			[attributeObservers removeObject:info];
			int count = [[self rootNodes] count];
			for(int index=0; index<count; index++)
				[[rootNodes objectAtIndex:index] removeAttributeObservers:observationArray recurse:YES];
		}
	}
	else
		[super removeObserver:anObserver forKeyPath:keyPath];
}

- (NSSet *)filteredObjects
{
	return filteredObjects;
}

- (NSSet *)filteredObjectsAndAncestors
{
	return filteredObjectsAndAncestors;
}

NSImage *txtIcon=nil;
NSImage *notxtIcon=nil;


- (float) outlineView: (NSOutlineView *) ov heightOfRowByItem: (id) item
{
	NSFont *font=[item font];
	if (font)
		return [font defaultLineHeightForFont]+4;
	return 17;
}

- (void)outlineView:(NSOutlineView *)ov willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
	NSManagedObject *node = [ov originalItemForItem:item];
	if ([[tableColumn identifier] isEqualToString:@"done"])
	{
        if ([item isLeaf])
			[cell setEnabled: YES];
		else
			[cell setEnabled: NO];
	}
	if ([[tableColumn identifier] isEqualToString:@"note"])
	{
        id object;
        if (!txtIcon) txtIcon=[[NSImage imageNamed:@"txt.icns"] retain];
        if (!notxtIcon) notxtIcon=[[NSImage imageNamed:@"notxt.icns"] retain];
		if (object=[node valueForKey:@"note"])
			[cell setImage: txtIcon];
		else
            [cell setImage: notxtIcon];
        [cell setRepresentedObject: node];
	}
	if ([[tableColumn identifier] isEqualToString:@"name"])
	{
		if([self filterPredicate] && ![[self filteredObjects] containsObject:node] )
			[cell setTextColor:[NSColor darkGrayColor]];
		else
		{
			NSColor *color=[node valueForKey:@"textColor"];
			if (color) [cell setTextColor:[node valueForKey:@"textColor"]];
		}
		NSFont *font=[item font];
		if (font) [cell setFont: font];
	}
	if ([[tableColumn identifier] isEqualToString:@"context"])
	{
        id object;
        if (object=[node valueForKey:@"context"])
            [cell selectItemWithTitle: [object valueForKey:@"name"]];
        else
            [cell selectItemWithTitle: @"-INBOX-"];
	}
	if ([[tableColumn identifier] isEqualToString:@"project"])
	{
        id object;
        if (object=[node valueForKey:@"project"])
            [cell selectItemWithTitle: [object valueForKey:@"name"]];
        else
            [cell selectItemWithTitle: @"---"];
	}
}

- (void) outlineViewSelectionIsChanging: (NSNotification *)notification
{
    [rootSource outlineViewSelectionIsChanging: notification];
}

-(BOOL) outlineView: (NSOutlineView *)view writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
{
    return [rootSource outlineView: view writeItems:items toPasteboard: pboard];
}

- (NSDragOperation)outlineView:(NSOutlineView *)view validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(int)index;
{
    return [rootSource outlineView: view validateDrop: info proposedItem: item proposedChildIndex: index];
}

- (BOOL)outlineView:(NSOutlineView *)view acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(int)index;
{
    return [rootSource outlineView: view acceptDrop: info item: item childIndex: index];
}

- (void) outlineView: (NSOutlineView *)view didFinishDragOperation:(NSDragOperation)operation
{
    [rootSource outlineView: view didFinishDragOperation: operation];
}

- (NSArray *) tableViewDefaultColumnIdentifiers: (NSTableView *) tv
{
    return [rootSource tableViewDefaultColumnIdentifiers: tv];
}

- (id) configurableColumnOutlineView: (id) ov shouldAllowTogglingColumn: column
{
	return [rootSource configurableColumnOutlineView: ov shouldAllowTogglingColumn: column];
}

- (NSString *) configurableColumnOutlineView: (id) ov menuStringForColumn: column
{
	return [rootSource configurableColumnOutlineView: ov menuStringForColumn: column];
}
@end
