#import "UHInspectorManager.h"
#import "UHSelectView.h"
#import "UHInspector.h"
#import "uhInspection.h"	//inspection protocols
#import <objc/objc-runtime.h>

@interface UHInspectorManager(PrivateMethods)
- setupInspector;
- inspectSelection;
- getInspectorList;
- getInspectorFor:object;
- getObjectList;
- displayInspector;
- adjustPopup;
- adjustModeButton;
@end

/* additions to be made:
- removeInspectorFor:object;
- selectInspectorFor:object;  //?????
- selectLastItem:(BOOL)flag;
*/

@implementation UHInspectorManager

- init
{
	id bundle;
	char path[MAXPATHLEN+1];
	
	[super init];
	nameHash=[[HashTable alloc] initKeyDesc:"*"];
	
	/* Assumption is that the nib file is in the same bundle from which
		the inspector class was loaded.  This could be the main bundle,
		or it could be a dynamically loaded bundle. */
	bundle=[NXBundle bundleForClass:[self class]];
	[bundle getPath:path forResource:[self name] ofType:"nib"];
	[NXApp loadNibFile:path owner:self];
	
	/* Set up the shared panel(s). */
	[[window1 setFloatingPanel:YES] setHideOnDeactivate:YES];
	[[window2 setFloatingPanel:YES] setHideOnDeactivate:YES];
	[selectView1 makeFramed:NO];
	[selectView2 makeFramed:NO];
	[buttonSelectView1 makeFramed:NO];
	[buttonSelectView2 makeFramed:NO];
	[modeSelectView makeFramed:NO];
	
	objectList=[[List alloc] init];		//holds objects corresponding to an inspector
	references=[[List alloc] init];		//holds reference objects for the above objects
	inspectorList=[[List alloc] init];	//list of inspectors for one object selection
	selectionWasEmpty=YES;
	
	/* Various popup buttons and their menus. */
	objPopup = [objPopupButton target];
	objPopupCellList=[[objPopup itemList] cellList];
	refPopup = [refPopupButton target];
	refPopupCellList=[[refPopup itemList] cellList];
	singlePopup = [singlePopupButton target];
	singlePopupCellList=[[singlePopup itemList] cellList];
	
	inspectionMode=OBJECT_MODE;
	isDoubleInspectorEnabled=NO;
	[self setupInspector];	//some initialization

	return self;
}

/* To inspect a single object, without specifying the requestor. */
- inspect:anObject
{
	return [self inspect:anObject requestor:nil];
}

/* Inspect a single object, requestor gets notification of inspection. */
- inspect:anObject requestor:aRequestor
{
	listMode=NO;				//so the delegate will be correspondingly notified
	if(!singleList)singleList=[[List alloc]init];  //must make my own list of one
	[[singleList empty] addObject:anObject];
	[references empty];
	if([anObject respondsTo:@selector(reference)])
		[references addObject:[anObject reference]];
	objects=singleList;		//the list of objects to be inspected (a single item here!)
	requestor=aRequestor;
	[self inspectSelection];	//go inspect this selection
	return self;
}

/* To inspect an object list, without specifying the requestor. */
- inspectList:aList
{
	return [self inspectList:aList requestor:nil];
}

/* Inspect an object list, requestor gets notification of inspection. */
- inspectList:aList requestor:aRequestor
{
	int i;
	listMode=YES;				//so the delegate will be correspondingly notified
	requestor=aRequestor;
	objects=aList;	//the list of objects to be inspected
	[references empty];
	for(i=0; i<[objects count]; i++)
		if([[objects objectAt:i] respondsTo:@selector(reference)])
			[references addObject:[[objects objectAt:i] reference]];
	[self inspectSelection];	//go inspect this selection
	return self;
}

/* Common starting point to inspect a selection list or single item put into a list. */
- inspectSelection			//private method
{
	[window disableDisplay];	//in case the popuplist gets resized and repositioned
	selection = inspectionMode==OBJECT_MODE ? objects : references ;
	
	/* First, go get the list of inspectors corresponding to the objects in this selection. */
	[self getInspectorList];

	/* If selection is empty, no inspector. */
	if([selection count]==0){
		selectionWasEmpty=YES;
		currentInspector=nil;
	}
	else {
		/* If selection was previously empty, use inspector for first item in list. */
		if(selectionWasEmpty){
			currentInspector=[inspectorList objectAt:0];	//get first inspector
			selectionWasEmpty=NO;
		}
		/* If selection is simply modified, use same inspector if it is still in list. */
		else {
			/* If current inspector is no longer in inspector list, get a new one. */
			if(NX_NOT_IN_LIST==[inspectorList indexOf:currentInspector])
				currentInspector=[inspectorList objectAt:0];	//get first inspector
		}
	}

	/* The list of objects for this inspector is ready.  Time to display the inspector. */
	[self displayInspector];
	[[window reenableDisplay] display];	//popup is all squared away, can now display

	/* There may be a delegate that wants to know that the inspector panel may have changed. */
	if(delegate && [delegate respondsTo:@selector(inspectorChanged:)])
		[delegate inspectorChanged:self];
	return self;
}

/* Returns the current inspector selected by the popup list. */
- currentInspector {return currentInspector;}

/* Returns the current label on the popup list. */
- (const char *)currentInspectorLabel {return currentInspectorLabel;}

/* Fill inspectorList with the inspectors corresponding to the selection of objects. 
	At this point, the objects list may be the principal objects, or their reference objects. */
- getInspectorList			//private method
{
	int i;
	id inspector;
	id cell;
	id object;
	
	[inspectorList empty];
	
	/* Get list of inspectors for all the objects in the list. */
	for(i=0; i<[selection count]; i++){
		object=[selection objectAt:i];					//get next object in the selection
		if(inspector=[self getInspectorFor:object])
			[inspectorList addObjectIfAbsent:inspector];	//add its inspector to inspector list
	}
	
	/* Now enable only those popuplist cells corresponding to these inspectors. */
	for(i=0; i<[popupCellList count]; i++){
		cell=[popupCellList objectAt:i];
		if(NX_NOT_IN_LIST==[inspectorList indexOf:(id)[cell tag]])
			[cell setEnabled:NO];
		else [cell setEnabled:YES];
	}
	
	/* If no inspectors, enable "None" at the top. */
	if([inspectorList count]==0)[[popupCellList objectAt:0] setEnabled:YES];
	
	return self;
}

/* On entry to this method, "object" is already either the OBJECT or its REFERENCE. */
- getInspectorFor:object			//private method
{
	const char *inspectorLabel;
	const char *inspectorClassName;
	char *localInspectorClassName=NULL;
	id inspector;
	id inspectorClass;

	if(object==nil)return nil;		//nil inspector for nil object
	
	/* This method manages a hashTable.  The items in the hashTable are the inspectors, and
		the keys into the hastTable are the corresponding inspector labels.  Inspector labels are
		determined in this order:
			1.  from getLabelForInspector
			2.  from object's class name
		For example, a UHAxisInspector instance might be in the table, and the key to find it would be
		the label returned by the X-axis instance, "X-Axis", or if that method is not implemented, the
		name of the object's class, i.e., "UHAxis".  Thus, we ask any instance to be inspected
		its inspector label, or class name, then use that string to find the corresponding 
		inspector instance. */
	
	if([object respondsTo:@selector(getLabelForInspector)])
		inspectorLabel=[object getLabelForInspector];
	else 
		inspectorLabel=[object name];			//get object's class name
	
	/* If this inspector is already in the hashTable, just return it. */
	if( (inspector=(id)[nameHash valueForKey:inspectorLabel]) ) return inspector;  //RETURN
	
	/* If we arrive here, the inspector for this object is not in the hashTable. */
	/* Since this is a new class of object, register its inspector. */
	
	/* First, ask the object the inspector class name. */
	if([object respondsTo:@selector(getInspectorClassName)]){
		inspectorClassName=[object getInspectorClassName];	//get inspector's class name
	}
	else {				  //won't give me the name, so I will create one
		localInspectorClassName=(char *)NXZoneMalloc([self zone],(strlen([object name])+10));
		strcpy(localInspectorClassName,[object name]);
		strcat(localInspectorClassName,"Inspector");
		inspectorClassName=localInspectorClassName;
	}
	
	/* We now have some sort of inspector class name, so look for that inspector. */	
		inspectorClass=objc_lookUpClass(inspectorClassName);		//get the class object for this name
		
	/* If the class object is nil (not found), try loading it from a bundle in a std directory. */
	if(!inspectorClass){
		char path[MAXPATHLEN+1];
		id inspectorBundle;
		strcpy(path,[[NXBundle mainBundle] directory]);		//get path to the .app directory
		strcat(path,"/UHInspectors/");				//make path to /UHInspectors subdirectory
		strcat(path,inspectorClassName);
		strcat(path,".bundle");					//make path to this inspector bundle
		inspectorBundle=[[NXBundle alloc] initForDirectory:path];
		if(inspectorBundle)
				inspectorClass=[inspectorBundle principalClass];	//get the class object for this name
	}
	if(localInspectorClassName)NXZoneFree([self zone],localInspectorClassName);  //let's free that space

	/* If we finally found an inspector class object, instantiate an inspector and store it. */
	if(inspectorClass){
		inspector=[[inspectorClass alloc] init];	//instantiate the inspector
		
		/* Put inspector into hashTable.  Key is the inspector label found above. */
		[nameHash insertKey:inspectorLabel value:inspector];
			
		/* In the init, the inspector should have loaded its nib.  Add the panel's view to the selectView. 
			Use the inspector's id as the index to find that view again (why not?). */
		[selectView setView:[inspector inspectorView] number:(int)inspector];

		/* Use the inspector label on the popup list. */
		[popup addItem:inspectorLabel];
			
		/* Set all the attributes of the new cell in the popuplist. */
		[[[[popupCellList lastObject]	setTag:(int)inspector]
											setTarget:self]
											setAction:@selector(getInspectorFrom:)];
		/* Now resize and reposition the popup. */
		[self adjustPopup];
		
		return inspector;  //RETURN
	}

	return nil;		//if fell through to here, an inspector was never found (for one reason or another)
}
/* This simply gathers all the code to resize and reposition! */
- adjustPopup			//private method
{
	NXRect viewFrame,buttonFrame;
	NXSize cellSize;
	
	/* Now great grief to resize and reposition! */
	[popup sizeToFit];			//resize menu to fit all cells
	[[popup itemList] getCellSize:&cellSize];	//get new size of the menu's matrix
	[popupButton sizeTo:(cellSize.width+2.) :cellSize.height];  //resize popup button
	[popupButton getFrame:&buttonFrame];			//get current button's location
	[[popupButton superview] getFrame:&viewFrame];		//get frame of button's superview
	[popupButton moveTo:(viewFrame.size.width/2.-buttonFrame.size.width/2.)
						:(buttonFrame.origin.y)];
	[popupButton display];
	return self;
}

/* Get a list of items which can be inspected by the inspector currently showing in the panel. */
- getObjectList			//private method
{
	int i;
	id item;
	
	[objectList empty];
	if(!currentInspector)return self;
	
	for(i=0; i<[selection count]; i++){
		item=[selection objectAt:i];
		/* If item's inspector matches current inspector, add item to the object list. */
		if([self getInspectorFor:item]==currentInspector)[objectList addObject:item];
	}
	return self;
}

/* Switch in the chosen inspector panel's view, and set the popup to correspond. */
- displayInspector			//private method
{	
	/* First collect the sublist of objects from the selection for the current inspector. */
	[self getObjectList];
	
	/* We can now call up the inspector. */
	if(currentInspector){
		if(listMode)
			[currentInspector inspectList:objectList requestor:requestor];	//tell it the objects to inspect
		else
			[currentInspector inspect:[objectList objectAt:0] requestor:requestor];	//tell it the object to inspect
		[selectView selectViewNumber:(int)currentInspector];  //select its inspector view
		[popupButton setTitle:[[[popup itemList] findCellWithTag:(int)currentInspector] title]];
		currentInspectorLabel=[popupButton title];		//in case anyone asks for this
		[[popup itemList] selectCellWithTag:(int)currentInspector];
		if([currentInspector wantsButtons])[buttonSelectView selectViewNumber:1];
		else [buttonSelectView selectViewNumber:0];
	}
	else {
		[selectView selectViewNumber:(-1)];		//no inspector, say so on inspector panel
		[popupButton setTitle:"None"];
		currentInspectorLabel="None";
		[[popup itemList] selectCellAt:0 :0];	//select "None" cell at top
		[buttonSelectView selectViewNumber:0];	//if no inspector, certainly no buttons
	}
	return self;
}

- doRevertButton:sender
{
	return [currentInspector revert:sender];
}

- doOkButton:sender
{
	return [currentInspector ok:sender];
}

/* Select a particular inspector in the popup list, if there are corresponding objects in the selection. */
/*  ????????  */
- selectInspectorFor:object
{
	currentInspector=[self getInspectorFor:object];
	[self displayInspector];		//display that inspector
	if(delegate && [delegate respondsTo:@selector(inspectorChanged:)])
		[delegate inspectorChanged:self];
	return self;
}
	
/* The popup list connects to this and selects a new inspector. */
- getInspectorFrom:sender
{
	currentInspector=(id)[sender selectedTag];	//new inspector chosen by popuplist
	[self displayInspector];		//display that inspector
	if(delegate && [delegate respondsTo:@selector(inspectorChanged:)])
		[delegate inspectorChanged:self];
	return self;
}

/* Order the appropriate panel forward. */
- showInspector:sender
{
	if([self isDoubleInspectorEnabled])
		[window2 makeKeyAndOrderFront:self];
	else
		[window1 makeKeyAndOrderFront:self];
	return self;
}

/* Set flag to enable/disable the double inspector mode. */
- enableDoubleInspector:(BOOL)flag
{
	isDoubleInspectorEnabled=flag;
	[self setupInspector];
	return self;
}

- (BOOL)isDoubleInspectorEnabled {return isDoubleInspectorEnabled; }

/* Set the mode button title for the double inspector. */
- setButtonTitleForObjects:(char *)aString
{
	[modeButton setTitle:aString];
	[self adjustModeButton];
	return self;
}

/* Set the mode button alternate title for the double inspector. */
- setButtonTitleForReferences:(char *)aString
{
	[modeButton setAltTitle:aString];
	[self adjustModeButton];
	return self;
}

/* Simply gathers the code for resizing and repositioning the mode button. */
- adjustModeButton			//private method
{
	NXRect viewFrame,buttonFrame;
	[modeButton sizeToFit];
	[modeButton getFrame:&buttonFrame];			//get current button's location
	[[modeButton superview] getFrame:&viewFrame];		//get frame of button's superview
	[modeButton moveTo:(viewFrame.size.width/2.-buttonFrame.size.width/2.)
						:(buttonFrame.origin.y)];
	[[modeButton window] display];
	return self;
}

/* Selects double or single inspection mode. */
- setInspectionMode:(int)mode
{
	inspectionMode=mode;
	[self setupInspector];
	return self;
}

- (int)inspectionMode {return inspectionMode;}

/* Invoked by the init method to setup stuff on the panel. */
- setupInspector			//private method
{
	if([self isDoubleInspectorEnabled]){
		selectView=selectView2;
		buttonSelectView=buttonSelectView2;
		window=window2;
		if(inspectionMode==OBJECT_MODE){
			popup=objPopup;
			popupButton=objPopupButton;
			popupCellList=objPopupCellList;
		}
		else {
			popup=refPopup;
			popupButton=refPopupButton;
			popupCellList=refPopupCellList;
		}
		[modeSelectView selectViewNumber:inspectionMode];  //select correct popuplist
		if([modeButton state] != inspectionMode) [modeButton setState:inspectionMode];
	}
	else {
		selectView=selectView1;
		buttonSelectView=buttonSelectView1;
		window=window1;
		popup=singlePopup;
		popupButton=singlePopupButton;
		popupCellList=singlePopupCellList;
	}
	
	[self inspectSelection];
	return self;
}

/* The mode button on the panel is connected to this. */
- takeInspectionModeFrom:sender
{
	[self setInspectionMode:[sender state]];
	return self;
}

/* Can choose a delegate to be notified when the inspector changes. */
- setDelegate:anObject
{
	delegate=anObject;
	return self;
}
- delegate {return delegate;}

/* Returns the selectView in the single inspector mode panel.  Why?  Well, you might want
	to use it as the inspector view in yet another inspector mangager! */ 
- singleInspectorView {return window1InspectorView;}

/* Returns the window for the single inspector mode. */
- singleWindow {return window1;}

/* Returns the window for the double inspector mode. */
- doubleWindow {return window2;}

/* Returns the selection of objects currently being inspected. */
- selection {return selection;}

@end	    
     