#import "NGSTemplateWindowController.h"
#import "NGSElement.h"
#import <stdarg.h>

@implementation TemplateWindowController

- (id)initWithResource:(id)newResource
{
	return [self initWithResources:newResource, nil];
}

- (id)initWithResources:(id)newResource, ...
{
	id currentResource;
	va_list resourceList;
	
	// one instance of your principal class will be created for every resource set the user wants to edit (similar to Windows apps)
	self = [self initWithWindowNibName:@"TemplateWindow"];
	if(!self) return self;
	
	resource = [newResource retain];
	tmpl = [[NSMutableArray alloc] init];
	res = [[NSMutableArray alloc] init];
	
	va_start(resourceList, newResource);
	[self readTemplate:va_arg(resourceList, id)];	// reads (but doesn't retain) the template for this resource (the TMPL resource with a name equal to the passed resource's type)
	while(currentResource = va_arg(resourceList, id))
		NSLog(@"too many params passed to -initWithResources: %@", [currentResource description]);
	va_end(resourceList);
	
	NSString *warning = [self checkTemplate];
	if(warning != nil)
	{
		// should display error message here
		NSLog(warning);
		return nil;
	}
	
	// load the window from the nib
	[self window];
	return self;
}

- (void)dealloc
{
	[[NSNotificationCenter defaultCenter] removeObserver:self];
	[(id)resource release];
	[tmpl release];
	[res release];
	[super dealloc];
}

- (void)windowDidLoad
{
	[super windowDidLoad];

	// set the window's title
	[[self window] setTitle:[resource defaultWindowTitle]];
	
	// parse data using pre-scanned template and create the fields as needed
	[self readData];
	[self createUI];
	
	// insert the resources' data into the text fields
	[self refreshData:[resource data]];
	
	// we don't want this notification until we have a window! (Only register for notifications on the resource we're editing)
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resourceDataDidChange:) name:ResourceDataDidChangeNotification object:resource];
	
	// finally, show the window
	[self showWindow:self];
}

- (void)readTemplate:(id <ResKnifeResourceProtocol>)tmplResource
{
	if(tmplResource)
	{
		NSString *label, *type;
		char *currentByte = (char *) [[tmplResource data] bytes];
		unsigned long size = [[tmplResource data] length], position = 0;
		while(position < size)
		{
			// check where pointer will be AFTER having loaded this element
			position += *currentByte +5;
			if(position >= size)
			{
				NSLog(@"Corrupt TMPL resource: not enough data. Dumping remaining resource as hex.");
				[tmpl addObject:[Element elementOfType:@"HEXD" withLabel:NSLocalizedString(@"Corrupted Template: Hex Dump",nil)]];
				break;
			}
			
			// obtain label and type
			label = [[NSString alloc] initWithBytes:currentByte+1 length:*currentByte encoding:NSMacOSRomanStringEncoding];
			currentByte += *currentByte +1;
			type = [[NSString alloc] initWithBytes:currentByte length:4 encoding:NSMacOSRomanStringEncoding];
			currentByte += 4;
			
			// add element to array
			[tmpl addObject:[Element elementOfType:type withLabel:label]];
		}
	}
	else
	{
		// for some reason we got a nill object as the TMPL, do the best we can
		NSLog(@"Corrupt TMPL resource: no template given. Dumping resource as hex.");
		[tmpl addObject:[Element elementOfType:@"HEXD" withLabel:NSLocalizedString(@"Corrupted Template: Hex Dump",nil)]];
	}
}

- (NSString *)checkTemplate
{
	NSSet *recognised = [NSSet setWithObjects:nil];
	NSMutableSet *fields = [NSMutableSet set];
	NSEnumerator *enumerator = [tmpl objectEnumerator];
	while(Element *element = [enumerator nextObject])
		[fields addObject:[element type]];	// doesn't take into account Pxxx, Cxxx and Hxxx
	NSSet *unrecognised = [fields copy];
	[unrecognised minusSet:recognised];
	if([unrecognised count] > 0)
		return [NSString stringWithFormat:NSLocalizedString(@"The template contains the following unrecognised field types: '%@'",nil), [[unrecognised allObjects] componentsJoinedByString:@"', '"]];
	else return nil;
}

- (void)readData
{
	// tmpl == array of Elements describing the template for this resource
	// res == array of either Elements or Arrays containing the resource data
	
	// this function creates the res instance variable, filling it with data from the resource if there's data available.
	
//	unsigned long position = 0;							// position (byte offset) in resource I'm currently reading from
//	char *data = (char *) [[resource data] bytes];		// address of initial byte of resource in memory
	
	unsigned long templateCounter = 0;										// index into template array of the current template element
	Element *currentTemplateElement;										// current template element
	NSMutableArray *targetStack = [NSMutableArray arrayWithObject:res];		// stack of arrays (target for addition of new elements)
//	NSMutableArray *loopStack = [NSMutableArray array];						// stack for 'LSTB' and 'LSTC' elements
//	NSMutableArray *loopCountStack = [NSMutableArray array];				// stack for counting how many times to loop
	
	// when templateCounter >= [tmpl count], loop is only exited if targetStack has more than one target (this handles empty templates)
	while(templateCounter < [tmpl count] || [targetStack count] > 1)
	{
		currentTemplateElement = [tmpl objectAtIndex:templateCounter];
		NSLog(@"template = %@", currentTemplateElement);
/*		unsigned long type = [currentTemplateElement typeAsLong];
		switch(type)
		{
			case 'BCNT':
			case 'BZCT':
				[resourceElement setNumberWithLong:*(unsigned char *)(data + position)];
				lim = *(unsigned char *)(data + position) + (type == 'BZCT'? 1:0);
				position += 1;
				break;
			case 'OCNT':
			case 'ZCNT':
				[resourceElement setNumberWithLong:*(unsigned short *)(data + position)];
				lim = *(unsigned short *)(data + position) + (type == 'ZCNT'? 1:0);
				position += 2;
				break;
			case 'LCNT':
			case 'LZCT':
				[resourceElement setNumberWithLong:*(unsigned long *)(data + position)];
				lim = *(unsigned long *)(data + position) + (type == 'LZCT'? 1:0);
				position += 4;
				break;
			
			case 'LSTB':
			case 'LSTC':
				[(NSMutableArray *)[targetStack lastObject] addObject:[NSMutableArray array]];
				[loopStack addObject:currentTemplateElement];	// append the template loop start object to the array
				break;
			
			default:
				
//				[(NSMutableArray *)[targetStack lastObject] addItem:[self createElementForTemplate:currentTemplateElement
		}
*/		
		templateCounter++;
	}
}

- (void)parseData
{
	unsigned long position = 0;
	char *data = (char *) [[resource data] bytes];
	
	// used for nesting of elements, 'target' is current object to append to, targetStack is a FILO stack of mutable array pointers, loopStack is a stack of element indicies to the start of loops, so I can go back to the head of a loop when iterating it
	NSMutableArray *target = res;
	NSMutableArray *targetStack = [NSMutableArray arrayWithObject:res];
//	NSMutableArray *loopStack = [NSMutableArray array];
	
	// n = current item in TMPL to read, c = loop counter, when exiting loop, go back 'c' items in the template, lim is how many times to loop, obtained from a loop count
	unsigned long n = 0, c = 0, lim = 0;
	
	// creates an array of elements containing the data in whatever format the template dictates
	//	array can then simply be manipulated one element at a time, or flattened to save
	Element *currentTemplateElement, *resourceElement;
//	NSEnumerator *enumerator = [tmpl objectEnumerator];
//	while(currentTemplateElement = [enumerator nextObject])
	while(position < [[resource size] unsignedLongValue])
	{
		unsigned long type;
		currentTemplateElement = [tmpl objectAtIndex:n];
		type = [currentTemplateElement typeAsLong];
		resourceElement = [[currentTemplateElement copy] autorelease];
		NSLog(@"tmpl element = %@; position = %d", currentTemplateElement, position);
		
		n++, c++;
		switch(type)
		{
			/* Alignment */
			case 'AWRD':
				position += position % 2;
				break;
			case 'ALNG':
				position += position % 4;
				break;
			
			/* Fillers */
			case 'FBYT':
				position += 1;
				break;
			case 'FWRD':
				position += 2;
				break;
			case 'FLNG':
				position += 4;
				break;
			
			/* Decimal */
			case 'DBYT':
				[resourceElement setNumberWithLong:*(char *)(data + position)];
				position += 1;
				break;
			case 'DWRD':
				[resourceElement setNumberWithLong:*(short *)(data + position)];
				position += 2;
				break;
			case 'DLNG':
				[resourceElement setNumberWithLong:*(long *)(data + position)];
				position += 4;
				break;
			
			/* Hex */
			case 'HBYT':
				[resourceElement setData:[NSData dataWithBytes:(void *)(data + position) length:1]];
				position += 1;
				break;
			case 'HWRD':
				[resourceElement setData:[NSData dataWithBytes:(void *)(data + position) length:2]];
				position += 2;
				break;
			case 'HLNG':
				[resourceElement setData:[NSData dataWithBytes:(void *)(data + position) length:4]];
				position += 4;
				break;
			case 'HEXD':
				// bug: doesn't check HEXD is the last element
				[resourceElement setData:[NSData dataWithBytes:(void *)(data + position) length:([[resource size] intValue] - position)]];
				position = [[resource size] intValue];
				break;
				
			/* Strings */
			case 'CHAR':
				[resourceElement setString:[[NSString alloc] initWithData:[NSData dataWithBytes:(void *)(data + position) length:1] encoding:NSMacOSRomanStringEncoding]];
				position += 1;
				break;
			case 'TNAM':
				[resourceElement setString:[[NSString alloc] initWithData:[NSData dataWithBytes:(void *)(data + position) length:4] encoding:NSMacOSRomanStringEncoding]];
				position += 4;
				break;
			case 'PSTR':
				[resourceElement setString:[[NSString alloc] initWithData:[NSData dataWithBytes:(void *)(data + position + 1) length:*(unsigned char *)(data + position)] encoding:NSMacOSRomanStringEncoding]];
				position += *(unsigned char *)(data + position) + 1;
				break;
			
			/* List Counts */
			case 'BCNT':
			case 'BZCT':
				[resourceElement setNumberWithLong:*(unsigned char *)(data + position)];
				lim = *(unsigned char *)(data + position) + (type == 'BZCT'? 1:0);
				position += 1;
				break;
			case 'OCNT':
			case 'ZCNT':
			case 'WCNT':
			case 'WZCT':
				[resourceElement setNumberWithLong:*(unsigned short *)(data + position)];
				lim = *(unsigned short *)(data + position) + (type == 'ZCNT'? 1:0);
				position += 2;
				break;
			case 'LCNT':
			case 'LZCT':
				[resourceElement setNumberWithLong:*(unsigned long *)(data + position)];
				lim = *(unsigned long *)(data + position) + (type == 'LZCT'? 1:0);
				position += 4;
				break;
				
			/* List beginning and end */
			case 'LSTB':
			case 'LSTC':
				[target addObject:resourceElement];			// add list item to current target array
//				target = [resourceElement subelements];		// change current array to list's sub-elements
				[targetStack addObject:target];				// append sub-element array to target stack so it can be popped off afterwards
				resourceElement = nil;						// don't add item to it's own sub-elements later!
				break;
			case 'LSTE':
				// bug: if there is a LSTE without a preceeding LSTB or LSTC this will crash
				[targetStack removeLastObject];				// pop off current target from stack
				target = [targetStack lastObject];			// set current target to whatever was second from top on the stack
				resourceElement = nil;						// list end items are not needed in a resource array
				if(n < lim) n -= c;
				c = 0;
				break;
			
			/* Cxxx, Hxxx or P0xx */
			default:
			// bug: should look for Cxxx, Hxxx or P0xx and complain if it's something else (an unknown type)!!
			{/*	long lengthStr = (type & 0x00FFFFFF) << 8;
				unsigned long length = strtoul((char *) &lengthStr, nil, 10);
				char *lengthStr = (type & 0x00FFFFFF) & (3 << 24);
				unsigned long length;
				StringToNum(lengthStr, &length);
				NSLog(@"error, '%@' is unsupported, skipping %d bytes", [resourceElement type], length);
				resourceElement = nil;		// relies on element being previously autoreleased to avoid a leak
				position += length;*/
			}	break;
		}	// end template element type switch
		
		if(resourceElement)
		{
			NSLog(@"adding %@", resourceElement);
			[target addObject:resourceElement];
		}
	}	// end while position < size
	
	NSLog([target description]);
}

- (void)createUI
{
	// iterate through res (the resource element array) creating fields
	[self enumerateElements:res];
}

- (void)enumerateElements:(NSMutableArray *)elements
{
	// iterate through the array of resource elements, creating fields
	Element *currentResourceElement;
	NSEnumerator *enumerator = [elements objectEnumerator];
	NSLog(@"elements in resource array = %d", [elements count]);
	while(currentResourceElement = [enumerator nextObject])
	{
		// if element is a container (subelements != nil), iterate inside it first
/*		if([currentResourceElement subelements])
		{
			// bug: need to indent view right
			[self enumerateElements:[currentResourceElement subelements]];
			// bug: need to remove indentation
		}
		else	// element is normal
*/		{
		/*	NSFormCell *newField = [[NSFormCell alloc] initTextCell:[currentResourceElement label]];
			[fieldsMatrix addRowWithCells:[NSArray arrayWithObject:[newField autorelease]]];	*/
			NSLog([currentResourceElement description]);
		}
	}
}

- (void)resourceDataDidChange:(NSNotification *)notification
{
	// ensure it's our resource which got changed (should always be true, we don't register for notifications on other resource objects)
	if([notification object] == (id)resource)
		[self refreshData:[resource data]];
}

- (void)refreshData:(NSData *)data;
{
	// put data from resource into correct fields 
}

- (id)resource
{
	return resource;
}

- (NSData *)data
{
	return [resource data];
}

@end
