
class glFont
{
    constructor(gl)
    {
        // Offscreen canvas for rendering individual characters
        this.charCanvas = document.createElement("canvas");
        this.charContext = this.charCanvas.getContext("2d");

        // Describe the font
        const font_size = 9;
        this.fontWidth = 5;
        this.fontHeight = 13;
        const font_face = "LocalFiraCode";
        const font_desc = font_size + "px " + font_face;

        // Ensure the CSS font is loaded before we do any work with it
        const self = this;
        document.fonts.load(font_desc).then(function (){

            // Create a canvas atlas for all characters in the font
            const atlas_canvas = document.createElement("canvas");
            const atlas_context = atlas_canvas.getContext("2d");
            atlas_canvas.width = 16 * self.fontWidth;
            atlas_canvas.height = 16 * self.fontHeight;

            // Add each character to the atlas
            const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-+=[]{};\'~#,./<>?!\"£$%%^&*()";
            for (let char of chars)
            {
                // Render this character to the canvas on its own
                self.RenderTextToCanvas(char, font_desc, self.fontWidth, self.fontHeight);

                // Calculate a location for it in the atlas using its ASCII code
                const ascii_code = char.charCodeAt(0);
                assert(ascii_code < 256);
                const y_index = Math.floor(ascii_code / 16);
                const x_index = ascii_code - y_index * 16
                assert(x_index < 16);
                assert(y_index < 16);

                // Copy into the atlas
                atlas_context.drawImage(self.charCanvas, x_index * self.fontWidth, y_index * self.fontHeight);
            }

            // Create the atlas texture and store it in the destination object
            self.atlasTexture = glCreateTexture(gl, atlas_canvas.width, atlas_canvas.height, atlas_canvas);
        });
    }

    RenderTextToCanvas(text, font, width, height)
    {
        // Resize canvas to match
        this.charCanvas.width = width;
        this.charCanvas.height = height;
    
        // Clear the background
        this.charContext.fillStyle = "black";
        this.charContext.clearRect(0, 0, width, height);

        // TODO(don): I don't know why this results in the crispest text!
        // Every pattern I've checked so far has thrown up no ideas... but it works, so it will do for now
        let offset = 0.25;
        if ("AFILMTWijmw4+{};\'#,.?!\"£*()".includes(text))
        {
            offset = 0.0;
        }

        // Render the text
        this.charContext.font = font;
        this.charContext.textAlign = "left";
        this.charContext.textBaseline = "top";
        this.charContext.fillText(text, offset, 2.5);
    }
}

class glTextBuffer
{
    constructor(gl, font)
    {
        this.font = font;
        this.textMap = {};
        this.textBuffer = new glDynamicBuffer(gl, glDynamicBufferType.Texture, gl.UNSIGNED_BYTE, 1, 8);
        this.textBufferPos = 0;
        this.textEncoder = new TextEncoder();
    }

    AddText(text)
    {
        // Return if it already exists
        const existing_entry = this.textMap[text];
        if (existing_entry != undefined)
        {
            return existing_entry;
        }

        // Add to the map
        // Note we're leaving an extra NULL character before every piece of text so that the shader can sample into it on text
        // boundaries and sample a zero colour for clamp.
        let entry = {
            offset: this.textBufferPos + 1,
            length: text.length,
        };
        this.textMap[text] = entry;

        // Ensure there's always enough space in the text buffer before adding
        this.textBuffer.ResizeToFitNextPow2(entry.offset + entry.length + 1);
        this.textBuffer.cpuArray.set(this.textEncoder.encode(text), entry.offset, entry.length);
        this.textBuffer.dirty = true;
        this.textBufferPos = entry.offset + entry.length;

        return entry;
    }

    UploadData()
    {
        this.textBuffer.UploadDirtyData();
    }

    SetAsUniform(gl, program, name, index)
    {
        glSetUniform(gl, program, name, this.textBuffer.texture, index);
		glSetUniform(gl, program, "inTextBufferDesc.textBufferLength", this.textBuffer.nbEntries);
    }
}