#define GL_GLEXT_PROTOTYPES
#define _USE_MATH_DEFINES

#include <uthash.h>
#include <ft2build.h>
#include FT_FREETYPE_H

#if defined(_WIN32)
    #if defined(__GNUC__)
        #include <cpuid.h>
    #elif defined(_MSC_VER)
        #include <intrin.h>
    #endif
    //#define APIENTRY __stdcall
    #include <windows.h>
    #include <errno.h>
    #define GLEW_STATIC
    #include <GL/glew.h>
    //#include "glew/glew.h"
    //#include <GL/gl.h>
    //#include <GLES2/gl2.h>
    //#include <GLES2/gl2ext.h>
    //#define GLFW_INCLUDE_ES3
	#define GLFW_EXPOSE_NATIVE_WGL
    #define GLFW_EXPOSE_NATIVE_WIN32
#else
    #include <GL/gl.h>
    //#include <GL/glext.h>
	#define GLFW_EXPOSE_NATIVE_X11
	#define GLFW_EXPOSE_NATIVE_GLX
#endif

#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include <GLFW/glfw3native.h>

#include "linmathv2.h"
//#include "geometry.h"

#include <math.h>
#ifndef M_PI
#define M_PI 3.14159265358979323846264338327950288
#endif
#define degToRad(angleInDegrees) ((angleInDegrees) * M_PI / 180.0)
#define radToDeg(angleInRadians) ((angleInRadians) * 180.0 / M_PI)

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#include "custom.h"

#include "shaders.h"
#include "input.h"
#include "text.h"

#define nullptr ((void*)0)

#define vstr(s) str(s)
#define str(s) #s

float avgfps=0.0f;
float avgupdates=0.0f;
float avgfixed=0.0f;

char *fpstext;
char *timertext;

int firstTextRun = 0;

unsigned int TXT_WIDTH = 1920;
unsigned int TXT_HEIGHT = 1080; // values calculated on start
float txt_quality = 2.0;

//unsigned int textFB, textFBsample, textFBbuffer, textTexture;
unsigned int textFB, textFBsample, textTexture;
unsigned int cft_state = 0;
int CreateFrameText() {
    if(cft_state == 1) { // fu
	    glDeleteFramebuffers(1, &textFB);
	    glDeleteTextures(1, &textFBsample);
	    //glDeleteRenderbuffers(1, &textFBbuffer);
        //cft_state = 0;
        //printf("deleting old custom fbo--text\n");
    }
    cft_state = 1;

    //printf("setting up new custom buffer--text\n");
    glGenFramebuffers(1, &textFB);
	glBindFramebuffer(GL_FRAMEBUFFER, textFB); //bind both read/write to the target framebuffer

    glGenTextures(1, &textFBsample); // texture object
    glBindTexture(GL_TEXTURE_2D, textFBsample);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); // automatic mipmap
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, TXT_WIDTH, TXT_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
    glBindTexture(GL_TEXTURE_2D, 0);

	//glGenRenderbuffers(1, &textFBbuffer); // renderbuffer object
	//glBindRenderbuffer(GL_RENDERBUFFER, textFBbuffer);
	//glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT,SCR_WIDTH, SCR_HEIGHT);
    //glBindRenderbuffer(GL_RENDERBUFFER, 0);
    
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textFBsample, 0);

    //glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, textFBbuffer);

	if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
	{
        printf("error on setup of custom buffer--text\n");
	}
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    //glBindTexture(GL_TEXTURE_2D, 0);

    return 0;
}

unsigned int uiFB, uiFBsample;//, uiFBdepth;
unsigned int cfu_state = 0;
int CreateFrameUI() {
    if(cfu_state == 1) { // fu
	    glDeleteFramebuffers(1, &uiFB);
	    glDeleteTextures(1, &uiFBsample);
	    //glDeleteTextures(1, &uiFBdepth); // reset next run
	    //glDeleteRenderbuffers(1, &uiFBdepth); // reset next run
        //cfu_state = 0;
        //printf("deletingo old custom fbo--waterdepth\n");
    }
    cfu_state = 1;

    //printf("setting up new custom fbo--waterdepth\n");
    glGenFramebuffers(1, &uiFB);
	glBindFramebuffer(GL_FRAMEBUFFER, uiFB); //bind both read/write to the target framebuffer

    glGenTextures(1, &uiFBsample); // texture object
    glBindTexture(GL_TEXTURE_2D, uiFBsample);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); // automatic mipmap
	glBindTexture(GL_TEXTURE_2D, 0);

    //--glGenTextures(1, &uiFBdepth); // texture object can access in shader
    //--glBindTexture(GL_TEXTURE_2D, uiFBdepth);
    //glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, SCR_WIDTH, SCR_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL);
    //glTexStorage2D(GL_TEXTURE_2D, 1, GL_DEPTH_COMPONENT32F, SCR_WIDTH, SCR_HEIGHT);
    //--glTexStorage2D(GL_TEXTURE_2D, 1, GL_DEPTH_COMPONENT24, SCR_WIDTH, SCR_HEIGHT);
    //glBindTexture(GL_TEXTURE_2D, 0);

    //glGenRenderbuffers(1, &uiFBdepth); // renderbuffer object cannot access in shader
    //glBindRenderbuffer(GL_RENDERBUFFER, uiFBdepth);
    //glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT,SCR_WIDTH, SCR_HEIGHT);
    //glBindRenderbuffer(GL_RENDERBUFFER, 0);
  
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, uiFBsample, 0);
//
    //--glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, uiFBdepth, 0);
    //glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, uiFBdepth);

	if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
	{
        printf("error on setup of buffer--ui\n");
	}
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    //glBindTexture(GL_TEXTURE_2D, 0);

    return 0;
}

struct GCharacter *pair = NULL;
void add_glyph(struct GCharacter *s) { HASH_ADD_INT( pair, ch, s ); }
struct GCharacter *find_glyph(int pair_id) {
    struct GCharacter *s;

    HASH_FIND_INT( pair, &pair_id, s );
    return s;
}
void delete_glyph(struct GCharacter *s) { HASH_DEL( pair, s); }

struct Sentences *sts = NULL;
void add_sts(struct Sentences *s) { HASH_ADD_INT( sts, i, s ); }
struct Sentences *find_sts(int sts_id) {
    struct Sentences *s;

    HASH_FIND_INT( sts, &sts_id, s );
    return s;
}
void delete_sts(struct Sentences *s) { HASH_DEL( sts, s); free(s); }

struct SentenceUpdates *uts = NULL;
void add_uts(struct SentenceUpdates *s) { HASH_ADD_INT( uts, i, s ); }
struct SentenceUpdates *find_uts(int uts_id) {
    struct SentenceUpdates *s;

    HASH_FIND_INT( uts, &uts_id, s );
    return s;
}
void delete_uts(struct SentenceUpdates *s) { HASH_DEL( uts, s); free(s); }

float txtLineHeight=0.0f;
unsigned int VAO, VBO; // used for glyph and sentence quads
unsigned int highestTidx = 0;
int rerenderText=0;

unsigned int highestGidx = 0;

const struct Align Align = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// 0 = defaut bottom left corner
// 1 = top left
// 3 = bottom right
// 3 = top right
//
// 4 = world
// 
// 5 = center
// 6 = middle left
// 7 = middle right
// 8 = middle top
// 9 = middle bottom

int UpdateText(unsigned int tidx, struct Vec3 *pos, struct Vec3 *rot, float scale, int align) {
    SentenceUpdates *uts;
    if((uts = find_uts(tidx))) {
        //printf("delete old uts for tidx: %d, traf->pos->x: %f, traf->pos->y: %f\n",tidx, uts->transform->position->x, uts->transform->position->y);
        free(uts->transform->position);
        free(uts->transform->rotation);
        free(uts->transform->scale);
        free(uts->transform);

        delete_uts(uts);
    }
    //if((uts = find_uts(tidx))) {
    uts = malloc(sizeof(SentenceUpdates));
   
    uts->transform = malloc(sizeof(Transform));

    uts->transform->position = malloc(sizeof(Vec3)); 
    uts->transform->rotation = malloc(sizeof(Vec3)); 
    uts->transform->scale = malloc(sizeof(Vec3)); 

    uts->transform->scale->x = scale;
    uts->transform->scale->y = scale;
    uts->transform->scale->z = scale;
    uts->transform->rotation->x = rot->x;
    uts->transform->rotation->y = rot->y;
    uts->transform->rotation->z = rot->z;
    uts->transform->position->x = pos->x;
    uts->transform->position->y = pos->y;
    uts->transform->position->z = pos->z;
    
    uts->alignment = align;
    
    uts->i = tidx;
    uts->refresh = 1;

    add_uts(uts);

    return 0;
}

//float scrolltestY=0.0f;
int RenderText(unsigned int tidx, struct Vec3 *pos, struct Vec3 *rot, float scale, int align, char* text)
{
    bool found=false;
    int refresh=0;
    SentenceUpdates *uts;
    if((uts = find_uts(tidx))) {
        refresh = uts->refresh;
        found=true;
    }
    Sentences *sts;
    if(!(sts = find_sts(tidx)) || rerenderText==1 || refresh==1) {
        bool updateOnly=false;
        bool prevExists=false;
        float lastupdate = 0.0f;
        if(sts != NULL) { // rerender(all) or refresh(specific)
            //lastupdate = sts->lastupdate / (1.0f/60.0f);
            lastupdate = updateCurrentTime - sts->lastupdate;

            if(rerenderText!=1 && (lastupdate < 1.0f/12.0f)) { // 12 fps max
            //if(rerenderText!=1 && (currentTime - sts->lastupdate < 0.1f)) {
                return 0; // do not refresh auto refreshing text before 1 sec interval, only if when also rerenderText happens/resize etc
            }
            prevExists=true;
        }
        if(found && prevExists) { // rerender(all) or refresh(specific)
            updateOnly=true; // or update ?

            // existing refresh with UpdateText
            scale = uts->transform->scale->x;
            scale = uts->transform->scale->y;
            scale = uts->transform->scale->z;
            rot->x = uts->transform->rotation->x;
            rot->y = uts->transform->rotation->y;
            rot->z = uts->transform->rotation->z;
            pos->x = uts->transform->position->x;
            pos->y = uts->transform->position->y;
            pos->z = uts->transform->position->z;
                
            align = uts->alignment;
        }

	    
        glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // background framebuffer
        //glClearColor(0.0f, 0.0f, 1.0f, 1.0f); // background framebuffer
        glClear(GL_COLOR_BUFFER_BIT);
        //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        
        //glDrawBuffer(GL_COLOR_ATTACHMENT0); // _FRONT _BACK ?

        glDisable(GL_CULL_FACE);
        glDisable(GL_DEPTH_TEST); 
        glDepthMask(GL_FALSE);

        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

        float x = pos->x;
        float y = pos->y;
        //float z = pos->z;

        if(align != 4) { // not world
            pos->x = ((x/100.0f)-.5f) * 2.0f; // input is percentage 0-100 left-right
            pos->y = (1 - (y/100.0f)-.5f) * 2.0f; // percentage 0-100 bottom to top
        }

        float wclip = 0.0f;
        float hclip = 0.0f;

        if(tidx > highestTidx) {
            highestTidx=tidx;
        }

        float total_width = 0.0f;
        float total_height = 0.0f;
        int safeW = 0;
        int safeH = 0;

        float firstX=-2.0;
        // ftmpY = quality derived~
        // quality      ftmpY
        // 1            1
        // 1.5          2
        // 2            3
        // 2.5          4
        // 3            5
        // 3.5          6
        // 4            7
        // 4.5          8
        // 5            9
        // 5.5          10
        //unsigned int lowerQ = floor(txt_quality)-1;
        //unsigned int upperQ = ceil(txt_quality);
        //float ftmpY=upperQ+lowerQ; // top edge from -1, 0, 1, 2, ..
        float ftmpY=0.97f; // coord scale -2 -3, start 1 for writing?
        float ftmpX=-0.998f; // left
        //float xclip = ((ftmpX + 1.0f) * 0.5f) * SCR_WIDTH; //to pixel
        //float yclip = ((ftmpY + 1.0f) * 0.5f) * SCR_HEIGHT; //to pixel
        float xclip = 0.0f;
        float yclip = TXT_HEIGHT; // bottom
        float tmpY = ftmpY; // top
        float tmpX = ftmpX; // left
        float fW=0.0f;
        float fH=0.0f;

        // render each char then clip
        size_t length = strlen(text)+1; // +1 for null terminator
        char *string = malloc(length);
        memcpy(string, text, length);

        float subscale = 0.001f; // subscale turn 48 px into 0.048 in screen ortho range -1 to 1 size
        //float src_c=sqrt(pow(SCR_WIDTH,2)+pow(SCR_HEIGHT,2));
        //float subscale = 10.0f/src_c;
 
        for (size_t i = 0; i < length; i++)
        {
            char c = string[i];
            //int ascii_value = getAsciiValue(c);
 
            GCharacter *glyph;
            if((glyph = find_glyph(c))) {
                float bearingY = glyph->Bearing->y * subscale;
                float bearingX = glyph->Bearing->x * subscale;
                float sizeY = glyph->Size->y * subscale;
                float sizeX = glyph->Size->x * subscale;
                float NP2X = glyph->Size->np2x * subscale;
                float NP2Y = glyph->Size->np2y * subscale;

                float h = sizeY; // glyph normal
                float w = sizeX;

                hclip = h * TXT_HEIGHT; // glyph pixel
                wclip = w * TXT_WIDTH;
                //float a = w/h;
                //wclip = hclip * a;

                //--total_width += wclip;
                if(tidx==0) {
                    if(hclip > total_height) { // currently only 1 line
                        total_height = hclip;
                    
                        if(tmpY==ftmpY) { // first set increment
                            //tmpY = scrolltestY;
                            tmpY -= (total_height / TXT_HEIGHT);
                            //tmpY -= (sizeY + bearingY);
                            //tmpY = 1.0f;
                        }
                    }
                } else {
                    total_height=txtLineHeight;

                    if(tmpY==ftmpY) { // first set increment
                        //tmpY = scrolltestY;
                        //tmpY -= (total_height * subscale) * (512.0f/SCR_WIDTH);
                        tmpY -= (total_height / TXT_HEIGHT);
                        //tmpY -= (total_height * subscale);
                        //tmpY -= (sizeY + bearingY);
                        //tmpY = 1.0f;
                    }
                }
                float xpos = (tmpX + bearingX);
                float ypos = (tmpY - (sizeY - bearingY));

                if(firstX==-2.0) {
                    firstX=xpos;
                }

                // refit uv to texture pow2 frame
                float u = sizeX/NP2X;
                float v = sizeY/NP2Y;

                float vmin=v;
                float vmax=0.0f;

                // framing / full width normalized -1to1=2 *.5=0-1
                fW = (1.0f - ((firstX + 1.0f) - xpos)) * 0.5f; // normal width
                fH = (ftmpY - ypos); // normal width
                //printf("fW==%f\n",fW);
                // update VBO for each character
                float textVertices[6][5] = {
                    { xpos,     ypos + h, 0.0f, 0.0f,  vmax },
                    { xpos,     ypos,     0.0f, 0.0f,  vmin },
                    { xpos + w, ypos,     0.0f,    u,  vmin },
                                                           
                    { xpos,     ypos + h, 0.0f, 0.0f,  vmax },
                    { xpos + w, ypos,     0.0f,    u,  vmin },
                    { xpos + w, ypos + h, 0.0f,    u,  vmax }
                };
                glBindTexture(GL_TEXTURE_2D, glyph->TextureID);

                //glActiveTexture(GL_TEXTURE0);
                glBindBuffer(GL_ARRAY_BUFFER, VBO);

                glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(textVertices), textVertices);
                //render quad
                glDrawArrays(GL_TRIANGLES, 0, 6);

                glBindBuffer(GL_ARRAY_BUFFER, 0);
                // now advance cursors for next glyph (note that advance is number of 1/64 pixels)
                //x += ((glyph->Advance) >> 6) * subscale * scale; // bitshift by 6 to get value in pixels (2^6 = 64)
                float adv = ((glyph->Advance) >> 6) * subscale;
                tmpX += adv; // bitshift by 6 to get value in pixels (2^6 = 64)
                //y -= 10.0f * subscale;
                //
                //if(spamonce<100) {
                //    printf("char '%c' texid: %d, xpos: %f, ypos: %f, w: %f, h: %f, adv: %f\n", c, glyph->TextureID, xpos, ypos, w, h, adv);
                //spamonce++;
                //}
            }
        }
        free(string);

        glBindTexture( GL_TEXTURE_2D, 0);
      
        total_width = fW * TXT_WIDTH; 
        total_width += wclip;
        if(tidx==0) { // initial test render
            //total_height = fH / subscale;
            //total_height = fH * SCR_HEIGHT; 
            total_height = fH * TXT_HEIGHT; 

            printf("fW=%f,fH=%f, total_width=%f, total_height=%f\n",fW,fH,total_width,total_height);
        }
        //safeW = (int)ceil(TXT_WIDTH);
        //safeH = (int)ceil(TXT_HEIGHT);
        //yclip = 0; // 800 - y - 1

        //yclip = (int)TXT_HEIGHT/2; // 800 - y - 1
        
        total_height+=6; // final padding adjustments
        safeW = (int)ceil(total_width);
        safeH = (int)ceil(total_height);
        yclip-=(float)safeH-6; // final padding adjustments
        
        //scrolltestY+=0.025f; // tests
        //if(scrolltestY>txt_quality) scrolltestY=-1.0f;
        unsigned int np2w = to_nearest_pow2(safeW);
        unsigned int np2h = to_nearest_pow2(safeH);
        
        //if(fullScreen==1) {
        //    printf("TXT_WIDTH=%d, TXT_HEIGHT=%d, glReadPixel(%f,%f,%d,%d)=>%d,%d,ftmpY=%f\n",TXT_WIDTH,TXT_HEIGHT,xclip,yclip,safeW,safeH,np2w,np2h,ftmpY);
        //}

        int CHANNEL_NUM=4;
        //unsigned char *data = calloc(1,sizeof(unsigned char)*safeW*safeH*CHANNEL_NUM); // calloc to zero out memory
        unsigned char *data = calloc(safeW*safeH*CHANNEL_NUM,sizeof(unsigned char)); // calloc to zero out memory

        //--if(data) {
        //--    printf("data initialized OK\n");
        //--} else {
        //--    printf("ERROR data not initialized!\n");
        //--}

        //--printf("point=%p , empty data='%s'\n", &data, data);
        //glPixelStorei(GL_PACK_ALIGNMENT, 1);
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        //--
        //glCopyTexSubImage2D(textFBsample, 0, 0, 0, xclip, yclip, safeW, safeH);
        glReadBuffer(GL_COLOR_ATTACHMENT0); // _FRONT _BACK ?
        //glReadBuffer(GL_FRONT); // _FRONT _BACK ?
        //glReadPixels(0, 0, SCR_WIDTH, SCR_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, -sts->data );

        glReadPixels(xclip, yclip, safeW, safeH, GL_RGBA, GL_UNSIGNED_BYTE, data);

        if(prevExists) {
            if(np2w != sts->np2w || np2h != sts->np2h || rerenderText==1) {
                updateOnly=false; // force new
                glDeleteTextures(1,&sts->textureid);
            }
        }
        if(!updateOnly || rerenderText==1) {
            if(prevExists) {
                free(sts->transform->position);
                free(sts->transform->rotation);
                free(sts->transform->scale);
                free(sts->transform);
                //printf("--delete old sts->tidx=%d, (old np2W=%d, o_np2H=%d), (new np2W=%d, np2H=%d)\n",tidx, sts->np2w, sts->np2h, np2w, np2h);
                delete_sts(sts);
            }
                
            sts = malloc(sizeof(Sentences));
      
            sts->transform = malloc(sizeof(Transform));
            
            sts->transform->position = malloc(sizeof(Vec3));
            sts->transform->rotation = malloc(sizeof(Vec3));
            sts->transform->scale = malloc(sizeof(Vec3));
           
             
            sts->i = tidx;

            // xclip,yclip discarded after this stage

            //glGenTextures(1, &textTexture);
            //glBindTexture(GL_TEXTURE_2D, textTexture);
            glGenTextures(1, &sts->textureid);
            glBindTexture(GL_TEXTURE_2D, sts->textureid);

            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,  GL_CLAMP_TO_BORDER);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,  GL_CLAMP_TO_BORDER);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
            glTexImage2D(
                GL_TEXTURE_2D,      // target
                0,                  // level
                GL_RGBA8,            // internal format
                np2w,        // width
                np2h,       // height
                //SCR_WIDTH,        // width
                //SCR_HEIGHT,       // height
                0,                  // number of layers?1 zero required?0
                GL_RGBA,            // format
                GL_UNSIGNED_BYTE,   // type
                NULL                // zero/memory
            );
            //--glBindTexture( GL_TEXTURE_2D, 0);
        } else {
            glBindTexture(GL_TEXTURE_2D, sts->textureid);
        }   

        // now we know both sizes and position original
        // include alignment to recompute coordinates for
        // ortho screen, skip 4 (world):pos->x etc
        float SW=SCR_WIDTH;
        float SH=SCR_HEIGHT;
        float SW2=SW/2;
        float SH2=SH/2;
        float W = total_width/4;
        float H = total_height/2;
        float W2 = W/2;
        float H2 = H/2;

        // redo scale step from above with alignment
        pos->x = x;
        pos->y = y;
        switch(align) {
            case 1: // top left
                pos->y = SH - pos->y - H;
                break;
            case 2: // bottom right
                pos->x = SW - pos->x - W;
                break;
            case 3: // top right
                pos->x = SW - pos->x - W;
                pos->y = SH - pos->y - H;
                break;
            case 5: // center
                pos->x = SW2 - pos->x - W2;
                pos->y = SH2 - pos->y - H2;
                break;
            case 6: // middle left
                pos->y = SH2 - pos->y - H2;
                break;
            case 7: // middle right
                pos->x = SW - pos->x - W;
                pos->y = SH2 - pos->y - H2;
                break;
            case 8: // middle top
                pos->x = SW2 - pos->x - W2;
                pos->y = SH - pos->y - H;
                break;
            case 9: // middle bottom
                pos->x = SW2 - pos->x - W2;
                break;
        }
        if(align!=4) { // skip world
            //pos->x = ((pos->x/100.0f)-.5f) * 2.0f; // input is percentage 0-100 left-right
            //pos->y = (1 - (pos->y/100.0f)-.5f) * 2.0f; // percentage 0-100 bottom to top
            // use pixel input coord instead of percentage
            pos->x = ((pos->x/SCR_WIDTH)-.5f) * 2.0f; // input is percentage 0-100 left-right
            pos->y = ((pos->y/SCR_HEIGHT)-.5f) * 2.0f; // percentage 0-100 bottom to top
        }
        //printf("idx: %d, align=%d, pos->x=%f, pos->y=%f, W=%f, H=%f\n",tidx,align,pos->x,pos->y,W,H);

        sts->transform->scale->x = scale;
        sts->transform->scale->y = scale;
        sts->transform->scale->z = scale;
        sts->transform->rotation->x = rot->x;
        sts->transform->rotation->y = rot->y;
        sts->transform->rotation->z = rot->z;
        sts->transform->position->x = pos->x;
        sts->transform->position->y = pos->y;
        sts->transform->position->z = pos->z;
       
        //int diffH=(int)np2h-safeH; 
        //int diffH=((int)np2h-safeH)/2; 
        int diffH=0;
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, diffH, safeW, safeH, GL_RGBA, GL_UNSIGNED_BYTE, data);
        //--
        
        //glGenerateMipmap(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, 0);

        //glPixelStorei(GL_PACK_ALIGNMENT, 4);
        glPixelStorei(GL_UNPACK_ALIGNMENT, 4);

        sts->pxW = safeW;
        sts->pxH = safeH;
        sts->np2w = np2w;
        sts->np2h = np2h;
        sts->alignment = align;
        
        sts->lastupdate = updateCurrentTime;

        //--printf(" %f] Sentence tidx: %d, texid: %d, width: %d(%d), height: %d(%d), xclip: %f, yclip: %f, x(out): %f, y(out): %f, data: '%s', bits: ", currentTime, tidx, sts->textureid, safeW, np2w, safeH, np2h, xclip, yclip, sts->transform->position->x, sts->transform->position->y, data); // data empty
        
        // save sentence id 
        //if(!prevExists && tidx!=0) { // 0 = early test only
        if(!updateOnly && tidx!=0) { // 0 = early test only
            add_sts(sts); // uthash
            //printf(">>>>adding sts tidx = %d\n",tidx);
        } else if(tidx==0) {
            txtLineHeight=total_height;
            //printf("set new line height\n");
        }

        if(found) {
            free(uts->transform->position);
            free(uts->transform->rotation);
            free(uts->transform->scale);
            free(uts->transform);

            delete_uts(uts);
        }
        
        free(data);
        
        glBlendFunc(GL_ONE, GL_ZERO);
        glDisable(GL_BLEND);

        glDepthMask(GL_TRUE);
	    glEnable(GL_DEPTH_TEST);
        glEnable(GL_CULL_FACE);
    }

    return 0;
}

int PrepareAllText() {
    // render text/hud orto mvp
    glUseProgram(0);
    glUseProgram(programText);
    
    setRenderModeOrthographic();
    execRenderModeOrthographic(v2);

    setUniform4m(&programText, "MVP", mvp);
    setUniform4m(&programText, "projection", uVp);
    setUniform1i(&programText, "text", 0);
    //glPixelStorei(GL_PACK_ALIGNMENT, 1); // to opengl
    //glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // to gpu

    //--glBindFramebuffer(GL_FRAMEBUFFER, textFB);
	//glBindRenderbuffer(GL_RENDERBUFFER, textFBbuffer);
    //uVp is 'projection * view' on the CPU sid

    // after render lines also within framebuffer msaaFB
    glBindVertexArray(VAO);

    struct Vec3 *pos = malloc(sizeof(struct Vec3));
    struct Vec3 *rot = malloc(sizeof(struct Vec3));
    pos->x=0.0f; pos->y=0.0f; pos->z=0.0f;
    rot->x=0.0f; rot->y=0.0f; rot->z=0.0f;

    // do a pre-test string ABCQbcdgjlpqty0129 to find line-height?
    // width symbol: !*"'`,@#$|/^AQbdgjlpqty0
    if(firstTextRun==0 || rerenderText==1) { // sample test first run txtLineHeight
        firstTextRun=1;

        pos->x=0.0f; pos->y=0.0f;
        setUniform3f(&programText, "textColor", 0.3f, 0.7f, 0.9f);
        RenderText(0, pos, rot, 1.0f, Align.topleft, "!*\"'`,@#$|/^AGQbdgjlpqty0");
    }

    // defined 2d textx, updatetext is secondary
    // rotation not included in 2d
    pos->x=0.0f; pos->y=0.0f;
    setUniform3f(&programText, "textColor", 0.0f, 0.8f, 0.2f);
    RenderText(1, pos, rot, 1.0f, Align.topleft, fpstext);

    pos->x=0.0f; pos->y=0.0f;
    setUniform3f(&programText, "textColor", 0.0f, 0.8f, 0.2f);
    RenderText(2, pos, rot, 1.0f, Align.topright, timertext);

    pos->x=0.0f; pos->y=0.0f;
    setUniform3f(&programText, "textColor", 1.0f, 0.2f, 0.0f);
    RenderText(3, pos, rot, 1.0f, Align.bottomright, "awv");
 

    // defined 3d textx, updatetext works the same 
    // for 3d text full range posiion and rotation 
    pos->x=-10.0f; pos->y=-2.0f; pos->z=0.0f;
    rot->x=0.0f; rot->y=0.0f; rot->z=0.0f;
    setUniform3f(&programText, "textColor", 0.8f, 0.0f, 1.0f);
    RenderText(4, pos, rot, 1.0f, Align.world, "hello world!");

    pos->x=10.0f; pos->y=2.0f; pos->z=0.0f;
    rot->x=0.0f; rot->y=0.0f; rot->z=0.0f;
    setUniform3f(&programText, "textColor", 0.0f, 0.8f, 0.4f);
    RenderText(5, pos, rot, 10.0f, Align.world, "Big Test");

    // free mallocs used by all texts when done
    free(pos);
    free(rot);

    glBindVertexArray(0);

    rerenderText=0; // resize etc rerender all
    return 0;
}

int RenderAllText2D(int depth) {
    for(unsigned int i = 0; i <= highestTidx; i++) {
        Sentences *sts;

        // render text/hud orto mvp
        glUseProgram(0);
        if(depth == -1) {
            // custom stencil depth
            glUseProgram(programStencil);
        } else {
            glUseProgram(programUI);
        }

        glDisable(GL_CULL_FACE);
	    glDisable(GL_DEPTH_TEST);
        if(depth==-1) {
            glDepthMask(GL_FALSE); // enable/disable writing+test
        } else {
            glDepthMask(GL_FALSE); // enable/disable writing+test
        }

        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

        setRenderModeOrthographic();
        execRenderModeOrthographic(v2);

        setUniform1i(&programUI, "sampler", 0);
        setUniform1f(&programUI, "alp", 1.0f);
        setUniform1f(&programUI, "ris", .5f);
        setUniform4m(&programUI, "MVP", mvp);
        setUniform4m(&programUI, "projection", uVp);

        glBindVertexArray(VAO);

        if((sts = find_sts(i))) {
            if(sts->alignment != 4) { // not world
                float xpos = sts->transform->position->x;
                float ypos = sts->transform->position->y; 

                float scale=sts->transform->scale->x;
                float a=sts->pxW/sts->pxH; // safeH yclip

                // screen scale
                float h = (sts->pxH/SCR_HEIGHT)*scale;
                //float w = (sts->pxW/TXT_WIDTH)*2.0f*scale;
                //float w = h * a * .5 * .5;
                float w = (h / ratio) * a * 0.5f;

                // refit uv to texture pow2 frame
                float u = sts->pxW/sts->np2w;
                float v = sts->pxH/sts->np2h;

                float vmax=v;
                //float vmax=1.0f;
                float vmin=0.0f;

                float sentenceVertices[6][5] = {
                    { xpos,     ypos + h, 0.0f, 0.0f,  vmax },
                    { xpos,     ypos,     0.0f, 0.0f,  vmin },
                    { xpos + w, ypos,     0.0f,    u,  vmin },
 
                    { xpos,     ypos + h, 0.0f, 0.0f,  vmax },
                    { xpos + w, ypos,     0.0f,    u,  vmin },
                    { xpos + w, ypos + h, 0.0f,    u,  vmax }
                };

                //glActiveTexture(GL_TEXTURE0);
                //glBindTexture(GL_TEXTURE_2D, texture1);
                glBindTexture(GL_TEXTURE_2D, sts->textureid);

                glBindBuffer(GL_ARRAY_BUFFER, VBO);

                glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(sentenceVertices), sentenceVertices);

                glDrawArrays(GL_TRIANGLES, 0, 6);
                
                glBindBuffer(GL_ARRAY_BUFFER, 0);

                //if(spamonce<5) {
                //    printf("spamonce2D drawing sts=%d, texid=%d; xpos:%f, ypos:%f, w:%f, h:%f, u:%f, v:%f, vmin: %f, vmax: %f, align=%d\n",i,sts->textureid,xpos,ypos,w,h,u,v,vmin,vmax,sts->alignment);
                //    spamonce++;
                //}
                glBindVertexArray(0);
    
                RenderBoundBox(xpos,ypos+h,0.0f,w,h,0.0f,0,mvp);
            } // skip world render here
        }

        glBlendFunc(GL_ONE, GL_ZERO);
        glDisable(GL_BLEND);

        glDepthMask(GL_TRUE);
	    glEnable(GL_DEPTH_TEST);
        glEnable(GL_CULL_FACE);
    }


    return 0;
}

int RenderAllText3D(int depth) {
    for(unsigned int i = 0; i <= highestTidx; i++) {
        Sentences *sts;

        glUseProgram(0);
        // render text/hud orto mvp
        if(depth == -1) {
            // custom stencil depth
            glUseProgram(programStencil);
        } else {
            glUseProgram(programT3D);
        }

        glDisable(GL_CULL_FACE);
	    glEnable(GL_DEPTH_TEST);
        glDepthMask(GL_FALSE);
        glDepthFunc(GL_LEQUAL);

//if(stencil==1) {
//glEnable(GL_STENCIL_TEST);
//glStencilFunc(GL_ALWAYS, 1, 0xFF);
//glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
//glStencilMask(0xFF); // test and write stencil
//}


        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

        //setRenderModeOrthographic();
        //execRenderModeOrthographic(v2);
        setRenderModePerspective();
        execRenderModePerspective(v);

        if(depth == -1) {
            setUniform4m(&programStencil, "MVP", mvp);
            setUniform4m(&programStencil, "projection", uVp);
        } else {
            //setUniform1i(&programT3D, "sampler", 0);
            setUniform4m(&programT3D, "MVP", mvp);
            setUniform4m(&programT3D, "projection", uVp);
            setUniform1f(&programT3D, "alp", 0.75f);
            setUniform1f(&programT3D, "ris", 0.25f);
        }

        glBindVertexArray(VAO);

        if((sts = find_sts(i))) {
            if(sts->alignment == 4) { // world only
                float xpos = sts->transform->position->x;
                float ypos = sts->transform->position->y; 
                float zpos = sts->transform->position->z;  // depth only
                float scale=sts->transform->scale->x;
                float a=sts->pxW/sts->pxH; // safeH yclip

                // world scale
                float h = (1.0f)*scale;
                //float w = (sts->pxW*0.01f)*scale;
                //float w = (h / ratio) * a * 0.5f;
                float w = (h / ratio) * a;

                // refit uv to texture pow2 frame
                float u = sts->pxW/sts->np2w;
                float v = sts->pxH/sts->np2h;
                                             
                float vmin=v;
                //float vmin=1.0f;
                float vmax=0.0f;
                
                float sentenceVertices[6][5] = { // added z?
                    { xpos,     ypos + h, zpos, 0.0f,  vmin },
                    { xpos,     ypos,     zpos, 0.0f,  vmax },
                    { xpos + w, ypos,     zpos,    u,  vmax },
 
                    { xpos,     ypos + h, zpos, 0.0f,  vmin },
                    { xpos + w, ypos,     zpos,    u,  vmax },
                    { xpos + w, ypos + h, zpos,    u,  vmin }
                };

                //glActiveTexture(GL_TEXTURE0);
                //glBindTexture(GL_TEXTURE_2D, texture1);
                glBindTexture(GL_TEXTURE_2D, sts->textureid);

                glBindBuffer(GL_ARRAY_BUFFER, VBO);

                glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(sentenceVertices), sentenceVertices);

                glDrawArrays(GL_TRIANGLES, 0, 6);
                
                glBindBuffer(GL_ARRAY_BUFFER, 0);

                //if(spamonce<5) {
                //    printf("spamonce3D drawing sts=%d, texid=%d; xpos:%f, ypos:%f, zpos:%f, w:%f, h:%f, u:%f, v:%f, align=%d\n",i,sts->textureid,xpos,ypos,zpos,w,h,u,v,sts->alignment);
                //    spamonce++;
                //}
                glBindVertexArray(0);

                RenderBoundBox(xpos,ypos+h,zpos,w,h,zpos,1,mvp);
            }
        }

        glBlendFunc(GL_ONE, GL_ZERO);
        glDisable(GL_BLEND);

//if(stencil==1) {
//glStencilMask(0xFF);
//glDisable(GL_STENCIL_TEST);
//}

        glDepthFunc(GL_LESS);
        glDepthMask(GL_TRUE);
	    glEnable(GL_DEPTH_TEST);
        glEnable(GL_CULL_FACE);
    }

    return 0;
}

static const char *ftstrerror(__attribute__((unused)) FT_Error error)
{
#undef FTERRORS_H_
#define FT_ERRORDEF(error_code, value, string) case error_code: return string;
#define FT_ERROR_START_LIST switch(error) {
#define FT_ERROR_END_LIST default: return "Unknown error"; }
#include FT_ERRORS_H
return "eee";
}

int loadFont() {
    //char* font_name = "data/fonts/OldStandard-Regular.ttf";
    char *pathname=malloc(sizeof(char)*250);
    if(fontIdx==0) {
        sprintf(pathname,"data/fonts/%s","OldStandard-Regular.ttf");
    } else if(fontIdx==1) {
        sprintf(pathname,"data/fonts/%s","SourceSansPro-Regular.ttf");
    } else if(fontIdx==2) {
        sprintf(pathname,"data/fonts/%s","Vera.ttf");
    } else if(fontIdx==3) {
        sprintf(pathname,"data/fonts/%s","VeraMoBd.ttf");
    } else if(fontIdx==4) {
        sprintf(pathname,"data/fonts/%s","LuckiestGuy.ttf");
    }
    //
    //printf("calling path: %s\n", argv[0]);

    // FT init and new face return <Not 0> if error
    // ifs below are true if <Not 0>
    FT_Library ft;
    if (FT_Init_FreeType(&ft))
    {
        printf("ERROR::FREETYPE: Could not init FreeType Library");
        return -1;
    }
    FT_Face face;
    if (FT_New_Face(ft, pathname, 0, &face))
    {
        printf("ERROR::FREETYPE: Failed to load font");
        return -1;
    }

    // set size to load glyphs as
    //FT_Set_Pixel_Sizes(face, 256, 256);
    FT_Set_Pixel_Sizes(face, 0, 64);
    //FT_Set_Char_Size(face, 16 * 64, 16 * 64, 72, 72);
    // unicode enable for emoji? use wchat_t wchar = *it to index
    //FT_Select_Charmap(face, FT_ENCODING_UNICODE);

    // disable byte-alignment restriction
    //glPixelStorei(GL_PACK_ALIGNMENT, 1); // to opengl
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // to gpu
    //FT_GlyphSlot slot = face->glyph;

    printf("Loading font glyph bitmap buffers\n");
    printf("[_detail_load_redacted_]\n");
    // load first 128 characters of ASCII set
    //for (wchar_t c = 0; c < 1151; c++) {
    for (unsigned char c = 32; c < 127; c++) { // skipping first 32 require glyph nullptr check in rendertext
        // Load character glyph 
        //if (FT_Load_Glyph(face, c, FT_LOAD_DEFAULT)) {
        if (FT_Load_Char(face, c, FT_LOAD_RENDER)) {
            printf("ERROR::FREETYTPE: Failed to load Glyph\n");
            continue;
        }
        int err;
        //if((err = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_LCD))) { // rgb
        //if((err = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_SDF))) {
        if ((err = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL))) { // l
            printf("err: %d, char: %c, dec: %d\n", err, (char)c, c);
            //printf("Err code: %s\n",FT_Error_String(err));
            printf("Err code: %s\n",ftstrerror(err));
            continue;
        }
        FT_GlyphSlot g = face->glyph;
        //int width = to_nearest_pow2( g->bitmap.width );
        //int height = to_nearest_pow2( g->bitmap.rows );
        int width = (int)g->bitmap.width;
        int height = (int)g->bitmap.rows;
        //
        unsigned int np2w = to_nearest_pow2(width);
        unsigned int np2h = to_nearest_pow2(height);
        //printf("main: num_glyphs %u\n", (unsigned int) face->num_glyphs);
        //printf("main: units_per_EM %d\n", face->units_per_EM);
        //--printf("bitmap width: np2(%d) = %d, rows: np2(%d) = %d pitch %d, char=%c\n", g->bitmap.width, np2w, g->bitmap.rows, np2h, g->bitmap.pitch, (char)c);

        //unsigned char *tdata; // pointer to pointer
        //tdata = g->bitmap.buffer; // linked
        //strcat((char *)tdata,"\0");
        //size_t plen = sizeof(tdata);
        //size_t slen = strlen((char *)tdata);
        //printf("tdata plen : %ld, slen: %ld, buff: %p\n", plen, slen, tdata);
        //--printf("-bufferdata: '%s'\n", g->bitmap.buffer);
        //FILE *f1 = fopen("file.bmp", "wb");
        //if(f1) {
        //    size_t f11 = fwrite(tdata, slen, sizeof(unsigned char), f1);
        //    printf("wrote %zu elements requested \"%s\"\n", f11, tdata);
        //    fclose(f1);
        //}

        unsigned int texture;
        glGenTextures(1, &texture);
        glBindTexture(GL_TEXTURE_2D, texture);
        // set texture options
        //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,  GL_CLAMP_TO_BORDER);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,  GL_CLAMP_TO_BORDER);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        //GLint swizzleMask[] = {GL_RED, GL_RED, GL_RED, GL_RED};
        //glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask);
        //
        glTexImage2D(
            GL_TEXTURE_2D, // target
            0,          // level
            GL_R8,     // internal format
            //GL_RGBA8,     // internal format
            //GL_LUMINANCE_ALPHA,     // internal format
            //width,  // width
            //height,   // height
            np2w,  // width
            np2h,   // height
            0,          // number of layers?1 zero required?0
            GL_RED,     // format
            //GL_LUMINANCE_ALPHA,     // internal format
            GL_UNSIGNED_BYTE,   // type
            //tdata  // zero/memory
            //g->bitmap.buffer  // zero/memory
            NULL  // zero/memory
        );
        //printf("glyph buffer data %s\n", g->bitmap.buffer);
        
        //int diffH=np2h-height;
        int diffH=0;
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, diffH, width, height, GL_RED, GL_UNSIGNED_BYTE, g->bitmap.buffer);
        //
        //
        //glGenerateMipmap(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D_ARRAY, 0);

        // now store character for later use
        GCharacter *glyph = malloc(sizeof(GCharacter));
        Size *gsz = malloc(sizeof(Size));
        Bearing *gbr = malloc(sizeof(Bearing));

        glyph->Size = gsz;
        glyph->Bearing = gbr;

        highestGidx = c;

        glyph->ch = c;
        glyph->Size->x = width;
        glyph->Size->y = height;
        glyph->Size->np2x = np2w;
        glyph->Size->np2y = np2h;
        glyph->Bearing->x = g->bitmap_left;
        glyph->Bearing->y = g->bitmap_top;
        glyph->Advance = g->advance.x;
        glyph->TextureID = texture;
        
        //printf("loading char idx %d bitleft=%d\n",c,g->bitmap_left);
        //printf("loading char idx %d advx=%ld\n",c,g->advance.x);

        //Characters.insert(std::pair<char, Character>(c, character));
        //mapInsert(&hash_table, (void)&i, (void*)glyph);
        //HASH_ADD_INT(characters, id, characters);
        //add_glyph(pair);
        add_glyph(glyph);
        //printf("sizeof = %d\n",(int)sizeof(/glyph));
    }

    // free up resources after done processing glyphs
    FT_Done_Face(face);
    FT_Done_FreeType(ft);

    glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // to opengl
    //glPixelStorei(GL_PACK_ALIGNMENT, 4); // to gpu
    
    return 0;
}

int cleanupFonts(int reload) {
    while(highestGidx>0) { // cleanup sentences
        GCharacter *glyph;
        if((glyph = find_glyph(highestGidx))) {
            if(glyph != NULL) { // rerender(all) or refresh(specific)
                glDeleteTextures(1,&glyph->TextureID);
                
                free(glyph->Size);
                free(glyph->Bearing);

                delete_glyph(glyph);
            }
        }
        highestGidx--;
    }

    if(reload==1) {
        highestGidx=0;
        loadFont();
        rerenderText=1;
    }

    return 0;
}

int InitializeText() {
    // text quad initialize
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 5, NULL, GL_DYNAMIC_DRAW); // no vertices, quad created later
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    glUseProgram(0);
    glUseProgram(programText);
    setUniform1i(&programText, "text", 0);

    loadFont();

    return 0;  
}
