//
//  CueSheet.m
//  CueSheet
//
//  Created by Zaphod Beeblebrox on 10/8/07.
//  Copyright 2007 __MyCompanyName__. All rights reserved.
//

#import "CueSheet.h"
#import "CueSheetTrack.h"

#import "Plugin.h"

#import "Logging.h"

@implementation CueSheet

+ (id)cueSheetWithFile:(NSString *)filename {
	return [[CueSheet alloc] initWithFile:filename];
}

+ (id)cueSheetWithString:(NSString *)cuesheet withFilename:(NSString *)filename {
	return [[CueSheet alloc] initWithString:cuesheet withFilename:filename];
}

- (NSURL *)urlForPath:(NSString *)path relativeTo:(NSString *)baseFilename {
	NSRange protocolRange = [path rangeOfString:@"://"];
	if(protocolRange.location != NSNotFound) {
		return [NSURL URLWithString:path];
	}

	NSMutableString *unixPath = [path mutableCopy];

	// Get the fragment
	NSString *fragment = @"";
	NSScanner *scanner = [NSScanner scannerWithString:unixPath];
	NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:@"#1234567890"];
	while(![scanner isAtEnd]) {
		NSString *possibleFragment;
		[scanner scanUpToString:@"#" intoString:nil];

		if([scanner scanCharactersFromSet:characterSet intoString:&possibleFragment] && [scanner isAtEnd]) {
			fragment = possibleFragment;
			[unixPath deleteCharactersInRange:NSMakeRange([scanner scanLocation] - [possibleFragment length], [possibleFragment length])];
			break;
		}
	}

	if(![unixPath hasPrefix:@"/"]) {
		// Only relative paths would have windows backslashes.
		[unixPath replaceOccurrencesOfString:@"\\" withString:@"/" options:0 range:NSMakeRange(0, [unixPath length])];

		NSString *basePath = [[[baseFilename stringByStandardizingPath] stringByDeletingLastPathComponent] stringByAppendingString:@"/"];

		[unixPath insertString:basePath atIndex:0];
	}

	// Append the fragment
	NSURL *url = [NSURL URLWithString:[[[NSURL fileURLWithPath:unixPath] absoluteString] stringByAppendingString:fragment]];
	return url;
}

- (void)parseFile:(NSString *)filename {
	NSStringEncoding encoding;
	NSError *error = nil;
	NSString *contents = [NSString stringWithContentsOfFile:filename usedEncoding:&encoding error:&error];
	if(error) {
		error = nil;
		contents = [NSString stringWithContentsOfFile:filename encoding:NSUTF8StringEncoding error:&error];
	}
	if(error) {
		error = nil;
		contents = [NSString stringWithContentsOfFile:filename encoding:CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000) error:&error];
	}
	if(error) {
		error = nil;
		contents = [NSString stringWithContentsOfFile:filename encoding:NSWindowsCP1251StringEncoding error:&error];
	}
	if(error) {
		error = nil;
		contents = [NSString stringWithContentsOfFile:filename encoding:NSISOLatin1StringEncoding error:&error];
	}
	if(error || !contents) {
		ALog(@"Could not open file...%@ %@ %@", filename, contents, error);
		return;
	}

	[self parseString:contents withFilename:filename];
}

- (void)parseString:(NSString *)contents withFilename:(NSString *)filename {
	NSMutableArray *entries = [[NSMutableArray alloc] init];

	NSString *track = nil;
	NSString *path = nil;
	NSString *artist = nil;
	NSString *album = nil;
	NSString *title = nil;
	NSString *genre = nil;
	NSString *year = nil;

	float albumGain = 0.0f;
	float albumPeak = 0.0f;
	float trackGain = 0.0f;
	float trackPeak = 0.0f;

	BOOL trackAdded = NO;

	NSCharacterSet *whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet];

	NSScanner *scanner = nil;
	for(NSString *line in [contents componentsSeparatedByString:@"\n"]) {
		scanner = [[NSScanner alloc] initWithString:line];

		NSString *command;
		if(![scanner scanUpToCharactersFromSet:whitespace intoString:&command]) {
			continue;
		}

		// FILE "filename.shn" WAVE
		if([command isEqualToString:@"FILE"]) {
			trackAdded = NO;

			if(![scanner scanString:@"\"" intoString:nil]) {
				continue;
			}

			// Read in the path
			if(![scanner scanUpToString:@"\"" intoString:&path]) {
				continue;
			}
		}
		// TRACK 01 AUDIO
		else if([command isEqualToString:@"TRACK"]) {
			trackAdded = NO;

			if(![scanner scanUpToCharactersFromSet:whitespace intoString:&track]) {
				continue;
			}

			NSString *type = nil;
			if(![scanner scanUpToCharactersFromSet:whitespace intoString:&type] || ![type isEqualToString:@"AUDIO"]) {
				continue;
			}
		}
		// INDEX 01 00:00:10
		// Note that time is written in Minutes:Seconds:Frames, where frames are 1/75 of a second
		else if([command isEqualToString:@"INDEX"]) {
			if(trackAdded) {
				continue;
			}

			if(!path) {
				continue;
			}

			NSString *index = nil;
			if(![scanner scanUpToCharactersFromSet:whitespace intoString:&index] || [index intValue] != 1) {
				continue;
			}

			[scanner scanCharactersFromSet:whitespace intoString:nil];

			NSString *time = nil;
			if(![scanner scanUpToCharactersFromSet:whitespace intoString:&time]) {
				continue;
			}

			NSArray *msf = [time componentsSeparatedByString:@":"];
			size_t count = [msf count];
			if(count != 1 && count != 3) {
				continue;
			}

			double seconds;
			BOOL timeInSamples = NO;

			if(count == 1) {
				seconds = [[msf objectAtIndex:0] floatValue];
				timeInSamples = YES;
			} else
				seconds = (60 * [[msf objectAtIndex:0] intValue]) + [[msf objectAtIndex:1] intValue] + ([[msf objectAtIndex:2] floatValue] / 75);

			if(track == nil) {
				track = @"01";
			}

			// Need to add basePath, and convert to URL
			[entries addObject:
			         [CueSheetTrack trackWithURL:[self urlForPath:path relativeTo:filename]
			                               track:track
			                                time:seconds
			                       timeInSamples:timeInSamples
			                              artist:artist
			                               album:album
			                               title:title
			                               genre:genre
			                                year:year
			                           albumGain:albumGain
			                           albumPeak:albumPeak
			                           trackGain:trackGain
			                           trackPeak:trackPeak]];
			trackAdded = YES;
		} else if([command isEqualToString:@"PERFORMER"]) {
			if(![scanner scanString:@"\"" intoString:nil]) {
				continue;
			}

			// Read in the path
			if(![scanner scanUpToString:@"\"" intoString:&artist]) {
				continue;
			}
		} else if([command isEqualToString:@"TITLE"]) {
			NSString *titleDest;

			if(![scanner scanString:@"\"" intoString:nil]) {
				continue;
			}

			// Read in the path
			if(![scanner scanUpToString:@"\"" intoString:&titleDest]) {
				continue;
			}

			if(!path) // Have not come across a file yet.
				album = titleDest;
			else
				title = titleDest;
		} else if([command isEqualToString:@"REM"]) // Additional metadata sometimes stored in comments
		{
			NSString *type;
			if(![scanner scanUpToCharactersFromSet:whitespace intoString:&type]) {
				continue;
			}

			if([type isEqualToString:@"GENRE"]) {
				// DLog(@"GENRE!");
				if([scanner scanString:@"\"" intoString:nil]) {
					// DLog(@"QUOTED");
					if(![scanner scanUpToString:@"\"" intoString:&genre]) {
						DLog(@"FAILED TO SCAN");
						continue;
					}
				} else {
					// DLog(@"UNQUOTED");
					if(![scanner scanUpToCharactersFromSet:whitespace intoString:&genre]) {
						continue;
					}
				}
			} else if([type isEqualToString:@"DATE"]) {
				// DLog(@"DATE!");
				if(![scanner scanUpToCharactersFromSet:whitespace intoString:&year]) {
					continue;
				}
			} else if([type hasPrefix:@"REPLAYGAIN_"]) {
				NSString *rgTag = nil;
				if(![scanner scanUpToCharactersFromSet:whitespace intoString:&rgTag])
					continue;
				if([type hasPrefix:@"REPLAYGAIN_ALBUM_"]) {
					if([type hasSuffix:@"GAIN"])
						albumGain = [rgTag floatValue];
					else if([type hasSuffix:@"PEAK"])
						albumPeak = [rgTag floatValue];
				} else if([type hasPrefix:@"REPLAYGAIN_TRACK_"]) {
					if([type hasSuffix:@"GAIN"])
						trackGain = [rgTag floatValue];
					else if([type hasSuffix:@"PEAK"])
						trackPeak = [rgTag floatValue];
				}
			}
		}
	}

	tracks = [entries copy];
}

- (id)initWithFile:(NSString *)filename {
	self = [super init];
	if(self) {
		[self parseFile:filename];
	}

	return self;
}

- (id)initWithString:(NSString *)cuesheet withFilename:(NSString *)filename {
	self = [super init];
	if(self) {
		[self parseString:cuesheet withFilename:filename];
	}

	return self;
}

- (NSArray *)tracks {
	return tracks;
}

- (CueSheetTrack *)track:(NSString *)fragment {
	for(CueSheetTrack *t in tracks) {
		if([[t track] isEqualToString:fragment]) {
			return t;
		}
	}

	return nil;
}

@end
