//
//  BWSelectableToolbar.m
//  BWToolkit
//
//  Created by Brandon Walkin (www.brandonwalkin.com)
//  All code is provided under the New BSD license.
//

#import "BWSelectableToolbar.h"
#import "BWSelectableToolbarHelper.h"
#import "NSWindow+BWAdditions.h"

NSString * const BWSelectableToolbarItemClickedNotification = @"BWSelectableToolbarItemClicked";

static BWSelectableToolbar *documentToolbar;
static NSToolbar *editableToolbar;

@interface NSToolbar (BWSTPrivate)
- (id)_defaultItemIdentifiers;
- (id)_window;
- (id)initWithCoder:(NSCoder *)decoder;
- (void)encodeWithCoder:(NSCoder*)coder;
@end

@interface BWSelectableToolbar (BWSTPrivate)
- (NSArray *)selectableItemIdentifiers;
- (void)setItemSelectors;
- (void)initialSetup;
- (void)toggleActiveView:(id)sender;
- (NSString *)identifierAtIndex:(int)index;
- (void)switchToItemAtIndex:(int)anIndex animate:(BOOL)flag;
- (int)toolbarIndexFromSelectableIndex:(int)selectableIndex;
- (void)selectInitialItem;
- (void)selectItemAtIndex:(int)anIndex;
// IBDocument methods
- (void)addObject:(id)object toParent:(id)parent;
- (void)moveObject:(id)object toParent:(id)parent;
- (void)removeObject:(id)object;
- (id)parentOfObject:(id)anObj;
- (NSArray *)objectsforDocumentObject:(id)anObj;
- (NSArray *)childrenOfObject:(id)object;
@end

@interface BWSelectableToolbar ()
@property (retain) BWSelectableToolbarHelper *helper;
@property (readonly) NSMutableArray *labels;
@property (copy) NSMutableDictionary *enabledByIdentifier;
@property BOOL isPreferencesToolbar;
@end

@implementation BWSelectableToolbar

@synthesize helper;
@synthesize isPreferencesToolbar;
@synthesize enabledByIdentifier;

- (BWSelectableToolbar *)documentToolbar
{
	return [[documentToolbar retain] autorelease];
}

- (void)setDocumentToolbar:(BWSelectableToolbar *)obj
{
	[documentToolbar release];
	documentToolbar = [obj retain];
}

- (NSToolbar *)editableToolbar
{
	if ([self respondsToSelector:@selector(ibDidAddToDesignableDocument:)] == NO)
		return self;
	
	return [[editableToolbar retain] autorelease];
}

- (void)setEditableToolbar:(NSToolbar *)obj
{
	if ([self respondsToSelector:@selector(ibDidAddToDesignableDocument:)])
	{
//		NSLog(@"--self: %@",self);
//		NSLog(@"--editable toolbar is: %@",editableToolbar);
//		NSLog(@"--setting editable toolbar to: %@",obj);

		[editableToolbar release];
		editableToolbar = [obj retain];
	}

}

- (id)initWithCoder:(NSCoder *)decoder
{
    if ((self = [super initWithCoder:decoder]) != nil)
	{
		[self setDocumentToolbar:[decoder decodeObjectForKey:@"BWSTDocumentToolbar"]];
		[self setHelper:[decoder decodeObjectForKey:@"BWSTHelper"]];
		isPreferencesToolbar = [decoder decodeBoolForKey:@"BWSTIsPreferencesToolbar"];
		[self setEnabledByIdentifier:[decoder decodeObjectForKey:@"BWSTEnabledByIdentifier"]];
		
//		NSLog(@"init with coder. helper decoded: %@", helper);
	}
	return self;
}

- (void)encodeWithCoder:(NSCoder*)coder
{
    [super encodeWithCoder:coder];
	
	[coder encodeObject:[self documentToolbar] forKey:@"BWSTDocumentToolbar"];
	[coder encodeObject:[self helper] forKey:@"BWSTHelper"];
	[coder encodeBool:isPreferencesToolbar forKey:@"BWSTIsPreferencesToolbar"];
	[coder encodeObject:[self enabledByIdentifier] forKey:@"BWSTEnabledByIdentifier"];
	
//	NSLog(@"encode with coder. helper encoded: %@",helper);
}

// When the user drags the toolbar on the canvas, we want the default toolbar items to be a specific set that's more appropriate for a selectable toolbar (in particular,
// for a preferences window). Generally, you would supply these items in your -toolbarDefaultItemIdentifiers: delegate method. However, since Interface Builder stores 
// its default item identifiers in user defaults, the delegate method never gets called. To force the toolbar to have a different set of default items, we supply the
// identifiers in this private method.
- (id)_defaultItemIdentifiers
{
	NSArray *defaultItemIdentfiers = [super _defaultItemIdentifiers];
	NSArray *defaultIBItemIdentifiers = [NSArray arrayWithObjects:@"NSToolbarSeparatorItem",@"NSToolbarSpaceItem",@"NSToolbarFlexibleSpaceItem",nil];
	
	NSArray *idealDefaultItemIdentifiers = [NSArray arrayWithObjects:@"0D5950D1-D4A8-44C6-9DBC-251CFEF852E2",@"BWToolbarShowColorsItem",
											@"BWToolbarShowFontsItem",@"7E6A9228-C9F3-4F21-8054-E4BF3F2F6BA8",nil];
	
	if ([defaultItemIdentfiers isEqualToArray:defaultIBItemIdentifiers])
	{
		return idealDefaultItemIdentifiers;
	}

	return defaultItemIdentfiers;
}

- (id)initWithIdentifier:(NSString *)identifier
{
	if (self = [super initWithIdentifier:identifier])
	{		
		itemIdentifiers = [[NSMutableArray alloc] init];
        itemsByIdentifier = [[NSMutableDictionary alloc] init];
		
		selectedIndex = 0;
		inIB = YES;
		[self setEditableToolbar:self];
		
		[self performSelector:@selector(initialSetup) withObject:nil afterDelay:0];
	}
	return self;
}

- (void)awakeFromNib
{
	if ([self respondsToSelector:@selector(ibDidAddToDesignableDocument:)] == NO)
	{
		inIB = NO;
		
		if ([helper isPreferencesToolbar])
		{
			[[self _window] setShowsToolbarButton:NO];
			[self setAllowsUserCustomization:NO];		
		}
		
		[self performSelector:@selector(selectInitialItem) withObject:nil afterDelay:0];
	}
}

- (void)selectFirstItem
{
	int toolbarIndex = [self toolbarIndexFromSelectableIndex:0];
	[self switchToItemAtIndex:toolbarIndex animate:NO];
}

- (void)selectInitialItem
{
	// When the window launches, we want to select the toolbar item that was previously selected.
	// So we have to find the toolbar index for our saved selected identifier.
	int toolbarIndex;
	
	if ([helper selectedIdentifier] != nil)
		toolbarIndex = [itemIdentifiers indexOfObject:[helper selectedIdentifier]];
	else
		toolbarIndex = [self toolbarIndexFromSelectableIndex:0];

	[self switchToItemAtIndex:toolbarIndex animate:NO];
}

- (void)initialSetup
{
	// Get a reference to the helper object in the document if we don't have one already
	if (helper == nil && inIB)
	{
		NSArray *windowChildren = [self childrenOfObject:[self parentOfObject:documentToolbar]];
		
		for (id anObj in windowChildren)
		{
			if ([anObj isMemberOfClass:NSClassFromString(@"BWSelectableToolbarHelper")])
			{
				helper = anObj;
//				NSLog(@"Got a reference to helper: %@",helper);
			}
		}
	}
	
//	if (helper == nil && inIB)
//		NSLog(@"Helper is nil");
	
	// Get reference to the editable toolbar in IB
	if ([self isMemberOfClass:NSClassFromString(@"IBEditableBWSelectableToolbar")])
	{
		[self setEditableToolbar:self];
		
		if ([helper contentViewsByIdentifier].count == 0)
			[helper setInitialIBWindowSize:[[[self editableToolbar] _window] frame].size];
			
		[[NSNotificationCenter defaultCenter] addObserver:self 
												 selector:@selector(windowDidResize:)
													 name:NSWindowDidResizeNotification 
												   object:[[self editableToolbar] _window]];

	}
	
	NSToolbarItem *currentItem;
	for (currentItem in [self items]) 
	{
		[itemIdentifiers addObject:[currentItem itemIdentifier]];
		[itemsByIdentifier setObject:currentItem forKey:[currentItem itemIdentifier]];
	}
	
	[self setDelegate:self];
	[self setItemSelectors];
	
	if ([helper selectedIdentifier] != nil && [helper contentViewsByIdentifier].count != 0)
	{
		// When the actual app is ran or an item is added or removed from the toolbar in IB, we need to select the previously stored identifier
		[self selectItemAtIndex:[itemIdentifiers indexOfObject:[helper selectedIdentifier]]];
	}
	else
	{
		// If we don't have a stored identifier, we select the first item in the toolbar
		[self selectItemAtIndex:[self toolbarIndexFromSelectableIndex:0]];
	}
	
	if ([self isMemberOfClass:NSClassFromString(@"IBEditableBWSelectableToolbar")])
	{
		// When the toolbar is initially dragged onto the canvas, record the content view and size of the window		
		NSMutableDictionary *tempCVBI = [[[helper contentViewsByIdentifier] mutableCopy] autorelease];
		[tempCVBI setObject:[[[self editableToolbar] _window] contentView] forKey:[helper selectedIdentifier]];
		[helper setContentViewsByIdentifier:tempCVBI];
		
		NSMutableDictionary *tempWSBI = [[[helper windowSizesByIdentifier] mutableCopy] autorelease];	
		[tempWSBI setObject:[NSValue valueWithSize:[[[self editableToolbar] _window] frame].size] forKey:[helper selectedIdentifier]];
		[helper setWindowSizesByIdentifier:tempWSBI];
	}
}

- (int)toolbarIndexFromSelectableIndex:(int)selectableIndex
{
	NSMutableArray *selectableItems = [[[NSMutableArray alloc] init] autorelease];
	
	for (NSToolbarItem *currentItem in [[self editableToolbar] items]) 
	{
		if (![[currentItem itemIdentifier] isEqualToString:@"NSToolbarSeparatorItem"] && 
			![[currentItem itemIdentifier] isEqualToString:@"NSToolbarSpaceItem"] &&
			![[currentItem itemIdentifier] isEqualToString:@"NSToolbarFlexibleSpaceItem"])
		{
			[selectableItems addObject:currentItem];
		}
	}
	
	if (selectableItems.count == 0)
		return 0;
	
	NSString *item = [selectableItems objectAtIndex:selectableIndex];
	
	int toolbarIndex = [[[self editableToolbar] items] indexOfObject:item];
	
	return toolbarIndex;
}

// Tells the toolbar to draw the selection behind the toolbar item and records the selected item identifier
- (void)selectItemAtIndex:(int)anIndex
{
	NSArray *toolbarItems = self.items;
	
	if (toolbarItems.count > 1)
	{
		NSToolbarItem *item = [toolbarItems objectAtIndex:anIndex];
		NSString *identifier = [item itemIdentifier];
		[super setSelectedItemIdentifier:identifier];
		
		[helper setSelectedIdentifier:identifier];
	}
}

// This is called when a selectable item is clicked. This is not called in IB (-setSelectedIndex: is used instead).
- (void)toggleActiveView:(id)sender
{
    NSString *identifier = [sender itemIdentifier];

	selectedIndex = [itemIdentifiers indexOfObject:identifier];

	[[NSNotificationCenter defaultCenter] postNotificationName:BWSelectableToolbarItemClickedNotification object:self userInfo:[NSDictionary dictionaryWithObject:sender forKey:@"BWClickedItem"]];
	
	[self switchToItemAtIndex:selectedIndex animate:YES];
}

- (void)setItemSelectors
{
	NSToolbarItem *currentItem;
	
	for (currentItem in [self items]) 
	{
		[currentItem setTarget:self];
		[currentItem setAction:@selector(toggleActiveView:)];
	}
}

- (NSString *)identifierAtIndex:(int)index
{
	NSToolbarItem *item;
	NSString *newIdentifier = nil;
	if ([[self editableToolbar] items].count > 1)
	{
		item = [[[self editableToolbar] items] objectAtIndex:index];
		newIdentifier = [item itemIdentifier];
	}
	return newIdentifier;
}

- (void)dealloc
{
	[[NSNotificationCenter defaultCenter] removeObserver:self
													name:NSWindowDidResizeNotification
												  object:[[self editableToolbar] _window]];
	[itemIdentifiers release];
	[itemsByIdentifier release];
	[enabledByIdentifier release];
	[helper release];
    [super dealloc];
}

#pragma mark Public Methods

- (void)setSelectedItemIdentifier:(NSString *)itemIdentifier
{
	BOOL validIdentifier = NO;
	
	for (NSString *identifier in itemIdentifiers)
	{
		if ([identifier isEqualToString:itemIdentifier])
			validIdentifier = YES;
	}
	
	if (validIdentifier)
		[self switchToItemAtIndex:[itemIdentifiers indexOfObject:itemIdentifier] animate:YES];
}

- (void)setSelectedItemIdentifierWithoutAnimation:(NSString *)itemIdentifier
{
	BOOL validIdentifier = NO;
	
	for (NSString *identifier in itemIdentifiers)
	{
		if ([identifier isEqualToString:itemIdentifier])
			validIdentifier = YES;
	}
	
	if (validIdentifier)
		[self switchToItemAtIndex:[itemIdentifiers indexOfObject:itemIdentifier] animate:NO];
}

- (void)setEnabled:(BOOL)flag forIdentifier:(NSString *)itemIdentifier
{
	NSMutableDictionary *enabledDict = [[[self enabledByIdentifier] mutableCopy] autorelease];
	
	[enabledDict setObject:[NSNumber numberWithBool:flag] forKey:itemIdentifier];
	
	[self setEnabledByIdentifier:enabledDict];
}

#pragma mark Public Method Support Methods

- (BOOL)validateToolbarItem:(NSToolbarItem *)theItem
{
	if ([self respondsToSelector:@selector(ibDidAddToDesignableDocument:)] == NO)
	{
		NSString *identifier = [theItem itemIdentifier];		
		
		if ([[self enabledByIdentifier] objectForKey:identifier] != nil)
		{
			if ([[[self enabledByIdentifier] objectForKey:identifier] boolValue] == NO)
				return NO;
		}
	}
	
	return YES;
}

- (NSMutableDictionary *)enabledByIdentifier
{
	if (enabledByIdentifier == nil)
		enabledByIdentifier = [NSMutableDictionary new];
	
    return [[enabledByIdentifier retain] autorelease]; 
}

#pragma mark NSWindow notifications

- (void)windowDidResize:(NSNotification *)notification
{
	NSSize size = [[[self editableToolbar] _window] frame].size;
	NSValue *sizeValue = [NSValue valueWithSize:size];
	NSString *key = [helper selectedIdentifier];
	
	if ([helper selectedIdentifier])
	{
		NSMutableDictionary *tempWSBI = [[[helper windowSizesByIdentifier] mutableCopy] autorelease];	
		[tempWSBI setObject:sizeValue forKey:key];
		[helper setWindowSizesByIdentifier:tempWSBI];
	}
}

#pragma mark NSToolbar delegate methods

- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar
{
	return itemIdentifiers;
}

- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar*)toolbar 
{
	return itemIdentifiers;
}

- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)identifier willBeInsertedIntoToolbar:(BOOL)willBeInserted 
{
	return [itemsByIdentifier objectForKey:identifier];
}

- (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar *)toolbar
{
	return [self selectableItemIdentifiers];
}

#pragma mark Support methods for delegate methods

- (NSArray *)selectableItemIdentifiers
{
	NSMutableArray *selectableItemIdentifiers = [[[NSMutableArray alloc] init] autorelease];
	
	if ([self editableToolbar] != nil)
	{
		for (NSToolbarItem *currentItem in [[self editableToolbar] items]) 
		{
			if (![[currentItem itemIdentifier] isEqualToString:@"NSToolbarSeparatorItem"] && 
				![[currentItem itemIdentifier] isEqualToString:@"NSToolbarSpaceItem"] &&
				![[currentItem itemIdentifier] isEqualToString:@"NSToolbarFlexibleSpaceItem"])
			{
				[selectableItemIdentifiers addObject:[currentItem itemIdentifier]];
			}
		}
	}
	else
	{
		for (NSToolbarItem *currentItem in [self items]) 
		{
			if (![[currentItem itemIdentifier] isEqualToString:@"NSToolbarSeparatorItem"] && 
				![[currentItem itemIdentifier] isEqualToString:@"NSToolbarSpaceItem"] &&
				![[currentItem itemIdentifier] isEqualToString:@"NSToolbarFlexibleSpaceItem"])
			{
				[selectableItemIdentifiers addObject:[currentItem itemIdentifier]];
			}
		}
	}
	
	return selectableItemIdentifiers;
}

#pragma mark IB Inspector support methods

- (void)setIsPreferencesToolbar:(BOOL)flag
{
	[helper setIsPreferencesToolbar:flag];
	isPreferencesToolbar = flag;
	
	if (flag)
	{
		// Record the current window title
		[helper setOldWindowTitle:[[self parentOfObject:self] title]];
		
		// Change the window title to the name of the active tab
		NSToolbarItem *selectedItem = nil;
		for (NSToolbarItem *item in [[self editableToolbar] items])
		{
			if ([[item itemIdentifier] isEqualToString:[[self editableToolbar] selectedItemIdentifier]])
				selectedItem = item;
		}
		[[self parentOfObject:self] setTitle:[selectedItem label]];
		
		// Remove the toolbar button
		[[self parentOfObject:self] setShowsToolbarButton:NO];	
	}
	else
	{
		// Restore the old window title
		[[self parentOfObject:self] setTitle:[helper oldWindowTitle]];
		
		// Add the toolbar button
		[[self parentOfObject:self] setShowsToolbarButton:YES];
	}
}

- (NSMutableArray *)labels
{	
	NSMutableArray *labelArray = [NSMutableArray array];
	
	for (NSToolbarItem *currentItem in [[self editableToolbar] items]) 
	{
		if (![[currentItem itemIdentifier] isEqualToString:@"NSToolbarSeparatorItem"] && 
			![[currentItem itemIdentifier] isEqualToString:@"NSToolbarSpaceItem"] &&
			![[currentItem itemIdentifier] isEqualToString:@"NSToolbarFlexibleSpaceItem"])
		{
			[labelArray addObject:[currentItem label]];
		}
	}
	
	return labelArray;
}

- (int)selectedIndex
{
	// The actual selected index can change on us (for instance, when the user re-orders toolbar items). So we need to figure it out dynamically, based on the selected identifier.
	if ([[helper selectedIdentifier] isEqualToString:@""])
		selectedIndex = 0;
	else
		selectedIndex = [[self selectableItemIdentifiers] indexOfObject:[helper selectedIdentifier]];

	return selectedIndex;
}

- (void)setSelectedIndex:(int)anIndex
{
	selectedIndex = anIndex;
	[self switchToItemAtIndex:[self toolbarIndexFromSelectableIndex:anIndex] animate:YES];
}

#pragma mark Selection Switching

- (void)switchToItemAtIndex:(int)anIndex animate:(BOOL)shouldAnimate
{	
	NSString *oldIdentifier = [helper selectedIdentifier];
	
	// Put the selection highlight on the toolbar item
	[(BWSelectableToolbar *)[self editableToolbar] selectItemAtIndex:anIndex];

	// Clear out the window's first responder
	[[[self editableToolbar] _window] makeFirstResponder:nil];
	
	// Make a new container view and add it to the IB document
	NSView *containerView = [[[NSView alloc] initWithFrame:[[[[self editableToolbar] _window] contentView] frame]] autorelease];
	if (inIB)
		[self addObject:containerView toParent:[self parentOfObject:self]];
	
	// Move the subviews from the content view to the container view
	NSArray *oldSubviews = [[[[[[self editableToolbar] _window] contentView] subviews] copy] autorelease];
	for (NSView *view in oldSubviews)
	{
		if (inIB)
			[self moveObject:view toParent:containerView];
		[containerView addSubview:view];
	}
	
	// Store the container view and window size in the dictionaries
	NSMutableDictionary *tempCVBI = [[[helper contentViewsByIdentifier] mutableCopy] autorelease];
	[tempCVBI setObject:containerView forKey:oldIdentifier];
	[helper setContentViewsByIdentifier:tempCVBI];
	
	NSSize oldWindowSize = [[[self editableToolbar] _window] frame].size;
	NSMutableDictionary *tempWSBI = [[[helper windowSizesByIdentifier] mutableCopy] autorelease];
	[tempWSBI setObject:[NSValue valueWithSize:oldWindowSize] forKey:oldIdentifier];
	[helper setWindowSizesByIdentifier:tempWSBI];

	
	NSString *newIdentifier = [self identifierAtIndex:anIndex];
	
	if ([[helper contentViewsByIdentifier] objectForKey:newIdentifier] == nil) // If we haven't stored the content view in our dictionary. i.e. this is a new tab
	{
		// Resize the window
		[[[self editableToolbar] _window] resizeToSize:[helper initialIBWindowSize] animate:shouldAnimate];	
		
		// Record the new tab content view and window size
		if (inIB)
		{
			NSMutableDictionary *tempCVBI = [[[helper contentViewsByIdentifier] mutableCopy] autorelease];
			[tempCVBI setObject:[[[self editableToolbar] _window] contentView] forKey:newIdentifier];
			[helper setContentViewsByIdentifier:tempCVBI];
			
			NSMutableDictionary *tempWSBI = [[[helper windowSizesByIdentifier] mutableCopy] autorelease];	
			[tempWSBI setObject:[NSValue valueWithSize:[[[self editableToolbar] _window] frame].size] forKey:newIdentifier];
			[helper setWindowSizesByIdentifier:tempWSBI];
		}
	}
	else // If we have the content view in our dictionary, set the window's content view to be the saved view
	{
		// Resize the window
		NSSize windowSize = [[[helper windowSizesByIdentifier] objectForKey:newIdentifier] sizeValue];
		[[[self editableToolbar] _window] resizeToSize:windowSize animate:shouldAnimate];

		NSArray *newSubviews = [[[[[helper contentViewsByIdentifier] objectForKey:newIdentifier] subviews] copy] autorelease];
		
		if (newSubviews.count > 0 && newSubviews != nil)
		{
			for (NSView *view in newSubviews)
			{
				if (inIB)
				{
					[self moveObject:view toParent:[[self parentOfObject:self] contentView]];
				}
				
				[[[[self editableToolbar] _window] contentView] addSubview:view];
			}
		}
					
		// Remove the container view for the selected tab from the document since those items are now in the window's content view.			
		if (inIB)
			[self removeObject:[[helper contentViewsByIdentifier] objectForKey:newIdentifier]];
		
		// Tell the window to recalculate the key view loop so the views we added to the content view are keyboard accessible
		[[[self editableToolbar] _window] recalculateKeyViewLoop];
	}
	
	// After the new content view is swapped in, change the window title to be the selected item label
	if ([helper isPreferencesToolbar])
	{
		for (NSToolbarItem *item in [[self editableToolbar] items])
		{
			if ([[item itemIdentifier] isEqualToString:newIdentifier])
			{
				[[[self editableToolbar] _window] setTitle:[item label]];
				
				if (inIB)
					[[self parentOfObject:self] setTitle:[item label]];
			}
		}		
	}

}

@end