/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.

This file is part of Quake III Arena source code.

Quake III Arena 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 2 of the License,
or (at your option) any later version.

Quake III Arena 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 Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  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 <pthread.h>

//
// The main Q3 SMP API
//

static pthread_mutex_t  smpMutex;
static pthread_cond_t   mainThreadCondition;
static pthread_cond_t   renderThreadCondition;

static  volatile qboolean  smpDataChanged;
static	volatile void     *smpData;


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

    ((void (*)())arg)();

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

    Com_Printf("Render thread terminating\n");
	
    return arg;
}

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

    pthread_mutex_init(&smpMutex, NULL);
    pthread_cond_init(&mainThreadCondition, NULL);
    pthread_cond_init(&renderThreadCondition, NULL);
    
    rc = pthread_create(&renderThread, NULL, GLimp_RenderThreadWrapper, function);
    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;
}

// Called in the rendering thread to wait until a command buffer is ready.
// The command buffer returned might be NULL, indicating that the rendering thread should exit.
void *GLimp_RendererSleep(void)
{
    void *data;
    
    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

    pthread_mutex_lock(&smpMutex); {
        // Clear out any data we had and signal the main thread that we are no longer busy
        smpData = NULL;
        smpDataChanged = qfalse;
        pthread_cond_signal(&mainThreadCondition);
        
        // Wait until we get something new to work on
        while (!smpDataChanged)
            pthread_cond_wait(&renderThreadCondition, &smpMutex);
            
        // Record the data (if any).
        data = smpData;
    } pthread_mutex_unlock(&smpMutex);
    
#ifndef USE_CGLMACROS
    // We are going to render a frame... retake the context
    OSX_GLContextSetCurrent();
#endif

    GLSTAMP("GLimp_RendererSleep end", 0);

    return (void *)data;
}

// Called from the main thread to wait until the rendering thread is done with the command buffer.
void GLimp_FrontEndSleep(void)
{
    GLSTAMP("GLimp_FrontEndSleep start", 0);

    pthread_mutex_lock(&smpMutex); {
        while (smpData) {
#if 0
            struct timespec ts;
            int result;
            
            ts.tv_sec = 1;
            ts.tv_nsec = 0;
            result = pthread_cond_timedwait_relative_np(&mainThreadCondition, &smpMutex, &ts);
            if (result) {
                Com_Printf("GLimp_FrontEndSleep timed out.  Probably due to R_SyncRenderThread called due to Com_Error being called\n");
                break;
            }
#else
            pthread_cond_wait(&mainThreadCondition, &smpMutex);
#endif
        }
    } pthread_mutex_unlock(&smpMutex);


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

    GLSTAMP("GLimp_FrontEndSleep end", 0);
}

// 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", data);

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

    pthread_mutex_lock(&smpMutex); {
        // Store the new data pointer and wake up the rendering thread
        assert(smpData == NULL);
        smpData = data;
        smpDataChanged = qtrue;
        pthread_cond_signal(&renderThreadCondition);
    } pthread_mutex_unlock(&smpMutex);
    
    GLSTAMP("GLimp_WakeRenderer end", data);
}