/* 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.
 */
#import "spriteHelper.h"

@interface spriteHelper (Internals)

- (Track)spriteFromMovie:(Movie)mov time:(TimeValue)time beginning:(BOOL)beg;
- (void)setMovieToSize:(NSSize)size;
- (void)emptyMovie:(NSMovie *)aMovie;
- (void)addSprites:(NSArray *)sprites toMovieSegment:(NSMovie *)movie;
- (void)addSprite:(NSDictionary *)sprite toMovie:(NSMovie *)movie;
- (NSString *)nameTrack:(Track)track withAction:(NSString *)actionNumber;
- (void)setAction:(NSString *)action forAtom:(QTAtomContainer)button;
- (void)newSpriteTrack:(NSDictionary *)track forMovie:(NSMovie *)newMovie;
- (NSString *)trackName:(Track)track;
- (void)name:(NSString *)name track:(Track)track;
- (void) addSprite:(NSDictionary *)sprite toKeyFrame:(QTAtomContainer)theKeyFrameSample invisibility:(BOOL)invis spriteIndex:(QTAtomID)spriteIndex;
- (BOOL) setTrackEnabledAction:(QTAtomContainer)theSprite trackType:(OSType)theType ident:(id)theIdent;
- (RGBColor)rgbColor:(NSColor *)aColor;

@end

@implementation spriteHelper

static BOOL WriteMovieToPath(Movie movie, NSString *path, BOOL flatten);
FSSpec fsSpecFromPath2(NSString *path);

- (void)awakeFromNib
{
    //NSLog(@"awake");
    [NSApp hide:self];
    [window makeKeyAndOrderFront:nil];
    //[window orderOut:nil];
    
    NSArray *args = [MHUtil args];
    if (![args count])
        args = [NSArray arrayWithObjects:@"/1.mov", @"/plist.xml", nil];
    if ([args count] != 2) {
        NSLog(@"incorrect arguments for mhspritehelper.  Movie location, input sprites location");
        NSLog([args componentsJoinedByString:@", "]);
        [NSApp terminate:nil];
    }
    //NSLog([args objectAtIndex:0]);
    NSURL *url = [NSURL fileURLWithPath:[args objectAtIndex:0]];
    //NSLog(@"added");
    [self addSprites:[args objectAtIndex:1] toMovie:url];
    [NSApp terminate:nil];
}



- (OSErr)addSprites:(NSString *)dataLocation toMovie:(NSURL *)movieURL
{
    //NSLog(@"addSprites");
    NSDictionary *menu = [NSDictionary dictionaryWithContentsOfFile:dataLocation];
    if (!menu) {
        NSLog(@"menu data file mis-formed");
        return 1;
    }
    srcMovie = [[NSMovie alloc] initWithURL:movieURL byReference:NO];
    NSMovie *newMovie = [[NSMovie alloc] initWithURL:movieURL byReference:NO];
    if (!srcMovie) {
        NSLog(@"couldn't create movie!");
        return 1;
    }
    [self emptyMovie:newMovie];
    
    //TimeValue origDur = GetMovieDuration([srcMovie QTMovie]);
    Rect box;
    GetMovieBox([srcMovie QTMovie], &box);
    NSArray *tracks = [menu objectForKey:@"tracks"];
    // set the track names
    NSArray *trackNames = [menu objectForKey:@"trackNames"];
    long i;
    for (i=0;i<[trackNames count];i++) {
        Track aTrack = GetMovieIndTrack([srcMovie QTMovie], i+1);
        NSString *aTrackName = [trackNames objectAtIndex:i];
        char *buffer = malloc(sizeof(char) * ([aTrackName length] + 1));
        [aTrackName getCString:buffer];
        SetUserDataItem(GetTrackUserData(aTrack), buffer, [aTrackName length], kUserDataName, 1);
        free(buffer);
    }
    // find the track start times
    nTracks = [tracks count];
    TimeValue totalTime = 0;
    TimeValue trackDur;
    NSMovie *movSeg;
    NSString *bg;
    /*
    startTimes = malloc(sizeof(int) * nTracks);
    for (i=0;i<nTracks;i++) {
    startTimes[i] = totalTime;
	bg = [[tracks objectAtIndex:i] objectForKey:@"bgImageSource"];
	if ([bg hasSuffix:@".mov"]) {
	    movSeg = [[NSMovie alloc] initWithURL:[NSURL fileURLWithPath:bg] byReference:NO];
	    totalTime += GetMovieDuration([movSeg QTMovie]);
	    if ([[tracks objectAtIndex:i] objectForKey:@"loop"])
		totalTime += TRACK_DURATION * 2;
	} else
	    totalTime += TRACK_DURATION;
    }
    */
    
    totalTime = 0;
    NSDictionary *aTrack, *sprite;
    for (i=0;i<nTracks;i++) {
        aTrack = [tracks objectAtIndex:i];
        trackDur = TRACK_DURATION;
        sprite = [[NSMutableDictionary alloc] init];
        bg = [aTrack objectForKey:@"bgImageSource"];
        if (bg && [bg hasSuffix:@".mov"]) {
	    BOOL looping = [[aTrack objectForKey:@"loop"] boolValue];
	    if (looping)
                movSeg = [self loopingMovie:bg targetTime:totalTime];
	    else
		movSeg = [[NSMovie alloc] initWithURL:[NSURL fileURLWithPath:bg] byReference:NO];
	    if (!movSeg) NSLog(@"No Background Movie for Track %d", i);
	    // add the sprites
	    [self addSprites:[aTrack objectForKey:@"sprites"] toMovieSegment:movSeg];
		
	    // add in the looping movie
	    SetMovieSelection([newMovie QTMovie], totalTime, 0);  // zero selection at end
	    totalTime += GetMovieDuration([movSeg QTMovie]);
	    SetMovieSelection([movSeg QTMovie], 0, GetMovieDuration([movSeg QTMovie]));
	    AddMovieSelection([newMovie QTMovie], [movSeg QTMovie]);
	} else {
                [self newSpriteTrack:aTrack forMovie:newMovie];
	}
    }
    
    //WriteMovieToPath([movie QTMovie], [outPath stringByAppendingString:@"~"], YES);
    SetMovieBox([srcMovie QTMovie], &box);
    //TimeValue newDur = GetMovieDuration([srcMovie QTMovie]);
    //OSErr err = DeleteMovieSegment([movie QTMovie], origDur, newDur-origDur);
    //NSLog(@"err: %d", err);
    //WriteMovieToPath([movie QTMovie], @"/z.mov", NO);
    id vo = [NSConnection rootProxyForConnectionWithRegisteredName:@"mhSprite" host:nil];
    NSData *data = [NSArchiver archivedDataWithRootObject:newMovie];
    if (vo && data)
        [vo submitMenus:data];
    return noErr;
}

- (NSMovie *)loopingMovie:(NSString *)path targetTime:(TimeValue)time
{
    //NSLog(@"loopingMovie");
    NSMovie *movie = [[NSMovie alloc] initWithURL:[NSURL fileURLWithPath:path] byReference:NO];
    if (!movie) return nil;
    Movie mov = [movie QTMovie];
    NSString *aName = [self trackName:GetMovieIndTrackType(mov, 1, VideoMediaType, movieTrackMediaType)];
    if (!aName)
	aName = [path lastPathComponent];
    aName = [aName stringByAppendingString:@" Sprite"];
    Track aSpriteTrack = [self spriteFromMovie:mov time:time beginning:YES];
    [self name:aName track:aSpriteTrack];
    aSpriteTrack = [self spriteFromMovie:mov time:time beginning:NO];
    [self name:aName track:aSpriteTrack];
    return movie;
}
    
- (Track)spriteFromMovie:(Movie)mov time:(TimeValue)time beginning:(BOOL)beg
{
    //NSLog(@"spriteForLoop");
    Track vidTrack = GetMovieIndTrackType(mov, 1, VideoMediaType, movieTrackMediaType);
    Fixed myWidth, myHeight;
    GetTrackDimensions(vidTrack, &myWidth, &myHeight);
    MatrixRecord m;
    Rect box;
    box.top = box.left = 0;
    box.right = Fix2Long(myWidth);
    box.bottom = Fix2Long(myHeight);
    GetTrackMatrix(vidTrack, &m);
    TransformRect(&m, &box, NULL);
    myWidth = Long2Fix(box.right - box.left);
    myHeight = Long2Fix(box.bottom - box.top);
    Track spriteTrack = NewMovieTrack(mov, myWidth, myHeight, kNoVolume);
    Media spriteMedia = NewTrackMedia(spriteTrack, SpriteMediaType, GetMovieTimeScale(mov), NULL, 0);
    QTAtomContainer theKeyFrameSample;
    OSErr myErr = QTNewAtomContainer(&theKeyFrameSample);
    if (myErr) NSLog(@"failed to make key frame sample");
    SpriteUtils_AddPICTImageToKeyFrameSample(theKeyFrameSample, GetMoviePict(mov, 0), nil, nil, 1, NULL, NULL);
    BeginMediaEdits(spriteMedia);
    QTAtomContainer myButton, myActionButton;
    myErr = QTNewAtomContainer(&myButton);
    Point myLocation;
    myLocation.h = myLocation.v = 0;
    short myLayer = 1, myIndex = 1;
    short isVisible = 1;
    myErr = SpriteUtils_SetSpriteData(myButton, &myLocation, &isVisible, &myLayer, &myIndex, NULL, NULL, NULL);
    if (myErr) NSLog(@"failed to set sprite data");
    myErr = QTCopyAtom(myButton, kParentAtomIsContainer, &myActionButton);
    if (myErr) NSLog(@"failed to copy qt atom to action button");
    if (beg)
	WiredUtils_AddMovieSetRateAction(theKeyFrameSample, kParentAtomIsContainer, kQTEventFrameLoaded, fixed1);
    else
	WiredUtils_AddMovieGoToTimeAction(theKeyFrameSample, kParentAtomIsContainer, kQTEventFrameLoaded, time);
    SpriteUtils_AddSpriteToSample(theKeyFrameSample, myActionButton, 1);
    QTDisposeAtomContainer(myActionButton);
    SpriteUtils_AddSpriteSampleToMedia(spriteMedia, theKeyFrameSample, TRACK_DURATION, true, NULL);
    myErr = EndMediaEdits(spriteMedia);
    if (myErr) NSLog(@"failed end media edits");
    TimeValue start = 0;
    if (!beg)
	start = GetMovieDuration(mov);
    InsertMediaIntoTrack(spriteTrack, start, 0, GetMediaDuration(spriteMedia), fixed1);
    RGBColor bgColor;
    bgColor.red = bgColor.green = bgColor.blue = 0xffff;  // white
    MHWired_SetTrackProperties(spriteMedia, bgColor, YES);
    long i, n = GetMovieTrackCount(mov);
    if (beg)
	for (i=1;i<=n;i++)
	    if (spriteTrack != GetMovieIndTrack(mov, i))
		SetTrackOffset(GetMovieIndTrack(mov, i), TRACK_DURATION);
    return spriteTrack;
}

- (void)emptyMovie:(NSMovie *)aMovie
{
    //NSLog(@"emptyMovie");
    Movie mov = [aMovie QTMovie];
    long i = GetMovieTrackCount(mov);
    for(;i>0;i--)
        DisposeMovieTrack(GetMovieIndTrack(mov, i));
}

- (void)addSprites:(NSArray *)sprites toMovieSegment:(NSMovie *)movie
{
    int i, n = [sprites count];
    for (i=0;i<n;i++) {
        [self addSprite:[sprites objectAtIndex:i] toMovie:movie];
    }
}

- (void)addSprite:(NSDictionary *)sprite toMovie:(NSMovie *)movie
{
    //NSLog(@"addSprite");
    NSMutableDictionary *sprite2 = [NSMutableDictionary dictionaryWithDictionary:sprite];
    NSString *pic = [sprite2 objectForKey:@"Image"];
    NSSize picSize = NSMakeSize(100,75);
    if (pic && [pic hasPrefix:@"/"]) {
	NSImage *image = [[NSImage alloc] initWithContentsOfFile:pic];
	if (image)
	    picSize = [image size];
    }
    long w = [[sprite2 objectForKey:@"Width"] intValue];
    if (!w) w = picSize.width;
    long h = [[sprite2 objectForKey:@"Height"] intValue];
    if (!h) h = picSize.height;
    Fixed myWidth = Long2Fix(w);
    Fixed myHeight = Long2Fix(h);
    //NSLog(@"width: %d height: %d", (int)Fix2Long(myWidth), (int)Fix2Long(myHeight));
    Track theTrack = NewMovieTrack([movie QTMovie], myWidth, myHeight, kNoVolume);
    Media theMedia = NewTrackMedia(theTrack, SpriteMediaType, GetMovieTimeScale([movie QTMovie]), NULL, 0);
    QTAtomContainer theKeyFrameSample;
    OSErr myErr = QTNewAtomContainer(&theKeyFrameSample);
    BeginMediaEdits(theMedia);
    int x = [[sprite2 objectForKey:@"X"] intValue];
    int y = [[sprite2 objectForKey:@"Y"] intValue];
    [sprite2 removeObjectForKey:@"X"];
    [sprite2 removeObjectForKey:@"Y"];
    
    picIndex = 1;
    [self addSprite:sprite2 toKeyFrame:theKeyFrameSample invisibility:NO spriteIndex:1];
    NSString *action = [sprite2 objectForKey:@"Action"];
    if ([action length])
	QTWired_AddCursorChangeOnMouseOver(theKeyFrameSample, 1);
    SpriteUtils_AddSpriteSampleToMedia(theMedia, theKeyFrameSample, GetMovieDuration([movie QTMovie]), true, NULL);
    myErr = EndMediaEdits(theMedia);
    InsertMediaIntoTrack(theTrack, 0, 0, GetMediaDuration(theMedia), fixed1);
    BOOL visible = NO;
    if ([sprite2 objectForKey:@"Image"])
	visible = YES;
    MHWired_SetTrackProperties(theMedia, [self rgbColor:[NSColor blackColor]], visible);
    MatrixRecord m;
    GetTrackMatrix(theTrack, &m);
    Fixed deltaH = Long2Fix(x);  // the deltaH/deltaV seems to be switched from what the Docs say
    Fixed deltaV = Long2Fix(y);
    TranslateMatrix(&m, deltaH, deltaV);
    SetTrackMatrix(theTrack, &m);
    
    [self nameTrack:theTrack withAction:[sprite2 objectForKey:@"Action"]];
}

- (NSString *)nameTrack:(Track)track withAction:(NSString *)action
{
    //NSLog(@"nameTrack");
    if (!action) return nil;
    NSScanner *scan = [NSScanner scannerWithString:action];
    [scan setCaseSensitive:NO];
    NSString *buffer = nil;
    NSString *newName = nil;
    if ([scan scanString:@"GoToTime" intoString:nil]) newName = @"Navigation Sprite";
    else if ([scan scanString:@"GoToChapter(" intoString:nil]) {
        [scan scanUpToString:@")" intoString:&buffer];
        if (buffer) {
            NSRange r = [buffer rangeOfString:@" "];
            if (r.location > 10) r.location = 10;
            buffer = [buffer substringToIndex:r.location];
            newName = [NSString stringWithFormat:@"Jump To %@ Sprite", buffer];
        } else newName = @"Chapter Selection Sprite";
    } else if ([scan scanString:@"// set" intoString:nil]) {
        NSString *def = @"Track";
        if ([scan scanString:@"audio" intoString:nil]) def = @"Audio";
        else if ([scan scanString:@"text" intoString:nil]) def = @"Text";
        newName = [NSString stringWithFormat:@"%@ Selection Sprite", def];
    }
    
    if (newName) {
	char *buffer = malloc(([newName length]+1) * sizeof(char));
	[newName getCString:buffer];
	SetUserDataItem(GetTrackUserData(track), buffer, [newName length], kUserDataName, 1);
	free(buffer);
    }
    return newName;
}

- (void)setAction:(NSString *)action forAtom:(QTAtomContainer)button
{
    //NSLog(@"setAction");
    if (!action) return;
    OSErr err = noErr;
    NSScanner *scan = [NSScanner scannerWithString:action];
    [scan setCaseSensitive:NO];
    NSString *buffer = nil;
    while (![scan isAtEnd]) {
        buffer = nil;
        if ([scan scanString:@"//" intoString:nil])
            [scan scanPastString:@"\n"];
        else if ([scan scanString:@"GoToChapter(" intoString:nil]) {
            [scan scanUpToString:@"\n" intoString:&buffer];
            if (buffer) {
                NSRange r = [buffer rangeOfString:@")" options:NSBackwardsSearch];
                if (r.length)
                    buffer = [buffer substringToIndex:r.location];
            }
            if ([buffer length]) {
                Str255 tmpStr;
                QTTextCopyCStringToPascal([buffer cString], tmpStr);
                WiredUtils_AddMovieGoToTimeByNameAction(button, kParentAtomIsContainer, kQTEventMouseClickEnd, tmpStr);
            }
        } else if ([scan scanString:@"GoToTime(" intoString:nil]) {
            long long time = -1;
            [scan scanLongLong:&time];
            [scan scanString:@")" intoString:nil];
            [scan scanString:@";" intoString:nil];
            if (time >= 0) {
                WiredUtils_AddMovieGoToTimeAction(button, kParentAtomIsContainer, kQTEventMouseClickEnd, time);
            }
        } else if ([scan scanString:@"StartPlaying" intoString:nil]) {
            [scan scanString:@";" intoString:nil];
            WiredUtils_AddMovieSetRateAction(button, kParentAtomIsContainer, kQTEventMouseClickEnd, fixed1);
        } else if ([scan scanString:@"SetRate(" intoString:nil]) {
            int rate = NSNotFound;
            [scan scanInt:&rate];
            if (rate != NSNotFound)
                WiredUtils_AddMovieSetRateAction(button, kParentAtomIsContainer, kQTEventMouseClickEnd, Long2Fix((long)rate));
        } else if ([scan scanString:@"StepForward" intoString:nil]) {
            [scan scanString:@";" intoString:nil];
            WiredUtils_AddMovieStepForwardAction(button, kParentAtomIsContainer, kQTEventMouseClickEnd);
        } else if ([scan scanString:@"StepBackward" intoString:nil]) {
            [scan scanString:@";" intoString:nil];
            WiredUtils_AddMovieStepBackwardAction(button, kParentAtomIsContainer, kQTEventMouseClickEnd);
        } else if ([scan scanString:@"GoToBeginning" intoString:nil]) {
            [scan scanString:@";" intoString:nil];
            WiredUtils_AddMovieGoToBeginningAction(button, kParentAtomIsContainer, kQTEventMouseClickEnd);
        } else if ([scan scanString:@"GoToEnd" intoString:nil]) {
            [scan scanString:@";" intoString:nil];
            WiredUtils_AddMovieGoToEndAction(button, kParentAtomIsContainer, kQTEventMouseClickEnd);
        } else if ([scan scanString:@"TrackNamed(" intoString:nil]) {
            [scan scanUpToString:@")." intoString:&buffer];
            [scan scanString:@")." intoString:nil];
            if (buffer) {
                void *theTrackTarget = (void *)QTUtils_ConvertCToPascalString([buffer cString]);
                long theTrackTargetType = kTargetTrackName;
                BOOL enabled = YES;
                if ([scan scanString:@"SetEnabled" intoString:nil]) {
                    if ([scan scanString:@"(NO)" intoString:nil] || [scan scanString:@"(FALSE)" intoString:nil])
                        enabled = NO;
                } else if ([scan scanString:@"SetDisabled" intoString:nil]) enabled = NO;
                [scan scanString:@"()" intoString:nil];
                [scan scanString:@";" intoString:nil];
                err = WiredUtils_AddTrackSetEnabledAction(button, kParentAtomIsContainer, kQTEventMouseClickEnd, theTrackTargetType, theTrackTarget, 0, enabled);
            }
        } else if ([scan scanString:@"TrackAtIndex(" intoString:nil]) {
            int index = -1;
            [scan scanInt:&index];
            if (index >= 0) {
                index++;  // stupid QT and its 1-based arrays
                void *theTrackTarget = (void *)index;
                long theTrackTargetType = kTargetTrackID;
                BOOL enabled = YES;
                [scan scanString:@")." intoString:nil];
                if ([scan scanString:@"SetEnabled" intoString:nil]) {
                    if ([scan scanString:@"(NO)" intoString:nil] || [scan scanString:@"(FALSE)" intoString:nil])
                        enabled = NO;
                } else if ([scan scanString:@"SetDisabled" intoString:nil]) enabled = NO;
                [scan scanString:@"()" intoString:nil];
                [scan scanString:@";" intoString:nil];
                err = WiredUtils_AddTrackSetEnabledAction(button, kParentAtomIsContainer, kQTEventMouseClickEnd, theTrackTargetType, theTrackTarget, 0, enabled);
            }
        } else if ([scan scanString:@"SetMovieLanguage(" intoString:nil]) {
            [scan scanUpToString:@")" intoString:&buffer];
            [scan scanString:@")" intoString:nil];
            [scan scanString:@";" intoString:nil];
            int lang = [tLanguages stringToNumber:buffer];
            if (lang >= 0)
                err = WiredUtils_AddMovieSetLanguage(button, kParentAtomIsContainer, kQTEventMouseClickEnd, lang);
	} else if ([scan scanString:@"EnterFullScreen" intoString:nil]) {
	    [scan scanString:@"()" intoString:nil];
	    [scan scanString:@";" intoString:nil];
	    Str255 tmpStr;
	    QTTextCopyCStringToPascal("noah", tmpStr);
	    //err = WiredUtils_AddApplicationNumberAndStringAction(button, kParentAtomIsContainer, kQTEventMouseClickEnd, kQTAppMessageEnterFullScreenRequested, tmpStr);
        } else { // an unknown command, skip that line
            [scan scanUpToString:@"\n" intoString:&buffer];
            if (buffer)
                NSLog(@"Unknown sprite action found: %@", buffer);
        }
    }
}

- (void)newSpriteTrack:(NSDictionary *)track forMovie:(NSMovie *)newMovie
{
    //NSLog(@"newSpriteTrack");
    TimeValue oldDur = GetMovieDuration([newMovie QTMovie]);
    Track vidTrack = GetMovieIndTrackType([srcMovie QTMovie], 1, VideoMediaType, movieTrackMediaType);
    Fixed myWidth, myHeight;
    GetTrackDimensions(vidTrack, &myWidth, &myHeight);
    MatrixRecord m;
    Rect box;
    box.top = box.left = 0;
    box.right = Fix2Long(myWidth);
    box.bottom = Fix2Long(myHeight);
    GetTrackMatrix(vidTrack, &m);
    TransformRect(&m, &box, NULL);
    myWidth = Long2Fix(box.right - box.left);
    myHeight = Long2Fix(box.bottom - box.top);
    Track myTrack = NewMovieTrack([newMovie QTMovie], myWidth, myHeight, kNoVolume);
    Media myMedia = NewTrackMedia(myTrack, SpriteMediaType, GetMovieTimeScale([newMovie QTMovie]), NULL, 0);
    QTAtomContainer theKeyFrameSample;
    OSErr myErr = QTNewAtomContainer(&theKeyFrameSample);
    NSMutableArray *sprites = [NSMutableArray array];
    if ([track objectForKey:@"bgImageSource"]) {
	NSDictionary *bgSprite = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:0], @"X",
		[NSNumber numberWithInt:0], @"Y", [NSNumber numberWithInt: Fix2Long(myWidth)], @"Width",
		[NSNumber numberWithInt: Fix2Long(myHeight)], @"Height", [track objectForKey:@"bgImageSource"], @"Image", nil];
	[sprites addObject:bgSprite];
    }
    if ([track objectForKey:@"sprites"])
	[sprites addObjectsFromArray:[track objectForKey:@"sprites"]];
    
    BeginMediaEdits(myMedia);
    int i, n = [sprites count];
    picIndex = 1;
    NSString *goodAction = nil;
    for (i=0;i<n;i++) {
	[self addSprite:[sprites objectAtIndex:i] toKeyFrame:theKeyFrameSample invisibility:YES spriteIndex:i+1];
	NSString *action = [[sprites objectAtIndex:i] objectForKey:@"Action"];
	if ([action length]) {
	    QTWired_AddCursorChangeOnMouseOver(theKeyFrameSample, i+1);
	    if (!goodAction) goodAction = action;
	}
    }
    WiredUtils_AddMovieSetRateAction(theKeyFrameSample, kParentAtomIsContainer, kQTEventFrameLoaded, Long2Fix(0));
    SpriteUtils_AddSpriteSampleToMedia(myMedia, theKeyFrameSample, TRACK_DURATION, true, NULL);
    myErr = EndMediaEdits(myMedia);
    InsertMediaIntoTrack(myTrack, 0, 0, GetMediaDuration(myMedia), fixed1);
    RGBColor color;
    NSColor *bgColor = nil;
    NSData *bgColorD = [track objectForKey:@"color"];
    if (bgColorD)
	bgColor = [NSUnarchiver unarchiveObjectWithData:bgColorD];
    if (!bgColor)
	bgColor = [NSColor blackColor];
    color = [self rgbColor:bgColor];
    MHWired_SetTrackProperties(myMedia, color, YES);
    SetTrackOffset(myTrack, oldDur);
    [self nameTrack:myTrack withAction:goodAction];
}

- (NSString *)trackName:(Track)track
{
    Handle h = NewHandleClear(0);
    GetUserData(GetTrackUserData(track), h, kUserDataName, 1);
    char *buffer = (char *)*h;
    NSString *vidName = [NSString stringWithCString:buffer length:GetHandleSize(h)];
    if (!vidName || ![vidName length]) return nil;
    return vidName;
}

- (void)name:(NSString *)name track:(Track)track
{
    char *buffer = malloc(sizeof(char) * ([name length] + 1));
    [name getCString:buffer];
    SetUserDataItem(GetTrackUserData(track), buffer, [name length], kUserDataName, 1);
    free(buffer);
}

- (void)copyImage:(NSImage *)image toGWorld:(GWorldPtr)gWorld
{
    NSArray *reps = [image representations];
    if (![reps count]) return;
    NSBitmapImageRep *rep = [reps objectAtIndex:0];
    if (![rep isKindOfClass:[NSBitmapImageRep class]]) return;
    void *myPixels = (void *)[rep bitmapData];
    int row, column, widthInPixels = [image size].width, heightInPixels = [image size].height;
    //int bitsPerPixel = [rep bitsPerPixel];
    int a, r, g, b;
    Ptr pixBaseAddr = GetPixBaseAddr(GetGWorldPixMap(gWorld));
    Rect portRect;
    GetPortBounds(gWorld, &portRect);
    for (row = 0; row < heightInPixels; row++) {
        unsigned char *dst = pixBaseAddr + row * (portRect.right - portRect.left);
        unsigned char *src = myPixels + row * widthInPixels;
        for (column = 0; column < widthInPixels; column++) {
                        r = *src++;		// get source Red component
                        g = *src++;		// get source Green component
                        b = *src++;		// get source Blue component  
                        a = *src++;		// get source Alpha component
            
                        *dst++ = a;		// set dest. Blue component 
                        *dst++ = r;		// set dest. Alpha component
                        *dst++ = g;		// set dest. Red component
                        *dst++ = b;		// set dest. Green component
        }
    }
}

- (Handle)compressImage:(NSString *)imgSource withDescription:(ImageDescriptionHandle)myDesc size:(NSSize)size
{
    NSImage *image = nil;
    NSMovie *movie = nil;
    if ([imgSource hasPrefix:@"/"]) {
        image = [[NSImage alloc] initWithContentsOfFile:imgSource];
        movie = [[NSMovie alloc] initWithURL:[NSURL fileURLWithPath:imgSource] byReference:NO];
    }
    if (!movie) return nil;
    Rect                                        myRect, portRect;
    ImageSequence                               mySeqID = 0;			// compression sequence
    long                                        myDataSize = 0;
    Ptr                                         myData = NULL;
    UInt8                                       mySimilarity;
    OSErr                                       myErr = noErr;
    GWorldPtr                                   myGWorld = NULL;
    PixMapHandle                                myPixMap = NULL;
    Movie                                       mov = [movie QTMovie];
    
    GetMovieBox(mov, &myRect);
    int pixWidth = myRect.right - myRect.left;
    int pixHeight = myRect.bottom - myRect.top;
    myRect.right = myRect.left + size.width;
    myRect.bottom = myRect.top + size.height;
    [movieView setMovie:movie];
    SetMovieBox(mov, &myRect);
    myErr = NewGWorld(&myGWorld, k32ARGBPixelFormat, &myRect, NULL, NULL, 0);
    myPixMap = GetGWorldPixMap(myGWorld);
    LockPixels(myPixMap);
    SetGWorld(myGWorld, NULL);
    GetPortBounds(myGWorld, &portRect);
    EraseRect(&portRect);
    int rowBytes = GetPixRowBytes(myPixMap);
    memset((*myPixMap)->baseAddr, 0, rowBytes * pixHeight);
    SetMovieGWorld(mov, myGWorld, GetGWorldDevice(myGWorld));
    [movieView setMovie:nil];
    MoviesTask(mov, 0);
    
    
    
    NSBitmapImageRep *myRep = [[NSBitmapImageRep alloc] initWithData:[image TIFFRepresentationUsingCompression:NSTIFFCompressionNone factor:1.0]];
    
    char *theData = [myRep bitmapData];
    
    
    /*
    // get the current graphics port and device
    //GetGWorld(&mySavedPort, &mySavedDevice);
    myRect.left = myRect.top = 0;
    myRect.right = [image size].width;
    myRect.bottom = [image size].height;
    
    NSBitmapImageRep *rep = nil;
    NSArray *reps = [image representations];
    int i, n = [reps count];
    for (i=0;i<n;i++)
        if ([[reps objectAtIndex:i] isKindOfClass:[NSBitmapImageRep class]])
            rep = [reps objectAtIndex:i];
    if (!rep) return nil;
    
    //OSErr myErr = QTNewGWorldFromPtr(&myGWImage, k32RGBAPixelFormat, &myRect, NULL, NULL, 0, bytes, [image size].width * [[[image representations] objectAtIndex:0] bitsPerPixel]);
    
    NSBitmapImageRep *bmRep = [[NSBitmapImageRep alloc]	initWithBitmapDataPlanes:NULL 
                                                                pixelsWide:[image size].width 
                                                                pixelsHigh:[image size].height 
                                                            bitsPerSample:8 
                                                        samplesPerPixel:4
                                                                hasAlpha:YES
                                                                isPlanar:NO
                                                          colorSpaceName:NSDeviceRGBColorSpace 
                                                             bytesPerRow:[image size].width * 32
                                                            bitsPerPixel:0];
    
    
    
    
    PixMapHandle myPixMap = GetGWorldPixMap(myGWorld);
    
    
    // erase the image GWorld
    //SetGWorld(myGWImage, NULL);
    //RGBBackColor(theKeyColor);
    //ClipRect(&myRect);
    //EraseRect(&myRect);
    */
    //char *buffer = malloc([image size].width * [image size].height * 4);
    //memset(buffer, 255, [image size].width * [image size].height * 4);
    //char *theData = [rep bitmapData];
    unsigned char *src;
    //unsigned char *dst = buffer;
    int row, column;
    int i, a, r, g, b;
    for (row=0;row<myRect.bottom; row++) {
        src = (*myPixMap)->baseAddr + row * GetPixRowBytes(myPixMap);
        src = theData + row * [myRep bytesPerRow];
        //dst = buffer + row * [rep bytesPerRow];
        for (column = 0;column<myRect.right;column++) {
            r = *src++;
            g = *src++;
            b = *src++;
            a = *src++;
        /*
            *dst++ = a;
            *dst++ = r;
            *dst++ = g;
            *dst++ = b;
        */
            if ((a && a < 255) || (r && r < 255) || (g && g < 255) || (b && b < 255))
                i++;
        //((UInt32 *)buffer)[i] = 0xFF0000FF;
        }
    }
    /*
    (*myPixMap)->pixelFormat = k32ARGBPixelFormat;
    (*myPixMap)->baseAddr = buffer;
    (*myPixMap)->rowBytes = [image size].width * 4;
    (*myPixMap)->bounds = myRect;
    
    
    // lock our GWorld pixmap
    LockPixels(myPixMap);
    */
    myErr = CompressSequenceBegin(&mySeqID, myPixMap, NULL, &myRect, NULL, kCompressDepth, kAnimationCodecType, 0, codecLosslessQuality, codecLosslessQuality, 2, NULL, 0, myDesc);
    myErr = GetCSequenceMaxCompressionSize(mySeqID, myPixMap, &myDataSize);
    myData = NewPtr(myDataSize);
    myErr = MemError();
    // if transparency here
    //SetGWorld(myGWImage, NULL);
    // draw the image into the GWorld
    //[self copyImage:image toGWorld:myGWImage];
    myErr = CompressSequenceFrame(mySeqID, myPixMap, NULL, 0, myData, &myDataSize, &mySimilarity, NULL);
    // end the compression sequence and clean up
    CDSequenceEnd(mySeqID);
    mySeqID = 0;
    // free the GWorld and drop references so we have more memory for PtrToHand
    //if (myGWImage != NULL)
    //    DisposeGWorld(myGWImage);
    //myGWImage = NULL;
    
    Handle theImageData;
    myErr = PtrToHand(myData, &theImageData, myDataSize);
    //SetGWorld(mySavedPort, mySavedDevice);
    if (myData) DisposePtr(myData);
    return theImageData;
}

- (void) addSprite:(NSDictionary *)sprite toKeyFrame:(QTAtomContainer)theKeyFrameSample invisibility:(BOOL)invis spriteIndex:(QTAtomID)spriteIndex
{
    //NSLog(@"addSprite toKeyFrameSample");
    QTAtomID qtIDs[3];
    long w = [[sprite objectForKey:@"Width"] intValue];
    if (!w) w = 100;
    long h = [[sprite objectForKey:@"Height"] intValue];
    if (!h) h = 75;
    Rect box;
    box.top = box.left = 0;
    box.right = w;
    box.bottom = h;
    NSData *colorD = [sprite objectForKey:@"keyColor"];
    RGBColor myKeyColor, *theKeyColor = NULL;
    if (colorD) {
	myKeyColor = [self rgbColor:[NSUnarchiver unarchiveObjectWithData:colorD]];
	theKeyColor = &myKeyColor;
    }
    NSArray *picKeys = [NSArray arrayWithObjects:@"Image", @"Rollover", @"Click", nil];
    int i;
    BOOL visible = YES;
    for (i=0;i<3;i++) {
        FSSpec fsSpec;
        PicHandle picHandle = nil;
        NSString *aPic = [sprite objectForKey:[picKeys objectAtIndex:i]];
        if (aPic || i==0) {
            qtIDs[i] = picIndex++;
            if ([aPic hasPrefix:@"/"])
                fsSpec = fsSpecFromPath2(aPic);
            else if (!aPic) {
		visible = NO;
		NSMovie *blank = [[NSMovie alloc] initWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"blank" ofType:@"mov"]] byReference:NO];
		if (blank) {
		    SetMovieBox([blank QTMovie], &box);
		    picHandle = GetMoviePict([blank QTMovie], 0);
		} else
		    qtIDs[i] = 0;
	    } else {
                SetMovieBox([srcMovie QTMovie], &box);
                picHandle = GetMoviePict([srcMovie QTMovie], [aPic intValue]);
            }
            ImageDescriptionHandle imageDesc = (ImageDescriptionHandle)NewHandle(sizeof(ImageDescription));
            Handle hand = [self compressImage:aPic withDescription:imageDesc size:NSMakeSize(w, h)];
            if (hand)
                SpriteExtras_AddCompressedImageToKeyFrameSample(theKeyFrameSample, hand, qtIDs[i], NULL, NULL, imageDesc);
            //SpriteUtils_AddPICTImageToKeyFrameSample(theKeyFrameSample, picHandle, &fsSpec, theKeyColor, qtIDs[i], NULL, NULL);
        } else
            qtIDs[i] = 0;
    }
    QTAtomContainer myButton, myActionButton, myActions = NULL;
    short myIndex = qtIDs[0];
    OSErr myErr = QTNewAtomContainer(&myButton);
    Point myLocation;
    myLocation.h = [[sprite objectForKey:@"X"] intValue];
    myLocation.v = [[sprite objectForKey:@"Y"] intValue];
    short layer = 1;
    short isVisible = 1;
    if (invis && !visible)
	isVisible = 0;
    myErr = SpriteUtils_SetSpriteData(myButton, &myLocation, &isVisible, &layer, &myIndex, NULL, NULL, myActions);
    myErr = QTCopyAtom(myButton, kParentAtomIsContainer, &myActionButton);
    if (qtIDs[1]) {
        // add rollover atoms
        WiredUtils_AddSpriteSetImageIndexAction(myActionButton, kParentAtomIsContainer, kQTEventMouseEnter, 0, NULL, 0, 0, NULL, qtIDs[1], NULL);
        if (qtIDs[0])
            WiredUtils_AddSpriteSetImageIndexAction(myActionButton, kParentAtomIsContainer, kQTEventMouseExit, 0, NULL, 0, 0, NULL, qtIDs[0], NULL);
	if (!visible) {
	    if (invis) { // the track is visible but the sprite isn't
		WiredUtils_AddSpriteSetVisibleAction(myActionButton, kParentAtomIsContainer, kQTEventMouseEnter, 0, NULL, 0, 0, NULL, 1, NULL);
		WiredUtils_AddSpriteSetVisibleAction(myActionButton, kParentAtomIsContainer, kQTEventMouseExit, 0, NULL, 0, 0, NULL, 0, NULL);
	    } else { // the sprite stays visible but the track doesn't
		// can't seem to do this one
	    }
	}
    }
    if (qtIDs[2]) {
        // add clicked image atoms
        WiredUtils_AddSpriteSetImageIndexAction(myActionButton, kParentAtomIsContainer, kQTEventMouseClick, 0, NULL, 0, 0, NULL, qtIDs[2], NULL);
        if (qtIDs[1]) // rollover
            WiredUtils_AddSpriteSetImageIndexAction(myActionButton, kParentAtomIsContainer, kQTEventMouseClickEnd, 0, NULL, 0, 0, NULL, qtIDs[1], NULL);
        else if (qtIDs[0]) // main
            WiredUtils_AddSpriteSetImageIndexAction(myActionButton, kParentAtomIsContainer, kQTEventMouseClickEnd, 0, NULL, 0, 0, NULL, qtIDs[0], NULL);
    }
    [self setAction:[sprite objectForKey:@"Action"] forAtom:myActionButton];
    SpriteUtils_AddSpriteToSample(theKeyFrameSample, myActionButton, spriteIndex);
    QTDisposeAtomContainer(myActionButton);
    

}

- (BOOL) setTrackEnabledAction:(QTAtomContainer)theSprite trackType:(OSType)theType ident:(id)theIdent
{
    //NSLog(@"setTrackEnabledAction");
    Track aTrack, targetTrack = nil;
    int i, n = GetMovieTrackCount([srcMovie QTMovie]);
    BOOL enabled, byNumber;
    OSType aType;
    NSString *aTrackName;
    char *buffer;
    Handle h;
    OSErr myErr = noErr;
    void *theTrackTarget;
    long theTrackTargetType;
    if ([theIdent isKindOfClass:[NSNumber class]]) {
        byNumber = YES;
        targetTrack = GetMovieIndTrack([srcMovie QTMovie], [theIdent intValue]);
    } else
        byNumber = NO;
    for (i=1; i<n; i++) {
        aTrack = GetMovieIndTrack([srcMovie QTMovie], i);
        GetMediaHandlerDescription(GetTrackMedia(aTrack), &aType, 0, 0);
        if (aType == theType) {
            if (byNumber) { // by number
                if (aTrack == targetTrack)
                    enabled = YES;
                else
                    enabled = NO;
                theTrackTarget = (void *)i;
                theTrackTargetType = kTargetTrackID;
            } else { // by name
                h = NewHandleClear(0);
                if (![theIdent isKindOfClass:[NSString class]]) return NO;
                GetUserData(GetTrackUserData(aTrack), h, kUserDataName, 1);
                //buffer = malloc(sizeof(char) * GetHandleSize(h));
                //BlockMoveData(h, &buffer, GetHandleSize(h));
                buffer = (char *)*h;
                aTrackName = [NSString stringWithCString:buffer length:GetHandleSize(h)];
                if (aTrackName)
                    enabled = [theIdent isEqualToString:aTrackName];
                else
                    enabled = NO;
                theTrackTarget = (void *) QTUtils_ConvertCToPascalString(buffer);
                theTrackTargetType = kTargetTrackName;
                //DisposeHandle(h);
                //NSLog(@"set track %@ enabled: %d", aTrackName, enabled);
            }
            myErr = WiredUtils_AddTrackSetEnabledAction(theSprite, kParentAtomIsContainer, kQTEventMouseClickEnd, theTrackTargetType, theTrackTarget, 0, enabled);
            if (myErr) return NO;
        }
    }
    return YES;
}


- (RGBColor)rgbColor:(NSColor *)aColor
{
    //NSLog(@"rgbColor");
    RGBColor color;
    NSColor *newColor = [aColor colorUsingColorSpaceName:@"NSCalibratedRGBColorSpace"];
    color.red = [newColor redComponent] * (double)0xffff;
    color.green = [newColor greenComponent] * (double)0xffff;
    color.blue = [newColor blueComponent] * (double)0xffff;
    return color;
}

- (void)postUpdate:(NSString *)string done:(double)done;
{
    double scale = 1.0 / (double)nTracks;
    double fullDone = done * scale + (double) iTrack / (double) nTracks;
    id vo = [NSConnection rootProxyForConnectionWithRegisteredName:@"mhSprite" host:nil];
    if (vo)
        [vo postUpdate:string done:fullDone];
}

- (void)quit
{	[NSApp terminate:nil];	}


- (void)setMovieToSize:(NSSize)size
{
    //NSLog(@"Set Movie Width: %d Height: %d", (int)size.width, (int)size.height);
    Rect box;
    if (!size.height)
        size.height = 40;
    box.left = box.top = 0;
    box.right = size.width;
    box.bottom = size.height;
    Movie mov = [[movieView movie] QTMovie];
    SetMovieBox(mov, &box);
}


// from MyMovie.m
// http://developer.apple.com/samplecode/Sample_Code/Cocoa/bMoviePalette/MyMovie.m.htm
static BOOL WriteMovieToPath(Movie movie, NSString *path, BOOL flatten)
{
    OSErr    err;
    FSRef    fsRef;
    FSSpec   fsSpec;
    NSString *newPath, *newName, *tempPath, *newFolder;
    NSFileManager *fm = [NSFileManager defaultManager];
    Movie newmovie;
    short newResId;

    newPath = [NSString stringWithFormat:@"%@~",path];
    newName = [newPath lastPathComponent];
    if ([newName length] >= 25) {
        newName = [newName substringToIndex:25];
        newFolder = [newPath stringByDeletingLastPathComponent];
        newPath = [newFolder stringByAppendingPathComponent:newName];
        tempPath = newPath;
        int count = 1;
        while ([fm fileExistsAtPath:tempPath])
            tempPath = [NSString stringWithFormat:@"%@%d", newPath, count++];
        newPath = tempPath;
    }
    
    if (![fm fileExistsAtPath:newPath])
        [[NSString stringWithString:@"this file is empty"] writeToFile:newPath atomically:NO];
    err = FSPathMakeRef([newPath fileSystemRepresentation],&fsRef, NULL /* isDirectory */);
    if (err == noErr){
        err = FSGetCatalogInfo(&fsRef, kFSCatInfoNone, NULL /*catalogInfo*/, NULL /*outName*/, &fsSpec, NULL /*parentRef*/);

        if (err == noErr){
            short resId;
            if (flatten) {
                FlattenMovie(movie,
                         flattenAddMovieToDataFork | flattenForceMovieResourceBeforeMovieData | flattenCompressMovieResource,
                         &fsSpec,'TVOD',
                         smSystemScript,
                         createMovieFileDeleteCurFile|createMovieFileDontCreateResFile,
                         &resId,nil);
                CloseMovieFile(resId);
            } else {
                //Movie newmovie;
                //short newResId;
                OSType creatorType = FOUR_CHAR_CODE('TVOD');
                err = CreateMovieFile(&fsSpec, creatorType, smSystemScript, nil, &resId, &newmovie); 
                /* 
                nil can be replaced by createMovieFileFlags
                CreateMovieFile has been superseded by CreateMovieStorage in QuickTime 6
                CreateMovieFile    http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/SOURCESI/createmoviefile.htm
                CreateMovieStorage http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/SOURCESI/createmoviestorage.htm
                */
                err = AddMovieResource (movie, resId, &newResId, nil);
                CloseMovieFile(resId);
            }
        }
        //rename([newPath fileSystemRepresentation],[path fileSystemRepresentation]);
        [[NSFileManager defaultManager] movePath:newPath toPath:path handler:nil];
        return YES;
    }
        /* Clean up here */
    //unlink([newPath fileSystemRepresentation]);
    return NO;
}


FSSpec fsSpecFromPath2(NSString *path)
{
    FSRef fsRef;
    FSSpec fsSpec;
    OSErr myErr;
    (OSStatus)myErr = FSPathMakeRef([path fileSystemRepresentation],&fsRef, NULL /* isDirectory */);
    myErr = FSGetCatalogInfo(&fsRef, kFSCatInfoNone, NULL /*catalogInfo*/, NULL /*outName*/, &fsSpec, NULL /*parentRef*/);
    return fsSpec;
}

@end
