/*
===========================================================================
Copyright (C) 2023 the OpenMoHAA team

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
===========================================================================
*/

// DESCRIPTION:
// light style code

#include "cg_local.h"

/*
===================================================================

LIGHT STYLE CODE

===================================================================
*/

#define MAX_LIGHTSTYLE_LENGTH      128
#define MAX_LIGHTSTYLE_NAME_LENGTH 64

typedef struct {
    char     name[MAX_LIGHTSTYLE_NAME_LENGTH];
    qboolean hasalpha;
    int      length;
    float    map[MAX_LIGHTSTYLE_LENGTH][4];
} clightstyle_t;

clightstyle_t cg_lightstyle[MAX_LIGHTSTYLES * 2];

/*
================
CG_LightStyleColor
================
*/
qboolean CG_LightStyleColor(int style, int realtime, vec4_t color, qboolean clamp)
{
    clightstyle_t *ls;
    int            i, time;
    float          frac;
    qboolean       at_end;

    time = realtime / 50;
    frac = (realtime - (time * 50.0f)) / 50.0f;
    VectorClear4(color);
    if ((style < 0) || (style >= (MAX_LIGHTSTYLES * 2))) {
        cgi.DPrintf("CG_LightStyleColor: style out of range.\n");
        return qtrue;
    }

    ls = &cg_lightstyle[style];

    if (!ls->length) {
        cgi.DPrintf("CG_LightStyleColor: style %d has zero length.\n", style);
        return qtrue;
    }

    // by default we are not at the end
    at_end = qfalse;
    if (clamp) {
        if (time >= ls->length) {
            time   = ls->length - 1;
            at_end = qtrue;
        }
    } else {
        time %= ls->length;
    }

    //
    // only lerp if we are before the end
    //
    if (time == ls->length - 1) {
        memcpy(color, ls->map[time], 4 * sizeof(ls->map[time][0]));
    } else {
        for (i = 0; i < 4; i++) {
            color[i] = ls->map[time][i] + frac * (ls->map[time + 1][i] - ls->map[time][i]);
        }
    }

    if (!ls->hasalpha) {
        color[3] = 1;
    }

    return at_end;
}

/*
=============
LoadTGA
=============
*/
static qboolean LoadTGA(const char *name, byte **pic, int *width, int *height, qboolean *hasalpha)
{
    int         columns, rows, numPixels;
    byte       *pixbuf;
    int         row, column;
    byte       *buf_p;
    byte       *buffer;
    TargaHeader targa_header;
    byte       *targa_rgba;
    byte        targa_palette[256 * 4];
    int         i;

    *pic      = NULL;
    *hasalpha = false;

    //
    // load the file
    //
    cgi.FS_ReadFile(name, (void **)&buffer, qtrue);
    if (!buffer) {
        cgi.DPrintf("TGA File not found: %s\n", name);
        return false;
    }

    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++ / 8;
    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 != 1 && targa_header.image_type != 2 && targa_header.image_type != 10) {
        cgi.DPrintf("LoadTGA: Only type 1, 2 and 10 targa RGB images supported\n");
        return false;
    }

    if ((targa_header.colormap_type != 0 && targa_header.colormap_type != 1)
        || (targa_header.pixel_size != 32 && targa_header.pixel_size != 24 && targa_header.pixel_size != 8)) {
        cgi.DPrintf("LoadTGA: Only 24, 32 and colormap images supported (no colormaps)\n");
        return false;
    }

    columns   = targa_header.width;
    rows      = targa_header.height;
    numPixels = columns * rows;

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

    targa_rgba = (byte *)cgi.Malloc(numPixels * 4);
    *pic       = targa_rgba;

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

    for (i = 0; i < targa_header.colormap_length; i++) {
        byte blue  = *buf_p++;
        byte green = *buf_p++;
        byte red   = *buf_p++;
        byte alpha = 0xff;

        if (targa_header.colormap_size == 4) {
            alpha = *buf_p++;
        }

        targa_palette[i * 4 + 0] = red;
        targa_palette[i * 4 + 1] = green;
        targa_palette[i * 4 + 2] = blue;
        targa_palette[i * 4 + 3] = alpha;
        if (alpha != 0xff) {
            *hasalpha = true;
        }
    }

    if (targa_header.image_type == 1) {
        for (row = rows - 1; row >= 0; row--) {
            pixbuf = targa_rgba + row * columns;
            for (column = 0; column < columns; column++) {
                switch (targa_header.pixel_size) {
                case 8:
                    *pixbuf++ = targa_palette[*buf_p * 4 + 0];
                    *pixbuf++ = targa_palette[*buf_p * 4 + 1];
                    *pixbuf++ = targa_palette[*buf_p * 4 + 2];
                    *pixbuf++ = targa_palette[*buf_p * 4 + 3];
                    buf_p++;
                    break;
                }
            }
        }
    } else if (targa_header.image_type == 2) { // 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;
                    if (alphabyte != 0xff) {
                        *hasalpha = true;
                    }
                    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 32:
                        blue      = *buf_p++;
                        green     = *buf_p++;
                        red       = *buf_p++;
                        alphabyte = *buf_p++;
                        if (alphabyte != 0xff) {
                            *hasalpha = true;
                        }
                        break;
                    case 24:
                    default:
                        blue      = *buf_p++;
                        green     = *buf_p++;
                        red       = *buf_p++;
                        alphabyte = 255;
                        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) {
                        default:
                        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;
                            if (alphabyte != 0xff) {
                                *hasalpha = true;
                            }
                            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:;
        }
    }
    cgi.FS_FreeFile(buffer);
    return true;
}

void createLightstyleFromTGA(int num, const char *filename)
{
    byte    *data = 0;
    int      i, j, width, height;
    qboolean hasalpha;

    if (!LoadTGA(filename, &data, &width, &height, &hasalpha)) {
        return;
    }

    if (width > MAX_LIGHTSTYLE_LENGTH) {
        cgi.DPrintf("createLightstyleFromTGA: Light style too large, truncating.\n", filename);
        width = MAX_LIGHTSTYLE_LENGTH;
    }

    strncpy(cg_lightstyle[num].name, filename, MAX_LIGHTSTYLE_NAME_LENGTH);
    cg_lightstyle[num].hasalpha = hasalpha;
    cg_lightstyle[num].length   = width;

    for (i = 0; i < width; i++) {
        for (j = 0; j < 4; j++) {
            cg_lightstyle[num].map[i][j] = (float)data[(i * 4) + j] / 255.0f;
        }
    }

    if (data) {
        cgi.Free(data);
    }
}

void CG_SetLightStyle(int num, const char *s)
{
    const char *r, *g, *b, *a;
    int         j, k, n;
    size_t      len;
    float       t;

    if ((num < 0) || (num >= (MAX_LIGHTSTYLES * 2))) {
        cgi.DPrintf("CG_SetLightStyle: num out of range.\n");
        return;
    }

    len = strlen(s);

    if (!Q_stricmp(&s[len - 4], ".tga")) {
        createLightstyleFromTGA(num, s);
        return;
    }

    if (!s) {
        return;
    }

    r = strchr(s, 'R');
    g = strchr(s, 'G');
    b = strchr(s, 'B');
    a = strchr(s, 'A');

    n = 0;
    if (r) {
        n++;
    }
    if (g) {
        n++;
    }
    if (b) {
        n++;
    }
    if (a) {
        n++;
    }
    if (!n) {
        n++;
    }

    if (!a) {
        cg_lightstyle[num].hasalpha = false;
    } else {
        cg_lightstyle[num].hasalpha = true;
    }

    j = strlen(s) / n;
    if (j >= MAX_LIGHTSTYLE_LENGTH) {
        cgi.DPrintf("svc_lightstyle length=%i", j);
        j = MAX_LIGHTSTYLE_LENGTH - 1;
    }

    if (!r && !g && !b && !a) {
        cg_lightstyle[num].length = j;
        for (k = 0; k < j; k++) {
            t = (float)(s[k] - 'a') * 127.5f / 12.5;
            if (t > 255) {
                t = 255.0f;
            }
            cg_lightstyle[num].map[k][0] = t / 255.0f;
            cg_lightstyle[num].map[k][1] = t / 255.0f;
            cg_lightstyle[num].map[k][2] = t / 255.0f;
            cg_lightstyle[num].map[k][3] = 1;
        }
    } else {
        n = 0;
        // red
        k = 0;
        t = 0;
        if (r) {
            r++;
            while ((r[k] >= 'a') && (r[k] <= 'z')) {
                t = (float)(r[k] - 'a') * 127.5f / 12.5;
                if (t > 255) {
                    t = 255.0f;
                }
                cg_lightstyle[num].map[k][0] = t / 255.0f;
                k++;
            }
            n = k;
        }
        while (k < MAX_LIGHTSTYLE_LENGTH) {
            cg_lightstyle[num].map[k++][0] = t / 255.0f;
        }

        // green
        k = 0;
        t = 0;
        if (g) {
            g++;
            while ((g[k] >= 'a') && (g[k] <= 'z')) {
                t = (float)(g[k] - 'a') * 127.5f / 12.5;
                if (t > 255) {
                    t = 255.0f;
                }
                cg_lightstyle[num].map[k][1] = t / 255.0f;
                k++;
            }
            n = Q_max(n, k);
        }
        while (k < MAX_LIGHTSTYLE_LENGTH) {
            cg_lightstyle[num].map[k++][1] = t / 255.0f;
        }

        // blue
        k = 0;
        t = 0;
        if (b) {
            b++;
            while ((b[k] >= 'a') && (b[k] <= 'z')) {
                t = (float)(b[k] - 'a') * 127.5f / 12.5;
                if (t > 255) {
                    t = 255.0f;
                }
                cg_lightstyle[num].map[k][2] = t / 255.0f;
                k++;
            }
            n = Q_max(n, k);
        }
        while (k < MAX_LIGHTSTYLE_LENGTH) {
            cg_lightstyle[num].map[k++][2] = t / 255.0f;
        }

        // alpha
        k = 0;
        t = 255;
        if (a) {
            a++;
            while ((a[k] >= 'a') && (a[k] <= 'z')) {
                t = (float)(a[k] - 'a') * 127.5f / 25;
                if (t > 255) {
                    t = 255.0f;
                }
                cg_lightstyle[num].map[k][3] = t / 255.0f;
                k++;
            }
            n = Q_max(n, k);
        }
        while (k < MAX_LIGHTSTYLE_LENGTH) {
            cg_lightstyle[num].map[k++][3] = t / 255.0f;
        }

        cg_lightstyle[num].length = n;
    }
}

/*
================
CG_GetLightStyle
================
*/
int CG_GetLightStyle(const char *name)
{
    int i;

    for (i = MAX_LIGHTSTYLES; i < MAX_LIGHTSTYLES * 2; i++) {
        if (!Q_stricmp(cg_lightstyle[i].name, name)) {
            return i;
        }
    }
    return -1;
}

/*
================
CG_GetFreeLightStyle
================
*/
int CG_GetFreeLightStyle(void)
{
    int i;

    for (i = MAX_LIGHTSTYLES; i < MAX_LIGHTSTYLES * 2; i++) {
        if (!cg_lightstyle[i].name[0]) {
            return i;
        }
    }
    return -1;
}

/*
================
CG_ClearLightStyles
================
*/
void CG_ClearLightStyles(void)
{
    memset(cg_lightstyle, 0, sizeof(cg_lightstyle));
}

/*
================
CG_RegisterLightStyle
================
*/
int CG_RegisterLightStyle(const char *name)
{
    int num;

    num = CG_GetLightStyle(name);
    if (num == -1) {
        num = CG_GetFreeLightStyle();
        if (num != -1) {
            CG_SetLightStyle(num, name);
        } else {
            cgi.DPrintf("CG_RegisterLightStyle: no free spots for lightstyle %s.\n");
        }
    }

    return num;
}
