/* 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 "tTimeline.h"
#include "Movies.h"
#import "tController.h"

@interface NSObject (ttAdditions)
- (void)newChapter;
- (void)setTimeInSamples:(TimeValue)time;
@end

@implementation tTimeline


- (void)image:(NSData *)data forTimeValue:(TimeValue)tv
{
    if (nil == data) return;
    NSImage *image = [NSUnarchiver unarchiveObjectWithData:data];
    [arrayLock lock];
        [thePictures setImage:image forIndex:tv];
    [arrayLock unlock];
    [tlTable reloadData];
}

- (int)numberOfRowsInTableView:(NSTableView *)aTableView
{
    if (!theDuration || !theScale || !theInterval) return 0;
    int numberOfCols = [[aTableView tableColumns] count] - 1;
    int numberOfRows = (double)theDuration / (double)theScale / (double)theInterval / (double)numberOfCols;
    if (numberOfRows < 0) numberOfRows = 0;
    return numberOfRows;
}

- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
{
    //NSImage *image = [NSImage imageNamed:@"play.jpg"];
    if (nil == currentMovie) return nil;
    if (![[aTableColumn identifier] hasPrefix:@"Data"]) return nil;
    int cols = [[aTableView tableColumns] count] - 1;
    int picIndex = rowIndex * cols;
    TimeValue this = [self timeValueForIndex:picIndex];
    TimeValue last = [self timeValueForIndex:(rowIndex-1) * cols];
    NSString *output = [NSString stringWithFormat:@"%d\n", this];
    Movie gMovie = [currentMovie QTMovie];
    if ([optKB state]) {
        wide dataSize;
        double kbSize;
        OSErr theError = GetMovieDataSize64(gMovie, last, this - last, &dataSize);
        if (theError != noErr) kbSize = -1;
        else kbSize = (double)dataSize.lo / 1024.0;
        int n = kbSize * 100.0;
        kbSize = (double)n / 100.0;
        output = [output stringByAppendingString:[NSString stringWithFormat:@"%g kB\n", kbSize]];
    }
    if ([optFPS state]) {
        int numFrames = [MHUtil framesBetweenStart:last end:this movie:gMovie];
        output = [output stringByAppendingString:[NSString stringWithFormat:@"%d frames\n", numFrames]];
    }
    if ([optTime state]) {
        NSString *time = [MHUtil nSecondsToString:this/theScale];
        output = [output stringByAppendingString:time];
    }
    return output;
}

- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
{
    if (![[aTableColumn identifier] hasPrefix:@"TL "]) {
        [aCell setFont:[NSFont userFontOfSize:10]];
        return;
    }
    NSArray *cols = [aTableView tableColumns];
    int theCol = [[[aTableColumn identifier] substringFromIndex:3] intValue];
    int picIndex = rowIndex * ([cols count] - 1) + theCol;
    NSImage *image = nil;
    TimeValue tv = [self timeValueForIndex:picIndex];
    lastIndexForDisplay = tv;
    [arrayLock lock];
        image = [thePictures imageForIndex:tv];
    [arrayLock unlock];
    if (nil == image) {
        [arrayLock lock];
        [displayBuffer addObject:[NSNumber numberWithLong:tv]];
        [arrayLock unlock];
        if ([functionLock tryLock])
            [NSThread detachNewThreadSelector:@selector(getImages) toTarget:self withObject:nil];
    } else
        [aCell setImage:image];
}

- (BOOL) tableView: (NSTableView *) tableView shouldEditTableColumn: (NSTableColumn *) tableColumn row: (int) rowIndex
{	return NO;	}

- (BOOL) tableView: (NSTableView *) tableView shouldSelectRow: (int) rowIndex
{	return NO;	}

- (void)getImages
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    long tv;
    BOOL done, aborted;
    int theWindow = 30 * theInterval * theScale * ([tlTable numberOfColumns] - 1);
    while (YES) {
        [arrayLock lock];
        if (0 == [displayBuffer count])
            break;
		if (![tlWindow isVisible]) {
			[displayBuffer removeAllObjects];
			break;
		}
        //index = [[displayBuffer lastObject] longValue];
        //[displayBuffer removeLastObject];
        tv = [[displayBuffer objectAtIndex:0] longValue];
        [displayBuffer removeObjectAtIndex:0];
        done = [thePictures hasImageForIndex:tv];
        [arrayLock unlock];
		
		NSRect r= [tlTable visibleRect];
		int index = (double)tv / (double)theScale / (double)theInterval;
		int colCount = [[tlTable tableColumns] count] - 1; // first column is for info
		int row = index / colCount;
		NSRect rowRect = [tlTable rectOfRow:row];
		//int col = index % colCount;
		//NSLog(@"rect %d, %d, %dx%d", (int)r.origin.x, (int)r.origin.y, (int)r.size.width, (int)r.size.height);
		if (r.origin.y > rowRect.origin.y + rowRect.size.height) {
			//NSLog(@"%d > %d", (int)r.origin.y, rowRect.origin.y + rowRect.size.height);
			continue;
		}
		if (r.origin.y + r.size.height < rowRect.origin.y) {
			//NSLog(@"%d < %d", (int)r.origin.y + (int)r.size.height, rowRect.origin.y);
			continue;
		}
		
        aborted = tv + theWindow < lastIndexForDisplay || tv - theWindow > lastIndexForDisplay;
        if (!done && !aborted) {
            NSData *data = [[MHUtil imageHelper] getImage:tv sender:@"mhTimeline"];
            if (data) {
                NSImage *image = [NSUnarchiver unarchiveObjectWithData:data];
                [arrayLock lock];
                    [thePictures setImage:image forIndex:tv];
                [arrayLock unlock];
                //[tlTable reloadData];
                [tlTable performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO];
            }
        }
    }
    [arrayLock unlock];
    [functionLock unlock];
    [pool release];
}

- (TimeValue)timeValueForIndex:(int)index
{
    if (index < 0) index = 0;
    TimeValue tv = (double)index * (double)theScale * (double)theInterval;
    OSType 			theType = VideoMediaType;
    short			flags = nextTimeMediaSample;
    if ([keyframes state]) {
        flags = nextTimeSyncSample;
    }
    GetMovieNextInterestingTime([currentMovie QTMovie], flags, 1, &theType, tv, 1, &tv, nil);
    return tv;
}

- (IBAction)doSingleClick:(id)sender
{
    lastClickedColumn = [tlTable clickedColumn];
    lastClickedRow = [tlTable clickedRow];
    int picIndex = lastClickedRow * ([[tlTable tableColumns] count] - 1) + lastClickedColumn - 1;
    NSImage *image = [thePictures imageForIndex:[self timeValueForIndex:picIndex]];
    [imageView setImage:image];
    [imageView setNeedsDisplay:YES];
}

- (IBAction)doDoubleClick:(id)sender
{
    //if (![functionLock tryLock]) return;
    //[functionLock unlock];
    int column = lastClickedColumn - 1;
    int cols = [[tlTable tableColumns] count] - 1;
    int index = lastClickedRow * cols + column;
    NSMovie *theMovie = [movieView movie];
	if (![theMovie isKindOfClass:[NSMovie class]])
		theMovie = [theMovie delegate];
    if (theMovie == nil) return;
    TimeValue tv = [self timeValueForIndex:index];
    TimeRecord oldTR;
    TimeValue aTV;
    
    aTV = GetMovieTime([theMovie QTMovie], &oldTR);
    oldTR.value.lo = tv;
    SetMovieTime([theMovie QTMovie], &oldTR);
    [movieWindow makeKeyAndOrderFront:nil];
}

- (IBAction)newChapter:(id)sender
{
    [chapters newChapter];
    [self currentChapter:nil];
}

- (IBAction)currentChapter:(id)sender
{
    TimeValue time = [self timeValueForIndex: lastClickedRow * ([[tlTable tableColumns] count] - 1) + lastClickedColumn - 1];
    [chapters setTimeInSamples:time];
}

- (IBAction)movieTime:(id)sender
{
    NSMovie *aMovie = [movieView movie];
	if (![aMovie isKindOfClass:[NSMovie class]])
		aMovie = [aMovie delegate];
    if (nil == aMovie) return;
    
    TimeRecord TR;
    TimeValue TV = GetMovieTime([aMovie QTMovie], &TR);
    int cols = [[tlTable tableColumns] count] - 1;
    if (0 == cols || 0 == theInterval || 0 == theScale) return;
    int row = (double)TV / (double)cols / theInterval / (double)theScale;
    [tlTable scrollRowToVisible:row];
}

- (IBAction)open:(id)sender
{
    [tlWindow makeKeyAndOrderFront:nil];
    MHMovie *aMovie = currentMovie;
    currentMovie = nil;
    [self movieDidChangeTo:aMovie];
    [tlTable reloadData];
    [tlTable selectRow:-1 byExtendingSelection:NO];
}

- (void)awakeFromNib
{
    //NSLog(@"tTimeline");
    [tlTable setDrawsGrid:YES];
    [tlTable setTarget:self];
    [tlTable setDoubleAction:@selector(doDoubleClick:)];
    [(NSTableView *)tlTable setAction:@selector(doSingleClick:)];
    //NSTableColumn *theColumn = [tlTable tableColumnWithIdentifier:@"Timeline 0"];
    //NSCell *aCell = [NSImageCell new];
    //[theColumn setDataCell:aCell];
    if ([intervalField floatValue] <= 0) [intervalField setFloatValue:1];
    [self interval:nil];
    thePictures = [[tImageBuffer alloc] init];
    //theCells = [[NSMutableArray alloc] init];
    [tlScroll setScrollsDynamically:NO];
    displayBuffer = [[NSMutableArray alloc] init];
    arrayLock = [[NSLock alloc] init];
    functionLock = [[NSLock alloc] init];
    lastIndexForDisplay = 0;
    [self clear];
    lastClickedRow = lastClickedColumn = 0;
    
    NSConnection *theConnection = [NSConnection defaultConnection];
    [theConnection setRootObject:self];
    if ([theConnection registerName:@"mhTimeline"] == NO) {
      NSLog(@"Failed to register name\n");
    }
    [theConnection retain];
    [theConnection setDelegate:self];
}

- (IBAction)interval:(id)sender
{
    theInterval = [intervalField floatValue];
    [tlTable reloadData];
}

- (IBAction)columns:(id)sender
{
    NSArray *columns = [tlTable tableColumns];
    int i, n = [columns count];
    //NSLog([NSString stringWithFormat:@"Removing %d Columns", n]);
    for (i=n-1;i>-0;i--) {
        NSTableColumn *aColumn = [columns objectAtIndex:i];
        if ([[aColumn identifier] hasPrefix:@"TL "])
            [tlTable removeTableColumn:aColumn];
    }
    columns = [tlTable tableColumns];
    int target = [colSelect indexOfSelectedItem]+2;
    int current = [columns count];
    //NSLog([NSString stringWithFormat:@"current: %d", current]);
    NSTableColumn *aColumn;
    while (current != target) {
        if (current > target) {
            [tlTable removeTableColumn:[columns objectAtIndex:current-1]];
        } else {
            NSString *name = [NSString stringWithFormat:@"TL %d", current-1];
            //NSLog([NSString stringWithFormat:@"adding: %@", name]);
            aColumn = [[NSTableColumn alloc] initWithIdentifier:name];
            [[aColumn headerCell] setStringValue:name];
            [aColumn setDataCell:[NSImageCell new]];
            [aColumn setMinWidth:movieWidth];
            [aColumn setWidth:movieWidth];
            [aColumn setMaxWidth:movieWidth];
            [tlTable addTableColumn:aColumn];
        }
        columns = [tlTable tableColumns];
        current = [columns count];
    }
    [tlTable reloadData];
}

- (IBAction)singleFrame:(id)sender
{
    if (currentMovie == nil) return;
    int fps = [[currentMovie objectForKey:@"FPS"] intValue];
    if (0 == fps) return;
    [intervalField setFloatValue:1.0 / (double)fps];
    [self interval:nil];
}

- (void)movieDidChangeTo:(MHMovie *)newMovie
{
    //NSLog(@"tTimeline movieDidChangeTo");
    if (currentMovie == newMovie) return;
    [self clear];
    if (nil == newMovie)
        return;
    currentMovie = newMovie;
    if (![tlWindow isVisible])
        return;
    id helper = [MHUtil imageHelper];
    
    NSURL *url = [newMovie URL];
    if (nil == url)
        url = [MHUtil stringToURL:[newMovie objectForKey:@"File Path"]];
    
    [helper openMovie:url];
    
    [self initMovie];
}

- (void)initMovie
{
    //NSLog(@"timeline: movieDidChange");
    if (nil == currentMovie) {
        //NSLog(@"nil");
        return;
    }
    Movie gMovie = [currentMovie QTMovie];
    theDuration = GetMovieDuration(gMovie);
    theScale = GetMovieTimeScale(gMovie);
    NSRect frame = [movieWindow frame];
    movieWidth = (frame.size.width - 10) * 32 / (frame.size.height - 38);
    [self columns:nil];
}

- (void)clear
{
    [thePictures empty];
    theScale = 0;
    theDuration = 0;
    currentMovie = nil;
    lastClickedRow = 0;
    lastClickedColumn = 0;
}

@end
