/*	NibList - A program to list the contents of nib files.
    Copyright (C) 1991  Oceania Healthcare Systems

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 1, or (at your option)
    any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program, in a file called COPYING; if not, write to
	the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/*  This is where the action is.  A few of the methods in this file are copied from
 * NeXT's very helpful TextLab example.  The NibFile object is responsible for parsing
 * and printing the contents of a nib file in its own window.
 */
 
#import <appkit/appkit.h>
#import <objc/List.h>
#import <objc/Storage.h>
#import <objc/StreamTable.h>
#import <objc/typedstream.h>
#import	<string.h>
#import "libnib.h"
#import "Categories.h"
#import "NibFile.h"
#import <zone.h>
#import <mach.h>

char	cbuff[1024];

@implementation NibFile

- initFrame:(const NXRect *)frameRect
{
    NXRect rect = {0.0, 0.0, 0.0, 0.0};
	
    /* initialize view */
    [super initFrame:frameRect];
    
    /* specify scrollbars */
    [[self setVertScrollerRequired:YES] setHorizScrollerRequired:NO];

    [self getContentSize:&(rect.size)];
    mainText = [self newText:&rect];
    [self setDocView:mainText];
    [mainText setSel:0 :0];	
    
    // The following two lines allow the resizing of the scrollview
    // to be passed down to the docView (in this case, the text view 
    // itself).

    [contentView setAutoresizeSubviews:YES];
    [contentView setAutosizing:NX_HEIGHTSIZABLE | NX_WIDTHSIZABLE];

    // Create enclosing   and bring it upfront
    [self makeEnclosingWindow:frameRect];
    [[[window setTitle:"Untitled"] display] makeKeyAndOrderFront:self];
    	
    return self;
}

- initFile:(const char *)fileName
{
#define ORIGX 80.0
#define ORIGY 300.0
	static NXRect wRect = {{ORIGX, ORIGY},{700.0,500.0}};
	id	theText = mainText;
	int	textMark = [theText textLength];
	NXTypedStream	*inStream;

    NXOffsetRect(&wRect, 25.0, -25.0);
    if (wRect.origin.y < 0) {
		wRect.origin.y = ORIGY;
		wRect.origin.x = ORIGX;
    }
	
	[self initFrame:&wRect];
	
	[theText setSel: 0 : textMark];
    [theText replaceSel:""];
	
	[[mainText window] setTitle:"Loading..."];
	if( (inStream = NXOpenTypedStreamForFile( fileName, NX_READONLY )) == NULL )
	{
		[self print:"Error opening "];
		[self print:fileName];
		[self print:"\n"];
		return NO;
	}
	
	NXSetTypedStreamZone(inStream, [self zone]);
	table = NXReadObject( inStream );
	runData = [table valueForStreamKey:(void *)NIBFILE_RUNTIME_DATA];
	nibDataInst = [runData valueForKey:(void *)NIBRUNTIME_NIBDATA];
	objects = [nibDataInst objectList];
	object_getInstanceVariable( nibDataInst, "connectList", & (void *)connects );
	icons = [runData valueForKey:(void *)NIBRUNTIME_ICONS];
	sounds = [runData valueForKey:(void *)NIBRUNTIME_SOUNDS];
	
	[[mainText window] setTitle:"Analyzing..."];
	[self print:"NIBFileName: "];
	[self print:fileName];
	[self print:"\n"];
	sprintf(cbuff, "Contains %d objects with %d connections,\n",
							[objects count], [connects count]); 
	[self print:cbuff];
	sprintf(cbuff, "%d icons, and %d sounds.", [icons count], [sounds count]);
	[self print:cbuff];
	
	[self buildHash];
	
	[[mainText window] disableFlushWindow];
	[self print:"\n\n-----------OBJECTS------------\n"];
	[self dumpNibObjectFromTree:tree];
	[self print:"\n\n-----------ICONS------------\n"];
	[self dumpRootObj:icons];
	[self print:"\n\n-----------SOUNDS------------\n"];
	[self dumpRootObj:sounds];
	[self print:"" withFlush:YES];
	[[mainText window] reenableFlushWindow];
	[[mainText window] flushWindow];
	
	if( !NXEndOfTypedStream( inStream ) )
		[self print:"WHOAAA! There's more in the typed stream!\n\n\n"];

	NXCloseTypedStream( inStream);
	[[mainText window] setTitleAsFilename:fileName];
	[[mainText window] makeKeyAndOrderFront:self];
	
	return self;
}

- makeEnclosingWindow:(const NXRect *)rect
{
    id	  textWindow;
    
    textWindow = [[Window allocFromZone:[self zone]] initContent:rect
				 style:NX_TITLEDSTYLE
				 backing:NX_BUFFERED
				 buttonMask:NX_ALLBUTTONS
				 defer:NO];
    [[textWindow setContentView:self] free];
    [textWindow setBackgroundGray:NX_WHITE];
    [textWindow setFreeWhenClosed:YES];
	[textWindow setDelegate:self];
    
    return self;
}
    
- newText:(const NXRect *)frameRect
{
    id	text;
    NXSize aSize = {1.0E38, 1.0E38};
    
    text = [[Text allocFromZone:[self zone]] initFrame:frameRect
			text:NULL
			alignment:NX_LEFTALIGNED];
    [text setOpaque:YES];
    [[[[[[text notifyAncestorWhenFrameChanged:YES]
			setVertResizable:YES]
			setHorizResizable:NO]
			setMonoFont:NO]
			setEditable:NO]
			setDelegate:self];
    
    [text setMinSize:&(frameRect->size)];
    [text setMaxSize:&aSize];
    [text setAutosizing:NX_HEIGHTSIZABLE | NX_WIDTHSIZABLE];
    
    [text setCharFilter:NXEditorFilter];
    return text;
}

/*========================== Delegate methods from Window ====================================*/
/* This will be a routine to free all the data malloc'ed and alloc'ed by the NibFile object.  */
- windowWillClose:sender
{
	return self;
}


/* The following routines make output to the text object quick, easy and efficient.
 * A 1K buffer is kept of text going out to the text object.  This reduces the 
 * number of calls to replaceSel:, which seems to be pretty slow when dealing
 * with lot of little chunks of text.  The addition of the buffer sped up the
 * application by about 10X overall.
 */
- print:(const char *)output withFlush:(BOOL)flag
{
	#define BSIZE	(1024)
	
	static int	i = 0;
	static char	buff[BSIZE];
	id	theText;
	int	textMark, len;
	
	if( ((len = strlen(output)) + i >= BSIZE -1) || flag )
	{
		buff[i] = '\0';
		theText = mainText;
		textMark = [theText textLength];
		[theText setSel: textMark : textMark];
		[theText replaceSel:buff];
		i = 0;
	}
	strcpy( &(buff[i]), output );
	i += len;
	return self;
}

- print:(const char *)output
{
	if( output )
		return [self print:output withFlush:NO];
	else
		return [self print:"<<NULL pointer>>" withFlush:NO];
}

- printTab:(const char *)output
{
	int i;
	
	for( i = 0;  i < numTabs;  i++ )
		[self print:"    "];
	[self print:output];
	return self;
}

- enTab
{
	numTabs++;
	return self;
}

- unTab
{
	numTabs--;
	return self;
}


/* This routine cross-indexes the data from the nib file in a number of ways.
 * A tree of ownership relations is built, which facilitates the heirarchical
 * format of the output.  In order to move between the tree and the objects
 * themselves, three hash tables are built.
 */
- buildHash;
{
	struct	nibObj	*elem;  /*These structs defined in libnib.h */
	struct	nibCon	*celem;
	
	unsigned	count = [objects count], i;
	unsigned	ccount = [connects count];
	List	*curList, *parentList;
	Storage	*curCons;
	
	tree = [[List allocFromZone:[self zone]] init];
	treeByObj = [[HashTable allocFromZone:[self zone]] initKeyDesc:"@" valueDesc:"@"];
	nameByObj = [[HashTable allocFromZone:[self zone]] initKeyDesc:"@" valueDesc:"*"];
	connByObj = [[HashTable allocFromZone:[self zone]] initKeyDesc:"@" valueDesc:"@"];
	
	elem = [objects elementAt:0];
	[nameByObj insertKey:(elem->obj) value:(char *)(elem->name)];
	[tree addObject:elem->obj];
	[treeByObj insertKey:elem->obj value:tree];
	
	for( i = 1;  i < count;  i++ )
	{
		elem = [objects elementAt:i];
		[nameByObj insertKey:(elem->obj) value:(char *)(elem->name)];
		
		curList = [[List allocFromZone:[self zone]] init];
		[curList addObject:elem->obj];
		[treeByObj insertKey:elem->obj value:curList];
		
		parentList = [treeByObj valueForKey:elem->parent];
		[parentList addObject:curList];
	}

	for( i = 0;  i < ccount;  i++ )
	{
		celem = [connects elementAt:i];
		curCons = [connByObj valueForKey:celem->from];
		if( curCons == nil )
		{
			curCons = [[Storage allocFromZone:[self zone]]
							 initCount:0 elementSize:sizeof(struct nibCon)
									description: @encode(struct nibCon)];
			[connByObj insertKey:celem->from value:curCons];
		}
		[curCons addElement:celem];
	}
	
	return self;
}


/* This routine prints just enough information about an object to
 * identify it uniquely.
 */
- summarizeObj:target
{
	const char *c;
	
	if( target )
	{
		[self print:"<"];
		if( (c = [nameByObj valueForKey:target]) )
			[self print:c];
		[self print:">"];
		if( [target respondsTo:@selector(className)] )
		{
			[self print:[target className]];
		}
		sprintf( cbuff, "(%08x)", (int)target );
		[self print:cbuff];
	}
	else
		[self print:"nil"];
	return self;
}

/* This routine recursively prints all the objects in the ownership
 * tree.  It is called once for the root object by the initFile: method
 */
- dumpNibObjectFromTree:target
{
	unsigned	count = [target count], i;
	id	elem;
	
	elem = [target objectAt:0];
	[self printTab:""];
	[self summarizeObj:elem];
	[self dumpObj:elem];
	[self print:"\n"];
		numTabs++;
		[self dumpNibConnectionsforObject:elem];
		if( count > 1 )
		{
			[self printTab:"Owns:\n"];
			numTabs++;
			for( i = 1;  i < count;  i++ )
			{
				[self dumpNibObjectFromTree:[target objectAt:i]];
			}
			numTabs--;
		}
		numTabs--;

	return self;
}

/* This routine is called by dumpNibObjectFromTree:.  It lists all
 * connections FROM the object it is passed.
 */
- dumpNibConnectionsforObject:anObject
{
	struct	nibCon	*elem;  /* This struct defined in libnib.h */
	unsigned	count, i;
	Storage	*curCons;

	curCons = [connByObj valueForKey:anObject];
	if( curCons != nil )
	{
		[self printTab:"Connections:\n"];
		numTabs++;
		count = [curCons count];
		for( i = 0;  i < count;  i++ )
		{
			elem = (struct nibCon *)[curCons elementAt:i];
			[self printTab:""];
			switch( elem->type )
			{
				case NIBDATA_CONNECT_TARG:
					[self print:"target to action "];
					[self print:elem->name];
					[self print:" of "];
					[self summarizeObj:elem->to];
					break;
				case NIBDATA_CONNECT_OUTLET:
					[self print:"outlet "];
					[self print:elem->name];
					[self print:" to "];
					[self summarizeObj:elem->to];
					break;
				default:
					[self print:"****** Unknown connection type ******\n"];
					break;
			}
			[self print:"\n"];
		}
		numTabs--;
	}
	return self;
}

- dumpRootObj:target
{
	numTabs = 0;
	[self dumpObj:target];
	return self;
}

- dumpObj:target
{	
	if( !target )
	{
		[self printTab:"Object: NULL\n"];
		return self;
	}
	
	numTabs++;
	[target dump:self];
	numTabs--;
	
	return self;
}


@end
