/* Copyright (c) 2004, Noah Sorscher
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 	• 	Redistributions of source code must retain the above copyright
 * 	notice, this list of conditions and the following disclaimer.
 * 	• 	Redistributions in binary form must reproduce the above copyright
 * 	notice, this list of conditions and the following disclaimer in the
 * 	documentation and/or other materials provided with the distribution.
 * 	• 	Neither the name of the Noah Sorscher nor the names of its
 * 	contributors may be used to endorse or promote products derived from
 * 	this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */
//
//  MHChapters.m
//  Hootenanny
//
//  Created by Noah Sorscher on Wed Mar 26 2003.
//  Copyright (c) 2003 __MyCompanyName__. All rights reserved.
//

#import "MHChapters.h"
#import "MHMovie.h"

@implementation NSDictionary (debug)
- (BOOL) setObject:(NSObject *)anObject forKey:(NSString *)aKey
{
	NSLog(@"huh?");
	return NO;
}
@end

@interface MHChapters (Private)
- (NSMutableArray *)makeChaptersMutable:(NSMutableArray *)chapters;
@end

@implementation MHChapters (Private)

- (NSMutableArray *)makeChaptersMutable:(NSMutableArray *)chapters
{
	/* Jan Devos says:
	 * This function takes a MutableArray with (non-mutable) Dictionaries in it and turns the
	 * dictionaries into MutableDictionaries. Why aren't the dictionaries mutable in the first
	 * place you might ask. I don't know, it's not my program ;)
	 * Each dictionary represents one chapter with as contents the name of the chapter and its start time.
	 */
	 
	NSMutableDictionary	* currentMutableChapter;
	NSDictionary		* currentChapter;
	NSEnumerator		* chapterEnumerator = [chapters objectEnumerator];
	NSMutableArray		* mutableChapters = [[NSMutableArray alloc] initWithCapacity:[chapters count]];
	
	// Take each non-mutable chapter dictionary, copy it to a mutable dictionary and save it in the array
	while (currentChapter = [chapterEnumerator nextObject])
	{
		currentMutableChapter = [currentChapter mutableCopyWithZone:NULL];
		[mutableChapters addObject:currentMutableChapter];
	}
		
	// Make sure the mutableChapters object gets released at some stage
	[mutableChapters autorelease];
		
	return mutableChapters;
	
}

@end


@implementation MHChapters

int chapCompare(id one, id two, void *context);
//void removeChapterTrack(Movie theMovie);
//Track QTText_AddTextTrack (Movie theMovie, char *theStrings[], long theFrames[], short theNumFrames, OSType theType, Boolean isChapterTrack);
//TimeValue QTUtils_GetFrameDuration (Track theTrack);
//void QTText_CopyCStringToPascal (const char *theSrc, Str255 theDst);
//Track QTText_GetChapterTrackForTrack (Track theTrack);
Track QTText_GetChapterTrackForMovie (Movie theMovie);
OSErr QTText_GetFirstChapterTime (Track theChapterTrack, TimeValue *theTime);
OSErr QTText_GetNextChapterTime (Track theChapterTrack, TimeValue *theTime);
TimeValue QTText_GetIndChapterTime (Track theChapterTrack, long theIndex);
char *QTText_GetIndChapterText (Track theChapterTrack, long theIndex);
Boolean QTText_TrackHasAChapterTrack (Track theTrack);
Boolean QTText_MovieHasAChapterTrack (Movie theMovie);
OSErr QTUtils_DeleteAllReferencesToTrack (Track theTrack);


- (MHChapters *)init
{
   //NSLog(@"MHChapters init");
    //NSLog(@"init");
    theChapters = [[NSMutableArray alloc] init];
    realChapters = nil;
    updating = YES;
    multilangual = NO;
	igtemp = NO;
    //timeScale = 600;
    self = [super init];
    return self;
}

- (MHChapters *)initFromMovie:(NSMovie *)aMovie
{
    //NSLog(@"init from %@", [aMovie stringValue]);
    self = [self init];
    //[self setTimeScaleFromMovie:aMovie];
    [self getChaptersFromMovie:aMovie];
    [self copyToRealChapters];
    return self;
}


- (MHChapters *)copy
{
    //NSLog(@"MHChapters copy");
    MHChapters *newChapters = [[MHChapters alloc] init];
    NSMutableArray *newArray = [[NSMutableArray alloc] init];
    int i, n = [theChapters count];
    for (i=0;i<n;i++) {
        [newArray addObject:[NSMutableDictionary dictionaryWithDictionary:[theChapters objectAtIndex:i]]];
    }
    //[newChapters setTimeScale:timeScale];
    [newChapters addObjectsFromArray:newArray];
    return newChapters;
}

- (void)dealloc
{
    [theChapters release];
    [super dealloc];
}

NSMutableArray *listeners = nil;

+ (void) addChaptersChangeListener:(id)listener
{
    if (!listener) return;
    if (!listeners) listeners = [[NSMutableArray alloc] init];
    [listeners addObject:listener];
}

- (void) didChange
{
    if (!updating) return;
    if (!listeners) return;
    int i, n = [listeners count];
    for (i=0;i<n;i++) {
        [[listeners objectAtIndex:i] chaptersDidChange:self];
    }
}

/*
- (MHChapters *)initFromWeb
{
    [self init];
    // run web panel here
    return self;
}
*/
- (int)numberOfRowsInTableView:(NSTableView *)aTableView
{
    return [theChapters count];
}

- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
{
    //NSLog(@"MHChapters tableView:objectValueForTableColumn:row:");
    if (rowIndex < 0 || rowIndex >= [theChapters count]) return nil;
    NSMutableDictionary *aChapter = [theChapters objectAtIndex:rowIndex];
    NSString *idString = [aTableColumn identifier];
    NSString *value = [aChapter objectForKey:idString];
    if ([value isKindOfClass:[NSString class]]) return value;
    if ([value isKindOfClass:[NSNumber class]]) {
        //double seconds = (double)[value longValue] / (double)timeScale;
        return [MHUtil nSecondsToString:[value doubleValue]];
    }
    return @"";
}

- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
{
	if (!anObject) return;
    if (rowIndex < 0 || rowIndex >= [theChapters count]) return;
	NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    NSMutableDictionary *aChapter = [theChapters objectAtIndex:rowIndex];
    NSString *idString = [aTableColumn identifier];
    if ([idString isEqualToString:@"Start"]) {
        double seconds = [MHUtil stringToNSeconds:anObject];
		anObject = [NSNumber numberWithDouble:seconds];
        double oldSeconds = [[aChapter objectForKey:@"Start"] doubleValue];
        if (oldSeconds == seconds) return; 
    } else {
        NSString *oldValue = [aChapter objectForKey:idString];
        if (oldValue && [(NSString *)anObject isEqualToString:oldValue]) return;
    }
	[dict setObject:anObject forKey:idString];
	[dict setObject:[NSNumber numberWithBool:YES] forKey:@"dirty"];
	NSArray *keys = [aChapter allKeys];
	int i, n = [keys count];
	for (i=0;i<n;i++) {
		NSString *k = [keys objectAtIndex:i];
		if (![dict objectForKey:k]) {
			[dict setObject:[aChapter objectForKey:k] forKey:k];
		}
	}
	[theChapters replaceObjectAtIndex:rowIndex withObject:dict];
	[self didChange];
}


- (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
{
    //if (!row || row == [playlistTable selectedRow]) return NSDragOperationNone;
    //NSLog(@"copy");
    //if ([movie isOffline]) return NSDragOperationNone;
    return NSDragOperationCopy;
}

- (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
{
    //if ([movie isOffline]) return NO;
    NSPasteboard *pboard = [info draggingPasteboard];
    //if (![[pboard types] containsObject:NSFilenamesPboardType] ) return NO;
	NSString *s = nil;
	/*
	NSLog(@"drop");
	
	int i, n = [[pboard types] count];
	for (i=0;i<n;i++) {
		s = [[pboard types] objectAtIndex:i];
		if (!s) NSLog(@"type is nil");
		NSLog(s);
		s = [pboard stringForType:s];
		if (!s) NSLog(@"string is nil");
		NSLog([s className]);
		NSLog(s);
	}
	*/
	NSMovie *theMovie = nil;
	/*
	if ([[pboard types] containsObject:NSStringPboardType]) {
		s = [pboard stringForType:NSStringPboardType];
		if (!s) return NO;
		if ([[[NSUserDefaults standardUserDefaults] objectForKey:@"chapScale"] boolValue]) {
			if ([[tableView delegate] respondsToSelector:@selector(currentMovie)])
				theMovie = [[tableView delegate] currentMovie];
		}
		[self parseText:s scaleToMovie:theMovie];
	} else 
		*/
	if ([[pboard types] containsObject:NSFilenamesPboardType]) {
		NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];
		if ([files count]) {
			s = [files objectAtIndex:0];
			if ([[[NSUserDefaults standardUserDefaults] objectForKey:@"scaleImportedChapters"] boolValue]) {
				if ([[tableView delegate] respondsToSelector:@selector(currentMovie)])
					theMovie = [[tableView delegate] currentMovie];
			}
			
			if ([s hasSuffix:@".mov"]) {
				[self parseMovFile:[NSURL fileURLWithPath:s] scaleToMovie:theMovie];
			} else if ([s hasSuffix:@".ogm"]) {
				[self parseOgmFile:[NSURL fileURLWithPath:s] scaleToMovie:theMovie];
			} else if ([s hasSuffix:@".mkv"]) {
				[self parseMkvFile:[NSURL fileURLWithPath:s] scaleToMovie:theMovie];
			} else {
				[self parseTextFile:[NSURL fileURLWithPath:s] scaleToMovie:theMovie];
			}
		}
	}
    
    return YES;
}





- (void)setMultilingual:(BOOL)flag languages:(NSArray *)langs;
{
    if (multilangual == flag) return;
    multilangual = flag;
    int i, j, n = [theChapters count];
    NSString *value = nil, *key = nil;
    for (i=0;i<n;i++) {
        NSMutableDictionary *ch = [theChapters objectAtIndex:i];
        if (flag) value = [ch objectForKey:@"Content"];
        else if ([langs count]) value = [ch objectForKey:[NSString stringWithFormat:@"%@ Content", [langs objectAtIndex:0]]];
        if (!value) value = @"";
        if (flag) {
            for (j=0;j<[langs count];j++) {
                key = [NSString stringWithFormat:@"%@ Content", [langs objectAtIndex:j]];
                if ([value length] && ![ch objectForKey:key])
                    [ch setObject:value forKey:key];
            }
        } else if ([value length] && ![ch objectForKey:@"Content"])
            [ch setObject:value forKey:@"Content"];
    }
    [self didChange];
}

- (BOOL)multilingual {  return multilangual;    }

- (NSString *)description
{
    int i, n = [theChapters count];
    NSMutableString *string = [NSMutableString stringWithString:@""];
    for (i=0;i<n;i++) {
        NSMutableDictionary *chap = [theChapters objectAtIndex:i];
        NSArray *keys = [chap allKeys];
        int j, m = [keys count];
        for (j=0;j<m;j++) {
            NSString *key = [keys objectAtIndex:j];
            if ([key hasSuffix:@"Content"]) {
                NSString *content = [chap objectForKey:key];
                if ([content length]) {
                    if ([string length]) [string appendString:@"|"];
                    [string appendString:content];
                }
            }
        }
    }
    return string;
}

- (void)setUpdating:(BOOL)flag
{   updating = flag;        }

- (int)count
{
    return [theChapters count];
}

- (void)empty
{
    [theChapters removeAllObjects];
    [self didChange];
}

- (BOOL)containsString:(NSString *)searchString
{
    //NSLog(@"MHChapters containsString");
    if (nil == searchString) return NO;
    int i, n = [theChapters count];
    for (i=0;i<n;i++) {
        if ([[[theChapters objectAtIndex:i] objectForKey:@"Content"] rangeOfString:searchString options:NSCaseInsensitiveSearch].length != 0)
            return YES;
    }
    return NO;
}

- (void)addObjectsFromArray:(NSArray *)chapters
{
    //[theChapters addObjectsFromArray:chapters];
	int i, n = [chapters count];
	for (i=0;i<n;i++) {
		NSMutableDictionary *d = [chapters objectAtIndex:i];
		
		if ([d isKindOfClass:[NSMutableDictionary class]])
			[theChapters addObject:d];
		else {
			NSMutableDictionary *d2 = [[NSMutableDictionary alloc] init];
			[d2 addEntriesFromDictionary:d];
			[theChapters addObject:d2];
		}
	}
    [self didChange];
}

- (NSArray *) array
{
    NSMutableArray *array = [NSMutableArray array];
    int i, n = [theChapters count];
    for (i=0;i<n;i++)
        [array addObject:[NSMutableDictionary dictionaryWithDictionary:[theChapters objectAtIndex:i]]];
    return array;
}

- (void) updateWithArray:(NSArray *)newChapters
{
		
	// Make a mutableChapters object so we can update the chapters
	// and initialise the object by copying the static chapters
	NSMutableArray	* mutableChapters;
	mutableChapters = [[self makeChaptersMutable:theChapters] mutableCopyWithZone:NULL];
	
	// Jan Devos says: I'm not responsible for the warnings ;-)
    if (!newChapters || ![newChapters count]) return;
    int i, n = [newChapters count], m = [mutableChapters count];
    for (i=0;i<n;i++) {
        NSString *content = [[newChapters objectAtIndex:i] objectForKey:@"Content"];
        double newStart = [[[newChapters objectAtIndex:i] objectForKey:@"Start"] doubleValue];
        if (i<m) {
			[[mutableChapters objectAtIndex:i] setObject:content forKey:@"Content"];
            double oldStart = [[[mutableChapters objectAtIndex:i] objectForKey:@"Start"] doubleValue];
			if (!oldStart)
                [[mutableChapters objectAtIndex:i] setObject:[NSNumber numberWithDouble:newStart] forKey:@"Start"];
        } else {
            NSMutableDictionary *aChapter = [[NSMutableDictionary alloc] init];
            [aChapter setObject:content forKey:@"Content"];
            [aChapter setObject:[NSNumber numberWithDouble:newStart] forKey:@"Start"];
            [mutableChapters addObject:aChapter];
        }
    }
    NSNumber *nseconds = [[newChapters objectAtIndex:i-1] objectForKey:@"nSeconds"];
    if (nseconds)
        [[mutableChapters objectAtIndex:i-1] setObject:nseconds forKey:@"nSeconds"];
		
	// Time to update the static chapters object with the new chapter information
	[theChapters setArray:[mutableChapters mutableCopyWithZone:NULL]];
	
    [self didChange];
}

- (NSArray *)chapterNames
{
    return [self chapterNamesForLanguage:nil];
}

- (NSArray *)chapterNamesForLanguage:(NSString *)language
{
    NSString *key = @"Content";
    if (language) key = [NSString stringWithFormat:@"%@ Content", language];
    NSMutableArray *array = [[NSMutableArray alloc] init];
    int i, n = [theChapters count];
    for (i=0;i<n;i++)
        if ([(NSString *)[[theChapters objectAtIndex:i] objectForKey:key] length])
            [array addObject:[[theChapters objectAtIndex:i] objectForKey:key]];
    return array;
}

- (NSArray *)chapterTimes
{
    return [self chapterTimesForLanguage:nil];
}

- (NSArray *)chapterTimesForLanguage:(NSString *)language
{
    NSString *key = @"Content";
    if (language) key = [NSString stringWithFormat:@"%@ Content", language];
    NSMutableArray *array = [[NSMutableArray alloc] init];
    int i, n = [theChapters count];
    for (i=0;i<n;i++)
        if ([(NSString *)[[theChapters objectAtIndex:i] objectForKey:key] length])
            [array addObject:[[theChapters objectAtIndex:i] objectForKey:@"Start"]];
    return array;
}

- (void)addChapterAtIndex:(int)index
{
    if (index < 0 || index >= [theChapters count]) index = [theChapters count];
    NSMutableDictionary *newChapter = [[NSMutableDictionary alloc] init];
    [newChapter setObject:[NSString stringWithFormat:@"Chapter %d", index + 1] forKey:@"Content"];
    [newChapter setObject:[NSNumber numberWithDouble:0] forKey:@"Start"];
    [theChapters insertObject:newChapter atIndex:index];
    [self didChange];
}

- (void)removeChapterAtIndex:(int)index
{
    if (index < 0 || index >= [theChapters count]) return;
    [theChapters removeObjectAtIndex:index];
    [self didChange];
}
/*
- (void)setTime:(long)samples forIndex:(int)index
{
    if (index < 0 || index >= [theChapters count]) return;
    [[theChapters objectAtIndex:index] setObject:[NSNumber numberWithLong:samples] forKey:@"Start"];
    [self didChange];
}
*/

- (void)offsetAll:(double)seconds
{
    int i, n = [theChapters count];
    for (i=0;i<n;i++) {
        double start = [[[theChapters objectAtIndex:i] objectForKey:@"Start"] doubleValue];
        start += seconds;
        [(NSMutableDictionary *)[theChapters objectAtIndex:i] setObject:[NSNumber numberWithDouble:start] forKey:@"Start"];
    }
    [self didChange];
}

- (void)setSeconds:(double)seconds forIndex:(int)index
{
    if (index < 0 || index >= [theChapters count]) return;
    [(NSMutableDictionary *)[theChapters objectAtIndex:index] setObject:[NSNumber numberWithDouble:seconds] forKey:@"Start"];
    [self didChange];
}

- (int)indexForTime:(double)seconds
{
    return [self indexForTime:seconds language:nil];
}

- (int)indexForTime:(double)seconds language:(NSString *)language
{
    NSString *key = @"Content";
    if (language) key = [NSString stringWithFormat:@"%@ Content", language];
    int tally = 0;
    int i, j, n = [theChapters count];
    for (i=0;i<n;i++) {
        NSMutableDictionary *nextChapter = nil;
        if ([[theChapters objectAtIndex:i] objectForKey:key]) {
            for (j=i+1;j<n;j++)
                if ([[theChapters objectAtIndex:j] objectForKey:key])
                    nextChapter = [theChapters objectAtIndex:j];
            if (!nextChapter || seconds < [[nextChapter objectForKey:@"Start"] doubleValue]) return tally;
            else tally++;
        }
    }
    return tally;
}

- (NSString *)nameForTime:(double)seconds
{
    return [self nameForTime:seconds language:nil];
}

- (NSString *)nameForTime:(double)seconds language:(NSString *)language
{
    NSString *key = @"Content";
    if (language) key = [NSString stringWithFormat:@"%@ Content", language];
    int i, j, n = [theChapters count];
    for (i=0;i<n;i++) {
        NSMutableDictionary *nextChapter = nil, *thisChapter = [theChapters objectAtIndex:i];
        NSString *name = [thisChapter objectForKey:key];
        if (name) {
            for (j=i+1;!nextChapter && j<n;j++)
                if ([[theChapters objectAtIndex:j] objectForKey:key])
                    nextChapter = [theChapters objectAtIndex:j];
            if (!nextChapter || seconds < [[nextChapter objectForKey:@"Start"] doubleValue])
                return name;
        }
    }
    return nil;
}

- (double)timeForChapterAtIndex:(int)index
{
    if (index < 0 || index >= [theChapters count]) return 0;
    return [[[theChapters objectAtIndex:index] objectForKey:@"Start"] doubleValue];
}

- (double)timeForChapterAtIndex:(int)index language:(NSString *)language
{
    int i, n = [theChapters count];
    int tally = 0;
    NSString *key = @"Content";
    if (language) key = [NSString stringWithFormat:@"%@ Content", language];
    for (i=0;i<n;i++) {
        NSMutableDictionary *chapter = [theChapters objectAtIndex:i];
        if ([chapter objectForKey:key]) {
            if (tally == index) return [[chapter objectForKey:@"Start"] doubleValue];
            else tally++;
        }
    }
    return -1;
}

- (double)timeForChapterWithName:(NSString *)name
{
    int i, n = [theChapters count];
    for (i=0;i<n;i++) {
        if ([[[theChapters objectAtIndex:i] allValues] containsObject:name])
            return [[[theChapters objectAtIndex:i] objectForKey:@"Start"] doubleValue];
    }
    return -1;
}

- (double)nextTimeForTime:(double)seconds
{
    return [self nextTimeForTime:seconds language:nil];
}

- (double)nextTimeForTime:(double)seconds language:(NSString *)language
{
    NSString *key = @"Content";
    if (language) key = [NSString stringWithFormat:@"%@ Content", language];
    int i, n = [theChapters count];
    double closest = -1;
    for (i=0;i<n;i++) {
        NSMutableDictionary *chapter = [theChapters objectAtIndex:i];
        if ([chapter objectForKey:key]) {
            double start = [[chapter objectForKey:@"Start"] doubleValue];
            if (start > seconds && (closest == -1 || start < closest)) closest = start;
        }
    }
    return closest;
}

- (double)prevTimeForTime:(double)seconds
{
    return [self prevTimeForTime:seconds language:nil];
}

- (double)prevTimeForTime:(double)seconds language:(NSString *)language
{
    NSString *key = @"Content";
    if (language) key = [NSString stringWithFormat:@"%@ Content", language];
    int i, n = [theChapters count];
    double closest = 0;
    for (i=0;i<n;i++) {
        NSMutableDictionary *chapter = [theChapters objectAtIndex:i];
        if ([chapter objectForKey:key]) {
            double start = [[chapter objectForKey:@"Start"] doubleValue];
            if (start < seconds && start > closest) closest = start;
        }
    }
    return closest;
}

- (NSRange)rangeOfChapterContainingString:(NSString *)string index:(int)index
{
    //NSLog(@"MHChapters rangeOfChapterContainingString");
    NSRange result, aRange;
    result.length = 0;
    result.location = NSNotFound;
    if (nil == realChapters || nil == string) {
        //NSLog(@"realChapters is nil!");
        return result;
    }
    NSMutableDictionary *aChapter;
    NSString *aContent;
    int i, n = [realChapters count];
    int count = 0;
    for (i=0; i<n; i++) {
        aChapter = [realChapters objectAtIndex:i];
        aContent = [aChapter objectForKey:@"Content"];
        aRange = [aContent rangeOfString:string options:NSCaseInsensitiveSearch];
        if (aRange.length != 0) {
            if (count < index)
                count++;
            else {
                result.location = [[aChapter objectForKey:@"Start"] doubleValue];
                if (i != n - 1)
                    result.length = [[[realChapters objectAtIndex:i+1] objectForKey:@"Start"] doubleValue] - result.location;
                else
                    result.length = 'end';
                break;
            }
        }    
    }
    return result;
}

- (NSArray *)defsEntry
{
    return [NSArray arrayWithArray:theChapters];
}

- (BOOL) parseMovFile:(NSURL *)aURL scaleToMovie:(NSMovie *)movie
{
    MHMovie *m = [[MHMovie alloc] initWithURL:aURL byReference:NO];
    if (!m) return NO;
    MHChapters *chaps = [m chapters];
    if (!chaps) return NO;
    NSMutableArray *array = (NSMutableArray *)[chaps array];
    if ([array count]) {
        multilangual = [chaps multilingual];
    } else {
        array = [NSMutableArray array];
        TimeScale s = GetMovieTimeScale([m QTMovie]);
        int i, n = [m trackCount];
        for (i=0;i<n;i++) {
            if ([[m trackAtIndex:i] duration] < GetMovieDuration([m QTMovie])) {
                double seconds = (double)[[m trackAtIndex:i] duration] / (double)s;
                NSMutableDictionary *aChap = [NSMutableDictionary dictionary];
                [aChap setObject:[NSNumber numberWithDouble:seconds] forKey:@"Start"];
                [aChap setObject:[NSString stringWithFormat:@"Chapter %d", i+1] forKey:@"Content"];
                [array addObject:aChap];
            }
        }
        multilangual = NO;
    }
    [theChapters removeAllObjects];
    [theChapters addObjectsFromArray:array];
    [m release];
    [self didChange];
    return YES;
}

- (BOOL) parseOgmFile:(NSURL *)aURL scaleToMovie:(NSMovie *)movie
{
    NSTask *task = [[NSTask alloc] init];
    NSPipe *pipe = [[NSPipe alloc] init];
    NSFileHandle *handle = [pipe fileHandleForReading];
    NSString *resourcePath = [[NSBundle mainBundle] pathForResource:@"ogminfo" ofType:@""];
    [task setLaunchPath:resourcePath];
    [task setArguments:[NSArray arrayWithObjects:@"-v", [aURL path], nil]];
    [task setStandardOutput:pipe];
    [task setStandardError:pipe];
    [task launch];
    NSString *output = [[[NSString alloc] initWithData:[handle readDataToEndOfFile] encoding:NSASCIIStringEncoding] autorelease];
    [task waitUntilExit];
    [task release];
    [pipe release];
    if (![output length]) return NO;
    NSScanner *scan = [NSScanner scannerWithString:output];
    NSString *buffer, *key;
    int chapNum;
    while (![scan isAtEnd]) {
        buffer = key = nil;
        chapNum = -1;
        [scan scanPastString:@"CHAPTER"];
        [scan scanInt:&chapNum];
        chapNum--;
        if ([scan scanString:@"=" intoString:nil]) {
            [scan scanUpToString:@"\n" intoString:&buffer];
            buffer = (NSString *)[NSNumber numberWithDouble:[MHUtil stringToNSeconds:buffer]];
            key = @"Start";
        } else if ([scan scanString:@"NAME=" intoString:nil]) {
            [scan scanUpToString:@"\n" intoString:&buffer];
            key = @"Content";
        }
        if (buffer && key) {
            if (chapNum >= 0 && chapNum < [theChapters count])
                [(NSMutableDictionary *)[theChapters objectAtIndex:chapNum] setObject:buffer forKey:key];
            else
                [theChapters addObject:[NSMutableDictionary dictionaryWithObject:buffer forKey:key]];
        }
    }
    [self didChange];
    return YES;
}

- (BOOL) parseMkvFile:(NSURL *)aURL scaleToMovie:(NSMovie *)movie
{
    return NO;
}

- (BOOL) parseTextFile:(NSURL *)aURL
{
    return [self parseTextFile:aURL scaleToMovie:nil];
}

- (BOOL) parseTextFile:(NSURL *)aURL scaleToMovie:(NSMovie *)movie
{
    //NSLog(@"MHChapters parseTextFile");
	
	NSNumber *fileSize = [[[NSFileManager defaultManager] fileAttributesAtPath:[aURL path] traverseLink:YES] objectForKey:NSFileSize];
	if ([fileSize unsignedLongLongValue] > 1024 * 1024 * 1024)
		return NO;
    NSString *chapterText = nil;
    if ([aURL isFileURL])
        chapterText = [NSString stringWithContentsOfFile:[aURL path]];
    else
        chapterText = [NSString stringWithContentsOfURL:aURL];
    if (chapterText == nil) return NO;
	return [self parseText:chapterText scaleToMovie:movie];
}
	
- (BOOL) parseText:(NSString *)chapterText scaleToMovie:(NSMovie *)movie
{
	char ch;
    NSString *buffer, *value;
    double seconds, scalar;
    long start = 0, end;
    NSMutableDictionary *aChapter;
    NSRange aRange, bRange;
    NSScanner *scan = [[NSScanner alloc] initWithString:chapterText];
    [scan setCaseSensitive:NO];
    //[theChapters removeAllObjects];
	NSMutableArray *chaps = [NSMutableArray array];
    seconds = 0;
    int timebase = 0;
    NSScanner *s = nil;
    while (![scan isAtEnd]) {
        buffer = nil;
        aChapter = nil;
        [scan scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@"\n\r"] intoString:&buffer];
        if (buffer != nil && [buffer length] > 0) {
            ch = [buffer characterAtIndex:0];
            switch (ch) {
                case '{': s = [NSScanner scannerWithString:buffer];
                        [s scanPastString:@"timeScale:"];
                        [s scanInt:&timebase];
                        break;
                case '[':
                    seconds = [MHUtil stringToNSeconds:buffer timebase:timebase];
                    //samples = seconds * (double)timeScale;
                    aChapter = [[NSMutableDictionary alloc] initWithCapacity:2];
                    [aChapter setObject:[NSNumber numberWithDouble:seconds] forKey:@"Start"];
                    [chaps addObject:aChapter];
                    break;
                default:
                    value = nil;
                    start = 0;
                    if ([chaps count] > 0) {
                        aChapter = [chaps objectAtIndex:[chaps count]-1];
                        value = [aChapter objectForKey:@"Content"];
                    }
                    if (nil != value || nil == aChapter) {
                        aChapter = [[NSMutableDictionary alloc] initWithCapacity:2];
                        [chaps addObject:aChapter];
                    }
                    aRange = [buffer rangeOfString:@"["];
                    bRange = [buffer rangeOfString:@"]"];
                    if (aRange.location != NSNotFound) {
                        if (bRange.location != NSNotFound)
                            seconds = [MHUtil stringToNSeconds:[buffer substringFromIndex:aRange.location] timebase:timebase];
                        buffer = [buffer substringToIndex:aRange.location];
                        while ([buffer hasSuffix:@" "])
                            buffer = [buffer substringToIndex:[buffer length]-1];
                    }
                    [aChapter setObject:buffer forKey:@"Content"];
                    if (nil == [aChapter objectForKey:@"Start"])
                        [aChapter setObject:[NSNumber numberWithDouble:seconds] forKey:@"Start"];
            }
        }
    }
    if (seconds)
        end = seconds;
    else
        end = start;
    int i, n = [chaps count];
    for (i=n-1; i>=0; i--) {
        if ([[chaps objectAtIndex:i] objectForKey:@"Content"] == nil)
            [chaps removeObjectAtIndex:i];
    }
    double thisStart, lastStart = 0;
    BOOL ordered = YES;
    n = [chaps count];
    for (i=0;i<n;i++) {
        thisStart = [[[chaps objectAtIndex:i] objectForKey:@"Start"] doubleValue];
        if (thisStart < lastStart) {
            ordered = NO;
            break;
        }
        lastStart = thisStart;
    }
    if (!ordered) {
        lastStart = 0;
        for (i=0;i<n;i++) {
            thisStart = [[[chaps objectAtIndex:i] objectForKey:@"Start"] doubleValue];
            aChapter = [chaps objectAtIndex:i];
            [aChapter setObject:[NSNumber numberWithDouble:lastStart] forKey:@"Start"];
            lastStart += thisStart;
        }
    }
    if (movie) {
        lastStart = [[[chaps lastObject] objectForKey:@"Start"] doubleValue];
        if (end < lastStart)
            end += lastStart;
        if (end != 0) {
            scalar = (double) GetMovieDuration([movie QTMovie]) / (double)GetMovieTimeScale([movie QTMovie]) / (double) end;
            for (i=0;i<n;i++) {
                aChapter = [chaps objectAtIndex:i];
                double aStart = [[aChapter objectForKey:@"Start"] doubleValue];
                aStart = aStart * scalar;
                [aChapter setObject:[NSNumber numberWithDouble:aStart] forKey:@"Start"];
            }
        }
    }
	[self updateWithArray:chaps];
    [self didChange];
    return YES;
}

- (void)setIgnoresTempFile:(BOOL)flag
{
	igtemp = flag;
}

- (void)copyToRealChapters
{
    //NSLog(@"MHChapters copyToRealChapters");
    //[realChapters release];
    NSMutableArray *anArray = [NSMutableArray array];
    int i, n = [theChapters count];
    //NSLog(@"copyToRealChapters: %d", n);
    for (i=0;i<n;i++)
        [anArray addObject:[NSMutableDictionary dictionaryWithDictionary:[theChapters objectAtIndex:i]]];
    realChapters = [[NSArray arrayWithArray:anArray] retain];
    //NSLog(@"count: %d", [realChapters count]);
}

+ (NSString *)dvdxchapWithArgs:(NSArray *)args getTotal:(BOOL)hm
{
    NSTask *task = [[NSTask alloc] init];
    NSPipe *pipe = [[NSPipe alloc] init];
    NSFileHandle *nullHandle = [NSFileHandle fileHandleWithNullDevice];
    NSFileHandle *handle = [pipe fileHandleForReading];
	NSString *name = @"dvdxchap";
	if (hm) name = @"dvdxchap-HM";
    NSString *resourcePath = [[NSBundle mainBundle] pathForResource:name ofType:nil];
    [task setLaunchPath:resourcePath];
    [task setArguments:args];
    [task setStandardOutput:pipe];
    [task setStandardError:pipe]; //nullHandle];
    [task launch];
	/*
	NSDictionary *env = [task environment];
	if (!env)
		NSLog(@"nil");
	env = [[NSProcessInfo processInfo] environment];
	NSLog(@"%@",env);
	 */
    [task waitUntilExit];
    NSString *output = [[[NSString alloc] initWithData:[handle readDataToEndOfFile] encoding:NSASCIIStringEncoding] autorelease];
    [task release];
    [pipe release];
    return output;
}

+ (NSString *)dvdxchapWithArgs:(NSArray *)args
{
	return [MHChapters dvdxchapWithArgs:args getTotal:NO];
}

+ (NSArray *) titlesOfDVD:(NSString *)path
{
    NSString *time, *s, *output;
    int i = 1, n = 1000, chapter;
    NSArray *lines;
    NSScanner *scan;
    NSMutableArray *titles = [[NSMutableArray alloc] init];
	output = [self dvdxchapWithArgs:[NSArray arrayWithObjects:@"-v", path, nil]];
	lines = [output componentsSeparatedByString:@"\n"];
	for (i=0;i<[lines count];i++) {
		s = [lines objectAtIndex:i];
		if ([s containsString:@"This DVD contains "]) {
			scan = [NSScanner scannerWithString:s];
			[scan scanPastString:@"This DVD contains "];
			[scan scanInt:&n];
			break;
		}
	}
	
    for (i=1;i<n;i++) {
        output = [self dvdxchapWithArgs:[NSArray arrayWithObjects:path, @"-t", [NSString stringWithFormat:@"%d", i], nil]];
        if (!output || [output isEqualToString:@""])
			if (1000 == n) break;
			else continue;
        lines = [output componentsSeparatedByString:@"\n"];
		if ([lines count] > 2) {
			s = [lines objectAtIndex:[lines count]-3];
			scan = [NSScanner scannerWithString:s];
			if ([scan scanPastString:@"CHAPTER"]) {
				[scan scanInt:&chapter];
				//[scan scanPastString:@"="];
				//[scan scanUpToString:@"." intoString:&time];
				//int j;
				//for (j=0;j<10;j++) {
					//s = [lines objectAtIndex:j];
				if ([scan scanString:@"=" intoString:nil]) {
						[scan scanUpToString:@"." intoString:&time];
						if (chapter > 2) {
							s = [NSString stringWithFormat:@"Title %d: %d Chapters, %@", i, chapter, time];
							[titles addObject:s];
						}
					}
				//}
			}
		}
	}
    if (![titles count]) NSLog(@"error parsing dvdxchap output: %@", output);
    return titles;
}

- (BOOL) parseDVD:(NSString *)path
{
    return [self parseDVD:path title:1];
}

- (BOOL) parseDVD:(NSString *)path title:(int)title
{
    return [self parseDVD:path title:title scaleToMovie:nil];
}

- (BOOL) parseDVD:(NSString *)path title:(int)title scaleToMovie:(NSMovie *)movie
{
    //NSLog(@"MHChapters parseDVD");
    if (title < 1) title = 1;
    NSString *output = nil;
	if (movie)
		output = [MHChapters dvdxchapWithArgs:[NSArray arrayWithObjects:path, @"-t", [NSString stringWithFormat:@"%d", title], nil] getTotal:YES];
	if (!output)
		output = [MHChapters dvdxchapWithArgs:[NSArray arrayWithObjects:path, @"-t", [NSString stringWithFormat:@"%d", title], nil]];
	if (![output length]) return NO;
    NSArray *lines = [output componentsSeparatedByString:@"\n"];
    int hours, minutes, seconds, miliseconds, tally = 0, i, n = [lines count];
	hours = minutes = seconds = miliseconds = -1;
    double totalSeconds = 0, nSeconds = 0;
    NSRange aRange;
	NSString *aString = nil;
	const char *buffer = nil;
	if (movie) {
		aRange = [output rangeOfString:@"TOTAL="];
		aString = [output substringFromIndex:aRange.location+6];
		buffer = [aString cString];
		sscanf(buffer, "%d:%d:%d.%d", &hours, &minutes, &seconds, &miliseconds);
		if (hours != -1 && minutes != -1 && seconds != -1 && miliseconds != -1) {
			totalSeconds = (double)miliseconds / 1000 + (double)seconds + (double)minutes * 60 + (double)hours * 60 * 60;
		}
	}
    for (i=1;i<n;i++) {
        aString = [lines objectAtIndex:i];
		if ([aString hasPrefix:@"CHAPTER"]) {
			aRange = [aString rangeOfString:@"=" options:NSBackwardsSearch];
			hours = minutes = seconds = miliseconds = -1;
			if (aRange.location != NSNotFound) {
				aString = [aString substringFromIndex:aRange.location+1];
				buffer = [aString cString];
				sscanf(buffer, "%d:%d:%d.%d", &hours, &minutes, &seconds, &miliseconds);
				if (hours != -1 && minutes != -1 && seconds != -1 && miliseconds != -1) {
					nSeconds = (double)miliseconds / 1000 + (double)seconds + (double)minutes * 60 + (double)hours * 60 * 60;
					//samples = nSeconds * (double)timeScale;
					if (tally < 0 || tally >= [theChapters count]) {
						NSMutableDictionary *newChapter = [[NSMutableDictionary alloc] init];
						[newChapter setObject:[NSString stringWithFormat:@"Chapter %d", tally+1] forKey:@"Content"];
						[newChapter setObject:[NSNumber numberWithDouble:nSeconds] forKey:@"Start"];
						[theChapters addObject:newChapter];
					} else {
						[(NSMutableDictionary *)[theChapters objectAtIndex:tally] setObject:[NSNumber numberWithDouble:nSeconds] forKey:@"Start"];
					}
					tally++;
				}
			}
        }
    }
    
    for (i=[theChapters count]-1; i>=tally; i--) {
        [theChapters removeObjectAtIndex:i];
    }
    
    
    if (movie) {
		if (!totalSeconds) totalSeconds = nSeconds;
        double scalar = (double) GetMovieDuration([movie QTMovie]) / ((double) totalSeconds * (double)GetMovieTimeScale([movie QTMovie]));
        n = [theChapters count];
        for (i=0;i<n;i++) {
            NSMutableDictionary *ch = [theChapters objectAtIndex:i];
            [ch setObject:[NSNumber numberWithDouble:scalar * [[ch objectForKey:@"Start"] doubleValue]] forKey:@"Start"];
        }
    }
    [self didChange];
    return YES;
}

- (BOOL) addToMovie:(NSMovie *)aMovie
{
    NSString *name = nil;
    MHMovie *movie = nil;
    if ([aMovie isKindOfClass:[MHMovie class]]) {
        movie = (MHMovie *)aMovie;
        int i, n = [movie trackCount];
        for (i=0;i<n;i++) {
            name = [[[movie trackAtIndex:i] chapterTrack] name];
            if (name) break;
        }
    }
    BOOL result = [self addToQTMovie:[aMovie QTMovie]];
    if (name) [[movie trackAtIndex:[movie trackCount]-1] setName:name];
    return result;
}

- (BOOL) addToQTMovie:(Movie)qtMovie
{
    MHTrack *track;
    NSString *lang;
    BOOL failure = NO;
    [self removeChaptersFromQTMovie:qtMovie];
    if (!multilangual) { // easy, just one chapter track, on the video track
        track = [[MHTrack alloc] initWithTrack:GetMovieIndTrackType(qtMovie, 1, VideoMediaType, movieTrackMediaType)];
        if (!track) track = [[MHTrack alloc] initWithTrack:GetMovieIndTrackType(qtMovie, 1, MPEGMediaType, movieTrackMediaType)];
        BOOL result = [self addToQTTrack:[track QTTrack] language:nil];
        //[track release];
        return result;
    } else { // now we have to find the best target track for each language
        NSMutableArray *languages = [NSMutableArray array];
        NSMutableDictionary *aChapter = nil;
        long i, j, k, n = [theChapters count];
        for (i=0;i<n;i++) {
            aChapter = [theChapters objectAtIndex:i];
            NSArray *keys = [aChapter allKeys];
            k = [keys count];
            for (j=0;j<k;j++) {
                lang = [keys objectAtIndex:j];
                if ([lang hasSuffix:@" Content"]) {
                    lang = [lang substringToIndex:[lang length]-8];
                    if (![languages containsObject:lang]) [languages addObject:lang];
                }
            }
        }
        n = [languages count];
        for (i=0;i<n;i++) { // for each language, find a track with an alternate
            lang = [languages objectAtIndex:i];
            track = nil;
            k = GetMovieTrackCount(qtMovie);
            for (j=1;j<=k;j++) {
                track = [[[MHTrack alloc] initWithTrack:GetMovieIndTrack(qtMovie, j)] autorelease];
                if ([[track language] isEqualToString:lang] && [track alternate]) {
                    if (![self addToQTTrack:[track QTTrack] language:lang]) failure = YES;
                    break;
                } else track = nil;
            }
            if (!track) { // didn't find one with an alternate
                for (j=1;j<=k;j++) {
                    track = [[[MHTrack alloc] initWithTrack:GetMovieIndTrack(qtMovie, j)] autorelease];
                    if ([[track language] isEqualToString:lang]) {
                        if (![self addToQTTrack:[track QTTrack] language:lang]) failure = YES;
                        break;
                    } else track = nil;
                }
            }
        }
    }
    return !failure;
}

- (BOOL) addToQTTrack:(Track)targetTrack language:(NSString *)lang
{
    if (targetTrack == nil) return NO;
    //NSString *oldName = nil;
    OSErr err = noErr;
    MHTrack *track = nil;
    /*
    NSAutoreleasePool *pool2 = [[NSAutoreleasePool alloc] init];
    
    // remove old chapters, remembering the old track name
    track = [[MHTrack alloc] initWithTrack:targetTrack];
    track = [track chapterTrack];
    if (track) {
        oldName = [track name];
        [track remove];
    }
    [track release];
    track = nil;
    if (!oldName) {
        if (lang) oldName = [NSString stringWithFormat:@"%@ Chapter Track", lang];
        else oldName = @"Chapter Track";
    }
    */
    Movie qtMovie = GetTrackMovie(targetTrack);
    TimeValue oldDur = GetMovieDuration(qtMovie);
    TimeScale ts = GetMovieTimeScale(qtMovie);
    
    NSArray *sorted = [theChapters sortedArrayUsingFunction:(chapCompare) context:nil];
    
    AliasHandle alias = NULL;
    NSString *tempFile = nil;
	if (!igtemp) tempFile = [[NSUserDefaults standardUserDefaults] objectForKey:@"textDump"];
    if (tempFile) {
		if (![[NSFileManager defaultManager] fileExistsAtPath:tempFile])
			[tempFile writeToFile:tempFile atomically:NO];
        FSSpec theFile;
        FSRef ref;
        err = FSPathMakeRef([tempFile fileSystemRepresentation], &ref, NULL);
        err = FSGetCatalogInfo(&ref, kFSCatInfoNone, NULL, NULL, &theFile, NULL);
        err = NewAliasMinimal(&theFile, &alias);
    }
    
    Rect myBounds;
    Fixed myWidth, myHeight;
    GetTrackDimensions(targetTrack, &myWidth, &myHeight);
    myBounds.top = myBounds.left = 0;
    myBounds.bottom = Fix2Long(myHeight) / 2;
    myBounds.right = Fix2Long(myWidth) / 2;
    if (!myBounds.bottom) myBounds.bottom = 40;
    if (!myBounds.right) myBounds.right = 320;
    myWidth = Long2Fix(myBounds.right);
    myHeight = Long2Fix(myBounds.bottom);
    
    Handle dataRef = (Handle)alias;
    OSType dataRefType = 0;
    if (alias) dataRefType = rAliasType;
    Track textTrack = NewMovieTrack(qtMovie, myWidth, myHeight, kNoVolume);
    Media textMedia = NewTrackMedia(textTrack, TextMediaType, ts, dataRef, dataRefType);
    if (!textMedia) return NO;
    MediaHandler myHandler = GetMediaHandler(textMedia);
    if (!myHandler) return NO;
    //SetTrackEnabled(textTrack, NO);
	SetTrackLayer(textTrack, 10);
    err = BeginMediaEdits(textMedia);
    if (err) return NO;
    int myIndex, n = [sorted count];
    long thisStart = 0, nextStart = 0;
    NSString *key = @"Content";
    if (lang) key = [lang stringByAppendingString:@" Content"];
    NSMutableDictionary *aChapter = nil;
    NSString *theContent = nil;
    NSMutableArray *sorted2 = [NSMutableArray array];
    for (myIndex = 0; myIndex < n; myIndex++) {
        aChapter = [sorted objectAtIndex:myIndex];
        theContent = [aChapter objectForKey:key];
        if ([theContent length]) [sorted2 addObject:aChapter];
    }
    BOOL added = NO;
    n = [sorted2 count];
    //NSLog(@"timescale: %ld", ts);
    for (myIndex = 0; myIndex < n; myIndex++) {
        aChapter = [sorted2 objectAtIndex:myIndex];
        theContent = [aChapter objectForKey:key];
        if ([theContent length]) {
            int strLength = [theContent length];
            const char *buffer = [theContent cString];
            if (myIndex) thisStart = (double)ts * [[aChapter objectForKey:@"Start"] doubleValue];
            else thisStart= 0;
            if (myIndex == n - 1) nextStart = oldDur;
            else nextStart = (double)ts * [[[sorted2 objectAtIndex:myIndex+1] objectForKey:@"Start"] doubleValue];
            TimeValue tv = nextStart - thisStart;
            //NSLog(@"%s %ld - %ld = %ld", buffer, nextStart, thisStart, tv);
            if (tv < 0) tv = 0 - tv;
            if (tv) {
                err = TextMediaAddTextSample( myHandler, (Ptr)buffer, strLength, 0, 0, italic, NULL, NULL, teCenter, &myBounds, dfClipToTextBox, 0, 0, 0, NULL, tv, NULL);
                if (err) NSLog(@"error: %d", err);
                else added = YES;
            }
        }
    }
    
    //[pool2 release];
    
    
    if (!added) {
        DisposeMovieTrack(textTrack);
        return NO;
    }
    err = EndMediaEdits(textMedia);
    if (err) return NO;
    err = InsertMediaIntoTrack(textTrack, 0, 0, GetMediaDuration(textMedia), fixed1);
    if (err) return NO;
    
    AddTrackReference(targetTrack, textTrack, kTrackReferenceChapterList, NULL);
    
    SetTrackLoadSettings(textTrack, -1, 0, preloadAlways, 0);
    track = [[MHTrack alloc] initWithTrack:textTrack];
    //[track setName:oldName];
    [track setName:@"Chapter Track"];
    if (lang) {
        [track setLanguage:lang];
        [track setName:[NSString stringWithFormat:@"%@ Chapter Track", lang]];
    }
	[track setEnabled:NO];
    [track release];
	
	// iDVD stuff
	UserData ud = GetTrackUserData(textTrack);
	Handle h = NewHandleClear(5);
	BlockMoveData("chap", *h, 5);
	OSType type = 'chap';
	AddUserData(ud, h, type);
	type = 'kgit';
	AddUserData(ud, h, type);

    [self copyToRealChapters];
    
    // delete any spillover if the chapters are too long (or too many)
    SetMovieSelection(qtMovie, oldDur, GetMovieDuration(qtMovie) - oldDur);
    ClearMovieSelection(qtMovie);
    return YES;
}

- (BOOL) getChaptersFromMovie:(NSMovie *)aMovie
{
    if (!aMovie) return NO;
    MHTrack *track;
    Handle h = NewHandleClear(0);
    NSString *language;
    
    [theChapters removeAllObjects];

    int i, n = [(MHMovie *)aMovie trackCount], tally = 0;
    for (i=0;i<n;i++)
        if ([[(MHMovie *)aMovie trackAtIndex:i] chapterTrack]) tally++;
    if (tally > 1) multilangual = YES;
    else multilangual = NO;
    for (i=0;i<n;i++) {
        track = [(MHMovie *)aMovie trackAtIndex:i];
        if (multilangual) language = [track language];
        else language = nil;
        track = [track chapterTrack];
        if (track) {
            TimeValue timeValue;
            GetTrackNextInterestingTime([track QTTrack], nextTimeMediaSample + nextTimeEdgeOK, 0, fixed1, &timeValue, NULL);
            while (timeValue != kBogusStartingTime) {
                GetMediaSample(GetTrackMedia([track QTTrack]), h, 0, NULL, TrackTimeToMediaTime(timeValue, [track QTTrack]), NULL, NULL, NULL, NULL, 0, NULL, NULL);
                // for text media samples, the returned handle is a 16-bit size field followed by the actual text data
		short myTextSize = *(short *)(*h);
#ifdef __LITTLE_ENDIAN__
		myTextSize = NSSwapShort(myTextSize);
#endif
                if (myTextSize) {
                    NSString *content = [NSString stringWithCString:(char *)(*h + sizeof(short)) length:myTextSize];
                    double secs = (double)timeValue / (double)GetMovieTimeScale([aMovie QTMovie]);
                    [self setContent:content forTime:secs language:language];
                }
                GetTrackNextInterestingTime([track QTTrack], nextTimeMediaSample, timeValue, fixed1, &timeValue, NULL);
            }
        }
    }
    
    //NSAutoreleasePool *pool2 = [[NSAutoreleasePool alloc] init];
    
    //[self addToMovie:aMovie];
    
    //[pool2 release];
    
    DisposeHandle(h);
    return YES;
}

- (void)setContent:(NSString *)s forTime:(double)t language:(NSString *)l
{
    int i, n = [theChapters count];
    NSString *key = @"Content";
    NSMutableDictionary *aChapter = nil;
    if (l)
        key = [NSString stringWithFormat:@"%@ Content", l];
    if (!s)
        s = @"";
    for (i=0;i<n;i++) {
        aChapter = [theChapters objectAtIndex:i];
        double time = [[aChapter objectForKey:@"Start"] doubleValue];
        if (time == t) {
            [aChapter setObject:s forKey:key];
            return;
        }
    }
    aChapter = [NSMutableDictionary dictionary];
    [aChapter setObject:s forKey:key];
    [aChapter setObject:[NSNumber numberWithDouble:t] forKey:@"Start"];
    [theChapters addObject:aChapter];
}

- (BOOL) removeChaptersFromMovie:(NSMovie *)aMovie
{
    return [self removeChaptersFromQTMovie:[aMovie QTMovie]];
}

- (BOOL) removeChaptersFromQTMovie:(Movie)mov
{
    if (!mov) return NO;
    long index, trackCount = GetMovieTrackCount(mov);
    Track aTrack = NULL, chapterTrack = NULL;
    BOOL success = NO;
    
    for (index = trackCount; index > 0; index--) {
        aTrack = GetMovieIndTrack(mov, index);
        //if (GetTrackEnabled(aTrack)) {
            chapterTrack = GetTrackReference(aTrack, kTrackReferenceChapterList, 1);
            if (chapterTrack != NULL) {
                QTUtils_DeleteAllReferencesToTrack(chapterTrack);
                DisposeMovieTrack(chapterTrack);
                success = YES;
            }
        //}
    }
    return success;
}

- (BOOL) saveToFile:(NSString *)path
{
    NSString *outputString = [self getQTChapters];

    if ([outputString writeToFile:path atomically:YES])
        return YES;
    NSBeep();
    return NO;
}

// getQTChapters translates the internal chapter database into a string suitable for import into QTPro
- (NSString *)getQTChapters
{
    NSString *chTitle;
    int i, n;
    double nSeconds = 0;
    NSMutableString *theChapterString = [[NSMutableString alloc] init];
    
    [theChapterString setString:@"{QTtext}{font:Geneva}{plain}{size:12}{textColor: 65535, 65535, 65535}{backColor: 0, 0, 0}{justify:center}{timeScale:30}{width:160}{height:96}{timeStamps:absolute}{language:0}{textEncoding:0}\r[00:00:00.00]\r"];
    n = [theChapters count];
    for (i=0;i<n;i++) {
        chTitle = [[theChapters objectAtIndex:i] objectForKey:@"Content"];
        if (!chTitle) chTitle = @"";
        if (i == n-1) {
            NSNumber *length = [[theChapters objectAtIndex:i] objectForKey:@"nSeconds"];
            if (length)
                nSeconds += [length doubleValue];  // if the last chapter has a length number, use it
            else
                nSeconds += 1; // otherwise make the last chapter 1 second
        } else
            nSeconds = [[[theChapters objectAtIndex:i+1] objectForKey:@"Start"] doubleValue];
        [theChapterString appendString:[NSString stringWithFormat:@"%@\r[%@]\r",chTitle,[MHUtil nSecondsToString:nSeconds]]];
    }

    return theChapterString;
}

int chapCompare(id one, id two, void *context) {
    double tv1 = [[one objectForKey:@"Start"] doubleValue];
    double tv2 = [[two objectForKey:@"Start"] doubleValue];
    if (tv1 < tv2) return NSOrderedAscending;
    return NSOrderedDescending;
}
/*
void removeChapterTrack(Movie theMovie) {
    long index;
    long trackCount = GetMovieTrackCount(theMovie);
    Track aTrack = NULL;
    Track chapterTrack = NULL;
    
    for (index = 1; index <= trackCount; index++) {
        aTrack = GetMovieIndTrack(theMovie, index);
        if (GetTrackEnabled(aTrack)) {
            chapterTrack = GetTrackReference(aTrack, kTrackReferenceChapterList, 1);
            if (chapterTrack != NULL) {
                QTUtils_DeleteAllReferencesToTrack(chapterTrack);
                DisposeMovieTrack(chapterTrack);
            }
        }
    }
}


Track QTText_AddTextTrack (Movie theMovie, char *theStrings[], long theFrames[], short theNumFrames, OSType theType, Boolean isChapterTrack)
{
	// get the (first) track of the specified type; this track determines the width of the new text track
	// and (if isChapterTrack is true) is the target of the new chapter track
	Track myTypeTrack = GetMovieIndTrackType(theMovie, 1, theType, movieTrackMediaType);
        if (!myTypeTrack)
            myTypeTrack = GetMovieIndTrack(theMovie, 1);
	if (myTypeTrack == NULL)
		goto bail;
	
	// get the dimensions of the target track
        Fixed myWidth, myHeight;
	GetTrackDimensions(myTypeTrack, &myWidth, &myHeight);
	TimeScale myTimeScale = GetMediaTimeScale(GetTrackMedia(myTypeTrack));

	// create the text track and media
	Track myTextTrack = NewMovieTrack(theMovie, myWidth, myHeight, kNoVolume);
	if (myTextTrack == NULL) goto bail;
		
	Media myMedia = NewTrackMedia(myTextTrack, TextMediaType, myTimeScale, NULL, 0);
	if (myMedia == NULL) goto bail;
		
	MediaHandler myHandler = GetMediaHandler(myMedia);
	if (myHandler == NULL) goto bail;
	
	SetTrackEnabled(myTextTrack, NO);
	
	// edit the track media

	OSErr myErr = BeginMediaEdits(myMedia);
	if (myErr) goto bail;
            
        Rect				myBounds;	
        short				myIndex;
        TimeRecord			myTimeRec;
		
        myBounds.top = myBounds.left = 0;
        myBounds.right = Fix2Long(myWidth);
        myBounds.bottom = Fix2Long(myHeight);
		
        // determine the duration of a sample in the track of the specified type
        //TimeValue myTypeSampleDuration = QTUtils_GetFrameDuration(myTypeTrack);
	
        for (myIndex = 0; myIndex < theNumFrames; myIndex++) {
            TimeValue		myTextSampleDuration;
            Str255			mySampleText;

            //myTextSampleDuration = myTypeSampleDuration * theFrames[myIndex];
            myTextSampleDuration = theFrames[myIndex];
			
            // set the time scale of the media to that of the movie
            myTimeRec.value.lo = myTextSampleDuration;
            myTimeRec.value.hi = 0;
            myTimeRec.scale = GetMovieTimeScale(theMovie);
            ConvertTimeScale(&myTimeRec, GetMediaTimeScale(myMedia));
            myTextSampleDuration = myTimeRec.value.lo;

            QTText_CopyCStringToPascal(theStrings[myIndex], mySampleText);
            // write out the new data to the media
            myErr = TextMediaAddTextSample(	myHandler, (Ptr)(&mySampleText[1]), mySampleText[0], 0, 0, 0, NULL, NULL, teCenter, &myBounds, dfClipToTextBox, 0, 0, 0, NULL, myTextSampleDuration, NULL);
        }

        myErr = EndMediaEdits(myMedia);
        if (myErr != noErr) goto bail;
	
        // insert the text media into the text track
        myErr = InsertMediaIntoTrack(myTextTrack, 0, 0, GetMediaDuration(myMedia), fixed1);
	if (myErr != noErr) goto bail;

	if (isChapterTrack)
		AddTrackReference(myTypeTrack, myTextTrack, kTrackReferenceChapterList, NULL);

bail:
	return(myTextTrack);
}
*/

//////////
//
// QTUtils_GetFrameDuration
// Get the duration of the first sample frame in the specified movie track.
//
//////////
/*
TimeValue QTUtils_GetFrameDuration (Track theTrack)
{	
	TimeValue	mySampleDuration;
	OSErr		myErr = noErr;

	myErr = GetMediaSample(	GetTrackMedia(theTrack),
							NULL, 		// don't return sample data
							0,
							NULL,		// don't return number of bytes of sample data returned
							0,
							NULL,
							&mySampleDuration,
							NULL,		// don't return sample description
							NULL,		// don't return sample description index
							0,			// no max number of samples
							NULL,		// don't return number of samples returned
							NULL);		// don't return sample flags

	// make sure we return a legitimate value even if GetMediaSample encounters an error
	if (myErr != noErr)
		mySampleDuration = 0;

	return(mySampleDuration);
}
*/
//////////
//
// QTText_CopyCStringToPascal
// Convert the source C string to a destination Pascal string as it's copied.
//
// The destination string will be truncated to fit into a Str255 if necessary.
// If the C string pointer is NULL, the Pascal string's length is set to zero.
//
// This routine is borrowed from CGlue.c, by Nick Kledzik.
//
//////////
/*
void QTText_CopyCStringToPascal (const char *theSrc, Str255 theDst)
{
	short					myLength = 0;
	
	// handle case of overlapping strings
	if ((void *)theSrc == (void *)theDst) {
		unsigned char		*myCurDst = &theDst[1];
		unsigned char		myChar;
				
		myChar = *(const unsigned char *)theSrc++;
		while (myChar != '\0') {
			unsigned char	myNextChar;
			
			// use myNextChar so we don't overwrite what we are about to read
			myNextChar = *(const unsigned char *)theSrc++;
			*myCurDst++ = myChar;
			myChar = myNextChar;
			
			if (++myLength >= 255)
				break;
		}
	} else if (theSrc != NULL) {
		unsigned char		*myCurDst = &theDst[1];
		short 				myOverflow = 255;		// count down, so test it loop is faster
		register char		myTemp;
	
		// we can't do the K&R C thing of “while (*s++ = *t++)” because it will copy the trailing zero,
		// which might overrun the Pascal buffer; instead, we use a temp variable
		while ((myTemp = *theSrc++) != 0) {
			*(char *)myCurDst++ = myTemp;
				
			if (--myOverflow <= 0)
				break;
		}
		myLength = 255 - myOverflow;
	}
	
	// set the length of the destination Pascal string
	theDst[0] = myLength;
}

*/
//////////
//
// QTText_GetChapterTrackForTrack
// Return the first chapter track (if any) associated with the specified track.
//
//////////
/*
Track QTText_GetChapterTrackForTrack (Track theTrack)
{
	return(GetTrackReference(theTrack, kTrackReferenceChapterList, 1));
}
*/

//////////
//
// QTText_GetChapterTrackForMovie
// Return the first chapter track (if any) in the specified movie.
//
// A movie can have more than one chapter track; if so, QuickTime uses the chapter track associated
// with the first enabled track that it finds. Accordingly, this function returns the first chapter
// track that we find as we iterate thru the movie's tracks.
//
//////////
/*
Track QTText_GetChapterTrackForMovie (Movie theMovie)
{
	long		myCount;
	long		myTrackCount = GetMovieTrackCount(theMovie);
	Track		myTrack = NULL;
	Track		myChapTrack = NULL;
	
	for (myCount = 1; myCount <= myTrackCount; myCount++) {
		myTrack = GetMovieIndTrack(theMovie, myCount);
		if (GetTrackEnabled(myTrack))
			myChapTrack = GetTrackReference(myTrack, kTrackReferenceChapterList, 1);
			if (myChapTrack != NULL)
				break;
	}
	
	return(myChapTrack);
}
*/

//////////
//
// QTText_GetFirstChapterTime
// Return, through the theTime parameter, the starting time of the first chapter of the
// specified chapter track.
//
// If this function encounters an error, it returns a (bogus) starting time of -1. Note that
// GetTrackNextInterestingTime also returns -1 as a starting time if the search criteria
// specified in the myFlags parameter are not matched by any interesting time in the movie. 
//
//////////

OSErr QTText_GetFirstChapterTime (Track theChapterTrack, TimeValue *theTime)
{
	short	myFlags = nextTimeMediaSample + nextTimeEdgeOK;	// we want the first sample in the movie
	
	if (theChapterTrack == NULL) {
		*theTime = kBogusStartingTime;						// a bogus time
		return(invalidTrack);
	}
	
	GetTrackNextInterestingTime(theChapterTrack, myFlags, (TimeValue)0, fixed1, theTime, NULL);
	return(GetMoviesError());
}


//////////
//
// QTText_GetNextChapterTime
// Return, through the theTime parameter, the starting time of the chapter of the
// specified chapter track that immediately follows the chapter starting at the time
// passed in through theTime.
//
//////////

OSErr QTText_GetNextChapterTime (Track theChapterTrack, TimeValue *theTime)
{
	short	myFlags = nextTimeMediaSample;					// we want the next sample in the movie
	
	if (theChapterTrack == NULL) {
		*theTime = kBogusStartingTime;						// a bogus time
		return(invalidTrack);
	}
	
	GetTrackNextInterestingTime(theChapterTrack, myFlags, *theTime, fixed1, theTime, NULL);
	return(GetMoviesError());
}



//////////
//
// QTText_GetIndChapterTime
// Return the starting time of the chapter in the specified chapter track that has the specified index.
//
//////////

TimeValue QTText_GetIndChapterTime (Track theChapterTrack, long theIndex)
{
	long			myCount = 1;
	TimeValue		myTime = kBogusStartingTime;

	if ((theChapterTrack == NULL) || (theIndex < 1))
		return(myTime);

	QTText_GetFirstChapterTime(theChapterTrack, &myTime);
	if (theIndex == 1)
		return(myTime);
		
	while (myCount < theIndex) {
		QTText_GetNextChapterTime(theChapterTrack, &myTime);
		myCount++;
	}
	
	return(myTime);
}


//////////
//
// QTText_GetIndChapterText
// Return the text of the chapter in the specified chapter track that has the specified index.
//
// The caller is responsible for disposing of the pointer returned by this function (by calling free).
//
//////////

char *QTText_GetIndChapterText (Track theChapterTrack, long theIndex)
{
	//long			myCount = 1;
	long			mySize;			// size of entire handle returned, which may include appended style atoms
	short			myTextSize;
	TimeValue		myTime;
	Handle			myHandle = NewHandleClear(0);
	char			*myText = NULL;

	if ((theChapterTrack == NULL) || (theIndex < 1))
		return(myText);

	if (myHandle == NULL)
		return(myText);

	myTime = QTText_GetIndChapterTime(theChapterTrack, theIndex);
	if (myTime != kBogusStartingTime) {
	
		GetMediaSample(	GetTrackMedia(theChapterTrack), myHandle, 0, &mySize, TrackTimeToMediaTime(myTime, theChapterTrack),		// media time scale
                    NULL, NULL, NULL, NULL, 0, NULL, NULL);
					
		// for text media samples, the returned handle is a 16-bit size field followed by the actual text data
		myTextSize = *(short *)(*myHandle);
                if (myTextSize > 0) {
                    myText = malloc(myTextSize + 1);
                    BlockMove(*myHandle + sizeof(short), myText, myTextSize);
                    myText[myTextSize] = '\0';
		}
                DisposeHandle(myHandle);
	}

	return(myText);
}



//////////
//
// QTText_TrackHasAChapterTrack
// Does the specified track have a chapter track?
//
//////////

Boolean QTText_TrackHasAChapterTrack (Track theTrack)
{
	return(GetTrackReference(theTrack, kTrackReferenceChapterList, 1) != NULL);
}


//////////
//
// QTText_MovieHasAChapterTrack
// Does the specified movie have a chapter track? In other words, is there are least one enabled track
// in the specified movie that has a chapter track associated wih it?
//
//////////

Boolean QTText_MovieHasAChapterTrack (Movie theMovie)
{
	long		myCount;
	long		myTrackCount = GetMovieTrackCount(theMovie);
	Track		myTrack = NULL;
	Boolean		myGotChapter = false;
	
	for (myCount = 1; myCount <= myTrackCount; myCount++) {
		myTrack = GetMovieIndTrack(theMovie, myCount);
		if (GetTrackEnabled(myTrack))
			myGotChapter = QTText_TrackHasAChapterTrack(myTrack);
			if (myGotChapter)
				break;
	}
	
	return(myGotChapter);
}


//////////
//
// QTUtils_DeleteAllReferencesToTrack
// Delete all existing track references to the specified track.
//
//////////

OSErr QTUtils_DeleteAllReferencesToTrack (Track theTrack)
{
	Movie				myMovie = NULL;
	Track				myTrack = NULL;
	long				myTrackCount = 0L;
	long				myTrRefCount = 0L;
	long				myTrackIndex;
	long				myTrRefIndex;
	OSErr				myErr = noErr;

	myMovie = GetTrackMovie(theTrack);
	if (myMovie == NULL)
		return(paramErr);

	// iterate thru all the tracks in the movie (that are different from the specified track)
	myTrackCount = GetMovieTrackCount(myMovie);
	for (myTrackIndex = 1; myTrackIndex <= myTrackCount; myTrackIndex++) {
		myTrack = GetMovieIndTrack(myMovie, myTrackIndex);
		if ((myTrack != NULL) && (myTrack != theTrack)) {
			OSType		myType = 0L;
	
			// iterate thru all track reference types contained in the current track
			myType = GetNextTrackReferenceType(myTrack, myType);
			while (myType != 0L) {

				// iterate thru all track references of the current type;
				// note that we count down to 1, since DeleteTrackReference will cause
				// any higher-indexed track references to be renumbered
				myTrRefCount = GetTrackReferenceCount(myTrack, myType);
				for (myTrRefIndex = myTrRefCount; myTrRefIndex >= 1; myTrRefIndex--) {
					Track	myRefTrack = NULL;

					myRefTrack = GetTrackReference(myTrack, myType, myTrRefIndex);
					if (myRefTrack == theTrack)
						myErr = DeleteTrackReference(myTrack, myType, myTrRefIndex);
				}

				myType = GetNextTrackReferenceType(myTrack, myType);
			}
		}
	}

	return(myErr);
}


@end
