//
//  AppDelegate.m
//  XMLDemo
//
//  Created by Paul Goracke on 4/8/09.
//  Copyright 2009 Corporation Unknown. All rights reserved.
//

#import "AppDelegate.h"

#import "Song.h"

#pragma mark -

@interface AppDelegate ()

- (void) walkXMLDocument:(NSXMLDocument*)xmlDoc;
- (void) walkXMLWithElemNames:(NSXMLDocument*)xmlDoc;
- (void) tracksUsingXPath:(NSXMLDocument*)xmlDoc;

@end

#pragma mark -

@implementation AppDelegate

@synthesize filePath;
@synthesize parseFormatter;

- (id) init {
	if ( self = [super init] ) {
		demos = [[NSArray alloc] initWithObjects:
					@"1: DOM Traversal",
					@"2: Element Names",
					@"3: XPath",
					nil];
	}
	return self;
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
	self.filePath = [[NSBundle mainBundle] pathForResource:@"tunes" ofType:@"xml"];
	
	self.parseFormatter = [[[NSDateFormatter alloc] init] autorelease];
	[parseFormatter setDateStyle:NSDateFormatterLongStyle];
	[parseFormatter setTimeStyle:NSDateFormatterNoStyle];
	// necessary because iTunes RSS feed is not localized, so if the device region has been set to other than US
	// the date formatter must be set to US locale in order to parse the dates
	[parseFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"US"] autorelease]];
}

- (IBAction) parse:(id)sender {
	NSError * err = nil;
	NSData * data = [[NSData alloc] initWithContentsOfMappedFile:self.filePath];
	
	NSXMLDocument* xmlDoc = [[NSXMLDocument alloc] initWithData:data options:NSXMLDocumentTidyXML error:&err];
	[data release];
	switch ( [demoSelection indexOfSelectedItem] ) {
		case 0: // dom traversal;
			[self walkXMLDocument:xmlDoc];
			break;
			
		case 1: // elem names
			[self walkXMLWithElemNames:xmlDoc];
			break;
			
		case 2: // xpath
			[self tracksUsingXPath:xmlDoc];
			break;
			
		default:
			NSLog( @"Unknown demo" );
			break;
	}
	[xmlDoc release];
}

#pragma mark -
#pragma mark Demo 1 (DOM Traversal)
/*
 Walk the tree using nextNode and print out info for each node.
 Many of these nodes won't be found by walking the tree, but the case statement is there.
 This code is not very thorough, just a good starting exploration point.
 */
- (void) walkXMLDocument:(NSXMLDocument*)xmlDoc {
	NSXMLElement * root = [xmlDoc rootElement];
	
	NSXMLNode * node = root;
	while ( node = [node nextNode] ) {
		switch ( [node kind] ) {
			case NSXMLDocumentKind:
				NSLog( @"Document" );
				break;
				
			case NSXMLElementKind:
				NSLog( @"Element: %@ (local:%@,uri:%@)", [node name], [node localName], [node URI] );
				break;
				
			case NSXMLAttributeKind:
				NSLog( @"Attribute: %@", [node name] );
				break;
				
			case NSXMLCommentKind:
				NSLog( @"Comment: %@", [node stringValue] );
				break;
				
			case NSXMLTextKind:
				NSLog( @"Text: %@", [node stringValue] );
				break;
				
			default:
				NSLog( @"Unknown node: %@", node );
				break;
		}
	}
}

#pragma mark -
#pragma mark Demo 2 (Element Names)

/*
 Work down to the <item> elements of a document using elementsForName:
 Further processing of <item> is left to the reader.
 */
- (void) walkXMLWithElemNames:(NSXMLDocument*)xmlDoc {
	NSXMLElement* root = [xmlDoc rootElement];
	
	// naive "give me <item> elements"
	NSArray* items = [root elementsForName:@"item"];
	for ( NSXMLElement* item in items ) {
		// Oops! elementsForName: only returns children, not descendents
		NSLog( @"item #%d", [item index] );
	}
	
	// walk down to <item> through <channel>
	for ( NSXMLElement* elem in [root elementsForName:@"channel"] ) {
		for ( NSXMLElement* item in [elem elementsForName:@"item"] ) {
			NSLog( @"item #%d", [item index] );
		}
	}
}

#pragma mark -
#pragma mark Demo 3 (XPath)

- (void) tracksUsingXPath:(NSXMLDocument*)xmlDoc {
	NSString* itmsNamespace = @"http://phobos.apple.com/rss/1.0/modules/itms/";
	BOOL useNamespace = NO;
	
	NSXMLElement* root = [xmlDoc rootElement];
	NSError* error = nil;
	NSArray* items = [root nodesForXPath:@"/rss/channel/item" error:&error];
	for ( NSXMLElement* item in items ) {
		Song* song = [[Song alloc] init];
		song.title = [[[item elementsForName:@"title"] objectAtIndex:0] stringValue];
		song.category = [[[item elementsForName:@"category"] objectAtIndex:0] stringValue];
		if ( useNamespace ) {
			song.artist = [[[item elementsForLocalName:@"artist" URI:itmsNamespace] objectAtIndex:0] stringValue];
			song.album = [[[item elementsForLocalName:@"album" URI:itmsNamespace] objectAtIndex:0] stringValue];
			song.releaseDate = [parseFormatter dateFromString:[[[item elementsForLocalName:@"releasedate" URI:itmsNamespace] objectAtIndex:0] stringValue]];
		}
		else {
			song.artist = [[[item elementsForName:@"itms:artist"] objectAtIndex:0] stringValue];
			song.album = [[[item elementsForName:@"itms:album"] objectAtIndex:0] stringValue];
			song.releaseDate = [parseFormatter dateFromString:[[[item elementsForName:@"itms:releasedate"] objectAtIndex:0] stringValue]];			
		}
		
		NSLog( @"%@", song );
		
		// extract url for the 60-pixel artwork if available
		NSString* xpath = @"itms:coverArt[@height='60']";
		
		// coverArt elements regardless of namespace/prefix
//		xpath = @"*[local-name()='coverArt'][@height='60']";
		
		// namespace version should(?) work in 2.0 but doesn't
//		xpath = @"*[local-name()='coverArt' and namespace-uri()='http://phobos.apple.com/rss/1.0/modules/itms/'][@height='60']";
		NSArray* artwork = [item nodesForXPath:xpath error:&error];
		if ( [artwork count] > 0 ) {
			NSLog( @"60px artwork: %@", [[artwork objectAtIndex:0] stringValue] );
		}
		[song release];
	}
}

#pragma mark -
#pragma mark Memory Management Demo

/*
 The following IBAction methods are intended to show the benefit of using a memory-mapped
 file if you are loading large local XML files. Run the application under Instruments and
 observe the memory usage while pressing the corresponding buttons.
 To better see the memory required for the corresponding objects (NSData, NSURL, etc.)
 1-second thread sleeps are injected throughout--they serve no other purpose and should
 not be part of a "real" application.
 */

// Load the file into memory as NSData, then create an XML document from it
- (IBAction) createXMLDocumentFromFile:(id)sender {
	NSXMLDocument * xmlDoc;
	NSError * err = nil;
	NSData * data = [[NSData alloc] initWithContentsOfFile:self.filePath];
	
	xmlDoc = [[NSXMLDocument alloc] initWithData:data options:NSXMLDocumentTidyXML error:&err];
	
	[NSThread sleepForTimeInterval:1.0]; 
	[data release];
	[NSThread sleepForTimeInterval:1.0]; 
	[xmlDoc release];
}

// Create a filepath url for the document to read from--does this have smaller memory footprint?
- (IBAction) createXMLDocumentFromFileURL:(id)sender {
	NSXMLDocument *xmlDoc;
	NSError *err = nil;
	NSURL *furl = [[NSURL alloc] initFileURLWithPath:self.filePath];
	if (!furl) {
		NSLog(@"Can't create an URL from file %@.", self.filePath);
	}
	xmlDoc = [[NSXMLDocument alloc] initWithContentsOfURL:furl
																 options:NSXMLDocumentTidyXML
																	error:&err];
	[NSThread sleepForTimeInterval:1.0]; 
	[furl release];
	[NSThread sleepForTimeInterval:1.0]; 
	
	[xmlDoc release];
}

// memmap the file so it doesn't all have to be loaded into memory
- (IBAction) createXMLDocumentFromMMappedFile:(id)sender {
	NSXMLDocument * xmlDoc;
	NSError * err = nil;
	NSData * data = [[NSData alloc] initWithContentsOfMappedFile:self.filePath];
	
	xmlDoc = [[NSXMLDocument alloc] initWithData:data options:NSXMLDocumentTidyXML error:&err];
	
	[NSThread sleepForTimeInterval:1.0]; 
	[data release];
	[NSThread sleepForTimeInterval:1.0]; 
	[xmlDoc release];
}


@end
