// MPEGView.m

#import <appkit/NXImage.h>
#import <appkit/graphics.h>
#import <stdio.h>
#import <stdlib.h>
#import <appkit/NXBitmapImageRep.h>
#import <appkit/Application.h>
#import <appkit/Panel.h>
#import <streams/streams.h>
#import <sys/file.h>
#import <sys/time.h>
#import <string.h>
#import <appkit/tiff.h>
#import <dpsclient/event.h>		/* for NXEvent */
#import <dpsclient/dpsNeXT.h>	// for DPSTimedEntry
#import <dpsclient/psops.h>		// for PSshow()
#import <objc/NXBundle.h>
#import <mach/mach.h>			// vm_deallocate()
#import <mach/mach_error.h>
// Local Categories
#import "BitmapImageRepData.h"
// Local Objects
#import "wraps.h"
#import "Controller.h"
#import "MPEGThread.h"
#import "MPEGView.h"

#define PERIOD_KEY	46	/* period's character code (portable), used in checking for abort (command-period) */

@implementation MPEGView : View

/*** Initialization methods ***/

- initSize:(const NXSize *)aSize info:(mpegInfo *)pInfo
{
    NXRect imageRect;

	NXSetRect(&imageRect, 0.0, 0.0, aSize->width, aSize->height);
    if (![super initFrame:&imageRect])
		return nil;
	if (nil == (theBitmap = [[NXBitmapImageRep alloc] initData:NULL
			pixelsWide:(int)aSize->width
			pixelsHigh:(int)aSize->height
			bitsPerSample:8
			samplesPerPixel:3	// (cSpace == RGB_COLOR) ? 3 : 1
			hasAlpha:NO
			isPlanar:NO
			colorSpace:NX_RGBColorSpace
			bytesPerRow:0
			bitsPerPixel:0]))
		return [self free];
	frameLength = 3 * [theBitmap pixelsWide] * [theBitmap pixelsHigh];
	if (nil == (mpegThread = [[MPEGThread alloc]
			initSize:(4 + frameLength) forView:self]))
	{
		[theBitmap free];
		return [self free];
	}
	data = [(NXBitmapImageRep *)theBitmap data];
	theSize = *aSize;
	info = pInfo;
	[self newZoom:pInfo->zoom];
	procIndex = (pInfo->drop << 1) + pInfo->sync;
	controller = [NXApp delegate];
	tag = (DPSTimedEntry)-1;
    return self;
}

- free
{
	[mpegThread free];
	if ((DPSTimedEntry)-1 != tag)
		DPSRemoveTimedEntry(tag);
	[theBitmap setData:data];
	theBitmap = [theBitmap free];
	if (address && len)
	{
		kern_return_t kr;

		kr = vm_deallocate(task_self(), (vm_address_t)address, len);
		if (KERN_SUCCESS != kr)
			mach_error("vm_deallocate() failed in -free", kr);
	}
	address = NULL;
	if (info)
		free(info);
	info = NULL;
    return [super free];
}


/*** Standard C functions ***/

double ReadSysClock(void)
{
  struct timeval tv;

  gettimeofday(&tv, NULL);
  return (tv.tv_sec + tv.tv_usec / 1000000.0);
}


#define DROP_FRAME	\
	if (0.0 == self->now) \
	{ \
		self->now = now; \
		frameNumber = 0; \
	} \
	else \
		frameNumber = (int)((now - self->now) * self->info->fps); \
	if (frameNumber == self->frameNumber) \
		return; \
	self->adrs = self->address + (4 + self->frameLength) * frameNumber;
#define CHECK_FOR_END	\
	if (self->adrs >= self->end) \
	{ \
		self->info->elapsedTime = ReadSysClock() - self->startTime; \
		DPSRemoveTimedEntry(tag); \
		self->tag = (DPSTimedEntry)-1; \
		[self display]; \
		self->info->frameCount = self->frameCount; \
		if ([self->controller respondsTo:@selector(updateInfoPanels:)]) \
			[self->controller updateInfoPanels:self]; \
		return; \
	}
#define SLOW_STUFF	[controller setFrameNumber:frameNumber];
#define SHOW_FRAME	\
	[self->theBitmap setData:self->adrs]; \
	[self display]; \
	self->adrs += self->frameLength; \
	self->frameCount++;

#define FAST

void te_none(DPSTimedEntry tag, double now, void *userData)
{
	MPEGView *self = userData;
	int frameNumber;

	CHECK_FOR_END
	frameNumber = *((long *)self->adrs)++;
#ifndef FAST
	SLOW_STUFF
#endif
	SHOW_FRAME
}

void te_sync(DPSTimedEntry tag, double now, void *userData)
{
	MPEGView *self = userData;
	int frameNumber;

	CHECK_FOR_END
	frameNumber = *((long *)self->adrs)++;
#ifndef FAST
	SLOW_STUFF
#endif
	SHOW_FRAME
	NXPing();
}

void te_drop(DPSTimedEntry tag, double now, void *userData)
{
	MPEGView *self = userData;
	int frameNumber;

	DROP_FRAME
	CHECK_FOR_END
	frameNumber = *((long *)self->adrs)++;
#ifndef FAST
	SLOW_STUFF
#endif
	SHOW_FRAME
}

void te_syncNdrop(DPSTimedEntry tag, double now, void *userData)
{
	MPEGView *self = userData;
	int frameNumber;

	DROP_FRAME
	CHECK_FOR_END
	frameNumber = *((long *)self->adrs)++;
#ifndef FAST
	SLOW_STUFF
#endif
	SHOW_FRAME
	NXPing();
}

DPSTimedEntryProc procs[] = { te_none, te_sync, te_drop, te_syncNdrop };


/*** Instance methods ***/

- runAgain
{
	double period = 1.0 / info->fps;
	DPSTimedEntryProc proc = procs[procIndex];

	frameCount = 0;
	frameNumber = -1;
	[controller setFrameNumber:0];
	[controller updateInfoPanels:self];
	adrs = address;
	end = address + len - 4;
	now = 0.0;
	startTime = ReadSysClock();
	tag = DPSAddTimedEntry(period, proc, self, MAX_PRIORITY);
	if ((DPSTimedEntry)-1 == tag)
		fprintf(stderr, "DSPAddTimedEntry() failed in -runAgain\n");
	return self;
}


- runFromFile:(const char *)mpegFile
{
	// show something in the new window while preloading
	[self banner];

	[mpegThread decodeFile:mpegFile];
	return self;
}


- banner
{
	if ([self ready])
		return nil;
	[self lockFocus];
	PSWpreloading(theWidth, theHeight);
	[self unlockFocus];
	[[self window] flushWindow];
	return self;
}


- setAddress:(char *)anAddress len:(int)aLen maxlen:(int)aMaxlen
{
	address = anAddress;
	len = aLen;
	maxlen = aMaxlen;
	info->totalFrames = [controller frameNumber];
	return self;
}

- (BOOL)ready
{
	return address ? YES : NO;
}


- (mpegInfo *)info
{
	return info;
}


- (NXSize *)newZoom:(int)zoom
{
	info->zoom = zoom;
	theWidth = theSize.width * zoom;
	theHeight = theSize.height * zoom;
	NXSetRect(&theRect, 0, 0, theWidth, theHeight);
	return &theSize;
}

- newSync:(BOOL)flag
{
	info->sync = flag;
	procIndex = (info->drop << 1) + info->sync;
	return self;
}

- newDrop:(BOOL)flag
{
	info->drop = flag;
	procIndex = (info->drop << 1) + info->sync;
	return self;
}

- newFrameRate:(float)fps
{
	info->fps = fps;
	return self;
}


/*** View Instance methods ***/

- drawSelf:(const NXRect *)rects :(int)rectCount
{
	[theBitmap drawIn:&theRect];
//	[theBitmap draw];			// this method will not scale the bitmap
	return self;
}


@end
