// GSStackTrace inspired by  FYStackTrace.m created by Wim Oudshoorn on Mon 11-Apr-2006
// reworked by Lloyd Dupont @ NovaMind.com  on 4-May-2006

#import "StackTrace.h"
#import <Foundation/NSDebug.h>

@implementation GSFunctionInfo

- init
{
	[self release];
	return nil;
}
- initWithAddress:(void*)address 
             file:(NSString*)file 
         function:(NSString*)function 
             line:(int)lineNo
{
	_address = address;
	_fileName = [file retain];
	_functionName = [function retain];
	_lineNo = lineNo;

	return self;
}
- (oneway void) dealloc
{
	[_fileName release];
	_fileName = nil;

	[_functionName release];
	_functionName = nil;
}

- (void*) address       { return _address; }
- (int) lineNumber      { return _lineNo; }
- (NSString *) fileName { return _fileName; }
- (NSString *) function { return _functionName; }

- (NSString *) description
{
	return [NSString stringWithFormat:@"%p %@  %@:d",
		_address,
		_functionName,
		_fileName,
		_lineNo];
}

@end

@implementation GSBinaryFileInfo

+ (void) initialize
{
	static BOOL first = YES;
	if( !first )
		return;
	first = NO;

NSLog(@"initialize\n");
	bfd_init ();
}

- (oneway void) dealloc
{
	[_filename release];
	_filename = nil;
	if (_abfd)
		bfd_close (_abfd);
	_abfd = NULL;
	if (_symbols)
		free (_symbols);
	_symbols = NULL;
}

- init
{
    NSString *processName = [[[NSProcessInfo processInfo] arguments] objectAtIndex: 0];
	return [self initWithBinaryFile: processName];
}
- initWithBinaryFile: (NSString *)filename
{
	// 1st initialize the bfd
	if( !filename )
	{
		NSLog (@"GSBinaryFileInfo: No File");
		[self release];
		return nil;
	}
	_filename = [filename retain];
	_abfd = bfd_openr ([filename cString], NULL);
	if( !_abfd )
	{
		NSLog (@"GSBinaryFileInfo: No Binary Info");
		[self release];
		return nil;
	}
	if (!bfd_check_format_matches (_abfd, bfd_object, NULL))
	{
		NSLog (@"GSBinaryFileInfo: BFD format object error");
		[self release];
		return nil;
	}

	// second read the symbols from it
	if (!(bfd_get_file_flags (_abfd) & HAS_SYMS))
	{
		NSLog (@"GSBinaryFileInfo: BFD does not contain any symbols");
		[self release];
		return nil;
	}
	int neededSpace = bfd_get_symtab_upper_bound (_abfd);
	if (neededSpace < 0)
	{
		NSLog (@"GSBinaryFileInfo: BFD error while deducing needed space");
		[self release];
		return nil;
	}
	if (neededSpace == 0)
	{
		NSLog (@"GSBinaryFileInfo: BFD no space for symbols needed");
		[self release];
		return nil;
	}
	_symbols = malloc (neededSpace);
	if ( !_symbols )
	{
		NSLog (@"GSBinaryFileInfo: Can't malloc buffer");
		[self release];
		return nil;
	}
	_symbolCount = bfd_canonicalize_symtab (_abfd, _symbols);
	if (_symbolCount < 0)
	{
		NSLog (@"GSBinaryFileInfo: BFD error while reading symbols");
		[self release];
		return nil;
	}

	return self;
}
- (NSString *) filename { return _filename; }

struct SearchAddressStruct
{
	void* theAddress;
	asymbol **symbols;
	GSFunctionInfo* theInfo;
};

static void find_address (bfd *abfd, asection *section, struct SearchAddressStruct * info)
{
	if ( info->theInfo )
		return;

	if ( !(bfd_get_section_flags (abfd, section) & SEC_ALLOC) )
		return;

    bfd_vma address = (bfd_vma) info->theAddress;
    bfd_vma vma = bfd_get_section_vma (abfd, section);
    unsigned size = bfd_get_section_size (section);
    if (address < vma || address >= vma + size)
        return;

    const char *fileName;
    const char *functionName;
    int line = 0;
	if(	bfd_find_nearest_line (abfd, section, info->symbols, address - vma, &fileName, &functionName, &line))
	{
		GSFunctionInfo * fi = [[GSFunctionInfo alloc] initWithAddress: info->theAddress
		                                                         file: [[[NSString alloc] initWithCString: fileName] autorelease]
		                                                     function: [[[NSString alloc] initWithCString: functionName] autorelease]
		                                                         line: line];
		[fi autorelease];
		info->theInfo = fi;
	}
}

- (GSFunctionInfo *) functionForAddress: (void*) address
{
	struct SearchAddressStruct searchInfo = { address, _symbols, nil };
	bfd_map_over_sections (_abfd, (void (*) (bfd *, asection *, void *)) find_address, &searchInfo);
	return searchInfo.theInfo;
}

@end

static NSString * pathToLib(NSString * lib)
{
	NSString * dir = [NSSearchPathForDirectoriesInDomains(GSToolsDirectory, NSSystemDomainMask, YES) objectAtIndex: 0];
	lib = [dir stringByAppendingPathComponent: lib];

	if([[NSFileManager defaultManager] fileExistsAtPath: lib])
		return lib;
	return nil;
}
static NSMutableArray * binModules = nil;
static NSMutableArray * GetFctModules()
{
	if( !binModules )
	{
		binModules = [[NSMutableArray alloc] init];

		// add current process
		GSBinaryFileInfo * module = [[[GSBinaryFileInfo alloc] init] autorelease];
		[binModules addObject: module];

		module = [[[GSBinaryFileInfo alloc] initWithBinaryFile: pathToLib(@"gnustep-base.dll")] autorelease];
		if(module)
			[binModules addObject: module];

		module = [[[GSBinaryFileInfo alloc] initWithBinaryFile: pathToLib(@"gnustep-gui.dll")] autorelease];
		if(module)
			[binModules addObject: module];
	}
	return binModules;
}

@implementation GSStackTrace : NSObject
{
	NSMutableArray *frames;
}
// initialize stack trace info
+ (void) loadModule:(NSString *)filename
{
	GSBinaryFileInfo *module = [[[GSBinaryFileInfo alloc] initWithBinaryFile: filename] autorelease];
	if(module)
		[GetFctModules() addObject: module];
}

#define MAX_FRAME	50
// query the stack
+ (GSStackTrace*) currentStack
{
	NSMutableArray * stack = [[NSMutableArray alloc] init];

	NSMutableArray * modules = GetFctModules();
	int i, n = NSCountFrames();
	int j, m = [modules count];
	for(i=0; i<n && i<MAX_FRAME; i++)
	{
		//void* address = NSFrameAddress(i);
		void* address = NSReturnAddress(i);
		for(j=0; j<m; j++)
		{
			GSBinaryFileInfo * bfi = [modules objectAtIndex: j];
			GSFunctionInfo * aFrame = [bfi functionForAddress: address];
			if(aFrame)
			{
				[stack addObject: aFrame];
				break;
			}
		}
	}

	GSStackTrace * ret = [[[GSStackTrace alloc] init] autorelease];
	ret->frames = stack;
	return ret;
}

- (unsigned) frameCount                      { return [frames count]; }
- (GSFunctionInfo*) frameAt:(unsigned) index { return [frames objectAtIndex: index]; }
- (NSEnumerator*) enumerator                 { return [frames objectEnumerator]; }
- (NSEnumerator*) reverseEnumerator          { return [frames reverseObjectEnumerator]; }

- (NSString*) description
{
	NSMutableString *result = [NSMutableString string];
	int i, n = [frames count];
	for(i=0; i<n; i++)
	{
		GSFunctionInfo * line = [frames objectAtIndex: i];
		[result appendFormat: @"%3d: %@\n", i, line];
	}
	return result;
}

@end
