/*
===========================================================================

Return to Castle Wolfenstein single player GPL Source Code
Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. 

This file is part of the Return to Castle Wolfenstein single player GPL Source Code (RTCW SP Source Code).  

RTCW SP Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

RTCW SP Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with RTCW SP Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the RTCW SP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW SP Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

===========================================================================
*/

#import "macosx_glimp.h"

#include "tr_local.h"
#import "macosx_local.h"
#import "macosx_display.h"

#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
#import <mach/mach.h>
#import <mach/mach_error.h>


#warning Using Mach Ports SMP acceleration implementation

/*
===========================================================

SMP acceleration

===========================================================
*/

#import <pthread.h>

#define USE_MACH_PORTS 1

// This is a small cover layer that makes for easier calling

typedef struct _MsgPort {
#if USE_MACH_PORTS
	mach_port_t port;
	id nsPort;
#else
	pthread_mutex_t mutex;
	pthread_cond_t condition;
	volatile unsigned int status;
	unsigned int msgCode;
	void          *msgData;
#endif
} MsgPort;

static BOOL portsInited = NO;
static pthread_mutex_t logMutex;

static unsigned int renderMsgOutstanding;
static unsigned int rendererProcessingCommand;

static MsgPort rendererMsgPort;
static MsgPort frontEndMsgPort;

enum {
	MsgNone,
	MsgPending,
};

enum {
	MsgCodeInvalid = 0,
	RenderCommandMsg = 1,
	RenderCompletedMsg = 2,
};

static /*inline*/ void MsgPortInit( MsgPort *port ) {
#if USE_MACH_PORTS
	port->nsPort = [[NSMachPort alloc] init];
	port->port = [port->nsPort machPort];

	//rc = mach_port_allocate(mach_task_self(), MACH_PORT_TYPE_SEND_RECEIVE, &port->port);
	//if (rc) {
	//  fprintf(stderr, "MsgPortInit: mach_port_allocate returned: %d: %s \n",rc, mach_error_string(rc));
	// }
#else
	int rc;
	rc = pthread_mutex_init( &port->mutex, NULL );
	if ( rc ) {
		ri.Printf( PRINT_ALL, "MsgPortInit: pthread_mutex_init returned: %d: %s\n", rc, strerror( rc ) );
	}
	rc = pthread_cond_init( &port->condition, NULL );
	if ( rc ) {
		ri.Printf( PRINT_ALL, "EventInit: pthread_cond_init returned %d: %s\n", rc, strerror( rc ) );
	}
	port->status = MsgNone;
	port->msgCode = MsgCodeInvalid;
	port->msgData = NULL;
#endif
}

static /*inline*/ void _SendMsg( MsgPort *port, unsigned int msgCode, void *msgData,
								 const char *functionName, const char *portName, const char *msgName ) {
	int rc;

#if USE_MACH_PORTS
	mach_msg_header_t msg;

	//printf("SendMsg: %s %s %s (%d %08lx)\n",functionName, portName, msgName, msgCode, msgData);
/*
 typedef	struct
 {
   mach_msg_bits_t	msgh_bits;
   mach_msg_size_t	msgh_size;
   mach_port_t		msgh_remote_port;
   mach_port_t		msgh_local_port;
   mach_msg_size_t  msgh_reserved;
   mach_msg_id_t		msgh_id;
 } mach_msg_header_t;
*/
	msg.msgh_bits = MACH_MSGH_BITS( MACH_MSG_TYPE_COPY_SEND,MACH_MSG_TYPE_MAKE_SEND_ONCE );
	msg.msgh_size = sizeof( msg );
	//msg.msg_type=MSG_TYPE_NORMAL;
	msg.msgh_local_port = MACH_PORT_NULL;
	msg.msgh_remote_port = port->port;
	msg.msgh_reserved = 0;
	msg.msgh_id = (mach_msg_id_t)msgData; // HACK

	rc = mach_msg_send( &msg );
	if ( rc ) {
		fprintf( stderr,"SendMsg: mach_msg_send returned %d: %s\n", rc, mach_error_string( rc ) );
	}
#else
	//printf("SendMsg: %s %s %s (%d %08lx)\n",functionName, portName, msgName, msgCode, msgData);
	rc = pthread_mutex_lock( &port->mutex );
	if ( rc ) {
		fprintf( stderr,"SendMsg: pthread_mutex_lock returned %d: %s\n", rc, strerror( rc ) );
	}

	/* Block until port is empty */
	while ( port->status != MsgNone ) {
		//fprintf(stderr, "SendMsg: %s blocking until port %s is empty\n", functionName, portName);
		rc = pthread_cond_wait( &port->condition, &port->mutex );
		if ( rc ) {
			fprintf( stderr, "SendMsg: pthread_cond_wait returned %d: %s\n", rc, strerror( rc ) );
		}
	}

	/* Queue msg */
	port->msgCode = msgCode;
	port->msgData = msgData;
	port->status = MsgPending;

	/* Unlock port */
	rc = pthread_mutex_unlock( &port->mutex );
	if ( rc ) {
		fprintf( stderr, "SendMsg: pthread_mutex_unlock returned %d: %s\n", rc, strerror( rc ) );
	}

	/* Wake up any threads blocked waiting for a message */
	rc = pthread_cond_broadcast( &port->condition );
	if ( rc ) {
		fprintf( stderr, "SendMsg: pthread_cond_broadcast returned %d: %s\n", rc, strerror( rc ) );
	}
#endif
}

static /*inline*/ void _WaitMsg( MsgPort *port, unsigned int *msgCode, void **msgData,
								 const char *functionName, const char *portName ) {
	int rc;
#if USE_MACH_PORTS
	mach_msg_empty_rcv_t msg;

	//printf("WaitMsg: %s %s\n",functionName, portName);

	msg.header.msgh_bits = MACH_MSGH_BITS( MACH_MSG_TYPE_COPY_SEND,MACH_MSG_TYPE_MAKE_SEND_ONCE );
	msg.header.msgh_size = sizeof( msg );
	//msg.msg_type=MSG_TYPE_NORMAL;
	msg.header.msgh_local_port = port->port;
	msg.header.msgh_remote_port = MACH_PORT_NULL;
	msg.header.msgh_reserved = 0;
	msg.header.msgh_id = (mach_msg_id_t)msgData; // HACK

	rc = mach_msg_receive( &msg.header );
	if ( rc ) {
		fprintf( stderr,"SendMsg: mach_msg_receive returned %d: %s\n", rc, mach_error_string( rc ) );
	}

	*msgData = (void *)msg.header.msgh_id;
	//printf("WaitMsg: %s %s got %08lx\n",functionName, portName, *msgData);
#else
	//printf("WaitMsg: %s %s\n",functionName, portName);

	rc = pthread_mutex_lock( &port->mutex );
	if ( rc ) {
		fprintf( stderr, "WaitMsg: pthread_mutex_lock returned %d: %s\n", rc, strerror( rc ) );
	}

	/* Block until port is empty */
	while ( port->status != MsgPending ) {
		rc = pthread_cond_wait( &port->condition, &port->mutex );
		if ( rc ) {
			fprintf( stderr, "WaitMsg: pthread_cond_wait returned %d: %s\n", rc, strerror( rc ) );
		}
	}

	/* Remove msg */
	*msgCode = port->msgCode;
	*msgData = port->msgData;

	//printf("WaitMsg: %s %s got %d %08lx\n",functionName, portName, *msgCode, *msgData);

	port->status = MsgNone;
	port->msgCode = 0;
	port->msgData = NULL;

	rc = pthread_mutex_unlock( &port->mutex );
	if ( rc ) {
		fprintf( stderr, "WaitMsg: pthread_mutex_unlock returned %d: %s\n", rc, strerror( rc ) );
	}

	/* Wake up any threads blocked waiting for port to be empty. */
	rc = pthread_cond_broadcast( &port->condition );
	if ( rc ) {
		fprintf( stderr, "SendMsg: pthread_cond_broadcast returned %d: %s\n", rc, strerror( rc ) );
	}
#endif
}


#define SendMsg( p, c, d ) _SendMsg( p, c, d, __PRETTY_FUNCTION__, # p, # c )
#define WaitMsg( p, c, d ) _WaitMsg( p, c, d, __PRETTY_FUNCTION__, # p )

#if 0
static void _Log( const char *msg ) {
	int rc;

	rc = pthread_mutex_lock( &logMutex );
	if ( rc ) {
		ri.Printf( PRINT_ALL, "_Log: pthread_mutex_lock returned %d: %s\n", rc, strerror( rc ) );
	}

	fputs( msg,stderr );
	fflush( stderr );

	rc = pthread_mutex_unlock( &logMutex );
	if ( rc ) {
		ri.Printf( PRINT_ALL, "_Log: pthread_mutex_unlock returned %d: %s\n", rc, strerror( rc ) );
	}
}
#endif


//
// The main Q3 SMP API
//

static void ( *glimpRenderThread )( void ) = NULL;

static void *GLimp_RenderThreadWrapper( void *arg ) {
	Com_Printf( "Render thread starting\n" );

	glimpRenderThread();

#ifndef USE_CGLMACROS
	// Unbind the context before we die
	OSX_GLContextClearCurrent();
#endif

	// Send one last message back to front end before we die...
	// This is somewhat of a hack.. fixme.
	if ( rendererProcessingCommand ) {
		SendMsg( &frontEndMsgPort, RenderCompletedMsg, NULL );
		rendererProcessingCommand = NO;
	}

	Com_Printf( "Render thread terminating\n" );

	return arg;
}

qboolean GLimp_SpawnRenderThread( void ( *function )( void ) ) {
	pthread_t renderThread;
	int rc;

	if ( !portsInited ) {
		portsInited = YES;
		MsgPortInit( &rendererMsgPort );
		MsgPortInit( &frontEndMsgPort );
		renderMsgOutstanding = NO;
		rendererProcessingCommand = NO;
		pthread_mutex_init( &logMutex, NULL );
	}

	glimpRenderThread = function;

	rc = pthread_create( &renderThread,
						 NULL, // attributes
						 GLimp_RenderThreadWrapper,
						 NULL ); // argument
	if ( rc ) {
		ri.Printf( PRINT_ALL, "pthread_create returned %d: %s", rc, strerror( rc ) );
		return qfalse;
	} else {
		rc = pthread_detach( renderThread );
		if ( rc ) {
			ri.Printf( PRINT_ALL, "pthread_detach returned %d: %s", rc, strerror( rc ) );
		}
	}

	return qtrue;
}

static volatile void   *smpData;

// TJW - This is calling in the rendering thread to wait until another
// command buffer is ready.  The command buffer returned might be NULL,
// indicating that the rendering thread should exit.
void *GLimp_RendererSleep( void ) {
	//_Log(__PRETTY_FUNCTION__ " entered");
	unsigned int msgCode;
	void *msgData;

	GLSTAMP( "GLimp_RendererSleep start", 0 );

#ifndef USE_CGLMACROS
	// Clear the current context while we sleep so the main thread can access it
	OSX_GLContextClearCurrent();
#endif

	// Let the main thread we are idle and that no work is queued
	//_Log("rs0\n");
	/* If we actually had some work to do, then tell the front end we completed it. */
	if ( rendererProcessingCommand ) {
		SendMsg( &frontEndMsgPort, RenderCompletedMsg, NULL );
		rendererProcessingCommand = NO;
	}

	// Wait for new msg
	for (;; ) {
		WaitMsg( &rendererMsgPort, &msgCode, &msgData );
		if ( 1 || msgCode == RenderCommandMsg ) {
			smpData = msgData;
			break;
		} else {
			printf( "renderer received unknown message: %d\n",msgCode );
		}
	}

#ifndef USE_CGLMACROS
	// We are going to render a frame... retake the context
	OSX_GLContextSetCurrent();
#endif

	rendererProcessingCommand = YES;

	GLSTAMP( "GLimp_RendererSleep end", 0 );

	return (void *)smpData;
}


// TJW - This is from the main thread to wait until the rendering thread
// has completed the command buffer that it has
void GLimp_FrontEndSleep( void ) {
	unsigned int msgCode;
	void *msgData;

	GLSTAMP( "GLimp_FrontEndSleep start", 1 );

	if ( renderMsgOutstanding ) {
		for (;; ) {
			WaitMsg( &frontEndMsgPort, &msgCode, &msgData );
			if ( 1 || msgCode == RenderCompletedMsg ) {
				break;
			} else {
				printf( "front end received unknown message: %d\n",msgCode );
			}
		}
		renderMsgOutstanding = NO;
	}

#ifndef USE_CGLMACROS
	// We are done waiting for the background thread, take the current context back.
	OSX_GLContextSetCurrent();
#endif

	GLSTAMP( "GLimp_FrontEndSleep end", 1 );
}


// TJW - This is called in the main thread to issue another command
// buffer to the rendering thread.  This is always called AFTER
// GLimp_FrontEndSleep, so we know that there is no command
// pending in 'smpData'.
void GLimp_WakeRenderer( void *data ) {
	GLSTAMP( "GLimp_WakeRenderer start", 1 );

#ifndef USE_CGLMACROS
	// We want the background thread to draw stuff.  Give up the current context
	OSX_GLContextClearCurrent();
#endif

	SendMsg( &rendererMsgPort, RenderCommandMsg, data );

	// Don't set flag saying that the renderer is processing something if it's just
	// being told to exit.
	//if(data != NULL)
	renderMsgOutstanding = YES;

	GLSTAMP( "GLimp_WakeRenderer end", 1 );
}
