/*
 * Copyright (c) 2024 Ross Cunniff
 *
 * 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.
 */
#include "libio"

#include "libglut"
#include "libgl1"

const TXF_FORMAT_BYTE = 0;
const TXF_FORMAT_BITMAP = 1;

// Forward declaration of TxfGlyph class
class TxfGlyph;
public char;

class TexFont {
    protected var tex_width;
    protected var tex_height;
    protected var max_ascent;
    protected var max_descent;
    protected var min_glyph, max_glyph;
    protected var glyphs;
    protected var image;
    protected var vertices;
    protected var texCoords;
    var texInit = false;

    public proc draw() {
        using namespace gl1;

        if (!texInit) {
            TexImage2D(TEXTURE_2D, 0, LUMINANCE, tex_width, tex_height, 0,
                      LUMINANCE, UNSIGNED_BYTE, image);
            TexEnvi(TEXTURE_2D, TEXTURE_ENV_MODE, REPLACE);
            TexParameteri(TEXTURE_2D, TEXTURE_MIN_FILTER, LINEAR);
            TexParameteri(TEXTURE_2D, TEXTURE_MAG_FILTER, LINEAR);
            TexParameteri(TEXTURE_2D, TEXTURE_WRAP_S, REPEAT);
            TexParameteri(TEXTURE_2D, TEXTURE_WRAP_T, REPEAT);
            texInit = true;
        }
        Enable(TEXTURE_2D);
        VertexPointer(3, FLOAT, 0, vertices);
        TexCoordPointer(2, FLOAT, 0, texCoords);
        EnableClientState( VERTEX_ARRAY );
        EnableClientState( TEXTURE_COORD_ARRAY );
        DrawArrays(TRIANGLE_FAN, 0, 4);
    }

    public proc create(filename) {
        var f;
        f = new File(filename, "rb");
        if( f == nil ) {
            "Error: can't open ", filename, "\n";
        }

        try {

            var s;

            // Read the magic number
            s = f.read(PackUbyte[4]);
            if( !(s #= [0xFF,'t','x','f']).reduce(`&) ) {
                throw("bad magic ");
            }

            // Read the byte swap indicator
            switch( f.read(Uint) ) {
            case 0x12345678U : f.setswab(false);
            case 0x78563412U : f.setswab(true);
            default : throw("bad byte order");
            }

            // Read the header
            var fmt;
            var num_glyphs;
            var a;

            a = f.read(PackInt[6]);
            fmt = a[0];
            tex_width = a[1];
            tex_height = a[2];
            max_ascent = a[3];
            max_descent = a[4];
            num_glyphs = a[5];
            glyphs = new Array(num_glyphs);

            // Read the glyphs
            min_glyph = 0x3FFFFFFF;
            max_glyph = -1;
            for( var i = 0; i < num_glyphs; i++ ) {
                glyphs[i] = new TxfGlyph(f, self) {};
                if( glyphs[i].char < min_glyph ) {
                    min_glyph = glyphs[i].char;
                }
                if( glyphs[i].char > max_glyph ) {
                    max_glyph = glyphs[i].char;
                }
            }

            // Sort the glyphs
            var lut;
            lut = new Array(max_glyph - min_glyph + 1);
            for( var i = 0; i < num_glyphs; i++ ) {
                lut[glyphs[i].char - min_glyph] = glyphs[i];
            }
            glyphs = lut;

            // Read the texture
            switch( fmt ) {
            case TXF_FORMAT_BYTE :
                image = f.read(PackChar[tex_width*tex_height]);
            case TXF_FORMAT_BITMAP :
                var byte_width;
                image = new PackChar(tex_width * tex_height);
                byte_width = (tex_width + 7) / 8;
                for( var i = 0; i < tex_height; i++ ) {
                    s = f.read(PackUbyte[byte_width]);
                    for( var j = 0; j < tex_width; j++ ) {
                        image[i*tex_width + j] = s[j/8] & (1<<(j%8)) ? 255 : 0;
                    }
                }
            default :
                throw("bad texture format");
            }
        }
        catch (msg, fil, lin) {
            "Error: ", msg, ' ', fil, ' ', lin, '\n';
        }

        f.close();

        vertices = oadl::perm([-1.,-1,0,  1,-1,0,  1,1,0,  -1,1,0]);
        texCoords = oadl::perm([0.,0,  1,0,  1,1,  0,1]);
    }
}

class TxfGlyph {
    protected var vertices;
    protected var texCoords;
    protected var char;

    public proc draw() {
        gl1::VertexPointer(3, gl1::FLOAT, 0, vertices);
        gl1::TexCoordPointer(2, gl1::FLOAT, 0, texCoords);
        gl1::EnableClientState( gl1::VERTEX_ARRAY );
        gl1::EnableClientState( gl1::TEXTURE_COORD_ARRAY );
        gl1::DrawArrays(gl1::TRIANGLE_FAN, 0, 4);
    }

    public proc create(f, font) {
        var width;
        var height;
        var xoffset;
        var yoffset;
        var advance;
        var posX;
        var posY;
        var a;

        char = f.read(Ushort);
        a = f.read(PackUbyte[6]);
        width   = a[0];
        height  = a[1];
        xoffset = a[2];
        yoffset = a[3];
        advance = a[4];
        posX = f.read(Ushort);
        posY = f.read(Ushort);

        vertices = oadl::perm(new PackFloat(3*4));
        texCoords = oadl::perm(new PackFloat(2*4));

        var tw, th, xstep, ystep;

        tw = Float(font.tex_width);
        th = Float(font.tex_height);
        xstep = 0.5 / tw;
        ystep = 0.5 / th;

        texCoords[  0] = posX / tw + xstep;
        texCoords[  1] = posY / th + ystep;
        vertices [  0] = Float(xoffset);
        vertices [  1] = Float(yoffset);
        vertices [  2] = 0.;

        texCoords[2+0] = (posX + width) / tw + xstep;
        texCoords[2+1] = posY / th + ystep;
        vertices [3+0] = Float(xoffset + width);
        vertices [3+1] = Float(yoffset);
        vertices [3+2] = 0.;

        texCoords[4+0] = (posX + width) / tw + xstep;
        texCoords[4+1] = (posY + height) / th + ystep;
        vertices [6+0] = Float(xoffset + width);
        vertices [6+1] = Float(yoffset + height);
        vertices [6+1] = 0.;

        texCoords[6+0] = posX / tw + xstep;
        texCoords[6+1] = (posY + height) / th + ystep;
        vertices [9+0] = Float(xoffset);
        vertices [9+1] = Float(yoffset + height);
        vertices [9+2] = 0.;
    }
}

var txf, txfName;

proc winshape(w, h)
{
    var x = 0, y = 0;

    if (w < h) { y = (h - w) / 2; h = w; }
    else       { x = (w - h) / 2; w = h; }
    gl1::Viewport(x, y, w, h);
}

proc display()
{
    if (!txf) {
        txf = new TexFont(txfName);
    }

    gl1::ClearColor(0., 0., 0., 1.);
    gl1::Clear(gl1::COLOR_BUFFER_BIT|gl1::DEPTH_BUFFER_BIT);
    txf.draw();
    glut::SwapBuffers();
    glut::PostRedisplay();
}

proc main(args)
{
    txfName = args[1];

    glut::Init(args);
    glut::InitDisplayMode(glut::RGBA | glut::DOUBLE | glut::DEPTH);
    glut::CreateWindow("DEMO\0");
    glut::DisplayFunc(display);
    glut::ReshapeFunc(winshape);
    glut::MainLoop();
}