/*
 * nvtexfont2-rast.c
 *
 * Copyright (c) 2003 - 2012 NVIDIA Corporation.  All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * This software is based upon texfont, with consent from Mark J. Kilgard,
 * provided under the following terms:
 *
 * Copyright (c) Mark J. Kilgard, 1997.
 *
 * This program is freely distributable without licensing fees  and is
 * provided without guarantee or warrantee expressed or  implied. This
 * program is -not- in the public domain.
 */

#include "GLES2/gl2.h"

#include "nvgldemo.h"
#include "nvtexfont.h"
#include "nvtexfont-priv.h"

#include "helvetica.h"

/* ctype.h introduces non-portability with C libraries.                 */
/* Specifically, the symbol __ctype_b could be undefined.               */
/* We use ctype.h for the upper and lower case functions.               */
/* Avoid the problem altogether with some simple macros.                */
/* Note toupper() and tolower() assume the input is lower and upper     */
/* respectively, which it is as used below in getGlyphIndex().          */
#define myislower(c) ((c) >= 'a' && (c) <= 'z')
#define myisupper(c) ((c) >= 'A' && (c) <= 'Z')
#define mytoupper(c) ('A' + ((c)-'a'))
#define mytolower(c) ('a' + ((c)-'A'))

static int
getGlyphIndex(
    NVTexfontRasterFont *txf,
    int                 c)
{
    int indx;

    /* Automatically substitute uppercase letters with lowercase if not
       uppercase available (and vice versa). */
    if ((c >= txf->min_glyph) && (c < txf->min_glyph + txf->range)) {
        indx = txf->lut[c - txf->min_glyph];
        if (indx != -1) {
            return indx;
        }
        if (myislower(c)) {
            c = mytoupper(c);
            if ((c >= txf->min_glyph) && (c < txf->min_glyph + txf->range)) {
                return txf->lut[c - txf->min_glyph];
            }
        }
        if (myisupper(c)) {
            c = mytolower(c);
            if ((c >= txf->min_glyph) && (c < txf->min_glyph + txf->range)) {
                return txf->lut[c - txf->min_glyph];
            }
        }
    }
    return -1;
}


static unsigned char*
getFontData(
    NVTexfontRasterFontName font)
{
    switch (font) {
    case NV_TEXFONT_HELVETICA:
        return helvetica_font_data;
    default:
        return 0;
    }
}

/*
 * TexFont originally would fread() from a binary TexFont file.
 * NVTexfontRasterFont has the TexFont data built into the library in the
 * form of a C header file.
 * So each "fread(..., file)" line in the original code has been replaced
 * by a "BUFREAD(..., buffer)" below, where BUFREAD is the following macro.
 */
#define BUFREAD(TO,SIZE,NUM,BUFFER)               \
        (MEMCPY(TO, BUFFER, SIZE*NUM), BUFFER += (SIZE*NUM), (NUM))

static NVTexfontRasterFont*
loadFontData(
    NVTexfontRasterFontName font)
{
    NVTexfontRasterFont *txf;
    GLfloat w, h, xstep, ystep;
    char fileid[4], tmp;
    unsigned char *texbitmap = NULL;
    int min_glyph, max_glyph;
    int endianness, swap, format, stride, width, height;
    int i, j, got;
    unsigned char *buffer = NULL;
    GLfloat *st;
    GLshort *vert;
    NVTexfontGlyphInfo *tgi;

    txf = NULL;

    buffer = getFontData(font);
    if (!buffer) {
        lastError = "Unsupported font";
        goto error;
    }

    txf = (NVTexfontRasterFont *) MALLOC(sizeof(NVTexfontRasterFont));
    if (txf == NULL) {
        lastError = "out of memory.";
        goto error;
    }
    /* For easy cleanup in error case. */
    MEMSET(txf, 0, sizeof(NVTexfontRasterFont));
    txf->tgi = NULL;
    txf->lut = NULL;
    txf->teximage = NULL;

    got = BUFREAD(fileid, 1, 4, buffer);
    if (got != 4 || STRNCMP(fileid, "\377txf", 4)) {
        lastError = "not a texture font file.";
        goto error;
    }
    ASSERT(sizeof(int) == 4);  /* Ensure external file format size. */
    got = BUFREAD(&endianness, sizeof(int), 1, buffer);
    if (got == 1 && endianness == 0x12345678) {
        swap = 0;
    } else if (got == 1 && endianness == 0x78563412) {
        swap = 1;
    } else {
        lastError = "not a texture font file.";
        goto error;
    }

#   define EXPECT(n) \
    if (got != n) { lastError = "premature end of file."; goto error; }

    got = BUFREAD(&format, sizeof(int), 1, buffer);
    EXPECT(1);
    got = BUFREAD(&txf->tex_width, sizeof(int), 1, buffer);
    EXPECT(1);
    got = BUFREAD(&txf->tex_height, sizeof(int), 1, buffer);
    EXPECT(1);
    got = BUFREAD(&txf->max_ascent, sizeof(int), 1, buffer);
    EXPECT(1);
    got = BUFREAD(&txf->max_descent, sizeof(int), 1, buffer);
    EXPECT(1);
    got = BUFREAD(&txf->num_glyphs, sizeof(int), 1, buffer);
    EXPECT(1);

    if (swap) {
        SWAPL(&format, tmp);
        SWAPL(&txf->tex_width, tmp);
        SWAPL(&txf->tex_height, tmp);
        SWAPL(&txf->max_ascent, tmp);
        SWAPL(&txf->max_descent, tmp);
        SWAPL(&txf->num_glyphs, tmp);
    }
    txf->tgi = (NVTexfontGlyphInfo *)
        MALLOC(txf->num_glyphs * sizeof(NVTexfontGlyphInfo));
    if (txf->tgi == NULL) {
        lastError = "out of memory.";
        goto error;
    }

    /* Ensure external file format size. */
    ASSERT(sizeof(NVTexfontGlyphInfo) == 12);

    got = BUFREAD(txf->tgi, sizeof(NVTexfontGlyphInfo),
                  txf->num_glyphs, buffer);
    EXPECT(txf->num_glyphs);

    if (swap) {
        for (i = 0; i < txf->num_glyphs; i++) {
            SWAPS(&txf->tgi[i].c, tmp);
            SWAPS(&txf->tgi[i].x, tmp);
            SWAPS(&txf->tgi[i].y, tmp);
        }
    }

    w = (GLfloat) txf->tex_width;
    h = (GLfloat) txf->tex_height;
    xstep = 0.5f / w;
    ystep = 0.5f / h;

    txf->st   = (GLfloat *) MALLOC(txf->num_glyphs * sizeof(GLfloat) * 8);
    txf->vert = (GLshort *) MALLOC(txf->num_glyphs * sizeof(GLshort) * 8);
    if(!txf->st || !txf->vert) {
        lastError = "out of memory.";
        goto error;
    }

    st   = txf->st;
    vert = txf->vert;
    tgi  = txf->tgi;
    for (i = 0; i < txf->num_glyphs; i++,tgi++,st+=8,vert+=8) {
        st  [0] = tgi->x / w - xstep;
        st  [1] = tgi->y / h - ystep;
        vert[0] = tgi->xoffset;
        vert[1] = tgi->yoffset;
        st  [2] = (tgi->x + tgi->width) / w + xstep;
        st  [3] = tgi->y / h - ystep;
        vert[2] = tgi->xoffset + tgi->width;
        vert[3] = tgi->yoffset;
        st  [4] = (tgi->x + tgi->width) / w + xstep;
        st  [5] = (tgi->y + tgi->height) / h + ystep;
        vert[4] = tgi->xoffset + tgi->width;
        vert[5] = tgi->yoffset + tgi->height;
        st  [6] = tgi->x / w - xstep;
        st  [7] = (tgi->y + tgi->height) / h + ystep;
        vert[6] = tgi->xoffset;
        vert[7] = tgi->yoffset + tgi->height;
    }

    min_glyph = txf->tgi[0].c;
    max_glyph = txf->tgi[0].c;
    for (i = 1; i < txf->num_glyphs; i++) {
        if (txf->tgi[i].c < min_glyph) {
            min_glyph = txf->tgi[i].c;
        }
        if (txf->tgi[i].c > max_glyph) {
            max_glyph = txf->tgi[i].c;
        }
    }
    txf->min_glyph = min_glyph;
    txf->range = max_glyph - min_glyph + 1;

    txf->lut = (int *) MALLOC(txf->range * sizeof(int));
    if (txf->lut == NULL) {
        lastError = "out of memory.";
        goto error;
    }
    for (i = 0; i < txf->range; i++) {
        txf->lut[i] = -1;
    }
    for (i = 0; i < txf->num_glyphs; i++) {
        txf->lut[txf->tgi[i].c - txf->min_glyph] = i;
    }

    switch (format) {
    case TXF_FORMAT_BYTE:
        txf->teximage = (unsigned char *)
            MALLOC(txf->tex_width * txf->tex_height);
        if (txf->teximage == NULL) {
            lastError = "out of memory.";
            goto error;
        }
        got = BUFREAD(txf->teximage, 1, txf->tex_width * txf->tex_height,
                      buffer);
        EXPECT(txf->tex_width * txf->tex_height);
        break;
    case TXF_FORMAT_BITMAP:
        width = txf->tex_width;
        height = txf->tex_height;
        stride = (width + 7) >> 3;
        texbitmap = (unsigned char *) MALLOC(stride * height);
        if (texbitmap == NULL) {
            lastError = "out of memory.";
            goto error;
        }
        got = BUFREAD(texbitmap, 1, stride * height, buffer);
        EXPECT(stride * height);
        txf->teximage = (unsigned char *) MALLOC(width * height);
        if (txf->teximage == NULL) {
            lastError = "out of memory.";
            goto error;
        }
        for (i = 0; i < height; i++) {
            for (j = 0; j < width; j++) {
                if (texbitmap[i * stride + (j >> 3)] & (1 << (j & 7))) {
                    txf->teximage[i * width + j] = 255;
                } else {
                    txf->teximage[i * width + j] = 0;
                }
            }
        }
        FREE(texbitmap);
        break;
    }

    return txf;

error:

    if (texbitmap) {
        FREE(texbitmap);
    }

    if (txf) {
        if (txf->tgi)
            FREE(txf->tgi);
        if (txf->lut)
            FREE(txf->lut);
        if (txf->teximage)
            FREE(txf->teximage);
        if (txf->st)
            FREE(txf->st);
        if (txf->vert)
            FREE(txf->vert);
        FREE(txf);
    }

    return NULL;
}

static int
renderGlyph(
    NVTexfontRasterFont *txf,
    int                 c)
{
    int indx = getGlyphIndex(txf, c);
    if (indx == -1)
        return 0;

    glDrawArrays(GL_TRIANGLE_FAN, 4*indx, 4);
    return txf->tgi[indx].advance;
}

static GLuint
establishTexture(
    NVTexfontRasterFont *txf,
    GLuint              texobj,
    GLboolean           setupMipmaps,
    GLenum              minFilter,
    GLenum              magFilter)
{
    GLint savedTextureBinding;

    if (txf->texobj == 0) {
        if (texobj == 0) {
            glGenTextures(1, &txf->texobj);
        } else {
            txf->texobj = texobj;
        }
    }

    glGetIntegerv(GL_TEXTURE_BINDING_2D, &savedTextureBinding);
    glBindTexture(GL_TEXTURE_2D, txf->texobj);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA,
                 txf->tex_width, txf->tex_height, 0,
                 GL_ALPHA, GL_UNSIGNED_BYTE, txf->teximage);
    if (setupMipmaps)
        glGenerateMipmap(GL_TEXTURE_2D);

    glTexParameterf(GL_TEXTURE_2D,
                    GL_TEXTURE_MIN_FILTER, (GLfloat) minFilter);
    glTexParameterf(GL_TEXTURE_2D,
                    GL_TEXTURE_MAG_FILTER, (GLfloat) magFilter);

    glBindTexture(GL_TEXTURE_2D, savedTextureBinding);

    return txf->texobj;
}

int
nvtexfontInRasterFont(
    NVTexfontRasterFont *txf,
    int                 c)
{
    /* NOTE: No uppercase/lowercase substituion. */
    if ((c >= txf->min_glyph) && (c < txf->min_glyph + txf->range)) {
        if (txf->lut[c - txf->min_glyph]) {
            return 1;
        }
    }
    return 0;
}


NVTexfontRasterFont*
nvtexfontInitRasterFont(
    NVTexfontRasterFontName font,
    GLuint                  texobj,
    GLboolean               setupMipmaps,
    GLenum                  minFilter,
    GLenum                  magFilter)
{
    NVTexfontRasterFont *txf = loadFontData(font);

    if (rasterProg == 0)
        initShaders();

    if (txf) {
        establishTexture(txf, texobj, setupMipmaps, minFilter, magFilter);
    }
    return txf;
}

void
nvtexfontUnloadRasterFont(
    NVTexfontRasterFont *txf)
{

    deinitShaders();

    if (txf->texobj)
        glDeleteTextures(1, &txf->texobj);
    if (txf->teximage)
        FREE(txf->teximage);

    FREE(txf->tgi);
    FREE(txf->lut);
    FREE(txf->st);
    FREE(txf->vert);
    FREE(txf);
}

void
nvtexfontRenderString_All(
    NVTexfontRasterFont *txf,
    char                *string,
    float               x,
    float               y,
    float               scaleX,
    float               scaleY,
    float               r,
    float               g,
    float               b)
{

    GLint savedActiveTexture;
    GLint savedTextureBinding;
    GLboolean savedBlendEnable, savedDepthTestEnabled;
    GLint savedBlendSrcRGB, savedBlendSrcAlpha;
    GLint savedBlendDstRGB, savedBlendDstAlpha;
    GLint savedArrayBufferBinding;
    GLint savedVertArrayBinding, savedVertArrayEnabled, savedVertArraySize;
    GLint savedVertArrayStride, savedVertArrayType, savedVertArrayNormalized;
    void *savedVertArrayPointer;
    GLint savedTexArrayBinding, savedTexArrayEnabled, savedTexArraySize;
    GLint savedTexArrayStride, savedTexArrayType, savedTexArrayNormalized;
    void *savedTexArrayPointer;
    GLint savedProgram;

    GLuint uniScaleX, uniScaleY, uniX, uniY, uniTex, uniColor, uniOffset;

    int offset = 0, i = 0;

    /*****************
     * SAVE GL STATE *
     *****************/

    glGetIntegerv(GL_ACTIVE_TEXTURE, &savedActiveTexture);
    glGetIntegerv(GL_TEXTURE_BINDING_2D, &savedTextureBinding);
    savedBlendEnable = glIsEnabled(GL_BLEND);
    savedDepthTestEnabled = glIsEnabled(GL_DEPTH_TEST);

    glGetIntegerv(GL_BLEND_SRC_RGB, &savedBlendSrcRGB);
    glGetIntegerv(GL_BLEND_SRC_ALPHA, &savedBlendSrcAlpha);
    glGetIntegerv(GL_BLEND_DST_RGB, &savedBlendDstRGB);
    glGetIntegerv(GL_BLEND_DST_ALPHA, &savedBlendDstAlpha);
    glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &savedArrayBufferBinding);

    glGetVertexAttribiv(VERT_ARRAY, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING,
                        &savedVertArrayBinding);
    glGetVertexAttribiv(VERT_ARRAY, GL_VERTEX_ATTRIB_ARRAY_ENABLED,
                        &savedVertArrayEnabled);
    glGetVertexAttribiv(VERT_ARRAY, GL_VERTEX_ATTRIB_ARRAY_SIZE,
                        &savedVertArraySize);
    glGetVertexAttribiv(VERT_ARRAY, GL_VERTEX_ATTRIB_ARRAY_STRIDE,
                        &savedVertArrayStride);
    glGetVertexAttribiv(VERT_ARRAY, GL_VERTEX_ATTRIB_ARRAY_TYPE,
                        &savedVertArrayType);
    glGetVertexAttribiv(VERT_ARRAY, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED,
                        &savedVertArrayNormalized);
    glGetVertexAttribPointerv(VERT_ARRAY, GL_VERTEX_ATTRIB_ARRAY_POINTER,
                              &savedVertArrayPointer);

    glGetVertexAttribiv(TEX_ARRAY, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING,
                        &savedTexArrayBinding);
    glGetVertexAttribiv(TEX_ARRAY, GL_VERTEX_ATTRIB_ARRAY_ENABLED,
                        &savedTexArrayEnabled);
    glGetVertexAttribiv(TEX_ARRAY, GL_VERTEX_ATTRIB_ARRAY_SIZE,
                        &savedTexArraySize);
    glGetVertexAttribiv(TEX_ARRAY, GL_VERTEX_ATTRIB_ARRAY_STRIDE,
                        &savedTexArrayStride);
    glGetVertexAttribiv(TEX_ARRAY, GL_VERTEX_ATTRIB_ARRAY_TYPE,
                        &savedTexArrayType);
    glGetVertexAttribiv(TEX_ARRAY, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED,
                        &savedTexArrayNormalized);
    glGetVertexAttribPointerv(TEX_ARRAY, GL_VERTEX_ATTRIB_ARRAY_POINTER,
                              &savedTexArrayPointer);
    glGetIntegerv(GL_CURRENT_PROGRAM, &savedProgram);

    /************
     * SETUP GL *
     ************/

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, txf->texobj);
    glEnable(GL_BLEND);
    glDisable(GL_DEPTH_TEST);
    glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
                        GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glEnableVertexAttribArray(VERT_ARRAY);
    glVertexAttribPointer(VERT_ARRAY, 2, GL_SHORT, GL_FALSE, 0, txf->vert);
    glEnableVertexAttribArray(TEX_ARRAY);
    glVertexAttribPointer(TEX_ARRAY, 2, GL_FLOAT, GL_FALSE, 0, txf->st);

    /*****************
     * SETUP SHADERS *
     *****************/

    glUseProgram(rasterProg);
    uniScaleX = glGetUniformLocation(rasterProg, "scaleX");
    uniScaleY = glGetUniformLocation(rasterProg, "scaleY");
    uniX = glGetUniformLocation(rasterProg, "screenX");
    uniY = glGetUniformLocation(rasterProg, "screenY");
    uniTex = glGetUniformLocation(rasterProg, "fontTex");
    uniColor = glGetUniformLocation(rasterProg, "color");

    // Adjust scale so character size is approximately 1/10th screen height
    glUniform1f(uniScaleX, scaleX * 0.2f / txf->max_ascent);
    glUniform1f(uniScaleY, scaleY * 0.2f / txf->max_ascent);
    glUniform1f(uniX, x);
    glUniform1f(uniY, y);
    glUniform1i(uniTex, 0);
    glUniform3f(uniColor, r, g, b);

    uniOffset = glGetUniformLocation(rasterProg, "offset");

    /*************
     * DRAW TEXT *
     *************/

    while (string[i]) {
        glUniform1i(uniOffset, offset);
        offset += renderGlyph(txf, string[i++]);
    }

    /********************
     * RESTORE GL STATE *
     ********************/

    if (!savedVertArrayEnabled)
        glDisableVertexAttribArray(VERT_ARRAY);
    if (!savedTexArrayEnabled)
        glDisableVertexAttribArray(TEX_ARRAY);

    glBindBuffer(GL_ARRAY_BUFFER, savedVertArrayBinding);
    glVertexAttribPointer(VERT_ARRAY, savedVertArraySize,
                          savedVertArrayType, savedVertArrayNormalized,
                          savedVertArrayStride, savedVertArrayPointer);
    glBindBuffer(GL_ARRAY_BUFFER, savedTexArrayBinding);
    glVertexAttribPointer(TEX_ARRAY, savedTexArraySize,
                          savedTexArrayType, savedTexArrayNormalized,
                          savedTexArrayStride, savedTexArrayPointer);
    glBindBuffer(GL_ARRAY_BUFFER, savedArrayBufferBinding);

    if (!savedBlendEnable)
        glDisable(GL_BLEND);
    glBlendFuncSeparate(savedBlendSrcRGB, savedBlendDstRGB,
                        savedBlendSrcAlpha, savedBlendDstAlpha);

    glUseProgram(savedProgram);

    glActiveTexture(savedActiveTexture);
    glBindTexture(GL_TEXTURE_2D, savedTextureBinding);

    if (savedDepthTestEnabled)
        glEnable(GL_DEPTH_TEST);
}

void
nvtexfontRenderString_Pos(
    NVTexfontRasterFont *txf,
    NVTexfontContext    tfc,
    char                *string,
    float               x,
    float               y)
{
    nvtexfontRenderString_All(txf, string, x, y,
                              ((nvtexfontState *)tfc)->scaleX,
                              ((nvtexfontState *)tfc)->scaleY,
                              ((nvtexfontState *)tfc)->r,
                              ((nvtexfontState *)tfc)->g,
                              ((nvtexfontState *)tfc)->b);
}

void
nvtexfontRenderString(
    NVTexfontRasterFont *txf,
    NVTexfontContext    tfc,
    char                *string)
{
    nvtexfontRenderString_All(txf, string,
                              ((nvtexfontState *)tfc)->x,
                              ((nvtexfontState *)tfc)->y,
                              ((nvtexfontState *)tfc)->scaleX,
                              ((nvtexfontState *)tfc)->scaleY,
                              ((nvtexfontState *)tfc)->r,
                              ((nvtexfontState *)tfc)->g,
                              ((nvtexfontState *)tfc)->b);
}

void
nvtexfontGetStringMetrics(
    NVTexfontRasterFont *txf,
    char                *string,
    int                 len,
    int                 *width,
    int                 *max_ascent,
    int                 *max_descent)
{
    int w, i, indx;

    w = 0;
    for (i = 0; i < len; i++) {
        if (string[i] == 27) {
            switch (string[i + 1]) {
            case 'M': i += 4;  break;
            case 'T': i += 7;  break;
            case 'L': i += 7;  break;
            case 'F': i += 13; break;
            }
        } else {
            indx = getGlyphIndex(txf, string[i]);
            if (indx != -1) {
                w += txf->tgi[indx].advance;
            }
        }
    }
    *width = w;
    *max_ascent = txf->max_ascent;
    *max_descent = txf->max_descent;
}
