#include "rdNRaster.h"

#include "Engine/rdCamera.h"
#include "Win95/stdDisplay.h"
#include "jk.h"

#ifdef RDRASTER_SOFTWARE_RENDERER

#include <math.h>

// Shading axes for the DrawNGon template (RDN_SHADE).
#define RDN_FLAT    0   // no light table (full bright)
#define RDN_LIT     1   // one constant light level through the light table
#define RDN_GOURAUD 2   // per-pixel interpolated light level through the light table

// Per-vertex screen-space attributes for the face currently being rasterized. Same layout as
// rdZRaster's rdZVertex minus any depth role: oneOverW is kept only as the perspective divisor for
// IT texturing (there is no depth buffer in this family).
typedef struct rdNVertex
{
    flex_t sx, sy;      // screen position
    flex_t oneOverW;    // 1/w (perspective divisor for IT texturing)
    flex_t uOverW;      // texel-u / w   (perspective / IT)
    flex_t vOverW;      // texel-v / w   (perspective / IT)
    flex_t u, v;        // texel-u, texel-v (affine / AT — interpolated linearly in screen space)
    flex_t intensity;   // light level 0..63
} rdNVertex;

// State shared by every generated DrawNGon variant. The mode axes (solid / masked / translucent /
// affine / shade) are compile-time, baked into each variant by rdNRaster_ngon.h.
typedef struct rdNRasterState
{
    const uint8_t* pTexels;      // selected mip texels (textured variants)
    const uint8_t* pLightBase;   // colormap light table, or NULL for raw texels
    const uint8_t* pTransTable;  // colormap transparency LUT (translucent variants)
    int mip, mipW, mipH, mipStride;
    int solidColor;              // flat fill color (solid variants)
} rdNRasterState;

static flex_t rdNRaster_Clamp01(flex_t f)
{
    if (f < 0.0f) return 0.0f;
    if (f > 1.0f) return 1.0f;
    return f;
}

// --- Generate the full GrimFandango rdNRaster DrawNGon family from one scanline body
// (rdNRaster_ngon.h). Each #include emits one real, separately-named function; the template
// trailer #undefs the mode macros. Naming: [M][T]<F|L|G><S|AT|IT> — M masked, T translucent,
// F/L/G flat/lit/gouraud shading, S solid, AT affine-textured, IT perspective(interpolated)-
// textured. (Masking does not apply to untextured solids, so those variants are absent, as in JK.
// LV/LW points/wireframe are a different, non-span draw and are not generated, same as rdZRaster.)
// Blocks + rdNRaster_DispatchNGon below are machine-generated by
// scripts/refactoring/rdraster_decomp/gen_rdn_variants.py. ---

#define RDN_NAME rdNRaster_DrawNGonFS
#define RDN_SOLID 1
#define RDN_AFFINE 0
#define RDN_MASKED 0
#define RDN_TRANSLUCENT 0
#define RDN_SHADE RDN_FLAT
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonLS
#define RDN_SOLID 1
#define RDN_AFFINE 0
#define RDN_MASKED 0
#define RDN_TRANSLUCENT 0
#define RDN_SHADE RDN_LIT
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonGS
#define RDN_SOLID 1
#define RDN_AFFINE 0
#define RDN_MASKED 0
#define RDN_TRANSLUCENT 0
#define RDN_SHADE RDN_GOURAUD
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonTFS
#define RDN_SOLID 1
#define RDN_AFFINE 0
#define RDN_MASKED 0
#define RDN_TRANSLUCENT 1
#define RDN_SHADE RDN_FLAT
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonTLS
#define RDN_SOLID 1
#define RDN_AFFINE 0
#define RDN_MASKED 0
#define RDN_TRANSLUCENT 1
#define RDN_SHADE RDN_LIT
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonTGS
#define RDN_SOLID 1
#define RDN_AFFINE 0
#define RDN_MASKED 0
#define RDN_TRANSLUCENT 1
#define RDN_SHADE RDN_GOURAUD
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonFAT
#define RDN_SOLID 0
#define RDN_AFFINE 1
#define RDN_MASKED 0
#define RDN_TRANSLUCENT 0
#define RDN_SHADE RDN_FLAT
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonLAT
#define RDN_SOLID 0
#define RDN_AFFINE 1
#define RDN_MASKED 0
#define RDN_TRANSLUCENT 0
#define RDN_SHADE RDN_LIT
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonGAT
#define RDN_SOLID 0
#define RDN_AFFINE 1
#define RDN_MASKED 0
#define RDN_TRANSLUCENT 0
#define RDN_SHADE RDN_GOURAUD
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonTFAT
#define RDN_SOLID 0
#define RDN_AFFINE 1
#define RDN_MASKED 0
#define RDN_TRANSLUCENT 1
#define RDN_SHADE RDN_FLAT
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonTLAT
#define RDN_SOLID 0
#define RDN_AFFINE 1
#define RDN_MASKED 0
#define RDN_TRANSLUCENT 1
#define RDN_SHADE RDN_LIT
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonTGAT
#define RDN_SOLID 0
#define RDN_AFFINE 1
#define RDN_MASKED 0
#define RDN_TRANSLUCENT 1
#define RDN_SHADE RDN_GOURAUD
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonMFAT
#define RDN_SOLID 0
#define RDN_AFFINE 1
#define RDN_MASKED 1
#define RDN_TRANSLUCENT 0
#define RDN_SHADE RDN_FLAT
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonMLAT
#define RDN_SOLID 0
#define RDN_AFFINE 1
#define RDN_MASKED 1
#define RDN_TRANSLUCENT 0
#define RDN_SHADE RDN_LIT
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonMGAT
#define RDN_SOLID 0
#define RDN_AFFINE 1
#define RDN_MASKED 1
#define RDN_TRANSLUCENT 0
#define RDN_SHADE RDN_GOURAUD
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonMTFAT
#define RDN_SOLID 0
#define RDN_AFFINE 1
#define RDN_MASKED 1
#define RDN_TRANSLUCENT 1
#define RDN_SHADE RDN_FLAT
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonMTLAT
#define RDN_SOLID 0
#define RDN_AFFINE 1
#define RDN_MASKED 1
#define RDN_TRANSLUCENT 1
#define RDN_SHADE RDN_LIT
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonMTGAT
#define RDN_SOLID 0
#define RDN_AFFINE 1
#define RDN_MASKED 1
#define RDN_TRANSLUCENT 1
#define RDN_SHADE RDN_GOURAUD
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonFIT
#define RDN_SOLID 0
#define RDN_AFFINE 0
#define RDN_MASKED 0
#define RDN_TRANSLUCENT 0
#define RDN_SHADE RDN_FLAT
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonLIT
#define RDN_SOLID 0
#define RDN_AFFINE 0
#define RDN_MASKED 0
#define RDN_TRANSLUCENT 0
#define RDN_SHADE RDN_LIT
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonGIT
#define RDN_SOLID 0
#define RDN_AFFINE 0
#define RDN_MASKED 0
#define RDN_TRANSLUCENT 0
#define RDN_SHADE RDN_GOURAUD
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonTFIT
#define RDN_SOLID 0
#define RDN_AFFINE 0
#define RDN_MASKED 0
#define RDN_TRANSLUCENT 1
#define RDN_SHADE RDN_FLAT
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonTLIT
#define RDN_SOLID 0
#define RDN_AFFINE 0
#define RDN_MASKED 0
#define RDN_TRANSLUCENT 1
#define RDN_SHADE RDN_LIT
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonTGIT
#define RDN_SOLID 0
#define RDN_AFFINE 0
#define RDN_MASKED 0
#define RDN_TRANSLUCENT 1
#define RDN_SHADE RDN_GOURAUD
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonMFIT
#define RDN_SOLID 0
#define RDN_AFFINE 0
#define RDN_MASKED 1
#define RDN_TRANSLUCENT 0
#define RDN_SHADE RDN_FLAT
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonMLIT
#define RDN_SOLID 0
#define RDN_AFFINE 0
#define RDN_MASKED 1
#define RDN_TRANSLUCENT 0
#define RDN_SHADE RDN_LIT
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonMGIT
#define RDN_SOLID 0
#define RDN_AFFINE 0
#define RDN_MASKED 1
#define RDN_TRANSLUCENT 0
#define RDN_SHADE RDN_GOURAUD
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonMTFIT
#define RDN_SOLID 0
#define RDN_AFFINE 0
#define RDN_MASKED 1
#define RDN_TRANSLUCENT 1
#define RDN_SHADE RDN_FLAT
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonMTLIT
#define RDN_SOLID 0
#define RDN_AFFINE 0
#define RDN_MASKED 1
#define RDN_TRANSLUCENT 1
#define RDN_SHADE RDN_LIT
#include "rdNRaster_ngon.h"

#define RDN_NAME rdNRaster_DrawNGonMTGIT
#define RDN_SOLID 0
#define RDN_AFFINE 0
#define RDN_MASKED 1
#define RDN_TRANSLUCENT 1
#define RDN_SHADE RDN_GOURAUD
#include "rdNRaster_ngon.h"

static void rdNRaster_DispatchNGon(const rdNVertex* pVerts, int numVerts, const rdNRasterState* st,
                                   int solid, int affine, int masked, int translucent, int shade)
{
    switch (shade) {
    case RDN_FLAT:
        if (solid) {
            if (translucent) rdNRaster_DrawNGonTFS(pVerts, numVerts, st); else rdNRaster_DrawNGonFS(pVerts, numVerts, st);
        } else if (affine) {
            if (masked && translucent) rdNRaster_DrawNGonMTFAT(pVerts, numVerts, st);
            else if (masked && !translucent) rdNRaster_DrawNGonMFAT(pVerts, numVerts, st);
            else if (!masked && translucent) rdNRaster_DrawNGonTFAT(pVerts, numVerts, st);
            else if (!masked && !translucent) rdNRaster_DrawNGonFAT(pVerts, numVerts, st);
        } else if (1) {
            if (masked && translucent) rdNRaster_DrawNGonMTFIT(pVerts, numVerts, st);
            else if (masked && !translucent) rdNRaster_DrawNGonMFIT(pVerts, numVerts, st);
            else if (!masked && translucent) rdNRaster_DrawNGonTFIT(pVerts, numVerts, st);
            else if (!masked && !translucent) rdNRaster_DrawNGonFIT(pVerts, numVerts, st);
        }
        break;
    case RDN_LIT:
        if (solid) {
            if (translucent) rdNRaster_DrawNGonTLS(pVerts, numVerts, st); else rdNRaster_DrawNGonLS(pVerts, numVerts, st);
        } else if (affine) {
            if (masked && translucent) rdNRaster_DrawNGonMTLAT(pVerts, numVerts, st);
            else if (masked && !translucent) rdNRaster_DrawNGonMLAT(pVerts, numVerts, st);
            else if (!masked && translucent) rdNRaster_DrawNGonTLAT(pVerts, numVerts, st);
            else if (!masked && !translucent) rdNRaster_DrawNGonLAT(pVerts, numVerts, st);
        } else if (1) {
            if (masked && translucent) rdNRaster_DrawNGonMTLIT(pVerts, numVerts, st);
            else if (masked && !translucent) rdNRaster_DrawNGonMLIT(pVerts, numVerts, st);
            else if (!masked && translucent) rdNRaster_DrawNGonTLIT(pVerts, numVerts, st);
            else if (!masked && !translucent) rdNRaster_DrawNGonLIT(pVerts, numVerts, st);
        }
        break;
    case RDN_GOURAUD:
        if (solid) {
            if (translucent) rdNRaster_DrawNGonTGS(pVerts, numVerts, st); else rdNRaster_DrawNGonGS(pVerts, numVerts, st);
        } else if (affine) {
            if (masked && translucent) rdNRaster_DrawNGonMTGAT(pVerts, numVerts, st);
            else if (masked && !translucent) rdNRaster_DrawNGonMGAT(pVerts, numVerts, st);
            else if (!masked && translucent) rdNRaster_DrawNGonTGAT(pVerts, numVerts, st);
            else if (!masked && !translucent) rdNRaster_DrawNGonGAT(pVerts, numVerts, st);
        } else if (1) {
            if (masked && translucent) rdNRaster_DrawNGonMTGIT(pVerts, numVerts, st);
            else if (masked && !translucent) rdNRaster_DrawNGonMGIT(pVerts, numVerts, st);
            else if (!masked && translucent) rdNRaster_DrawNGonTGIT(pVerts, numVerts, st);
            else if (!masked && !translucent) rdNRaster_DrawNGonGIT(pVerts, numVerts, st);
        }
        break;
    }
}

void rdNRaster_DrawFace(rdProcEntry* pProcEntry)
{
    // Handles the two filled geometry modes JK's DrawFaceN rasterizes: RD_GEOMETRY_FULL (textured)
    // and RD_GEOMETRY_SOLID (flat solid fill). Wireframe (mode 2) / points (mode 1) are the affine
    // path's job; the target must be the 8bpp canvas (16bpp is unreachable via the menu buffer).
    tVBuffer* pVBuffer = rdCamera_g_pCurCamera->pCanvas->pVBuffer;
    if (pVBuffer->surface_lock_alloc == NULL || pVBuffer->format.format.is16bit)
        return;

    int geometryMode = pProcEntry->geometryMode;
    if (geometryMode > rdroid_g_curGeometryMode)
        geometryMode = rdroid_g_curGeometryMode;
    int lightingMode = pProcEntry->lightingMode;
    if (lightingMode > rdroid_g_curLightingMode)
        lightingMode = rdroid_g_curLightingMode;
    if ((geometryMode != RD_GEOMETRY_FULL && geometryMode != RD_GEOMETRY_SOLID)
        || (pProcEntry->extraData & 1) != 0)
        return;
    if (lightingMode < 0 || lightingMode >= 5)
        return;

    // Resolve the material cel -> texinfo -> a resident mip surface.
    rdMaterial* pMaterial = pProcEntry->material;
    if (pMaterial == NULL)
        return;
    int cel = pProcEntry->wallCel;
    if (cel == -1)
        cel = pMaterial->curCelNum;
    if (cel < 0)
        cel = 0;
    else if (cel > (int)pMaterial->num_texinfo - 1)
        cel = pMaterial->num_texinfo - 1;
    rdTexinfo* pTexinfo = pMaterial->texinfos[cel];
    if (pTexinfo == NULL)
        return;

    // Decide solid vs textured. A non-"full" texture (texture_type & 8 clear) or a texinfo with no
    // texture renders as a flat solid color (the texinfo's solidColor); a full texture is sampled.
    rdTexture* pTexture = pTexinfo->texture_ptr;
    int solidColor = -1;
    int masked = 0;
    const uint8_t* pTexels = NULL;
    tVBuffer* pMip = NULL;
    int lockedMip = 0;
    int mip = 0, mipW = 0, mipH = 0, mipStride = 0;

    if (geometryMode == RD_GEOMETRY_SOLID || (pTexinfo->header.texture_type & 8) == 0 || pTexture == NULL)
    {
        solidColor = (int)(pTexinfo->header.solidColor & 0xFF);
    }
    else
    {
        masked = (pTexture->alpha_en & 1) != 0;      // transparent texture: skip index-0 texels

        // Mip level from face distance (matches CalcAffineGradients thresholds), clamped to a
        // resident mip surface.
        int numMipsMinus1 = (int)pTexture->num_mipmaps - 1;
        flex_t dist = pProcEntry->z_min;
        if (numMipsMinus1 == 1)
        {
            if (rdroid_aMipDistances.y < dist) mip = 1;
        }
        else if (numMipsMinus1 == 2)
        {
            if (rdroid_aMipDistances.x < dist) mip = (rdroid_aMipDistances.y < dist) ? 2 : 1;
        }
        else if (numMipsMinus1 >= 3)
        {
            if (rdroid_aMipDistances.x < dist)
            {
                mip = 1;
                if (rdroid_aMipDistances.y < dist)
                {
                    mip = 2;
                    if (rdroid_aMipDistances.z < dist) mip = 3;
                }
            }
        }
        while (mip > 0 && pTexture->texture_struct[mip] == NULL)
            mip--;
        pMip = pTexture->texture_struct[mip];
        if (pMip == NULL)
            return;

        // Lock the mip on desktop to expose its 8bpp texels (persistent on TWL/DC).
        pTexels = (const uint8_t*)pMip->surface_lock_alloc;
        if (pTexels == NULL)
        {
            stdDisplay_VBufferLock(pMip);
            pTexels = (const uint8_t*)pMip->surface_lock_alloc;
            lockedMip = 1;
        }
        if (pTexels == NULL)
            return;
        mipW = (int)pMip->format.width;
        mipH = (int)pMip->format.height;
        mipStride = (int)pMip->format.rowSize;
    }

    // Colormap light table (per-vertex intensity indexes it). Absent -> raw texels.
    const uint8_t* pLightBase = NULL;
    if (pProcEntry->colormap != NULL && pProcEntry->colormap->lightlevel != NULL)
        pLightBase = pProcEntry->colormap->lightlevel;

    // Translucent faces (type & 2) blend the lit source over the destination through a 256x256
    // palette transparency LUT. Only a colormap with a transparency section (flags & 1) allocates
    // the table — the pointer is uninitialized otherwise — so gate on the flag, not just non-NULL.
    // Prefer the proc's own colormap; fall back to the identity map.
    const uint8_t* pTransTable = NULL;
    if (pProcEntry->type & 2)
    {
        rdColormap* pCM = pProcEntry->colormap;
        if (pCM != NULL && (pCM->flags & 1) && pCM->transparency != NULL)
            pTransTable = (const uint8_t*)pCM->transparency;
        else if (rdColormap_pIdentityMap != NULL && (rdColormap_pIdentityMap->flags & 1)
                 && rdColormap_pIdentityMap->transparency != NULL)
            pTransTable = (const uint8_t*)rdColormap_pIdentityMap->transparency;
    }

    // Per-vertex light level (0..63), by shading mode (mirrors rdCache_DrawFaceN).
    flex_t ambient = (rdroid_g_curRenderOptions & 2) ? pProcEntry->ambientLight : 0.0f;
    flex_t flatLevel = 0.0f;
    int perVertex = 0;
    if (lightingMode == RD_LIGHTMODE_FULLYLIT || pLightBase == NULL)
    {
        flatLevel = 63.0f;   // no darkening
    }
    else if (lightingMode == RD_LIGHTMODE_GOURAUD)
    {
        perVertex = 1;
    }
    else   // NOTLIT / DIFFUSE
    {
        flex_t level = (lightingMode == RD_LIGHTMODE_NOTLIT)
                     ? rdNRaster_Clamp01(pProcEntry->extralight)
                     : rdNRaster_Clamp01(pProcEntry->extralight + pProcEntry->light_level_static);
        if (level < ambient) level = ambient;
        flatLevel = level * 63.0f;
    }

    // Shading variant for the dispatch: flat = full bright (no light table), lit = one constant
    // level, gouraud = per-pixel.
    int shade;
    if (lightingMode == RD_LIGHTMODE_FULLYLIT || pLightBase == NULL)
        shade = RDN_FLAT;
    else if (lightingMode == RD_LIGHTMODE_GOURAUD)
        shade = RDN_GOURAUD;
    else
        shade = RDN_LIT;

    int textureMode = pProcEntry->textureMode;
    if (textureMode > rdroid_curTextureMode)
        textureMode = rdroid_curTextureMode;
    // Affine (AT, textureMode 0 — pure linear u/v) vs perspective (IT, textureMode 1 — per-pixel
    // 1/w divide), the same axis JK's rdCache_DrawFaceN dispatches on.
    int affine = (textureMode == 0);

    // Build the per-vertex attribute array.
    int numVerts = (int)pProcEntry->numVertices;
    if (numVerts < 3 || numVerts > 32)
    {
        if (lockedMip) stdDisplay_VBufferUnlock(pMip);
        return;
    }
    rdNVertex verts[32];
    rdVector3* pV = pProcEntry->aVertices;
    rdVector2* pUV = pProcEntry->aTexVerticies;   // unused (and possibly NULL) for solid faces
    for (int i = 0; i < numVerts; i++)
    {
        flex_t z = pV[i].z;
        flex_t oow = (z != 0.0f) ? (1.0f / z) : 0.0f;
        verts[i].sx = pV[i].x;
        verts[i].sy = pV[i].y;
        verts[i].oneOverW = oow;
        verts[i].uOverW = (solidColor < 0) ? pUV[i].x * oow : 0.0f;
        verts[i].vOverW = (solidColor < 0) ? pUV[i].y * oow : 0.0f;
        verts[i].u = (solidColor < 0) ? pUV[i].x : 0.0f;   // affine (AT) texel coords
        verts[i].v = (solidColor < 0) ? pUV[i].y : 0.0f;
        if (perVertex)
        {
            flex_t vl = rdNRaster_Clamp01(pProcEntry->vertexIntensities[i] + pProcEntry->extralight);
            if (vl < ambient) vl = ambient;
            verts[i].intensity = vl * 63.0f;
        }
        else
        {
            verts[i].intensity = flatLevel;
        }
    }

    rdNRasterState st;
    st.pTexels = pTexels;
    st.pLightBase = pLightBase;
    st.pTransTable = pTransTable;
    st.mip = mip;
    st.mipW = mipW;
    st.mipH = mipH;
    st.mipStride = mipStride;
    st.solidColor = solidColor;
    rdNRaster_DispatchNGon(verts, numVerts, &st,
                           solidColor >= 0,          // solid (untextured) fill
                           affine,                    // AT (affine) vs IT (perspective)
                           masked,                    // skip index-0 texels
                           pTransTable != NULL,       // translucent blend
                           shade);                    // flat / lit / gouraud
    if (lockedMip)
        stdDisplay_VBufferUnlock(pMip);
}

#endif // RDRASTER_SOFTWARE_RENDERER
