/*
 * Functionality for 2D rendering
 *
 * Copyright (c) 1998 Criterion Software Ltd.
 */

/****************************************************************************
 *                                                                          *
 *  Module  :   ps2font.c                                                   *
 *                                                                          *
 *  Purpose :   graphics state                                              *
 *                                                                          *
 ****************************************************************************/

/****************************************************************************
 includes
 */

#include <math.h>

#include <rwcore.h>
#include <rpworld.h>

#include <rpdbgerr.h>

#include "rt2d.h"
#include "stroke.h"
#include "brush.h"
#include "font.h"
#include "gstate.h"

#include "ps2pipes.h"
#include "ps2font.h"

#include "vufont.h"

/****************************************************************************
 local defines
 */

#define MESSAGE(_string)                                            \
    RwDebugSendMessage(rwDEBUGMESSAGE, "Rt2dPS2Font", _string)

/****************************************************************************
 PS2 Functions
 */
#define     RT2DFONTPS2QWORD    7

typedef union Rt2dPtrFontChar Rt2dPtrFontChar;
union Rt2dPtrFontChar
{
    Rt2dFontChar * ptrFontChar;
    const RwChar *constptrChar;
};

static Rt2dPS2PipeState fontPipeState = {
    (RwUInt64) 0,                    /* subdiv */
    (RxPipeline *)NULL,              /* Pipeline */
    {NULL2D, NULL2D, NULL2D, NULL2D, /* vucode */
     NULL2D, NULL2D, NULL2D, NULL2D,
     NULL2D, NULL2D, NULL2D, NULL2D,
     NULL2D, NULL2D, NULL2D, NULL2D}
};

static              RwBool
PS2FontNode2d(RxPS2Mesh * __RWUNUSED__ ps2mesh,
              RxPS2DMASessionRecord * __RWUNUSED__  dmaSessionRec,
              RwReal * pUV0,
              RwReal * pUV1)
{
    RwInt32             i;
    RwReal              baseu;
    RwUInt64            tmp, tmp1;
    u_long128           ltmp = 0;
    Rt2dFont           *font;
    Rt2dFontChar       *str;
    rt2dCharRect       *cr;

    RWFUNCTION(RWSTRING("PS2FontNode2d"));

    str = Rt2dGlobals.str;
    font = Rt2dGlobals.font;

    baseu = (RwReal) 0.0;

    /* Instance the DMA  data */

    /* Do not upload the last two since they are just place holders. */
    for (i = 0; i < (Rt2dGlobals.numVerts - 2); i++)
    {
        cr = &font->map[*str];

        *pUV0++ = cr->uv[0].x;
        *pUV0++ = cr->uv[0].y;
        *pUV0++ = cr->width;

        *pUV1++ = cr->uv[1].x;
        *pUV1++ = cr->uv[1].y;

        baseu += (cr->width + font->intergap);

        str++;
    }

    /*************************************************
        * Call a RW3-PS2-specific stealth function to
        * create enough space to store an additional 11
        * quadwords of DMA data.  In fact the RW code
        * will most likely already have enough spare
        * space, since it allocates memory in much bigger
        * chunks.
        * SWE_PKT_LOCAL: RW is responsible for the memory
        *    used for this new packet and will free it
        *    up when the packet has been sent.
        * SWE_PKT_CIRCALLOC: The memory used is taken
        *    from a circularly allocated buffer, which is
        *    much faster than calling regular memory
        *    functions.
        * SWE_PKT_VU1: The packet is destined for the
        *    Vector Unit 1.  (If the SWE_LPS_NOFIXUP
        *    symbol was not used here, then the DMA
        *    manager would try to combine further DMA
        *    packets if they were also destined for the
        *    VU1.)
        ************************************************/

    /* Reserve RT2DFONTPS2QWORD quadwords for the following data.
     * *
     * * Brush halfwidth, inset, oobaseu, layerDepth
     * * Brush top RGBA
     * * Brush top delta RGBA
     * * Brush bot RGBA
     * * Brush bot delta RGBA
     * *
     * * Font intergap
     * *
     */
    sweFinaliseOpenLocalPkt(SWE_PKT_DMA_MODE_CHAIN_TTE |
                            SWE_PKT_LOCAL |
                            SWE_PKT_VU1 |
                            SWE_PKT_CIRCALLOC, -(RT2DFONTPS2QWORD + 2));

    /*************************************************
        * If the above call worked then we get a quad-
        * word pointer called sweLocalPacket which points
        * to the memory where we can fill in the DMA
        * data.  The macros below access this pointer and
        * advance this pointer.
        ************************************************/

    if (!sweLocalPacket)
    {
        printf("%s(%d): DMA memory failure\n", __FILE__, __LINE__);
    }
    else
    {
        RwUInt32            prim = 0x0l;

        /*************************************************
            * We build a DMA packet to transfer the necessary
            * data to the VIF.  This is a complete, ready-to
            * go packet, containing commands for the VIF. We
            * send data that must end up in the VU memory,
            * as well as commands that get passed to the GS
            * and program it's registers.
            * We end with an interrupt.
            ************************************************/

        /*************************************************
            * First off we set up a continuous DMA packet
            * to transfer 5 quadwords of data.
            ************************************************/

        tmp = (RT2DFONTPS2QWORD) | /* qwords */
            (1l << 28);        /* ID(cnt) */

        tmp1 =
        /*************************************************
            * We'll be transferring data with no scattering
            ************************************************/
            (VIFCMD_CYCLE | (4l << 8) | ((4))) |
        /*************************************************
            * Write 5 quadwords into VU1 memory
            ************************************************/
            /* UNPACK | qwords | where */
            ((VIFCMD_UNPACK | (RT2DFONTPS2QWORD << 16) |
              ((long) (vuFontSymbStaticData))) << 32);

        MAKE128(ltmp, tmp1, tmp);
        SWEADDCONTFAST(ltmp);

        /* width, inset, layerDepth, oobaseu */
        {
            Rt2dUnion128    pack;

            pack.float32[0] = Rt2dGlobals.brush->halfwidth;
            pack.float32[1] = Rt2dGlobals.pathInset;
            pack.float32[2] = Rt2dGlobals.layerDepth;
            pack.float32[3] = Rt2dGlobals.oobaseu;

            ltmp =  pack.int128;
            SWEADDCONTFAST(ltmp);
        }

        /* Brush colour. */
        {
            Rt2dUnion128    pack;

            pack.float32[0] = Rt2dGlobals.brush->top.col.red * (RwReal) RT2DPS2COLSCALE;
            pack.float32[1] = Rt2dGlobals.brush->top.col.green * (RwReal) RT2DPS2COLSCALE;
            pack.float32[2] = Rt2dGlobals.brush->top.col.blue * (RwReal) RT2DPS2COLSCALE;
            pack.float32[3] = Rt2dGlobals.brush->top.col.alpha * (RwReal) RT2DPS2COLSCALE;

            ltmp =  pack.int128;
            SWEADDCONTFAST(ltmp);
        }

        {
            Rt2dUnion128    pack;

            pack.float32[0] = Rt2dGlobals.brush->dtop.col.red * (RwReal) RT2DPS2COLSCALE;
            pack.float32[1] = Rt2dGlobals.brush->dtop.col.green * (RwReal) RT2DPS2COLSCALE;
            pack.float32[2] = Rt2dGlobals.brush->dtop.col.blue * (RwReal) RT2DPS2COLSCALE;
            pack.float32[3] = Rt2dGlobals.brush->dtop.col.alpha * (RwReal) RT2DPS2COLSCALE;

            ltmp =  pack.int128;
            SWEADDCONTFAST(ltmp);
        }

        {
            Rt2dUnion128    pack;

            pack.float32[0] = Rt2dGlobals.brush->bottom.col.red * (RwReal) RT2DPS2COLSCALE;
            pack.float32[1] = Rt2dGlobals.brush->bottom.col.green * (RwReal) RT2DPS2COLSCALE;
            pack.float32[2] = Rt2dGlobals.brush->bottom.col.blue * (RwReal) RT2DPS2COLSCALE;
            pack.float32[3] = Rt2dGlobals.brush->bottom.col.alpha * (RwReal) RT2DPS2COLSCALE;

            ltmp =  pack.int128;
            SWEADDCONTFAST(ltmp);
        }

        {
            Rt2dUnion128    pack;

            pack.float32[0] = Rt2dGlobals.brush->dbottom.col.red * (RwReal) RT2DPS2COLSCALE;
            pack.float32[1] = Rt2dGlobals.brush->dbottom.col.green * (RwReal) RT2DPS2COLSCALE;
            pack.float32[2] = Rt2dGlobals.brush->dbottom.col.blue * (RwReal) RT2DPS2COLSCALE;
            pack.float32[3] = Rt2dGlobals.brush->dbottom.col.alpha * (RwReal) RT2DPS2COLSCALE;

            ltmp =  pack.int128;
            SWEADDCONTFAST(ltmp);
        }

        /* intergap, font baseu */
        {
            Rt2dUnion128    pack;

            pack.float32[0] = Rt2dGlobals.font->intergap;
            pack.float32[1] = Rt2dGlobals.fontbaseu;
            pack.float32[2] = (RwReal) 0;
            pack.float32[3] = (RwReal) 0;

            ltmp = pack.int128;
            SWEADDCONTFAST(ltmp);
        }

        /* giftag says use context 1 qw */
        prim = ( /* fix  */ 0x0l << 10 |
                /* ctxt */ 0x0l << 9 |
                /* fst  */ 0x0l << 8 |
                /* aa1  */ 0x0l << 7 |
                /* abe  */ 0x1l << 6 |
                /* fge  */ 0x0l << 5 |
                /* tme  */ 0x1l << 4 |
                /* iip  */ 0x1l << 3 |
                /* prim */ 0x3l << 0);

        tmp = ( /* regs */ 0x3l << (60 - 32) |
               /* flg  */ 0x0l << (58 - 32) |
               /* prim */ prim << (47 - 32) |
               /* pre  */ 0x1l << (46 - 32)) << 32;

        tmp1 = 0x412l;         /* registers */
        MAKE128(ltmp, tmp1, tmp);
        SWEADDCONTFAST(ltmp);

        /*    End with intr */
        tmp = (0xfl << 28);
        MAKE128(ltmp, 0l, tmp);
        SWEADDCONTFAST(ltmp);

        sweFinaliseOpenLocalPkt(SWE_LPS_CONT, 0);
    }

    /* Return the width of the string uploaded. */
    Rt2dGlobals.fontbaseu = baseu;

    RWRETURN(TRUE);
}

static              RwBool
rxNode2dPS2FontPS2ManagerInstanceCallBack(void **clusterData,
                                          RwUInt32 numClusters
                                          __RWUNUSED__)
{
    RWFUNCTION(RWSTRING("rxNode2dPS2FontPS2ManagerInstanceCallBack"));

    RWRETURN(PS2FontNode2d((RxPS2Mesh *) (clusterData[0]),
                           (RxPS2DMASessionRecord *) (clusterData[1]),
                           (RwReal *) (clusterData[2]), /* Font Pos */
                           (RwReal *) (clusterData[3]) /* Font UV */
             ));
}

RxPipeline         *
_rt2dPS2FontPipe(void)
{
    RxPipeline         *pipe;

    RWFUNCTION(RWSTRING("_rt2dPS2FontPipe"));

    pipe = (RxPipeline *)NULL;

    /* Font Pipe. */
    pipe = RxPipelineCreate();

    if (pipe)
    {
        RwInt32             i;
        RxLockedPipe       *lpipe = (RxPipeline *)NULL;

        lpipe = RxPipelineLock(pipe);

        if (NULL != lpipe)
        {
            RxNodeDefinition   *ps2man;
            RxPipelineNode     *plnode, *result;

            ps2man = RxNodeDefinitionGetPS2Manager(rxOBJTYPE_IM3D);

            lpipe = RxLockedPipeAddFragment(lpipe,
                                            (RwUInt32 *)NULL,
                                            ps2man,
                                            (RxNodeDefinition *) NULL);
            RWASSERT(lpipe != NULL);

            plnode = RxPipelineFindNodeByName(lpipe,
                                              ps2man->name,
                                              (RxPipelineNode *)NULL,
                                              (RwInt32 *)NULL);
            RWASSERT(plnode != NULL);

            /* Set up the first to carry the char's u0, v0, width */
            Rt2dGlobals.RxClPS2FontUV1 = RxClPS2user1;
            Rt2dGlobals.RxClPS2FontUV1.defaultAttributes &= ~CL_V4_32;
            Rt2dGlobals.RxClPS2FontUV1.defaultAttributes |= CL_V3_32;

            result =
                RxPipelineNodePS2ManagerGenerateCluster(plnode,
                                                        &Rt2dGlobals.
                                                        RxClPS2FontUV1,
                                                        CL_USER1);

            /* Set up the second to carry the char's u1, v1 */
            Rt2dGlobals.RxClPS2FontUV2 = RxClPS2user2;
            Rt2dGlobals.RxClPS2FontUV2.defaultAttributes &= ~CL_V4_32;
            Rt2dGlobals.RxClPS2FontUV2.defaultAttributes |= CL_V2_32;

            result = RxPipelineNodePS2ManagerGenerateCluster
                (plnode, &Rt2dGlobals.RxClPS2FontUV2, CL_USER2);

            RxPipelineNodePS2ManagerSetVUBufferSizes(plnode, /* Node */
                                                     vuFontSymbStrideOfInputCluster, /* Stride of input cluster */
                                                     vuFontSymbTSVertexCount, /* Tristrip vertex count */
                                                     vuFontSymbTLTriCount); /* Trilist vertex count */

            lpipe = RxLockedPipeUnlock(lpipe);
        }

        if (NULL != lpipe)
        {
            RxNodeDefinition   *ps2man;
            RxPipelineNode     *plnode, *result;

            ps2man = RxNodeDefinitionGetPS2Manager(rxOBJTYPE_IM3D);
            plnode = RxPipelineFindNodeByName(lpipe,
                                              ps2man->name,
                                              (RxPipelineNode *)NULL,
                                              (RwInt32 *)NULL);

            RWASSERT(plnode != NULL);

            /* We now insert our own instance callback */
            result = RxPipelineNodePS2ManagerSetInstanceCallBack(plnode,
                                                                 rxNode2dPS2FontPS2ManagerInstanceCallBack);

            /*  Set most of the transforms to be null */
            for (i = 0; i < 16; i++)
            {
                fontPipeState.vucode[i] = &font2d;
            }

            /* Set the transforms/renderers */
            /* pipeState.vucode[TRANSNFOG | TRANSNCL | TRANSSTRIP | TRANSPER] = &font2d;  */

            /* We now insert our own VU code */
            result =
                RxPipelineNodePS2ManagerSetVU1CodeArray(plnode,
                                                        fontPipeState.vucode);

            Rt2dGlobals.default_font_pipe = pipe;
            Rt2dGlobals.use_font_pipe = pipe;
        }
    }

    RWRETURN(pipe);
}

Rt2dFont           *
_rt2dPS2FontShow(Rt2dFont * font,
                 const RwChar * string,
                 RwReal height, 
                 RwV2d * anchor,
                 Rt2dBrush * brush)
{
    RWFUNCTION(RWSTRING("_rt2dPS2FontShow"));

    if ((font) && (string))
    {
        RwInt32             vcount_t = 0;
        RwMatrix           *ctm;
        RwRaster           *currbm;
        RwReal              baseu, oobaseu;
        rt2dCharRect       *cr;
        rwIm3DPool         *pool;
        Rt2dPtrFontChar     ptrChar;

        ptrChar.constptrChar = string;

        /* establish geometry space */
        Rt2dCTMPush();
        Rt2dCTMTranslate(anchor->x, anchor->y);
        Rt2dCTMScale(height, height);
        Rt2dCTMTranslate(((RwReal) 0), -font->baseline);

        ctm = _rt2dCTMGet();

        /* establish colour space */
        baseu = Rt2dFontGetStringWidth(font,
                                       (RwChar *) ptrChar.ptrFontChar,
                                       ((RwReal) 1));

        oobaseu = (RwReal) baseu;
        baseu = (RwReal) 0.0;

        /* Initial bitmaps. */
        Rt2dGlobals.pathInset = (RwReal) 0.0;
        Rt2dGlobals.brush = brush;
        Rt2dGlobals.oobaseu = oobaseu;
        Rt2dGlobals.font = font;

        pool = _rwIm3DGetPool();

        /* Initial state. */
        cr = &font->map[(RwUInt32) * ptrChar.ptrFontChar];
        RwRenderStateSet(rwRENDERSTATETEXTURERASTER, cr->bm);
        currbm = cr->bm;

        Rt2dGlobals.fontbaseu = baseu;
        Rt2dGlobals.str = ptrChar.ptrFontChar;
        vcount_t = 0;

        /* Main loop. */
        while (*ptrChar.ptrFontChar)
        {
            pool->elements = brush->vertex;
            pool->stride = 20; /* 5 x 4 */

            pool->stash.ltm = ctm;
            pool->stash.flags = Rt2dGlobals.TransformFlags;

            pool->stash.pipeline = (RxPipeline *) NULL;
            pool->stash.primType = (RwPrimitiveType) rwPRIMTYPETRISTRIP;
            pool->stash.indices = (RxVertexIndex *) NULL;

            cr = &font->map[(RwUInt32) * ptrChar.ptrFontChar];

            /* do we need to flush? */
            if ((cr->bm != currbm)
                || (vcount_t > (RT2DPS2MAXVERT / 6) - 2))
            {
                /*
                 * Fool the upload code to think there at least 3 verts because
                 * we could be rendering just one char.
                 * The last two verts are ignored.
                 *
                 * This will be fixed once custom data is fully supported. For now
                 * we just use a tristrip as a wrapper.
                 */
                Rt2dGlobals.numVerts = vcount_t + 2;

                pool->numElements = (RwUInt16) Rt2dGlobals.numVerts;
                pool->stash.numIndices =
                    (RwUInt16) Rt2dGlobals.numVerts;

                if (RxPipelineExecute
                    (Rt2dGlobals.use_font_pipe, (void *) &pool->stash,
                     FALSE) == NULL)
                {
                    RWRETURN((Rt2dFont *)NULL);
                }

                /* Update starting vals for the next string. */
                if (cr->bm != currbm)
                {
                    RwRenderStateSet(rwRENDERSTATETEXTURERASTER,
                                     cr->bm);
                    currbm = cr->bm;
                }

                Rt2dGlobals.fontbaseu = baseu;
                Rt2dGlobals.str = ptrChar.ptrFontChar;
                vcount_t = 0;
            }

            baseu += cr->width + font->intergap;
            ptrChar.ptrFontChar++;
            vcount_t++;
        }

        /* do we need to flush? */
        if (vcount_t > 0)
        {
            pool->elements = brush->vertex;
            pool->stride = 20; /* 5 x 4 */

            pool->stash.ltm = ctm;
            /* pool->stash.flags = rwIM3D_VERTEXUV | Rt2dGlobals.TransformFlags; */
            pool->stash.flags = Rt2dGlobals.TransformFlags;

            pool->stash.pipeline = (RxPipeline *) NULL;
            pool->stash.primType = (RwPrimitiveType) rwPRIMTYPETRISTRIP;
            pool->stash.indices = (RxVertexIndex *) NULL;

            /*
             * * Fool the upload code to think there at least 3 verts because
             * * we could be rendering just one char.
             * * The last two verts are ignored.
             * *
             * * This will be fixed once custom data is fully supported. For now
             * * we just use a tristrip as a wrapper.
             */
            Rt2dGlobals.numVerts = vcount_t + 2;

            pool->numElements = (RwUInt16) Rt2dGlobals.numVerts;
            pool->stash.numIndices = (RwUInt16) Rt2dGlobals.numVerts;

            if (RxPipelineExecute
                (Rt2dGlobals.use_font_pipe, (void *) &pool->stash,
                 FALSE) == NULL)
            {
                RWRETURN((Rt2dFont *)NULL);
            }
        }

        /* write back final position */
        anchor->x += baseu * height;

        Rt2dCTMPop();
    }

    RWRETURN(font);
}

RwBool
_rt2dPS2FontOpen(void)
{
    RWFUNCTION(RWSTRING("_rt2dPS2FontOpen"));

    RWRETURN(TRUE);
}

RwBool
_rt2dPS2FontClose(void)
{
    RWFUNCTION(RWSTRING("_rt2dPS2FontClose"));

    RWRETURN(TRUE);
}
