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

#include "cr_pack.h"
#include "cr_mem.h"
#include "cr_net.h"
#include "cr_pixeldata.h"
#include "cr_protocol.h"
#include "cr_error.h"
#include "packspu.h"
#include "packspu_proto.h"

uint32_t g_u32VBoxHostCaps = 0;

static void
packspuWriteback( const CRMessageWriteback *wb )
{
    int *writeback;
    crMemcpy( &writeback, &(wb->writeback_ptr), sizeof( writeback ) );
    *writeback = 0;
}

/**
 * XXX Note that this routine is identical to crNetRecvReadback except
 * we set *writeback=0 instead of decrementing it.  Hmmm.
 */
static void
packspuReadback( const CRMessageReadback *rb, unsigned int len )
{
    /* minus the header, the destination pointer, 
     * *and* the implicit writeback pointer at the head. */

    int payload_len = len - sizeof( *rb );
    int *writeback;
    void *dest_ptr; 
    crMemcpy( &writeback, &(rb->writeback_ptr), sizeof( writeback ) );
    crMemcpy( &dest_ptr, &(rb->readback_ptr), sizeof( dest_ptr ) );

    *writeback = 0;
    crMemcpy( dest_ptr, ((char *)rb) + sizeof(*rb), payload_len );
}

static void
packspuReadPixels( const CRMessageReadPixels *rp, unsigned int len )
{
    crNetRecvReadPixels( rp, len );
    --pack_spu.ReadPixels;
}

static int
packspuReceiveData( CRConnection *conn, CRMessage *msg, unsigned int len )
{
    if (msg->header.type == CR_MESSAGE_REDIR_PTR)
        msg = (CRMessage*) msg->redirptr.pMessage;

    switch( msg->header.type )
    {
        case CR_MESSAGE_READ_PIXELS:
            packspuReadPixels( &(msg->readPixels), len );
            break;
        case CR_MESSAGE_WRITEBACK:
            packspuWriteback( &(msg->writeback) );
            break;
        case CR_MESSAGE_READBACK:
            packspuReadback( &(msg->readback), len );
            break;
        default:
            /*crWarning( "Why is the pack SPU getting a message of type 0x%x?", msg->type ); */
            return 0; /* NOT HANDLED */
    }
    return 1; /* HANDLED */
}

static CRMessageOpcodes *
__prependHeader( CRPackBuffer *buf, unsigned int *len, unsigned int senderID )
{
    int num_opcodes;
    CRMessageOpcodes *hdr;

    CRASSERT( buf );
    CRASSERT( buf->opcode_current < buf->opcode_start );
    CRASSERT( buf->opcode_current >= buf->opcode_end );
    CRASSERT( buf->data_current > buf->data_start );
    CRASSERT( buf->data_current <= buf->data_end );

    num_opcodes = buf->opcode_start - buf->opcode_current;
    hdr = (CRMessageOpcodes *) 
        ( buf->data_start - ( ( num_opcodes + 3 ) & ~0x3 ) - sizeof(*hdr) );

    CRASSERT( (void *) hdr >= buf->pack );

    if (pack_spu.swap)
    {
        hdr->header.type = (CRMessageType) SWAP32(CR_MESSAGE_OPCODES);
        hdr->numOpcodes  = SWAP32(num_opcodes);
    }
    else
    {
        hdr->header.type = CR_MESSAGE_OPCODES;
        hdr->numOpcodes  = num_opcodes;
    }

    *len = buf->data_current - (unsigned char *) hdr;

    return hdr;
}


/*
 * This is called from either the Pack SPU and the packer library whenever
 * we need to send a data buffer to the server.
 */
void packspuFlush(void *arg )
{
    ThreadInfo *thread = (ThreadInfo *) arg;
    ContextInfo *ctx;
    unsigned int len;
    CRMessageOpcodes *hdr;
    CRPackBuffer *buf;

    /* we should _always_ pass a valid <arg> value */
    CRASSERT(thread && thread->inUse);
#ifdef CHROMIUM_THREADSAFE
    CR_LOCK_PACKER_CONTEXT(thread->packer);
#endif
    ctx = thread->currentContext;
    buf = &(thread->buffer);
    CRASSERT(buf);

    if (ctx && ctx->fCheckZerroVertAttr)
        crStateCurrentRecoverNew(ctx->clientState, &thread->packer->current);

    /* We're done packing into the current buffer, unbind it */
    crPackReleaseBuffer( thread->packer );

    /*
    printf("%s thread=%p thread->id = %d thread->pc=%p t2->id=%d t2->pc=%p packbuf=%p packbuf=%p\n",
           __FUNCTION__, (void*) thread, (int) thread->id, thread->packer,
           (int) t2->id, t2->packer,
           buf->pack, thread->packer->buffer.pack);
    */

    if ( buf->opcode_current == buf->opcode_start ) {
           /*
           printf("%s early return\n", __FUNCTION__);
           */
           /* XXX these calls seem to help, but might be appropriate */
           crPackSetBuffer( thread->packer, buf );
           crPackResetPointers(thread->packer);
#ifdef CHROMIUM_THREADSAFE
           CR_UNLOCK_PACKER_CONTEXT(thread->packer);
#endif
           return;
    }

    hdr = __prependHeader( buf, &len, 0 );

    CRASSERT( thread->netServer.conn );

    if ( buf->holds_BeginEnd )
    {
        /*crDebug("crNetBarf %d, (%d)", len, buf->size);*/
        crNetBarf( thread->netServer.conn, &(buf->pack), hdr, len );
    }
    else
    {
        /*crDebug("crNetSend %d, (%d)", len, buf->size);*/
        crNetSend( thread->netServer.conn, &(buf->pack), hdr, len );
    }

    buf->pack = crNetAlloc( thread->netServer.conn );

    /* The network may have found a new mtu */
    buf->mtu = thread->netServer.conn->mtu;

    crPackSetBuffer( thread->packer, buf );

    crPackResetPointers(thread->packer);

#ifdef CHROMIUM_THREADSAFE
    CR_UNLOCK_PACKER_CONTEXT(thread->packer);
#endif
}


/**
 * XXX NOTE: there's a lot of duplicate code here common to the
 * pack, tilesort and replicate SPUs.  Try to simplify someday!
 */
void packspuHuge( CROpcode opcode, void *buf )
{
    GET_THREAD(thread);
    unsigned int          len;
    unsigned char        *src;
    CRMessageOpcodes *msg;

    CRASSERT(thread);

    /* packet length is indicated by the variable length field, and
       includes an additional word for the opcode (with alignment) and
       a header */
    len = ((unsigned int *) buf)[-1];
    if (pack_spu.swap)
    {
        /* It's already been swapped, swap it back. */
        len = SWAP32(len);
    }
    len += 4 + sizeof(CRMessageOpcodes);

    /* write the opcode in just before the length */
    ((unsigned char *) buf)[-5] = (unsigned char) opcode;

    /* fix up the pointer to the packet to include the length & opcode
       & header */
    src = (unsigned char *) buf - 8 - sizeof(CRMessageOpcodes);

    msg = (CRMessageOpcodes *) src;

    if (pack_spu.swap)
    {
        msg->header.type = (CRMessageType) SWAP32(CR_MESSAGE_OPCODES);
        msg->numOpcodes  = SWAP32(1);
    }
    else
    {
        msg->header.type = CR_MESSAGE_OPCODES;
        msg->numOpcodes  = 1;
    }

    CRASSERT( thread->netServer.conn );
    crNetSend( thread->netServer.conn, NULL, src, len );
}

static void packspuFirstConnectToServer( CRNetServer *server
#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST)
                , struct VBOXUHGSMI *pHgsmi
#endif
        )
{
    crNetInit( packspuReceiveData, NULL );
    crNetServerConnect( server
#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST)
                , pHgsmi
#endif
            );
    if (server->conn)
    {
        g_u32VBoxHostCaps = crNetHostCapsGet();
        crPackCapsSet(g_u32VBoxHostCaps);
    }
}

void packspuConnectToServer( CRNetServer *server
#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST)
                , struct VBOXUHGSMI *pHgsmi
#endif
        )
{
    if (pack_spu.numThreads == 0) {
        packspuFirstConnectToServer( server
#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST)
                , pHgsmi
#endif
                );
        if (!server->conn) {
            crError("packspuConnectToServer: no connection on first create!");
            return;
        }
        pack_spu.swap = server->conn->swap;
    }
    else {
        /* a new pthread */
        crNetNewClient(server
#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST)
                , pHgsmi
#endif
        );
    }
}
