
#import <math.h>
#import <strings.h>
#import <dpsclient/wraps.h>
#import <appkit/graphics.h>
#import <appkit/OpenPanel.h>
#import <appkit/NXImage.h>
#import <appkit/Application.h>
#import <appkit/Matrix.h>
#import <appkit/Form.h>
#import <appkit/NXBitmapImageRep.h>
#import <appkit/publicWraps.h>
#import <appkit/tiff.h>
#import <appkit/MenuCell.h>
#import <defaults/defaults.h>
#import <objc/NXBundle.h>
// Local Categories
#import "TimeCell.h"
// Local Objects
#import "Controller.h"


#import <sys/param.h>
#import <libc.h>

/* Globals */
char *gpchProgName = "MPEG Play";
char *gpchOK = "OK";

/* File extensions for MPEG files */
const char *mpgType = "mpg";


@implementation Controller:Object

/*** Instance methods ***/

- (mpegInfo *)getMPEGFrameSize:(NXSize *)fSize forFile:(const char *)mpegFile
{
	char *pchAlertTitle =
			"Error in Controller method -getMPEGFrameSize:forFile:";
	FILE *input;
	long data;
	mpegInfo *pInfo;

	// Check for NULL pointer or empty string
	if (!mpegFile || !*mpegFile)
	{
		NXRunAlertPanel(pchAlertTitle, "No filename was specified.",
				gpchOK, NULL, NULL);
		return NULL;
	}
	if (NULL == (input = fopen(mpegFile, "r")))
	{
		NXRunAlertPanel(pchAlertTitle, "Could not open file - %s.",
				gpchOK, NULL, NULL, mpegFile);
		return NULL;
	}
#define SEQ_START_CODE 0x000001b3
	// read first start code, make sure it is a sequence start code
	fread(&data, 4, 1, input);
	if (SEQ_START_CODE != data)
	{
		NXRunAlertPanel(pchAlertTitle, "This is not an MPEG stream.",
				gpchOK, NULL, NULL);
		return NULL;
	}
	if (!(pInfo = calloc(1, sizeof(mpegInfo))))
	{
		NXRunAlertPanel(pchAlertTitle, "Unable to allocate memory.",
				gpchOK, NULL, NULL);
		return NULL;
	}
	// Get horizontal and vertical size of image space
	// as two 12 bit words, respectively
	// then aspect ratio and picture rate
	// as two 4 bit words.
	fread(&data, 4, 1, input);
	pInfo->picture_rate = 0x0F & data;
	data >>= 4;
	pInfo->aspect_ratio = 0x0F & data;
	data >>= 4;
	// In Motorola format, least significant bits come last
	// v_size is actually the second value in the file
	// i.e. h:12,v:12,a:4,p:4
	pInfo->v_size = 0x0FFF & data;
	pInfo->h_size = 0x0FFF & data >> 12;
	fSize->width = ((pInfo->h_size + 15) / 16) * 16.0;
	fSize->height = ((pInfo->v_size + 15) / 16) * 16.0;
	// Get bit rate, vbv buffer size, and constrained parameter flag
	fread(&data, 4, 1, input);
	// throw away (non) intra quant matrix flags
	data >>= 2;
	pInfo->const_param_flag = 1 & data;
	data >>= 1;
	pInfo->vbv_buffer_size = 0x03FF & data;
	data >>= 10 + 1;	// 1 marker bit
	pInfo->bit_rate = 0x03FFFF & data;
	fclose(input);
	pInfo->fps = pInfo->picture_rate;
	[self showInfo:pInfo];
	return pInfo;
}


- showInfo:(mpegInfo *)pInfo
{
	[panel disableDisplay];
	// window settings
	[colorSpaceMatrix selectCellWithTag:pInfo->colorSpace];
	[zoomMatrix selectCellWithTag:pInfo->zoom];
	[backingMatrix selectCellWithTag:pInfo->backing];
	[buttonMatrix setState:pInfo->sync at:FLAG_SYNC :0];
	[buttonMatrix setState:pInfo->drop at:FLAG_DROP :0];
	[fpsForm setFloatValue:pInfo->fps at:0];
	[fpsSlider setFloatValue:pInfo->fps];
	// stream attributes
	[attributesForm setIntValue:pInfo->h_size at:I_WIDTH];
	[attributesForm setIntValue:pInfo->v_size at:I_HEIGHT];
	[attributesForm setIntValue:pInfo->aspect_ratio at:I_ASPECT];
	[attributesForm setIntValue:pInfo->picture_rate at:I_PICT_RATE];
	[attributesForm setIntValue:pInfo->bit_rate at:I_BIT_RATE];
	// measured statistics
	[statsForm setIntValue:pInfo->totalFrames at:STATS_TOTAL];
	[statsForm setIntValue:pInfo->frameCount at:STATS_FRAMES];
	[[statsForm cellAt:STATS_ELAPSED :0] setTimeFloatValue:pInfo->elapsedTime];
	[statsForm setFloatValue:((pInfo->frameCount + 1) / pInfo->elapsedTime)
			at:STATS_FPS_RATE];
	[[statsForm cellAt:STATS_ORIG_TIME :0]
			setTimeFloatValue:(pInfo->frameCount / pInfo->picture_rate)];
	[panel display];	// -reenableDisplay
	return self;
}


- setFrameNumber:(int)frame
{
	[form setIntValue:frame at:0];
	return self;
}

- (int)frameNumber
{
	return [form intValueAt:0];
}


/*** Instance variable access methods ***/

- setColorSpace:sender
{
	colorSpace = [[sender selectedCell] tag];
	return self;
}

- (int)colorSpace
{
	return colorSpace;
}


- setZoom:sender
{
	id window = [NXApp mainWindow];
	id contentView = [window contentView];

	zoom = [[sender selectedCell] tag];
	if ([contentView respondsTo:@selector(newZoom:)])
	{
		NXSize *pSize = [contentView newZoom:zoom];

		[window sizeWindow:(pSize->width * zoom) :(pSize->height * zoom)];
		[window center];
		[self windowDidMove:window];
		[contentView display];
		[contentView banner];
	}
	return self;
}

- (int)zoom
{
	return zoom;
}


- setBacking:sender
{
	backing = [[sender selectedCell] tag];
	[[NXApp mainWindow] setBackingType:backing];
	return self;
}


- setFlag:sender
{
	id view = [[NXApp mainWindow] contentView];
	id button = [sender selectedCell];
	BOOL flag = [button state];

	switch ([button tag])
	{
	case FLAG_SYNC:
		if ([view respondsTo:@selector(newSync:)])
			[view newSync:(sync = flag)];
		break;
	case FLAG_DROP:
		if ([view respondsTo:@selector(newDrop:)])
			[view newDrop:(drop = flag)];
		break;
	default:
		return nil;
	}
	return self;
}


- setFPS:sender
{
	id view = [[NXApp mainWindow] contentView];
	float value = [sender floatValue];

	if (sender == fpsSlider)
		[fpsForm setFloatValue:value at:0];
	else
		[fpsSlider setFloatValue:value];
	if ([view respondsTo:@selector(newFrameRate:)])
		[view newFrameRate:value];
	return self;
}


/*** Target/Action methods ***/

- openRequest:sender
{
	id 		openReq;
	const char	*const fileTypes[] = { mpgType, NULL };
	
	openReq = [OpenPanel new];
	if ([openReq runModalForTypes:fileTypes])
	{
		int ok;

		[NXApp openFile:[openReq filename] ok:&ok];
	}
	return self;
}

- playAgain:sender
{
	id view = [[NXApp mainWindow] contentView];

	if ([view respondsTo:@selector(runAgain)])
		[view runAgain];
	return self;
}

- updateInfoPanels:sender
{
	mpegInfo *pInfo = [sender info];

	[playAgainMenuItem setEnabled:[sender ready]];
	pInfo->backing = [[sender window] backingType];
	return [self showInfo:pInfo];
}


/*** Application delegate methods ***/

- appWillInit:sender
{
	[colorSpaceMatrix selectCellWithTag:(colorSpace = RGB_COLOR)];
	[zoomMatrix selectCellWithTag:(zoom = 1)];
	[backingMatrix selectCellWithTag:(backing = NX_BUFFERED)];
	[buttonMatrix setState:(sync = NO) at:FLAG_SYNC :0];
	[buttonMatrix setState:(drop = NO) at:FLAG_DROP :0];
	return self;
}


/* We always want to accept files. */
- (BOOL)appAcceptsAnotherFile:sender
{
	return YES;
}


/* Open mpg file from workspace. */
// -appOpenFile:type: is defunct and does not appear in the headers
- (int)app:sender openFile:(const char *)filename type:(const char *)aType
{
	id	window, view;
	mpegInfo *pInfo;
	NXSize	size;
	NXRect	rect;

	if (strcmp(aType, mpgType))
	{
		NXRunAlertPanel("Error Opening File",
				"Must have `.mpg' extension.  Cannot open %s.",
				gpchOK, NULL, NULL, filename);
		return NO;
	}
	if (NULL == (pInfo = [self getMPEGFrameSize:&size forFile:filename]))
	{
		NXRunAlertPanel("Error Parsing MPEG File",
				"Could not determine frame size for file %s.",
				gpchOK, NULL, NULL, filename);
		return NO;
	}
	pInfo->colorSpace = colorSpace;
	pInfo->zoom = zoom;
	pInfo->backing = backing;
	pInfo->sync = sync;
	pInfo->drop = drop;
	if (nil == (view = [[MPEGView alloc] initSize:(const NXSize *)&size
				info:pInfo]))
	{
		NXRunAlertPanel("Error Allocating View Object",
				"Could not allocate and initialize an instance of MPEGView "
				"with the following attributes: Width=%d, Height=%d, Zoom=%d",
				gpchOK, NULL, NULL, size.width, size.height, zoom);
		free(pInfo);
		return NO;
	}
	/* Create a new window the size of the image */
	NXSetRect(&rect, 0.0, 0.0, size.width * zoom, size.height * zoom);
	window = [[Window alloc] initContent:&rect
			style:NX_TITLEDSTYLE
			backing:backing
			buttonMask:NX_CLOSEBUTTONMASK
			defer:NO];
	[window setDelegate:self];
	[window setTitleAsFilename:filename];

	/* Replace the current contentView of the window with my new view */
	/*	and free up the old contentView */
	[[window setContentView:view] free];
	[window center];	/* Put the window in the center of the screen */
	// -center does not call -windowDidMove: ... so do it now
	[self windowDidMove:window];
	[window makeKeyAndOrderFront:self];
	[window display];
	[view runFromFile:filename];
	return YES;
}


/*** Window delegate methods ***/

- windowDidMove:sender
{
	NXRect	rect;

	[sender getFrame:&rect];
	// restrict left edge to 8 pixel boundary to enhance drawing speed
	[sender moveTo:(8.0 * rint(rect.origin.x / 8)) :rect.origin.y];
	return self;
}


- windowWillClose:sender
{
	// this won't work because Menus are counted as Windows
	if (!([[NXApp windowList] count]))
		[playAgainMenuItem setEnabled:NO];
	return self;
}


- windowDidBecomeMain:sender
{
	id view = [sender contentView];

	if ([view respondsTo:@selector(info)])
		[self updateInfoPanels:view];
	return self;
}


@end
