// MPEGThread.m

#import <mach/mach.h>			// post_allocate()
#import <mach/mach_error.h>
#import <dpsclient/dpsNeXT.h>	// DPSAddPort()
#import <objc/NXBundle.h>
#import <appkit/Application.h>
#import <appkit/Panel.h>
// local objects
#import "MPEGView.h"
#import "MPEGThread.h"

#define SEND_FCODE(x)	\
	pkt.fcode = x; \
	mr = msg_send((msg_header_t *)&pkt, MSG_OPTION_NONE, 0); \
	if (SEND_SUCCESS != mr) \
		fprintf(stderr, "msg_send() failed with error %d\n", mr);


@implementation MPEGThread

/*** Initialization methods ***/

- initSize:(int)size forView:anObject
{
	kern_return_t kr;

	if (![super init])
		return nil;
	frameSize = size;
	view = anObject;
	controller = [NXApp delegate];
	if (KERN_SUCCESS != (kr = port_allocate(task_self(), &mpegPort)))
	{
		mach_error("port_allocate() failed in -initSize:forView:", kr);
		return [self free];
	}
	DPSAddPort(mpegPort, (DPSPortProc)receive_fcode, sizeof(mpegPacket),
			(void *)self, NX_MODALRESPTHRESHOLD + 1);
	return self;
}

- free
{
	kern_return_t kr;

	[self abortDecode];
	DPSRemovePort(mpegPort);
	if (KERN_SUCCESS != (kr = port_deallocate(task_self(), mpegPort)))
		mach_error("port_deallocate() failed in -free", kr);
	return [super free];
}


/*** Instance methods and Standard C wrapper functions ***/

void receive_fcode(mpegPacket *pkt, id self)
{
	[self receiveFcode:pkt];
}

- receiveFcode:(mpegPacket *)pkt
{
	switch (pkt->fcode)
	{
	case FCODE_ABORT:
		[self abortDecode];
		break;
	case FCODE_END:
		[view setAddress:(char *)address len:(int)len maxlen:(int)maxlen];
		[view runAgain];
		[self endDecode];
		break;
	default:
		if ([controller respondsTo:@selector(setFrameNumber:)])
			[controller setFrameNumber:pkt->fcode];
		break;
	}
	return self;
}


- decodeFile:(const char *)mpegFile
{
	char *pchAlertTitle = "Error in MPEGThread method -decodeFile:";
	char command[256];

	// check for NULL pointer or empty string
	if (!mpegFile || !*mpegFile)
	{
		NXRunAlertPanel(pchAlertTitle, "No filename was specified.",
				gpchOK, NULL, NULL);
		return nil;
	}
	sprintf(command, "exec %s/mpegDecode %s",
			[[NXBundle mainBundle] directory], mpegFile);
	if (NULL == (ifp = popen(command, "r")))
	{
		NXRunAlertPanel(pchAlertTitle,
				"Could not create MPEG Decode process.  Command:%s",
				gpchOK, NULL, NULL, command);
		return nil;
	}
	if (NULL == (buffer = malloc(frameSize)))
	{
		NXRunAlertPanel(pchAlertTitle, "Could not allocate memory.",
				gpchOK, NULL, NULL);
		pclose(ifp);
		return nil;
	}
	// REVIEW: check the following for errors returned?
	pStream = NXOpenFile(fileno(ifp), O_RDONLY);
	mStream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
	[controller setFrameNumber:0];

	cthreadMain = cthread_fork(mpeg_thread, self);
	cthread_detach(cthreadMain);
	return self;
}

any_t mpeg_thread(any_t self)
{
	[(MPEGThread *)self mpegThread];
	return self;
}

- mpegThread
{
	mpegPacket pkt;
	int frameNumber;
	msg_return_t mr;

	pkt.h.msg_remote_port = mpegPort;
	pkt.h.msg_simple = TRUE;
	pkt.h.msg_size = sizeof(mpegPacket);
	pkt.h.msg_local_port = PORT_NULL;
	pkt.h.msg_type = MSG_TYPE_NORMAL;

	pkt.t.msg_type_name = MSG_TYPE_INTEGER_32;
	pkt.t.msg_type_size = 32;
	pkt.t.msg_type_number = FP_NUM_INT_DATA;
	pkt.t.msg_type_inline = TRUE;
	pkt.t.msg_type_longform = FALSE;
	pkt.t.msg_type_deallocate = FALSE;

	for (frameNumber = 1; 1; frameNumber++)
	{
		int bytes = frameSize;

		if (!(bytes = NXRead(pStream, buffer, bytes)))
			break;
		if (NXWrite(mStream, buffer, bytes) != bytes)
		{
			SEND_FCODE(FCODE_ABORT);
			fprintf(stderr, "NXWrite() failed in -mpegThread\n");
			return nil;
		}
		SEND_FCODE(frameNumber);
	}
	NXFlush(mStream);
	NXGetMemoryBuffer(mStream, &address, &len, &maxlen);
	NXCloseMemory(mStream, NX_SAVEBUFFER);
	mStream = NULL;
	SEND_FCODE(FCODE_END);
	return self;
}


- endDecode
{
	kern_return_t kr;
	id result = self;

	if (cthreadMain)
	{
		if (KERN_SUCCESS != (kr = thread_suspend(cthread_thread(cthreadMain))))
		{
			result = nil;			// error suspending thread
			mach_error("thread_suspend() failed in -endDecode", kr);
		}
		if (KERN_SUCCESS != (kr = cthread_abort(cthreadMain)))
		{
			result = nil;			// error aborting thread
			mach_error("cthread_abort() failed in -endDecode", kr);
		}
		else cthreadMain = 0;
	}
	if (buffer)
		free(buffer);
	buffer = NULL;
	if (pStream)
		NXClose(pStream);
	pStream = NULL;
	if (ifp)
		pclose(ifp);
	ifp = NULL;
	return result;
}


- abortDecode
{
	[self endDecode];
	if (mStream)
		NXCloseMemory(mStream, NX_FREEBUFFER);
	address = NULL, len = maxlen = 0;
	return self;
}


@end
