/*
===========================================================================
Copyright (C) 2008 Leszek Godlewski

This file is part of OpenMoHAA source code.

OpenMoHAA 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.

OpenMoHAA 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 OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

#include "cm_local.h"
#include "../client/client.h"

byte         cmf_dummy_trans_data;
cfencemask_t cmf_dummy_trans;

/*
====================
CM_LoadFTX
====================
*/
qboolean CM_LoadFTX(const char *name, byte **pic, int *width, int *height)
{
    int          length;
    int          numPixels;
    fileHandle_t h;
    ftx_t        header;

    *pic   = NULL;
    length = FS_FOpenFileRead(name, &h, qtrue, qtrue);
    if (length <= 0) {
        return qtrue;
    }

    FS_Read(&header, sizeof(ftx_t), h);

    header.has_alpha = LittleLong(header.has_alpha);
    header.height    = LittleLong(header.height);
    header.width     = LittleLong(header.width);

    if (!header.has_alpha) {
        FS_FCloseFile(h);
        return qfalse;
    }

    // Load the FTX data
    numPixels = header.width * header.height;
    *height   = header.height;
    *width    = header.width;
    *pic      = Hunk_AllocateTempMemory(numPixels * 4);

    FS_Read(*pic, numPixels * 4, h);
    FS_FCloseFile(h);

    return qtrue;
}

/*
====================
CM_LoadTGA
====================
*/
qboolean CM_LoadTGA(const char *name, byte **pic, int *width, int *height)
{
    int         columns, rows, numPixels;
    byte       *pixbuf;
    int         row, column;
    byte       *buf_p;
    byte       *buffer;
    int         length;
    TargaHeader targa_header;
    byte       *targa_rgba;

    *pic = NULL;

    //
    // load the file
    //
    length = FS_ReadFile(name, (void **)&buffer);
    if (!buffer) {
        return qfalse;
    }

    buf_p = buffer;

    targa_header.id_length     = *buf_p++;
    targa_header.colormap_type = *buf_p++;
    targa_header.image_type    = *buf_p++;

    targa_header.colormap_index = LittleShort(*((short *)buf_p));
    buf_p += 2;
    targa_header.colormap_length = LittleShort(*((short *)buf_p));
    buf_p += 2;
    targa_header.colormap_size = *buf_p++;
    targa_header.x_origin      = LittleShort(*((short *)buf_p));
    buf_p += 2;
    targa_header.y_origin = LittleShort(*((short *)buf_p));
    buf_p += 2;
    targa_header.width = LittleShort(*((short *)buf_p));
    buf_p += 2;
    targa_header.height = LittleShort(*((short *)buf_p));
    buf_p += 2;
    targa_header.pixel_size = *buf_p++;
    targa_header.attributes = *buf_p++;

    if (targa_header.image_type != 2 && targa_header.image_type != 10 && targa_header.image_type != 3) {
        Com_Error(ERR_DROP, "LoadTGA: Only type 2 (RGB), 3 (gray), and 10 (RGB) TGA images supported\n");
    }

    if (targa_header.colormap_type != 0) {
        Com_Error(ERR_DROP, "LoadTGA: colormaps not supported\n");
    }

    if (targa_header.pixel_size != 32) {
        if (targa_header.pixel_size != 24 && targa_header.image_type != 3) {
            Com_Error(ERR_DROP, "LoadTGA: Only 32 or 24 bit images supported (no colormaps)\n");
        }

        FS_FreeFile(buffer);
        return qfalse;
    }
    columns   = targa_header.width;
    rows      = targa_header.height;
    numPixels = columns * rows;

    if (width) {
        *width = columns;
    }
    if (height) {
        *height = rows;
    }

    targa_rgba = Hunk_AllocateTempMemory(numPixels * 4);
    *pic       = targa_rgba;

    if (targa_header.id_length != 0) {
        buf_p += targa_header.id_length; // skip TARGA image comment
    }

    if (targa_header.image_type == 2 || targa_header.image_type == 3) { // Uncompressed, RGB images
        for (row = rows - 1; row >= 0; row--) {
            pixbuf = targa_rgba + row * columns * 4;
            for (column = 0; column < columns; column++) {
                unsigned char red, green, blue, alphabyte;
                switch (targa_header.pixel_size) {
                case 24:

                    blue      = *buf_p++;
                    green     = *buf_p++;
                    red       = *buf_p++;
                    *pixbuf++ = red;
                    *pixbuf++ = green;
                    *pixbuf++ = blue;
                    *pixbuf++ = 255;
                    break;
                case 32:
                    blue      = *buf_p++;
                    green     = *buf_p++;
                    red       = *buf_p++;
                    alphabyte = *buf_p++;
                    *pixbuf++ = red;
                    *pixbuf++ = green;
                    *pixbuf++ = blue;
                    *pixbuf++ = alphabyte;
                    break;
                }
            }
        }
    } else if (targa_header.image_type == 10) { // Runlength encoded RGB images
        unsigned char red, green, blue, alphabyte, packetHeader, packetSize, j;
        for (row = rows - 1; row >= 0; row--) {
            pixbuf = targa_rgba + row * columns * 4;
            for (column = 0; column < columns;) {
                packetHeader = *buf_p++;
                packetSize   = 1 + (packetHeader & 0x7f);
                if (packetHeader & 0x80) { // run-length packet
                    switch (targa_header.pixel_size) {
                    case 24:
                        blue      = *buf_p++;
                        green     = *buf_p++;
                        red       = *buf_p++;
                        alphabyte = 255;
                        break;
                    case 32:
                        blue      = *buf_p++;
                        green     = *buf_p++;
                        red       = *buf_p++;
                        alphabyte = *buf_p++;
                        break;
                    }

                    for (j = 0; j < packetSize; j++) {
                        *pixbuf++ = red;
                        *pixbuf++ = green;
                        *pixbuf++ = blue;
                        *pixbuf++ = alphabyte;
                        column++;
                        if (column == columns) { // run spans across rows
                            column = 0;
                            if (row > 0) {
                                row--;
                            } else {
                                goto breakOut;
                            }
                            pixbuf = targa_rgba + row * columns * 4;
                        }
                    }
                } else { // non run-length packet
                    for (j = 0; j < packetSize; j++) {
                        switch (targa_header.pixel_size) {
                        case 24:
                            blue      = *buf_p++;
                            green     = *buf_p++;
                            red       = *buf_p++;
                            *pixbuf++ = red;
                            *pixbuf++ = green;
                            *pixbuf++ = blue;
                            *pixbuf++ = 255;
                            break;
                        case 32:
                            blue      = *buf_p++;
                            green     = *buf_p++;
                            red       = *buf_p++;
                            alphabyte = *buf_p++;
                            *pixbuf++ = red;
                            *pixbuf++ = green;
                            *pixbuf++ = blue;
                            *pixbuf++ = alphabyte;
                            break;
                        }
                        column++;
                        if (column == columns) { // pixel packet run spans across rows
                            column = 0;
                            if (row > 0) {
                                row--;
                            } else {
                                goto breakOut;
                            }
                            pixbuf = targa_rgba + row * columns * 4;
                        }
                    }
                }
            }
        breakOut:;
        }
    }

    FS_FreeFile(buffer);
    return qtrue;
}

/*
====================
CM_LoadFCM
====================
*/
qboolean CM_LoadFCM(const char *szName, cfencemask_t **pMask)
{
    unsigned int  version;
    int           length;
    int           iDataSize;
    fileHandle_t  h;
    fcm_t         header;
    cfencemask_t *m;
    char          tempName[256];

    *pMask = NULL;

    COM_StripExtension(szName, tempName, sizeof(tempName));
    strcat(tempName, ".fcm");

    length = FS_FOpenFileRead(tempName, &h, qtrue, qtrue);
    if (length <= 0) {
        return qfalse;
    }

    FS_Read(&version, sizeof(int), h);

    if (LittleLong(version) != FENCEMASK_VERSION) {
        FS_FCloseFile(h);
        Com_Printf("CM_LoadFCM: %s is not correct version, skipping\n", tempName);
        return qfalse;
    }

    FS_Read(&header, sizeof(fcm_t), h);

    iDataSize = LittleLong(header.iWidth) * LittleLong(header.iHeight) + 7;
    if (iDataSize < 0) {
        iDataSize = LittleLong(header.iWidth) * LittleLong(header.iHeight) + 14;
    }

    iDataSize >>= 3;

    if (iDataSize != (length - sizeof(fcm_t) - sizeof(unsigned int))) {
        FS_FCloseFile(h);
        Com_Printf("CM_LoadFCM: size mismatch in %s, skipping\n", tempName);
        return qfalse;
    }

    m      = (cfencemask_t *)Hunk_Alloc(sizeof(cfencemask_t), h_dontcare);
    *pMask = m;
    Q_strncpyz(m->name, szName, sizeof(m->name));
    m->iWidth  = header.iWidth;
    m->iHeight = header.iHeight;
    m->pData   = (byte *)Hunk_Alloc(iDataSize, h_dontcare);
    FS_Read(m->pData, iDataSize, h);
    FS_FCloseFile(h);

    Com_sprintf(tempName, sizeof(tempName), "f%s", szName);
    UI_LoadResource(tempName);

    return qtrue;
}

/*
====================
CM_SaveFCM
====================
*/
void CM_SaveFCM(const char *szName, cfencemask_t **pMask)
{
    fileHandle_t h;
    unsigned int version;
    byte        *pData;
    int          i, j;
    int          iMaskPos;
    char         endline, solid, clear;
    char         tempName[256];

    COM_StripExtension(szName, tempName, sizeof(tempName));
    strcat(tempName, ".fcm");
    h = FS_FOpenFileWrite(tempName);

    if (!h) {
        return;
    }

    version = FENCEMASK_VERSION;
    FS_Write(&version, sizeof(unsigned int), h);
    FS_Write(&(*pMask)->iWidth, sizeof(int), h);
    FS_Write(&(*pMask)->iHeight, sizeof(int), h);

    iMaskPos = (*pMask)->iHeight * (*pMask)->iWidth + 7;
    if (iMaskPos < 0) {
        iMaskPos = (*pMask)->iHeight * (*pMask)->iWidth + 7 * 2;
    }
    FS_Write((*pMask)->pData, iMaskPos >> 3, h);
    FS_FCloseFile(h);

    // save debug info
    if (developer->integer && cm_FCMdebug->integer) {
        endline = '\n';
        solid   = 'X';
        clear   = '.';

        COM_StripExtension(szName, tempName, sizeof(tempName));
        strcat(tempName, ".fcm");
        h = FS_FOpenFileWrite(tempName);
        if (!h) {
            return;
        }

        pData = (*pMask)->pData;
        for (i = 0; i < (*pMask)->iHeight; i++) {
            for (j = 0; j < (*pMask)->iWidth; j++) {
                int b1, b2;

                b1 = (j + (*pMask)->iWidth * i) & 7;
                b2 = pData[(j + (*pMask)->iWidth * i) >> 3];

                if (b2 & (1 << b1)) {
                    FS_Write(&solid, 1, h);
                } else {
                    FS_Write(&clear, 1, h);
                }
            }

            FS_Write(&endline, 1, h);
        }

        FS_FCloseFile(h);
    }
}

/*
====================
CM_GenerateDummyFenceMask
====================
*/
cfencemask_t *CM_GenerateDummyFenceMask(const char *szName, qboolean bOpaque)
{
    cfencemask_t *pMask;

    pMask          = Hunk_Alloc(sizeof(cfencemask_t) + sizeof(byte), h_dontcare);
    pMask->iWidth  = 2;
    pMask->iHeight = 2;
    pMask->pData   = (byte *)((char *)pMask + sizeof(cfencemask_t));

    if (bOpaque) {
        *(byte *)pMask->pData = -1;
    } else {
        *(byte *)pMask->pData = 0;
    }

    return pMask;
}

/*
====================
CM_GenerateFenceMask
====================
*/
qboolean CM_GenerateFenceMask(const char *szName, cfencemask_t **pMask)
{
    int      i;
    int      iImageWidth;
    int      iImageHeight;
    int      iImageSize;
    int      iMaskSize;
    int      iWidth;
    int      iHeight;
    byte    *pImage;
    byte    *pCurrImage;
    byte    *pCurrMask;
    qboolean bHasTrans  = qfalse;
    qboolean bHasOpaque = qfalse;
    char     tempName[256];

    *pMask = NULL;

    COM_StripExtension(szName, tempName, sizeof(tempName));
    strcat(tempName, ".ftx");

    if (!CM_LoadFTX(tempName, &pImage, &iImageWidth, &iImageHeight)) {
        return qfalse;
    }

    if (!pImage) {
        if (!CM_LoadTGA(szName, &pImage, &iImageWidth, &iImageHeight)) {
            return qfalse;
        }

        if (!pImage) {
            return qfalse;
        }
    }

    iWidth     = iImageWidth;
    iHeight    = iImageHeight;
    iImageSize = iImageWidth * iImageHeight;
    iMaskSize  = iImageSize + 7;
    if (iMaskSize < 0) {
        iMaskSize = iImageSize + 7 * 2;
    }

    pCurrImage = pImage + 3;

    for (i = 0; i < iImageSize; i++, pCurrImage += sizeof(unsigned int)) {
        if (*pCurrImage >= 128) {
            // Treat pixels with > 128 alpha value as opaque
            bHasOpaque = qtrue;
        } else {
            bHasTrans = qtrue;
        }

        if (bHasOpaque && bHasTrans) {
            break;
        }
    }

    // load the mask if it has transparence and is opaque
    if (bHasOpaque && bHasTrans) {
        *pMask = Hunk_Alloc((iMaskSize >> 3) + sizeof(cfencemask_t), h_dontcare);
        Q_strncpyz((*pMask)->name, szName, sizeof((*pMask)->name));
        (*pMask)->iWidth  = iWidth;
        (*pMask)->iHeight = iHeight;
        (*pMask)->pData   = (byte *)((byte *)*pMask + sizeof(**pMask));

        pCurrMask  = (*pMask)->pData;
        pCurrImage = pImage + 3;
        for (i = 0; i < iImageSize; i++, pCurrImage += sizeof(unsigned int)) {
            if (*pCurrImage >= 128) {
                pCurrMask[i >> 3] |= 1 << (i & 7);
            }
        }
    } else {
        *pMask = CM_GenerateDummyFenceMask(szName, bHasOpaque);
    }

    Hunk_FreeTempMemory(pImage);
    return qtrue;
}

/*
====================
CM_CheckFCMFileRefresh
====================
*/
qboolean CM_CheckFCMFileRefresh(const char *szFile)
{
    char tempName[256];

    COM_StripExtension(szFile, tempName, sizeof(tempName));
    strcat(tempName, ".fcm");
    return FS_FileNewer(szFile, tempName) > 0;
}

/*
====================
CM_GetFenceMask
====================
*/
cfencemask_t *CM_GetFenceMask(const char *szMaskName)
{
    qboolean      save;
    cfencemask_t *pMask;

    if (!szMaskName || !*szMaskName) {
        return NULL;
    }

    if (!memcmp(szMaskName, "nomask", 7)) {
        return NULL;
    }

    if (!memcmp(szMaskName, "ignore", 7)) {
        return &cmf_dummy_trans;
    }

    for (pMask = cm.fencemasks; pMask != NULL; pMask = pMask->pNext) {
        if (!strcmp(szMaskName, pMask->name)) {
            if (pMask->pData) {
                return pMask;
            } else {
                return NULL;
            }
        }
    }

    save = qfalse;

    if (developer->integer) {
        if (cm_FCMcacheall->integer) {
            save = qtrue;
        } else {
            if (CM_CheckFCMFileRefresh(szMaskName)) {
                save = qtrue;
            }
        }
    }

    if (save || !CM_LoadFCM(szMaskName, &pMask)) {
        if (!CM_GenerateFenceMask(szMaskName, &pMask)) {
            pMask = (cfencemask_t *)Hunk_Alloc(sizeof(cfencemask_t), h_dontcare);
            Q_strncpyz(pMask->name, szMaskName, sizeof(pMask->name));
            pMask->pData  = NULL;
            pMask->pNext  = cm.fencemasks;
            cm.fencemasks = pMask;
            return NULL;
        }
    }

    if (save) {
        CM_SaveFCM(szMaskName, &pMask);
    }

    pMask->pNext  = cm.fencemasks;
    cm.fencemasks = pMask;
    return pMask;
}

/*
====================
CM_TraceThroughFence
====================
*/
qboolean CM_TraceThroughFence(traceWork_t *tw, cbrush_t *brush, cbrushside_t *side, float fTraceFraction)
{
    int           i;
    int           iMaskPos;
    float         fS;
    float         fT;
    float        *vNorm;
    vec3_t        vPos;
    cfencemask_t *pMask;

    pMask = cm.shaders[side->shaderNum].mask;
    if (!pMask) {
        return qtrue;
    }

    if (pMask == &cmf_dummy_trans) {
        return qfalse;
    }

    if (!side->pEq) {
        return qtrue;
    }

    for (i = 0; i < 3; i++) {
        vNorm   = side->plane->normal;
        vPos[i] = tw->start[i] + fTraceFraction * (tw->end[i] - tw->start[i]) - SURFACE_CLIP_EPSILON * vNorm[i];
    }

    fS = DotProduct(vPos, side->pEq->fSeq) + side->pEq->fSeq[3];
    fS = fS - floor(fS);
    fT = DotProduct(vPos, side->pEq->fTeq) + side->pEq->fTeq[3];
    fT = fT - floor(fT);

    if (cm_FCMdebug->integer) {
        Com_Printf(
            "Trace ST coords: (%.2f %.2f) or (%i %i)\n",
            fS,
            fT,
            (int)((float)pMask->iWidth * fS),
            (int)((float)pMask->iHeight * fT)
        );
    }

    iMaskPos = (int)((float)pMask->iWidth * fS) + pMask->iWidth * (int)((float)pMask->iHeight * fT);
    return (1 << (iMaskPos & 7)) & pMask->pData[iMaskPos >> 3];
}
