/*--------------------------------------------------------------------------
 *
 * 	You may freely copy, distribute, and reuse the code in this example.
 * 	SHL Systemhouse disclaims any warranty of any kind, expressed or  
 *	implied, as to its fitness for any particular use.
 *
 *
 *	ObjectController
 *
 *	Inherits From:		Object
 *
 *	Conforms To:		None
 *
 *	Declared In:		ObjectController.h
 *
 *------------------------------------------------------------------------*/

#import "ObjectController.h"
#import "ImageExtensionsCategory.h"
#import "SwapView.h"
#import "ObjectWell.h"
#import "FoundationExtensions.h"

#import <mach/mach.h>



#define NS_ARRAY		0
#define NS_DICTIONARY	1
#define NS_STRING		2


@implementation ObjectController

- _setupWithView: theView
	type: (int) theType
/*--------------------------------------------------------------------------
 *	Swaps in the correct view for this object controller.  Puts up the
 *	right icon, and so on, and so on.
 *------------------------------------------------------------------------*/
{
	[swapView swapView:theView];
	[objectTypeSelector selectCellWithTag:theType];
	[objectWell setObject:theObject];
	[objectWell setImage:[NXImage findImageNamed:[theObject getIcon]]];
	return self;
}


- _save
/*--------------------------------------------------------------------------
 *	Does the real saving.
 *------------------------------------------------------------------------*/
{
	/*
	 * See also FoundationExtentions.h
	 */
	[theObject writeToFile:filename atomically:NO];
	return self;
}


- _createNewObjectControllerForObject: newObject
/*--------------------------------------------------------------------------
 *	When someone double-clicks in the array browser, or the dictionary
 *	table view, the correct method passes us an object, and we alloc/init
 *	a new object controller for that type.  So, for example, say I had a
 *	dictionary as one element in my array, I double click on it...boom
 *	I get a new dictionary object controller representing that dictionary!
 *------------------------------------------------------------------------*/
{
	if ([newObject isKindOfClass:[NSArray class]])
		{
		[[[ObjectController alloc] initWithArray:newObject] showWindow];
		}
	else
		{
		if ([newObject isKindOfClass:[NSDictionary class]])
			{
			[[[ObjectController alloc] initWithDictionary:newObject] showWindow];
			}
		else
			{
			if ([newObject isKindOfClass:[NSString class]])
				{
				[[[ObjectController alloc] initWithString:newObject] showWindow];
				}
			}
		}
	
	return self;
}


- init
/*--------------------------------------------------------------------------
 *	Main initializer.  Loads nib, sets up UI stuff.
 *------------------------------------------------------------------------*/
{
	[super init];
	
	[NXApp loadNibSection:"Object.nib" owner:self withNames:NO];
	
	[arrayBrowserView setTarget:self];
	[arrayBrowserView setDoubleAction:@selector(arrayBrowserDoubleClick:)];

	[dictionaryTableView setTarget:self];
	[dictionaryTableView setDoubleAction:@selector(dictionaryTableViewDoubleClick:)];
	[(NXTableView *)dictionaryTableView setDataSource:self];
	[[dictionaryTableView columnAt:0] setIdentifier:(id)0];
	[[dictionaryTableView columnAt:1] setIdentifier:(id)1];

	[(Text *)stringTextView setDelegate:self];

	return self;
}


- free
/*--------------------------------------------------------------------------
 *	Free...free...I long to be freeeeeee!  Finally released from the bonds
 *	of "objectdom"!
 *------------------------------------------------------------------------*/
{
	[theObject release];
	[filename release];
	[window free];
	return [super free];
}


- initWithContentsOfFile: (NSString *) theFilename
/*--------------------------------------------------------------------------
 *	Called by the "open:" method in our main controller.  Gives a full
 *	pathname, we init a string from the file.  Then...
 *------------------------------------------------------------------------*/
{
	NSString
		*myString = [[[NSString alloc] initWithContentsOfFile:theFilename] autorelease];
	id
		returnValue = nil,
		/*
		 * This dandy little method parses its contents allocing all
		 * necessary objects (dictionaries, arrays, more strings).
		 * This assumes that the file is in PropertyList format,
		 * which looks like:
		 *
		 * array:		(value, value, value, ...)
		 * dictionary:	{key = value; key = value; ...}
		 * string:		"some string in quotes if it has spaces
		 *				 or other escape\n sequences"
		 *
		 * NOTE:	These work recursively i.e.:
		 *
		 * (value, value, "a quoted string, {key = value; key = value;
		 * key = (a, b, c);})
		 *
		 * NOTE:	NSString will init from a file that is in array
		 *			or dictionary format even if it is not quoted.
		 *
		 * Pretty cool huh?
		 */
		myContents = [myString propertyList];
	
	filename = [theFilename retain];
	
	/*
	 * based upon the root class we received from the file,
	 * init the correct controller.
	 */
	if (myContents)
		{
		if ([myContents isKindOfClass:[NSArray class]])
			{
			returnValue = [self initWithArray:myContents];
			}
			
		if ([myContents isKindOfClass:[NSDictionary class]])
			{
			returnValue = [self initWithDictionary:myContents];
			}
			
		if ([myContents isKindOfClass:[NSString class]])
			{
			returnValue = [self initWithString:myContents];
			}
		}
		
	return returnValue;
}


- showWindow
/*--------------------------------------------------------------------------
 *	If you have to ask...
 *------------------------------------------------------------------------*/
{
	[window makeKeyAndOrderFront:nil];
	return self;
}


- changeObjectType: sender
/*--------------------------------------------------------------------------
 *	Convert from one type to another...report errors too.
 *------------------------------------------------------------------------*/
{
	switch ([[sender selectedCell] tag])
		{
		/*
		 * We're trying to convert TO an array, we can only convert from
		 * a string whose format is "(value, value, value)".
		 */
		case NS_ARRAY:
			if ([theObject isKindOfClass:[NSDictionary class]])
				{
				NXRunAlertPanel (NULL, "Cannot convert from NSDictionary to NSArray.", NULL, NULL, NULL, NULL);
				[sender selectCellWithTag:NS_DICTIONARY];
				}
			else
				{
				if ([theObject isKindOfClass:[NSString class]])
					{
					id
						myContents;
					
					NX_DURING
					
						myContents = [theObject propertyList];
					
					NX_HANDLER
					
						myContents = nil;
						
					NX_ENDHANDLER
					
					if (myContents && [myContents isKindOfClass:[NSArray class]])
						{
						[theObject release];
						theObject = [myContents retain];
						[arrayBrowserView loadColumnZero];
						[self _setupWithView:[arrayWindow contentView] type:NS_ARRAY];
						}
					else
						{
						NXRunAlertPanel (NULL, "Unable convert from NSString to NSArray.", NULL, NULL, NULL, NULL);
						[sender selectCellWithTag:NS_STRING];
						}
					}
				}
			
			break;
			
		/*
		 * We're trying to convert TO a dictionary, we can only convert from
		 * a string whose format is "{key = value; key = value; key = value;)".
		 */
		case NS_DICTIONARY:
			if ([theObject isKindOfClass:[NSArray class]])
				{
				NXRunAlertPanel (NULL, "Cannot convert from NSArray to NSDictionary.", NULL, NULL, NULL, NULL);
				[sender selectCellWithTag:NS_ARRAY];
				}
			else
				{
				if ([theObject isKindOfClass:[NSString class]])
					{
					id
						myContents;
					
					NX_DURING
					
						myContents = [theObject propertyList];
					
					NX_HANDLER
					
						myContents = nil;
						
					NX_ENDHANDLER
					
					if (myContents && [myContents isKindOfClass:[NSDictionary class]])
						{
						[theObject release];
						theObject = [myContents retain];
						[dictionaryTableView reloadData:nil];
						[self _setupWithView:[dictionaryWindow contentView] type:NS_DICTIONARY];
						}
					else
						{
						NXRunAlertPanel (NULL, "Unable convert from NSString to NSDictionary.", NULL, NULL, NULL, NULL);
						[sender selectCellWithTag:NS_STRING];
						}
					}
				}
			
			break;
			
		/*
		 * We're trying to convert TO a string, we can simply ask for the
		 * "description" of the object.
		 */
		case NS_STRING:
			if ([theObject isKindOfClass:[NSArray class]] || [theObject isKindOfClass:[NSDictionary class]])
				{
				[theObject autorelease];
				theObject = [[(NSObject *)theObject description] retain];
				[stringTextView setText:[theObject cString]];
				[self _setupWithView:[stringWindow contentView] type:NS_STRING];
				}
			
			break;
		}
	
	return self;
}


- save: sender
/*--------------------------------------------------------------------------
 *	Pop up a SavePanel if we're dirty and if there is no filename yet.
 *	Otherwise, if we're dirty, and have a filename, just call our "_save"
 *	method.
 *------------------------------------------------------------------------*/
{
	if ([window isDocEdited])
		{
		BOOL
			shouldSave = YES;
		
		if (filename == nil)
			{
			SavePanel
				*mySavePanel = [SavePanel new];
			
			if ([mySavePanel runModal] == NX_OKTAG)
				{
				filename = [[NSString alloc] initWithCString:[mySavePanel filename]];
				}
			else
				{
				shouldSave = NO;
				}
			}
		
		[self _save];
		[window setDocEdited:NO];
		}

	return self;
}


- close: sender
/*--------------------------------------------------------------------------
 *	If you have to ask...
 *------------------------------------------------------------------------*/
{
	[window performClose:nil];
	return self;
}


- windowWillClose: sender
/*--------------------------------------------------------------------------
 *	Free ourself.
 *------------------------------------------------------------------------*/
{
	[NXApp delayedFree:self];
	return self;
}


/*--------------------------------------------------------------------------
 *	Working as an NSArray...
 *------------------------------------------------------------------------*/

- initWithArray: (NSArray *) theArray
/*--------------------------------------------------------------------------
 *	Initializer for NSArray object controllers.  Retains the incoming
 *	array, tells the browser to load up.  Setup the window to
 *	display the array view.
 *------------------------------------------------------------------------*/
{
	[self init];

	theObject = [theArray retain];
	[arrayBrowserView loadColumnZero];
	
	[self _setupWithView:[arrayWindow contentView] type:NS_ARRAY];
	return self;
}


- addObjectToArray: sender
/*--------------------------------------------------------------------------
 *	Add the object in our receive well, to our object (array).  Sorry...no
 *	error checking here.  Sue me.
 *------------------------------------------------------------------------*/
{
	[(NSMutableArray *)theObject addObject:[arrayReceiverWell object]];
	[arrayBrowserView loadColumnZero];
	[window setDocEdited:YES];
	return self;
}


- arrayBrowserDoubleClick: sender
/*--------------------------------------------------------------------------
 *	When they double-click on a browser row, let's get the object that
 *	corresponds to the row selected.  We assume (probably okay in this
 *	really simple example) that the row number corresponds to an index
 *	within the "theObject" (which is an array).
 *------------------------------------------------------------------------*/
{
	[self _createNewObjectControllerForObject:[theObject objectAtIndex:[[sender matrixInColumn:0] selectedRow]]];
	return self;
}


- (int) browser: sender
	fillMatrix: matrix
	inColumn: (int) column
/*--------------------------------------------------------------------------
 *	Fill up the browser with "descriptions" of all of our objects.
 *------------------------------------------------------------------------*/
{
	int
		x = 0;
	id
		myCell,
		myObject;
	NSEnumerator
		*myArrayEnumerator;
	
	/*
	 * NSEnumerators are wonderful things, observe...
	 */
	myArrayEnumerator = [theObject objectEnumerator];
	
	while (myObject = [myArrayEnumerator nextObject]) 
		{
		[matrix addRow];
		myCell = [matrix cellAt:x :0];
		[myCell setStringValue:[[(NSObject *)myObject description] cString]];
		[myCell setLeaf:YES];
		[myCell setLoaded:YES];
		x++;
		}
	
	return x;
}



/*--------------------------------------------------------------------------
 *	Working as an NSDictionary...
 *------------------------------------------------------------------------*/

- initWithDictionary: (NSDictionary *) theDictionary
/*--------------------------------------------------------------------------
 *	Initializer for NSDictionary object controllers.  Retains the incoming
 *	dictionary, tells the table view to load up.  Setup the window to
 *	display the dictionary view.
 *------------------------------------------------------------------------*/
{
	[self init];

	theObject = [theDictionary retain];
	[dictionaryTableView reloadData:nil];

	[self _setupWithView:[dictionaryWindow contentView] type:NS_DICTIONARY];
	return self;
}


- addKeyAndObjectToDictionary: sender
/*--------------------------------------------------------------------------
 *	Add the object in our receive well, with the key entered, to our
 *	object (dictionary).  Sorry...no error checking here.  Sue me.
 *------------------------------------------------------------------------*/
{
	NSLog (@"[dictionaryReceiverWell object] = %@", [dictionaryReceiverWell object]);


	[theObject setObject:[dictionaryReceiverWell object]
				forKey:[NSString stringWithCString:[dictionaryKeyForm stringValueAt:0]]];
	[dictionaryTableView reloadData:nil];
	[window setDocEdited:YES];
	return self;
}


- dictionaryTableViewDoubleClick: sender
/*--------------------------------------------------------------------------
 *	When they double-click on a table view row, let's get the object that
 *	corresponds to the row selected.  We assume (probably okay in this
 *	really simple example) that the row number corresponds to an index
 *	within the "[theObject allValues]" array.
 *------------------------------------------------------------------------*/
{
	[self _createNewObjectControllerForObject:[[theObject allValues] objectAtIndex:[dictionaryTableView selectedRow]]];
	return self;
}


static id
	myDictionaryKeyEnumerator = nil,
	myDictionaryObjectEnumerator = nil;


/*--------------------------------------------------------------------------
 *	NXTableView datasource methods
 *------------------------------------------------------------------------*/

- (unsigned int) rowCount
/*--------------------------------------------------------------------------
 *	Tell me how many rows I'll need to put in the table view.  Also,
 *	create new ... hmmm ...
 *------------------------------------------------------------------------*/
{
	myDictionaryKeyEnumerator = [[theObject keyEnumerator] retain];
	myDictionaryObjectEnumerator = [[theObject objectEnumerator] retain];
	
	return [theObject count];
}


- getValueFor: identifier
	at: (unsigned int) aPosition
	into: aValue;
/*--------------------------------------------------------------------------
 *	Called for each row for each column.  The indentifier was set up in
 *	the init methods to just be an int (0 for column 0, and 1 for column 1).
 *------------------------------------------------------------------------*/
{
	id
		myObject;
	
	switch ((unsigned int)identifier)
		{
		case 0:
			if (myObject = [myDictionaryKeyEnumerator nextObject])
				{
				[aValue setStringValue:[myObject cString]];
				}
			else
				{
				[myDictionaryKeyEnumerator release];
				myDictionaryKeyEnumerator = nil;
				}
				
			break;
		
		case 1:
			if (myObject = [myDictionaryObjectEnumerator nextObject])
				{
				[aValue setStringValue:[[(NSObject *)myObject description] cString]];
				}
			else
				{
				[myDictionaryObjectEnumerator release];
				myDictionaryObjectEnumerator = nil;
				}
				
			break;
		}
	
	return self;
}



/*--------------------------------------------------------------------------
 *	Working as an NSString...
 *------------------------------------------------------------------------*/

- initWithString: (NSString *) theString
/*--------------------------------------------------------------------------
 *	Initializer for NSString object controllers.  Retains the incoming
 *	string.  Sets the text of the Text object to its contents.  Setup
 *	the window to display the string view.
 *------------------------------------------------------------------------*/
{
	[self init];

	theObject = [theString retain];
	[stringTextView setText:[theObject cString]];

	[self _setupWithView:[stringWindow contentView] type:NS_STRING];
	return self;
}


- dragSource: aDragSource
	didDropOnIconWell: sender
/*--------------------------------------------------------------------------
 *	Tells us that a new object dropped on our receive well, so let's
 *	put its "description" into the Text object...
 *------------------------------------------------------------------------*/
{
	[stringTextView setText:[[(NSObject *)[sender object] description] cString]];
	return self;
}


- textDidChange: sender
/*--------------------------------------------------------------------------
 *	To make sure we know when the contents has been changed
 *	Once they're done typing, let's set our string object's contents to be
 *	what is in the Text object.
 *------------------------------------------------------------------------*/
{
	[window setDocEdited:YES];
	return self;
}


- textDidEnd: sender
	endChar: (unsigned short) whyEnd
/*--------------------------------------------------------------------------
 *	To make sure we know when the contents has been changed
 *	Once they're done typing, let's set our string object's contents to be
 *	what is in the Text object.
 *------------------------------------------------------------------------*/
{
	NXStream
		*myStream = [stringTextView stream];
	char
		*myBuffer;
	int
		length;
	
	NXSeek (myStream, 0, NX_FROMEND);
	length = NXTell (myStream);
	NXSeek (myStream, 0, NX_FROMSTART);
	myBuffer = (char *) malloc (length * sizeof (char));
	NXRead (myStream, myBuffer, length);

	/*
	 * First release the old one (it's not mutable)
	 */
	if (theObject)
		{
		[theObject release];
		}
	
	/*
	 * Create a new one with the right data
	 */
	theObject = [[NSString alloc] initWithCString:myBuffer length:length];
	
	[objectWell setObject:theObject];
	NX_FREE (myBuffer);
	[window setDocEdited:YES];
	return self;
}


@end