/* Copyright (c) 2001, Stanford University
 * All rights reserved
 *
 * See the file LICENSE.txt for information on redistributing this software.
 */

#include "packspu.h"
#include "cr_mem.h"
#include "cr_packfunctions.h"
#include "cr_string.h"
#include "packspu_proto.h"

/*
 * Allocate a new ThreadInfo structure, setup a connection to the
 * server, allocate/init a packer context, bind this ThreadInfo to
 * the calling thread with crSetTSD().
 * We'll always call this function at least once even if we're not
 * using threads.
 */
ThreadInfo *packspuNewThread(
#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST)
                struct VBOXUHGSMI *pHgsmi
#endif
)
{
    ThreadInfo *thread=NULL;
    int i;

#ifdef CHROMIUM_THREADSAFE
    crLockMutex(&_PackMutex);
#else
    CRASSERT(pack_spu.numThreads == 0);
#endif

#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST)
    CRASSERT(!CRPACKSPU_IS_WDDM_CRHGSMI() == !pHgsmi);
#endif

    CRASSERT(pack_spu.numThreads < MAX_THREADS);
    for (i=0; i<MAX_THREADS; ++i)
    {
        if (!pack_spu.thread[i].inUse)
        {
            thread = &pack_spu.thread[i];
            break;
        }
    }
    CRASSERT(thread);

    thread->inUse = GL_TRUE;
    if (!CRPACKSPU_IS_WDDM_CRHGSMI())
        thread->id = crThreadID();
    else
        thread->id = THREAD_OFFSET_MAGIC + i;
    thread->currentContext = NULL;
    thread->bInjectThread = GL_FALSE;

    /* connect to the server */
    thread->netServer.name = crStrdup( pack_spu.name );
    thread->netServer.buffer_size = pack_spu.buffer_size;
    packspuConnectToServer( &(thread->netServer)
#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST)
            , pHgsmi
#endif
            );
    CRASSERT(thread->netServer.conn);
    /* packer setup */
    CRASSERT(thread->packer == NULL);
    thread->packer = crPackNewContext( pack_spu.swap );
    CRASSERT(thread->packer);
    crPackInitBuffer( &(thread->buffer), crNetAlloc(thread->netServer.conn),
                thread->netServer.conn->buffer_size, thread->netServer.conn->mtu );
    thread->buffer.canBarf = thread->netServer.conn->Barf ? GL_TRUE : GL_FALSE;
    crPackSetBuffer( thread->packer, &thread->buffer );
    crPackFlushFunc( thread->packer, packspuFlush );
    crPackFlushArg( thread->packer, (void *) thread );
    crPackSendHugeFunc( thread->packer, packspuHuge );

    if (!CRPACKSPU_IS_WDDM_CRHGSMI())
    {
        crPackSetContext( thread->packer );
    }


#ifdef CHROMIUM_THREADSAFE
    if (!CRPACKSPU_IS_WDDM_CRHGSMI())
    {
        crSetTSD(&_PackTSD, thread);
    }
#endif

    pack_spu.numThreads++;

#ifdef CHROMIUM_THREADSAFE
    crUnlockMutex(&_PackMutex);
#endif
    return thread;
}

GLint PACKSPU_APIENTRY
packspu_VBoxConCreate(struct VBOXUHGSMI *pHgsmi)
{
#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST)
    ThreadInfo * thread;
    CRASSERT(CRPACKSPU_IS_WDDM_CRHGSMI());
    CRASSERT(pHgsmi);

    thread = packspuNewThread(pHgsmi);

    if (thread)
    {
        CRASSERT(thread->id);
        CRASSERT(thread->id - THREAD_OFFSET_MAGIC < RT_ELEMENTS(pack_spu.thread)
                && GET_THREAD_VAL_ID(thread->id) == thread);
        return thread->id;
    }
    crError("packspuNewThread failed");
#endif
    return 0;
}

void PACKSPU_APIENTRY
packspu_VBoxConFlush(GLint con)
{
#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST)
    GET_THREAD_ID(thread, con);
    CRASSERT(con);
    CRASSERT(CRPACKSPU_IS_WDDM_CRHGSMI());
    CRASSERT(thread->packer);
    packspuFlush((void *) thread);
#else
    crError("VBoxConFlush not implemented!");
#endif
}

void PACKSPU_APIENTRY
packspu_VBoxConDestroy(GLint con)
{
#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST)
    GET_THREAD_ID(thread, con);
    CRASSERT(con);
    CRASSERT(CRPACKSPU_IS_WDDM_CRHGSMI());
    CRASSERT(pack_spu.numThreads>0);
    CRASSERT(thread->packer);
    packspuFlush((void *) thread);

    crLockMutex(&_PackMutex);

    crPackDeleteContext(thread->packer);

    if (thread->buffer.pack)
    {
        crNetFree(thread->netServer.conn, thread->buffer.pack);
        thread->buffer.pack = NULL;
    }

    crNetFreeConnection(thread->netServer.conn);

    if (thread->netServer.name)
        crFree(thread->netServer.name);

    pack_spu.numThreads--;
    /*note can't shift the array here, because other threads have TLS references to array elements*/
    crMemZero(thread, sizeof(ThreadInfo));

#if 0
    if (&pack_spu.thread[pack_spu.idxThreadInUse]==thread)
    {
        int i;
        crError("Should not be here since idxThreadInUse should be always 0 for the dummy connection created in packSPUInit!");
        for (i=0; i<MAX_THREADS; ++i)
        {
            if (pack_spu.thread[i].inUse)
            {
                pack_spu.idxThreadInUse=i;
                break;
            }
        }
    }
#endif
    crUnlockMutex(&_PackMutex);
#endif
}

GLvoid PACKSPU_APIENTRY
packspu_VBoxConChromiumParameteriCR(GLint con, GLenum param, GLint value)
{
    GET_THREAD(thread);
    CRPackContext * curPacker = crPackGetContext();
    ThreadInfo *curThread = thread;
    int writeback = 1;
    GLint serverCtx = (GLint) -1;

    CRASSERT(!curThread == !curPacker);
    CRASSERT(!curThread || !curPacker || curThread->packer == curPacker);
#ifdef CHROMIUM_THREADSAFE
    crLockMutex(&_PackMutex);
#endif

#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST)
    CRASSERT(!con == !CRPACKSPU_IS_WDDM_CRHGSMI());
#endif

    if (CRPACKSPU_IS_WDDM_CRHGSMI())
    {
        if (!con)
        {
            crError("connection should be specified!");
            return;
        }
        thread = GET_THREAD_VAL_ID(con);
    }
    else
    {
        CRASSERT(!con);
        if (!thread)
        {
            thread = packspuNewThread(
#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST)
                NULL
#endif
                );
        }
    }
    CRASSERT(thread);
    CRASSERT(thread->packer);

    crPackSetContext( thread->packer );

    packspu_ChromiumParameteriCR(param, value);

#ifdef CHROMIUM_THREADSAFE
    crUnlockMutex(&_PackMutex);
#endif

    if (CRPACKSPU_IS_WDDM_CRHGSMI())
    {
        /* restore the packer context to the tls */
        crPackSetContext(curPacker);
    }
}

GLvoid PACKSPU_APIENTRY
packspu_VBoxConChromiumParametervCR(GLint con, GLenum target, GLenum type, GLsizei count, const GLvoid *values)
{
    GET_THREAD(thread);
    CRPackContext * curPacker = crPackGetContext();
    ThreadInfo *curThread = thread;
    int writeback = 1;
    GLint serverCtx = (GLint) -1;

    CRASSERT(!curThread == !curPacker);
    CRASSERT(!curThread || !curPacker || curThread->packer == curPacker);
#ifdef CHROMIUM_THREADSAFE
    crLockMutex(&_PackMutex);
#endif

#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST)
    CRASSERT(!con == !CRPACKSPU_IS_WDDM_CRHGSMI());
#endif

    if (CRPACKSPU_IS_WDDM_CRHGSMI())
    {
        if (!con)
        {
            crError("connection should be specified!");
            return;
        }
        thread = GET_THREAD_VAL_ID(con);
    }
    else
    {
        CRASSERT(!con);
        if (!thread)
        {
            thread = packspuNewThread(
#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST)
                NULL
#endif
                );
        }
    }
    CRASSERT(thread);
    CRASSERT(thread->packer);

    crPackSetContext( thread->packer );

    packspu_ChromiumParametervCR(target, type, count, values);

#ifdef CHROMIUM_THREADSAFE
    crUnlockMutex(&_PackMutex);
#endif

    if (CRPACKSPU_IS_WDDM_CRHGSMI())
    {
        /* restore the packer context to the tls */
        crPackSetContext(curPacker);
    }
}

GLint PACKSPU_APIENTRY
packspu_VBoxCreateContext( GLint con, const char *dpyName, GLint visual, GLint shareCtx )
{
    GET_THREAD(thread);
    CRPackContext * curPacker = crPackGetContext();
    ThreadInfo *curThread = thread;
    int writeback = 1;
    GLint serverCtx = (GLint) -1;
    int slot;

    CRASSERT(!curThread == !curPacker);
    CRASSERT(!curThread || !curPacker || curThread->packer == curPacker);
#ifdef CHROMIUM_THREADSAFE
    crLockMutex(&_PackMutex);
#endif

#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST)
    CRASSERT(!con == !CRPACKSPU_IS_WDDM_CRHGSMI());
#endif

    if (CRPACKSPU_IS_WDDM_CRHGSMI())
    {
        if (!con)
        {
            crError("connection should be specified!");
            return -1;
        }
        thread = GET_THREAD_VAL_ID(con);
    }
    else
    {
        CRASSERT(!con);
        if (!thread)
        {
            thread = packspuNewThread(
#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST)
                NULL
#endif
                );
        }
    }
    CRASSERT(thread);
    CRASSERT(thread->packer);

    if (shareCtx > 0) {
        /* translate to server ctx id */
        shareCtx -= MAGIC_OFFSET;
        if (shareCtx >= 0 && shareCtx < pack_spu.numContexts) {
            shareCtx = pack_spu.context[shareCtx].serverCtx;
        }
    }

    crPackSetContext( thread->packer );

    /* Pack the command */
    if (pack_spu.swap)
        crPackCreateContextSWAP( dpyName, visual, shareCtx, &serverCtx, &writeback );
    else
        crPackCreateContext( dpyName, visual, shareCtx, &serverCtx, &writeback );

    /* Flush buffer and get return value */
    packspuFlush(thread);
    if (!(thread->netServer.conn->actual_network))
    {
        /* HUMUNGOUS HACK TO MATCH SERVER NUMBERING
         *
         * The hack exists solely to make file networking work for now.  This
         * is totally gross, but since the server expects the numbers to start
         * from 5000, we need to write them out this way.  This would be
         * marginally less gross if the numbers (500 and 5000) were maybe
         * some sort of #define'd constants somewhere so the client and the
         * server could be aware of how each other were numbering things in
         * cases like file networking where they actually
         * care. 
         *
         *  -Humper 
         *
         */
        serverCtx = 5000;
    }
    else {
        CRPACKSPU_WRITEBACK_WAIT(thread, writeback);

        if (pack_spu.swap) {
            serverCtx = (GLint) SWAP32(serverCtx);
        }
        if (serverCtx < 0) {
#ifdef CHROMIUM_THREADSAFE
            crUnlockMutex(&_PackMutex);
#endif
            crWarning("Failure in packspu_CreateContext");

            if (CRPACKSPU_IS_WDDM_CRHGSMI())
            {
                /* restore the packer context to the tls */
                crPackSetContext(curPacker);
            }
            return -1;  /* failed */
        }
    }

    /* find an empty context slot */
    for (slot = 0; slot < pack_spu.numContexts; slot++) {
        if (!pack_spu.context[slot].clientState) {
            /* found empty slot */
            break;
        }
    }
    if (slot == pack_spu.numContexts) {
        pack_spu.numContexts++;
    }

    if (CRPACKSPU_IS_WDDM_CRHGSMI())
    {
        thread->currentContext = &pack_spu.context[slot];
        pack_spu.context[slot].currentThread = thread;
    }

    /* Fill in the new context info */
    /* XXX fix-up sharedCtx param here */
    pack_spu.context[slot].clientState = crStateCreateContext(NULL, visual, NULL);
    pack_spu.context[slot].clientState->bufferobject.retainBufferData = GL_TRUE;
    pack_spu.context[slot].serverCtx = serverCtx;

#ifdef CHROMIUM_THREADSAFE
    crUnlockMutex(&_PackMutex);
#endif

    if (CRPACKSPU_IS_WDDM_CRHGSMI())
    {
        /* restore the packer context to the tls */
        crPackSetContext(curPacker);
    }

    return MAGIC_OFFSET + slot;
}

GLint PACKSPU_APIENTRY
packspu_CreateContext( const char *dpyName, GLint visual, GLint shareCtx )
{
    return packspu_VBoxCreateContext( 0, dpyName, visual, shareCtx );
}


void PACKSPU_APIENTRY packspu_DestroyContext( GLint ctx )
{
    GET_THREAD(thread);
    ThreadInfo *curThread = thread;
    const int slot = ctx - MAGIC_OFFSET;
    ContextInfo *context, *curContext;
    CRPackContext * curPacker = crPackGetContext();

    CRASSERT(slot >= 0);
    CRASSERT(slot < pack_spu.numContexts);

    context = &(pack_spu.context[slot]);

    if (CRPACKSPU_IS_WDDM_CRHGSMI())
    {
        thread = context->currentThread;
        crPackSetContext(thread->packer);
        CRASSERT(!(thread->packer == curPacker) == !(thread == curThread));
    }
    CRASSERT(thread);
    curContext = curThread ? curThread->currentContext : NULL;

    if (pack_spu.swap)
        crPackDestroyContextSWAP( context->serverCtx );
    else
        crPackDestroyContext( context->serverCtx );

    crStateDestroyContext( context->clientState );

    context->clientState = NULL;
    context->serverCtx = 0;
    context->currentThread = NULL;

    crMemset (&context->zvaBufferInfo, 0, sizeof (context->zvaBufferInfo));

    if (curContext == context)
    {
        if (!CRPACKSPU_IS_WDDM_CRHGSMI())
        {
            curThread->currentContext = NULL;
        }
        else
        {
            CRASSERT(thread == curThread);
            crSetTSD(&_PackTSD, NULL);
            crPackSetContext(NULL);
        }
        crStateMakeCurrent( NULL );
    }
    else
    {
        if (CRPACKSPU_IS_WDDM_CRHGSMI())
        {
            crPackSetContext(curPacker);
        }
    }
}

void PACKSPU_APIENTRY packspu_MakeCurrent( GLint window, GLint nativeWindow, GLint ctx )
{
    ThreadInfo *thread;
    GLint serverCtx;
    ContextInfo *newCtx;

    if (!CRPACKSPU_IS_WDDM_CRHGSMI())
    {
        thread = GET_THREAD_VAL();
        if (!thread) {
            thread = packspuNewThread(
#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST)
                    NULL
#endif
                    );
        }
        CRASSERT(thread);
        CRASSERT(thread->packer);
    }

    if (ctx) {
        const int slot = ctx - MAGIC_OFFSET;

        CRASSERT(slot >= 0);
        CRASSERT(slot < pack_spu.numContexts);

        newCtx = &pack_spu.context[slot];
        CRASSERT(newCtx->clientState);  /* verify valid */

        if (CRPACKSPU_IS_WDDM_CRHGSMI())
        {
            thread = newCtx->currentThread;
            CRASSERT(thread);
            crSetTSD(&_PackTSD, thread);
            crPackSetContext( thread->packer );
        }
        else
        {
            if (newCtx->fAutoFlush)
            {
                if (newCtx->currentThread && newCtx->currentThread != thread)
                {
                    crLockMutex(&_PackMutex);
                    /* do a flush for the previously assigned thread
                     * to ensure all commands issued there are submitted */
                    if (newCtx->currentThread
                        && newCtx->currentThread->inUse
                        && newCtx->currentThread->netServer.conn
                        && newCtx->currentThread->packer && newCtx->currentThread->packer->currentBuffer)
                    {
                        packspuFlush((void *) newCtx->currentThread);
                    }
                    crUnlockMutex(&_PackMutex);
                }
                newCtx->currentThread = thread;
            }

            if (thread->currentContext && newCtx != thread->currentContext && thread->currentContext->fCheckZerroVertAttr)
                crStateCurrentRecoverNew(thread->currentContext->clientState, &thread->packer->current);

            thread->currentContext = newCtx;
            crPackSetContext( thread->packer );
        }

        crStateMakeCurrent( newCtx->clientState );
        //crStateSetCurrentPointers(newCtx->clientState, &thread->packer->current);
        serverCtx = pack_spu.context[slot].serverCtx;
    }
    else {
        crStateMakeCurrent( NULL );
        if (CRPACKSPU_IS_WDDM_CRHGSMI())
        {
            thread = GET_THREAD_VAL();
            if (!thread)
            {
                CRASSERT(crPackGetContext() == NULL);
                return;
            }
            CRASSERT(thread->currentContext);
            CRASSERT(thread->packer == crPackGetContext());
        }
        else
        {
            thread->currentContext = NULL;
        }
        newCtx = NULL;
        serverCtx = 0;
    }

    if (pack_spu.swap)
        crPackMakeCurrentSWAP( window, nativeWindow, serverCtx );
    else
        crPackMakeCurrent( window, nativeWindow, serverCtx );

    if (serverCtx)
    {
        packspuInitStrings();
    }

    {
        GET_THREAD(t);
        (void) t;
        CRASSERT(t);
    }
}
