#define GL_GLEXT_PROTOTYPES
#define _USE_MATH_DEFINES

#include <uthash.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 <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

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

#include "custom.h"
#include "input.h"
#include "shaders.h"

// linmath extension -- END
 
void setUniform1i(const GLuint* shader, char *name, int i1)
{
    int loc = glGetUniformLocation(*shader, name);
    glUniform1i(loc, i1);
}
void setUniform1f(const GLuint* shader, char *name, float f1)
{
    int loc = glGetUniformLocation(*shader, name);
    glUniform1f(loc, f1);
}
void setUniform3f(const GLuint* shader, char *name, float x, float y, float z)
{
    int loc = glGetUniformLocation(*shader, name);
    glUniform3f(loc, x, y, z);
}
void setUniform4m(const GLuint* shader, char *name, mat4x4 m4) {
    int loc = glGetUniformLocation(*shader, name);
    glUniformMatrix4fv(loc, 1, GL_FALSE, (const GLfloat*) m4);
}

int bidx=0;
struct BoundsDrawList *bdl = NULL;
void add_bdl(struct BoundsDrawList *s) { HASH_ADD_INT( bdl, i, s ); }
struct BoundsDrawList *find_bdl(int bdl_id) {
    struct BoundsDrawList *s;

    HASH_FIND_INT( bdl, &bdl_id, s );
    return s;
}
void delete_bdl(struct BoundsDrawList *s) { HASH_DEL( bdl, s); free(s); }

int lidx=0;
struct LinesDrawList *ldl = NULL;
void add_ldl(struct LinesDrawList *s) { HASH_ADD_INT( ldl, i, s ); }
struct LinesDrawList *find_ldl(int ldl_id) {
    struct LinesDrawList *s;

    HASH_FIND_INT( ldl, &ldl_id, s );
    return s;
}
void delete_ldl(struct LinesDrawList *s) { HASH_DEL( ldl, s); free(s); }

// shader program list
unsigned int program, programPost, programCube;
unsigned int programText, programT3D, programUI, programLine;
unsigned int programTerrain, programStencil, programDepth;
unsigned int programWater, programWaterHeight, programWaterDepth;
unsigned int programLava, programFire, programFireBall, programVortex;
unsigned int programSky, programSun, programMoon, programClouds;

mat4x4 proj_ortho = {
    {1.0f, 0.0f, 0.0f, 0.0f},
    {0.0f, 1.0f, 0.0f, 0.0f},
    {0.0f, 0.0f, 0.0f, 0.0f},
    {0.0f, 0.0f, 0.0f, 1.0f}
};
//mat4 mvp_text = multiply(m, v);
//mvp_text = multiply(mvp_text, proj_text);
// m * v * p = mvp


mat4x4 m, p, v, v2, mvp, uVp, ip, iv;
int setRenderModeOrthographic() {
    mat4x4_identity(m);
    // ortho
    mat4x4_ortho(p, -1.0f, 1.0f, -1.f, 1.f, 1.f, -1.f);
    //mat4x4_ortho(p, 2/SCR_WIDTH, 2/SCR_HEIGHT, -1.f, 1.f, 1.f, -1.f);
    ////or m = proj_ortho;
    //
    //for(int j=0;j<4;j++) {
    //    for(int k=0;k<4;k++) {
    //        m[j][k] = proj_ortho[j][k];
    //    }
    //}
    //m[0][0]=-ratio/2;
    //m[1][1]=1.0f;
    //m[0][0]=2/SCR_WIDTH;
    //m[1][1]=2/SCR_HEIGHT;

   // float right=1;
   // float left=-1;
   // float bottom=1;
   // float top=-1;
   // float zFar = 1000.0f;
   // float zNear = 0.1f;
   // m[0][0] = 2.0f / (right - left);
   // m[1][1] = 2.0f / (top - bottom);
   // m[2][2] = - 2.0f / (zFar - zNear);
   // m[3][0] = - (right + left) / (right - left);
   // m[3][1] = - (top + bottom) / (top - bottom);
   // m[3][2] = - (zFar + zNear) / (zFar - zNear);
    return 0;
}
int execRenderModeOrthographic(mat4x4 v2) {
    //mat4x4_mul(mvp, m, v);
    //mat4x4_mul(mvp, mvp, p);

    mat4x4_mul(uVp, p, v2);
    mat4x4_mul(mvp, uVp, m);

    return 0;
}
int setRenderModePerspective() {
    mat4x4_identity(m);

    //printf("deg to rad FOV %f\n",fovrad);
    // perspective
    mat4x4_perspective(p, fovrad, SCR_WIDTH/(float)SCR_HEIGHT, 0.1f,5000.0f); //FOV of 90°

    return 0;
}
int execRenderModePerspective(mat4x4 v) {
    mat4x4_mul(uVp, p, v);
    mat4x4_mul(mvp, uVp, m);

    return 0;
}

int RenderAllLines(int depth) { // single lines
    glUseProgram(0);
    if(depth==-1) {
        glUseProgram(programStencil);
    } else {
        glUseProgram(programLine);
    }
    //printf("render bound box x=%f, y=%f, z=%f, w=%f, h=%f, d=%f, mode=%d\n",x,y,z,w,h,d,mode);

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

    for(int i=0;i<lidx;i++) {
        LinesDrawList *ldl;

        if((ldl = find_ldl(i))) {
            float x1 = ldl->x1;
            float y1 = ldl->y1;
            float z1 = ldl->z1;
            float x2 = ldl->x2;
            float y2 = ldl->y2;
            float z2 = ldl->z2;
            //float r = bdl->r;
            //float g = bdl->g;
            //float b = bdl->b;
            int mode = ldl->mode;
            //mvp = bdl->m;
            //memcpy(uVp,bdl->m,sizeof(bdl->m));
            //printf("mvp[0][0] == %f == bdl->m[0][0] == %f\n",mvp[0][0],bdl->m[0][0]);
            //--printf("lidx:%d, draw line x1:%f, y1:%f, z1:%f, x2:%f, y2:%f, z2:%f\n",i,x1,y1,z1,x2,y2,z2);

            if(mode==0) { // ortho
                setRenderModeOrthographic();
                execRenderModeOrthographic(v2);
                memcpy(mvp,ldl->m,sizeof(ldl->m));
            } else if(mode==1) { // persp
                setRenderModePerspective();
                execRenderModePerspective(v);
                memcpy(mvp,ldl->m,sizeof(ldl->m));
            } // 2 = keep original

            setUniform4m(&programLine, "MVP", mvp);
            setUniform4m(&programLine, "projection", uVp);

            //setUniform3f(&programLine, "lineColor", r, g, b);
            setUniform3f(&programLine, "lineColor", 1.0f, 0.0f, 0.5f);
            //--    glVertex3f(x+w,y-h,z-d);
            //--    glEnd();

            glBegin(GL_LINES);
            // horizontal -- front
            glVertex3f(x1,y1,z1);
            glVertex3f(x2,y2,z2);
            
            glEnd();


            if(depth!=-1) {
                delete_ldl(ldl);
            }
        }
    }
    //glDepthMask(GL_TRUE);
    //glEnable(GL_DEPTH_TEST); 
    glDisable(GL_CULL_FACE);
    return 0;
}

int RenderLine(float x1, float y1, float z1, float x2, float y2, float z2, int mode, mat4x4 m) {
    //if(!boundsTog)
    //    return 0;

    LinesDrawList *ldl = malloc(sizeof(LinesDrawList));

    ldl->i = lidx;
    ldl->x1 = x1;
    ldl->y1 = y1;
    ldl->z1 = z1;
    ldl->x2 = x2;
    ldl->y2 = y2;
    ldl->z2 = z2;
    //bdl->r = r;
    //bdl->g = g;
    //bdl->b = b;
    ldl->mode = mode;
    //bdl->m = m;
    memcpy(ldl->m,m,sizeof(ldl->m));

    add_ldl(ldl);

    lidx++; // reset to 0 after every render frame
    return 0;
}

int RenderAllBounds() {
    glUseProgram(0);
    glUseProgram(programLine);
    //printf("render bound box x=%f, y=%f, z=%f, w=%f, h=%f, d=%f, mode=%d\n",x,y,z,w,h,d,mode);

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

    for(int i=0;i<bidx;i++) {
        BoundsDrawList *bdl;

        if((bdl = find_bdl(i))) {
            float x = bdl->x;
            float y = bdl->y;
            float z = bdl->z;
            float w = bdl->w;
            float h = bdl->h;
            float d = bdl->d;
            //float r = bdl->r;
            //float g = bdl->g;
            //float b = bdl->b;
            int mode = bdl->mode;
            //mvp = bdl->m;
            //memcpy(uVp,bdl->m,sizeof(bdl->m));
            //printf("mvp[0][0] == %f == bdl->m[0][0] == %f\n",mvp[0][0],bdl->m[0][0]);

            if(mode==0) { // ortho
                setRenderModeOrthographic();
                execRenderModeOrthographic(v2);
                memcpy(mvp,bdl->m,sizeof(bdl->m));
            } else if(mode==1) { // persp
                setRenderModePerspective();
                execRenderModePerspective(v);
                memcpy(mvp,bdl->m,sizeof(bdl->m));
            } // 2 = keep original

            setUniform4m(&programLine, "MVP", mvp);
            setUniform4m(&programLine, "projection", uVp);

            //setUniform3f(&programLine, "lineColor", r, g, b);
            setUniform3f(&programLine, "lineColor", 0.0f, 1.0f, 0.0f);
            if(z == d) { // no depth = 2D/flat
            //--    glBegin(GL_LINES);
            //--    // horizontal -- front
            //--    glVertex3f(x,y,z);
            //--    glVertex3f(w,y,z);
            //--    glVertex3f(x,h,z);
            //--    glVertex3f(w,h,z);
            //--    
            //--    // vertical
            //--    glVertex3f(x,y,z);
            //--    glVertex3f(x,h,z);
            //--    glVertex3f(w,y,z);
            //--    glVertex3f(w,h,z);
            //--    glEnd();
                glBegin(GL_LINES);
                // horizontal -- front
                glVertex3f(x,y,z);
                glVertex3f(x+w,y,z);
                glVertex3f(x,y-h,z);
                glVertex3f(x+w,y-h,z);
                
                // vertical
                glVertex3f(x,y,z);
                glVertex3f(x,y-h,z);
                glVertex3f(x+w,y,z);
                glVertex3f(x+w,y-h,z);
                glEnd();
            } else {
                glBegin(GL_LINES);
                // horizontal -- front
                glVertex3f(x,y,z);
                glVertex3f(w,y,z);
                glVertex3f(x,h,z);
                glVertex3f(w,h,z);
                
                // vertical
                glVertex3f(x,y,z);
                glVertex3f(x,h,z);
                glVertex3f(w,y,z);
                glVertex3f(w,h,z);

                // horizontal -- back
                glVertex3f(x,y,d);
                glVertex3f(w,y,d);
                glVertex3f(x,h,d);
                glVertex3f(w,h,d);
                
                // vertical
                glVertex3f(x,y,d);
                glVertex3f(x,h,d);
                glVertex3f(w,y,d);
                glVertex3f(w,h,d);

                // horizontal --depth/horizontal all
                glVertex3f(x,y,z);
                glVertex3f(x,y,d);
                glVertex3f(w,y,z);
                glVertex3f(w,y,d);
                
                // horizontal --depth/horizontal all
                glVertex3f(w,h,z);
                glVertex3f(w,h,d);
                glVertex3f(x,h,z);
                glVertex3f(x,h,d);
                glEnd();
            //--    glBegin(GL_LINES);
            //--    // horizontal -- front
            //--    glVertex3f(x,y,z);
            //--    glVertex3f(x+w,y,z);
            //--    glVertex3f(x,y-h,z);
            //--    glVertex3f(x+w,y-h,z);
            //--    
            //--    // vertical
            //--    glVertex3f(x,y,z);
            //--    glVertex3f(x,y-h,z);
            //--    glVertex3f(x+w,y,z);
            //--    glVertex3f(x+w,y-h,z);

            //--    // horizontal -- back
            //--    glVertex3f(x,y,z-d);
            //--    glVertex3f(x+w,y,z-d);
            //--    glVertex3f(x,y-h,z-d);
            //--    glVertex3f(x+w,y-h,z-d);
            //--    
            //--    // vertical
            //--    glVertex3f(x,y,z-d);
            //--    glVertex3f(x,y-h,z-d);
            //--    glVertex3f(x+w,y,z-d);
            //--    glVertex3f(x+w,y-h,z-d);

            //--    // horizontal --depth/horizontal all
            //--    glVertex3f(x,y,z);
            //--    glVertex3f(x,y,z-d);
            //--    glVertex3f(x+w,y,z);
            //--    glVertex3f(x+w,y,z-d);
            //--    
            //--    // horizontal --depth/horizontal all
            //--    glVertex3f(x+w,y,z);
            //--    glVertex3f(x+w,y,z-d);
            //--    glVertex3f(x+w,y-h,z);
            //--    glVertex3f(x+w,y-h,z-d);
            //--    glEnd();
            }

            delete_bdl(bdl);
        }
    }
    //glDepthMask(GL_TRUE);
    //glEnable(GL_DEPTH_TEST); 
    glDisable(GL_CULL_FACE);
    return 0;
}

int RenderBoundBox(float x, float y, float z, float w, float h, float d, int mode, mat4x4 m) {
    if(!boundsTog)
        return 0;

    BoundsDrawList *bdl = malloc(sizeof(BoundsDrawList));

    bdl->i = bidx;
    bdl->x = x;
    bdl->y = y;
    bdl->z = z;
    bdl->w = w;
    bdl->h = h;
    bdl->d = d;
    //bdl->r = r;
    //bdl->g = g;
    //bdl->b = b;
    bdl->mode = mode;
    //bdl->m = m;
    memcpy(bdl->m,m,sizeof(bdl->m));

    add_bdl(bdl);

    bidx++; // reset to 0 after every render frame
    return 0;
}

int RenderGridLines() { // blue
    // render line grid on mvp perspective
    glUseProgram(0);
    glUseProgram(programLine);

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

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

    setUniform4m(&programLine, "MVP", mvp);
    setUniform4m(&programLine, "projection", uVp);

    setUniform3f(&programLine, "lineColor", 0.3f, 0.5f, 0.5f);
    glBegin(GL_LINES);
    int numlines=100;
    float numdiv=(50.0f/(float)numlines) * 2.0f;
    for (int i = 0; i <= numlines; i ++) // 100x100f grid
    {   
        // horizontal
        glVertex3f(-50.0f + i*numdiv, -50.0f, 0.0f);
        glVertex3f(-50.0f + i*numdiv,  50.0f, 0.0f);
    
        // vertical
        glVertex3f(-50.0f, -50.0f + i*numdiv, 0.0f);
        glVertex3f( 50.0f, -50.0f + i*numdiv, 0.0f);
    }
    glEnd();

    setUniform3f(&programLine, "lineColor", 0.0f, 1.0f, 1.0f);
    glBegin(GL_LINES);
    numlines=10;
    numdiv=(50.0f/(float)numlines) * 2.0f;
    for (int i = 0; i <= numlines; i ++) // 100x100f grid
    {   
        // horizontal
        glVertex3f(-50.0f + i*numdiv, -50.0f, 0.0f);
        glVertex3f(-50.0f + i*numdiv,  50.0f, 0.0f);
    
        // vertical
        glVertex3f(-50.0f, -50.0f + i*numdiv, 0.0f);
        glVertex3f( 50.0f, -50.0f + i*numdiv, 0.0f);
    }
    glEnd();

    //glDepthMask(GL_TRUE);
    //glEnable(GL_DEPTH_TEST); 
    glDisable(GL_CULL_FACE);
    return 0;
}

int RenderGridLines2() { // yellow
    // render line grid on mvp perspective
    glUseProgram(0);
    glUseProgram(programLine);

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

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

    setUniform4m(&programLine, "MVP", mvp);
    setUniform4m(&programLine, "projection", uVp);

    setUniform3f(&programLine, "lineColor", 0.6f, 0.5f, 0.3f);
    glBegin(GL_LINES);
    int numlines=100;
    float numdiv=(50.0f/(float)numlines) * 2.0f;
    for (int i = 0; i <= numlines; i ++) // 100x100f grid
    {   
        // horizontal
        glVertex3f(-50.0f + i*numdiv, 0.0f, -50.0f);
        glVertex3f(-50.0f + i*numdiv, 0.0f,  50.0f);
    
        // vertical
        glVertex3f(-50.0f, 0.0f, -50.0f + i*numdiv);
        glVertex3f( 50.0f, 0.0f, -50.0f + i*numdiv);
    }
    glEnd();
    
    setUniform3f(&programLine, "lineColor", 1.0f, 1.0f, 0.0f);
    glBegin(GL_LINES);
    numlines=10;
    numdiv=(50.0f/(float)numlines) * 2.0f;
    for (int i = 0; i <= numlines; i ++) // 100x100f grid
    {   
        // horizontal
        glVertex3f(-50.0f + i*numdiv, 0.0f, -50.0f);
        glVertex3f(-50.0f + i*numdiv, 0.0f,  50.0f);
    
        // vertical
        glVertex3f(-50.0f, 0.0f, -50.0f + i*numdiv);
        glVertex3f( 50.0f, 0.0f, -50.0f + i*numdiv);
    }
    glEnd();

    //glDepthMask(GL_TRUE);
    //glEnable(GL_DEPTH_TEST); 
    glDisable(GL_CULL_FACE);

    return 0;
}

int RenderGridLines3() { // red
    // render line grid on mvp perspective
    glUseProgram(0);
    glUseProgram(programLine);

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

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

    setUniform4m(&programLine, "MVP", mvp);
    setUniform4m(&programLine, "projection", uVp);

    setUniform3f(&programLine, "lineColor", 0.8f, 0.0f, 0.3f);
    glBegin(GL_LINES);
    int numlines=100;
    float numdiv=(50.0f/(float)numlines) * 2.0f;
    for (int i = 0; i <= numlines; i ++) // 100x100f grid
    {   
        // horizontal
        glVertex3f(0.0f, -50.0f + i*numdiv, -50.0f);
        glVertex3f(0.0f, -50.0f + i*numdiv,  50.0f);
    
        // vertical
        glVertex3f(0.0f, -50.0f, -50.0f + i*numdiv);
        glVertex3f(0.0f,  50.0f, -50.0f + i*numdiv);
    }
    glEnd();
    
    setUniform3f(&programLine, "lineColor", 1.0f, 0.2f, 0.2f);
    glBegin(GL_LINES);
    numlines=10;
    numdiv=(50.0f/(float)numlines) * 2.0f;
    for (int i = 0; i <= numlines; i ++) // 100x100f grid
    {   
        // horizontal
        glVertex3f(0.0f, -50.0f + i*numdiv, -50.0f);
        glVertex3f(0.0f, -50.0f + i*numdiv,  50.0f);
    
        // vertical
        glVertex3f(0.0f, -50.0f, -50.0f + i*numdiv);
        glVertex3f(0.0f,  50.0f, -50.0f + i*numdiv);
    }
    glEnd();

    //glDepthMask(GL_TRUE);
    //glEnable(GL_DEPTH_TEST); 
    glDisable(GL_CULL_FACE);

    return 0;
}

char *readShader(char *filename) {
    int fsize;;
    char *buffer;
    char *pathname=malloc(sizeof(char)*250);
    sprintf(pathname,"data/shaders/%s",filename);
    FILE *fp = fopen(pathname, "r");
    if(fp==NULL) {
        printf("error: unable to open shader file.\n");
        return NULL; // err
    }
    fseek(fp,0,SEEK_END);
    fsize=ftell(fp);
    fseek(fp,0,SEEK_SET);
    buffer = calloc(fsize + 8, 1);
    if(buffer) {
        fsize-=fread(buffer,1,fsize,fp);
    }
    if(fsize) {
        fclose(fp);
        return NULL; // err ?
    }
    fclose(fp);
    return buffer;
}

int loadAllShaders() {
    vertex_shader_line = readShader("vert_shader_line.glsl");
    fragment_shader_line = readShader("frag_shader_line.glsl");
    vertex_shader_ui = readShader("vert_shader_ui.glsl");
    fragment_shader_ui = readShader("frag_shader_ui.glsl");
    vertex_shader_t3d = readShader("vert_shader_t3d.glsl");
    fragment_shader_t3d = readShader("frag_shader_t3d.glsl");
    vertex_shader_text = readShader("vert_shader_text.glsl");
    fragment_shader_text = readShader("frag_shader_text.glsl");
    vertex_shader_main = readShader("vert_shader_main.glsl");
    fragment_shader_main = readShader("frag_shader_main.glsl");
    vertex_shader_cube = readShader("vert_shader_cube.glsl");
    fragment_shader_cube = readShader("frag_shader_cube.glsl");
    vertex_shader_terrain = readShader("vert_shader_terrain.glsl");
    fragment_shader_terrain = readShader("frag_shader_terrain.glsl");
	vertex_shader_water = readShader("vert_shader_water2.glsl");
    fragment_shader_water = readShader("frag_shader_water2.glsl");
	vertex_shader_water_depth = readShader("vert_shader_water_depth.glsl");
    fragment_shader_water_depth = readShader("frag_shader_water_depth.glsl");
	vertex_shader_water_height = readShader("vert_shader_water_height.glsl");
    fragment_shader_water_height = readShader("frag_shader_water_height.glsl");
	vertex_shader_stencil = readShader("vert_shader_stencil.glsl");
    fragment_shader_stencil = readShader("frag_shader_stencil.glsl");
	vertex_shader_depth = readShader("vert_shader_depth.glsl");
    fragment_shader_depth = readShader("frag_shader_depth.glsl");
	vertex_shader_fire = readShader("vert_shader_fire.glsl");
    fragment_shader_fire = readShader("frag_shader_fire.glsl");
	vertex_shader_fireball = readShader("vert_shader_fireball.glsl");
    fragment_shader_fireball = readShader("frag_shader_fireball.glsl");
	vertex_shader_vortex = readShader("vert_shader_vortex.glsl");
    fragment_shader_vortex = readShader("frag_shader_vortex.glsl");
	vertex_shader_lava = readShader("vert_shader_lava.glsl");
    fragment_shader_lava = readShader("frag_shader_lava.glsl");
    vertex_shader_sky = readShader("vert_shader_sky.glsl");
    fragment_shader_sky = readShader("frag_shader_sky.glsl");
    vertex_shader_sun = readShader("vert_shader_sun.glsl");
    fragment_shader_sun = readShader("frag_shader_sun.glsl");
    vertex_shader_moon = readShader("vert_shader_moon.glsl");
    fragment_shader_moon = readShader("frag_shader_moon.glsl");
    vertex_shader_clouds = readShader("vert_shader_clouds.glsl");
    fragment_shader_clouds = readShader("frag_shader_clouds.glsl");
    vertex_shader_post = readShader("vert_shader_post.glsl");
    fragment_shader_post = readShader("frag_shader_post.glsl");
	

    return 0;
}

int cleanupAllShaders(int reload) {
    glDeleteProgram(program);//main(spinquad)
    glDeleteProgram(programPost);//underwater
    glDeleteProgram(programText);
    glDeleteProgram(programLine);
    glDeleteProgram(programUI);
    glDeleteProgram(programT3D); // text3d
    glDeleteProgram(programCube);
    glDeleteProgram(programSun);
    glDeleteProgram(programMoon);
    glDeleteProgram(programClouds);
    glDeleteProgram(programSky);
    glDeleteProgram(programTerrain);
	glDeleteProgram(programWater);
	glDeleteProgram(programWaterDepth);
	glDeleteProgram(programWaterHeight);
	glDeleteProgram(programStencil);
	glDeleteProgram(programDepth);
	glDeleteProgram(programFire);
	glDeleteProgram(programFireBall);
	glDeleteProgram(programVortex);
	glDeleteProgram(programLava);

    free(vertex_shader_line);
    free(fragment_shader_line);
    free(vertex_shader_ui);
    free(fragment_shader_ui);
    free(vertex_shader_t3d);
    free(fragment_shader_t3d);
    free(vertex_shader_text);
    free(fragment_shader_text);
    free(vertex_shader_main);
    free(fragment_shader_main);
    free(vertex_shader_cube);
    free(fragment_shader_cube);
    free(vertex_shader_terrain);
    free(fragment_shader_terrain);
	free(vertex_shader_water);
    free(fragment_shader_water);
	free(vertex_shader_water_depth);
    free(fragment_shader_water_depth);
	free(vertex_shader_water_height);
    free(fragment_shader_water_height);
	free(vertex_shader_stencil);
    free(fragment_shader_stencil);
	free(vertex_shader_depth);
    free(fragment_shader_depth);
	free(vertex_shader_fire);
    free(fragment_shader_fire);
	free(vertex_shader_fireball);
    free(fragment_shader_fireball);
	free(vertex_shader_vortex);
    free(fragment_shader_vortex);
	free(vertex_shader_lava);
    free(fragment_shader_lava);
    free(vertex_shader_sky);
    free(fragment_shader_sky);
    free(vertex_shader_sun);
    free(fragment_shader_sun);
    free(vertex_shader_moon);
    free(fragment_shader_moon);
    free(vertex_shader_clouds);
    free(fragment_shader_clouds);
    free(vertex_shader_post);
    free(fragment_shader_post);

    if(reload==1) {
        loadAllShaders();
        InitializeShadersAll();
    }

    return 0;
}

unsigned int vsm, fsm; // main/spinquad
unsigned int vst, fst; // text(glyph)
unsigned int vsu, fsu; // ui(ortho)
unsigned int vst3, fst3; // text3d
unsigned int vsl, fsl; // line
unsigned int vsp, fsp; // underwater
unsigned int vsc, fsc; // cube
unsigned int vsg, fsg; // terrain/ground
unsigned int vsw, fsw; // water
unsigned int vswd, fswd; // water depth (no longer used)
unsigned int vswh, fswh; // water height
unsigned int vse, fse; // stencil custom
unsigned int vsd, fsd; // depth custom
unsigned int vsf, fsf; // fire
unsigned int vsfb, fsfb; // fireball
unsigned int vsx, fsx; // vortx
unsigned int vsa, fsa; // lava
unsigned int vss, fss; // sky/gradient
unsigned int vsss, fsss; // sun
unsigned int vssm, fssm; // moon
unsigned int vssc, fssc; // clouds

int InitializeShadersAll() {
    char *programError = malloc(sizeof(char)*1000);
    //int programRet = 0;
    //int programErrorLen = 0;
    //casting (char const * const *)
    //because glshadersource excpects const char *
    //but to have reload/read cant be const
    //
    //GLenum eNum = 0;
    // shader prep declared in shader.c
    vssm = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vssm, 1, (char const * const *)&vertex_shader_moon, NULL);
    glCompileShader(vssm);
 
    fssm = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fssm, 1, (char const * const *)&fragment_shader_moon, NULL);
    glCompileShader(fssm);
 
    programMoon = glCreateProgram();
    glAttachShader(programMoon, vssm);
    glAttachShader(programMoon, fssm);
    glLinkProgram(programMoon);
    //eNum=glGetError();

    //switch(eNum) {
    //    case GL_INVALID_ENUM:
    //        programError="Invalid Enum\0";
    //        break;
    //}
    //printf("shader::compilation::errors--%s\n",programError);
    //glGetShaderiv(programMoon, GL_INFO_LOG_LENGTH, &programRet);
    //if(programRet!=0) {
    //    glGetShaderInfoLog(programMoon,1000,&programErrorLen,programError);
    //}
    //printf("programret === %d\n",programRet);
    //printf("programErrorLen === %d\n",programErrorLen);
    //if(programErrorLen>0) {
    //    printf("shader::compilation::errors--%s\n",programError);
    //}

    // shader prep declared in shader.c
    vsss = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vsss, 1, (char const * const *)&vertex_shader_sun, NULL);
    glCompileShader(vsss);
 
    fsss = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fsss, 1, (char const * const *)&fragment_shader_sun, NULL);
    glCompileShader(fsss);
 
    programSun = glCreateProgram();
    glAttachShader(programSun, vsss);
    glAttachShader(programSun, fsss);
    glLinkProgram(programSun);
    //glGetShaderiv(programSun, GL_INFO_LOG_LENGTH, programError);
    
    //

    // shader prep declared in shader.c
    vss = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vss, 1, (char const * const *)&vertex_shader_sky, NULL);
    glCompileShader(vss);
 
    fss = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fss, 1, (char const * const *)&fragment_shader_sky, NULL);
    glCompileShader(fss);
 
    programSky = glCreateProgram();
    glAttachShader(programSky, vss);
    glAttachShader(programSky, fss);
    glLinkProgram(programSky);
    //glGetShaderiv(programSky, GL_INFO_LOG_LENGTH, programError);

    //
    
    // shader prep declared in shader.c
    vssc = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vssc, 1, (char const * const *)&vertex_shader_clouds, NULL);
    glCompileShader(vssc);
 
    fssc = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fssc, 1, (char const * const *)&fragment_shader_clouds, NULL);
    glCompileShader(fssc);
 
    programClouds = glCreateProgram();
    glAttachShader(programClouds, vssc);
    glAttachShader(programClouds, fssc);
    glLinkProgram(programClouds);
    //glGetShaderiv(programClouds, GL_INFO_LOG_LENGTH, programError);
    
    //

    vsc = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vsc, 1, (char const * const *)&vertex_shader_cube, NULL);
    glCompileShader(vsc);

    fsc = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fsc, 1, (char const * const *)&fragment_shader_cube, NULL);
    glCompileShader(fsc);

    programCube = glCreateProgram(); //                 program1
    glAttachShader(programCube, vsc);
    glAttachShader(programCube, fsc);
    glLinkProgram(programCube);
    //glGetShaderiv(programCube, GL_INFO_LOG_LENGTH, programError);

    //

    vst = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vst, 1, (char const * const *)&vertex_shader_text, NULL);
    glCompileShader(vst);
 
    fst = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fst, 1, (char const * const *)&fragment_shader_text, NULL);
    glCompileShader(fst);
 
    programText = glCreateProgram(); //             program3
    glAttachShader(programText, vst);
    glAttachShader(programText, fst);
    glLinkProgram(programText);
    //glGetShaderiv(programText, GL_INFO_LOG_LENGTH, programError);

    //

    vsg = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vsg, 1, (char const * const *)&vertex_shader_terrain, NULL);
    glCompileShader(vsg);
 
    fsg = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fsg, 1, (char const * const *)&fragment_shader_terrain, NULL);
    glCompileShader(fsg);
 
    programTerrain = glCreateProgram();
    glAttachShader(programTerrain, vsg);
    glAttachShader(programTerrain, fsg);
    glLinkProgram(programTerrain);
    //glGetShaderiv(programTerrain, GL_INFO_LOG_LENGTH, programError);

    //
	
	vsw = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vsw, 1, (char const * const *)&vertex_shader_water, NULL);
    glCompileShader(vsw);
 
    fsw = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fsw, 1, (char const * const *)&fragment_shader_water, NULL);
    glCompileShader(fsw);
 
    programWater = glCreateProgram();
    glAttachShader(programWater, vsw);
    glAttachShader(programWater, fsw);
    glLinkProgram(programWater);
    //glGetShaderiv(programWater, GL_INFO_LOG_LENGTH, programError);

    //
    
    vswd = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vswd, 1, (char const * const *)&vertex_shader_water_depth, NULL);
    glCompileShader(vswd);
 
    fswd = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fswd, 1, (char const * const *)&fragment_shader_water_depth, NULL);
    glCompileShader(fswd);
 
    programWaterDepth = glCreateProgram();
    glAttachShader(programWaterDepth, vswd);
    glAttachShader(programWaterDepth, fswd);
    glLinkProgram(programWaterDepth);

    //

    vswh = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vswh, 1, (char const * const *)&vertex_shader_water_height, NULL);
    glCompileShader(vswh);
 
    fswh = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fswh, 1, (char const * const *)&fragment_shader_water_height, NULL);
    glCompileShader(fswh);
 
    programWaterHeight = glCreateProgram();
    glAttachShader(programWaterHeight, vswh);
    glAttachShader(programWaterHeight, fswh);
    glLinkProgram(programWaterHeight);
    //glGetShaderiv(programWaterHeight, GL_INFO_LOG_LENGTH,programError);

    //
    
    vse = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vse, 1, (char const * const *)&vertex_shader_stencil, NULL);
    glCompileShader(vse);
 
    fse = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fse, 1, (char const * const *)&fragment_shader_stencil, NULL);
    glCompileShader(fse);
 
    programStencil = glCreateProgram();
    glAttachShader(programStencil, vse);
    glAttachShader(programStencil, fse);
    glLinkProgram(programStencil);
    //glGetShaderiv(programStencil, GL_INFO_LOG_LENGTH, programError);

    //
    
    vsd = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vsd, 1, (char const * const *)&vertex_shader_depth, NULL);
    glCompileShader(vsd);
 
    fsd = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fsd, 1, (char const * const *)&fragment_shader_depth, NULL);
    glCompileShader(fsd);
 
    programDepth = glCreateProgram();
    glAttachShader(programDepth, vsd);
    glAttachShader(programDepth, fsd);
    glLinkProgram(programDepth);
    //glGetShaderiv(programDepth, GL_INFO_LOG_LENGTH, programError);

    //

    vsf = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vsf, 1, (char const * const *)&vertex_shader_fire, NULL);
    glCompileShader(vsf);
 
    fsf = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fsf, 1, (char const * const *)&fragment_shader_fire, NULL);
    glCompileShader(fsf);
 
    programFire = glCreateProgram();
    glAttachShader(programFire, vsf);
    glAttachShader(programFire, fsf);
    glLinkProgram(programFire);
    //glGetShaderiv(programFire, GL_INFO_LOG_LENGTH, programError);

    //
    
    vsfb = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vsfb, 1, (char const * const *)&vertex_shader_fireball, NULL);
    glCompileShader(vsfb);
 
    fsfb = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fsfb, 1, (char const * const *)&fragment_shader_fireball, NULL);
    glCompileShader(fsfb);
 
    programFireBall = glCreateProgram();
    glAttachShader(programFireBall, vsfb);
    glAttachShader(programFireBall, fsfb);
    glLinkProgram(programFireBall);
    //glGetShaderiv(programFireBall, GL_INFO_LOG_LENGTH, programError);

    //
    
    vsx = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vsx, 1, (char const * const *)&vertex_shader_vortex, NULL);
    glCompileShader(vsx);
 
    fsx = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fsx, 1, (char const * const *)&fragment_shader_vortex, NULL);
    glCompileShader(fsx);
 
    programVortex = glCreateProgram();
    glAttachShader(programVortex, vsx);
    glAttachShader(programVortex, fsx);
    glLinkProgram(programVortex);
    //glGetShaderiv(programFVortex, GL_INFO_LOG_LENGTH, programError);

    //
    
    vsa = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vsa, 1, (char const * const *)&vertex_shader_lava, NULL);
    glCompileShader(vsa);
 
    fsa = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fsa, 1, (char const * const *)&fragment_shader_lava, NULL);
    glCompileShader(fsa);
 
    programLava = glCreateProgram();
    glAttachShader(programLava, vsa);
    glAttachShader(programLava, fsa);
    glLinkProgram(programLava);
    //glGetShaderiv(programLava, GL_INFO_LOG_LENGTH, programError);

    //
    
    vsm = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vsm, 1, (char const * const *)&vertex_shader_main, NULL);
    glCompileShader(vsm);

    fsm = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fsm, 1, (char const * const *)&fragment_shader_main, NULL);
    glCompileShader(fsm);
 
    program = glCreateProgram(); //                 program1
    glAttachShader(program, vsm);
    glAttachShader(program, fsm);
    glLinkProgram(program);
    //glGetShaderiv(program, GL_INFO_LOG_LENGTH, programError);

    // --

    vsu = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vsu, 1, (char const * const *)&vertex_shader_ui, NULL);
    glCompileShader(vsu);
 
    fsu = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fsu, 1, (char const * const *)&fragment_shader_ui, NULL);
    glCompileShader(fsu);
 
    programUI = glCreateProgram();
    glAttachShader(programUI, vsu);
    glAttachShader(programUI, fsu);
    glLinkProgram(programUI);
    //glGetShaderiv(programUI, GL_INFO_LOG_LENGTH, programError);
    
    // --
    
    vst3 = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vst3, 1, (char const * const *)&vertex_shader_t3d, NULL);
    glCompileShader(vst3);
 
    fst3 = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fst3, 1, (char const * const *)&fragment_shader_t3d, NULL);
    glCompileShader(fst3);
 
    programT3D = glCreateProgram();
    glAttachShader(programT3D, vst3);
    glAttachShader(programT3D, fst3);
    glLinkProgram(programT3D);
    //glGetShaderiv(programT3D, GL_INFO_LOG_LENGTH, programError);

    // ---

    vsl = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vsl, 1, (char const * const *)&vertex_shader_line, NULL);
    glCompileShader(vsl);
 
    fsl = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fsl, 1, (char const * const *)&fragment_shader_line, NULL);
    glCompileShader(fsl);
 
    programLine = glCreateProgram();
    glAttachShader(programLine, vsl);
    glAttachShader(programLine, fsl);
    glLinkProgram(programLine);
    //glGetShaderiv(programLine, GL_INFO_LOG_LENGTH, programError);
    
    // ---

    vsp = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vsp, 1, (char const * const *)&vertex_shader_post, NULL);
    glCompileShader(vsp);
 
    fsp = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fsp, 1, (char const * const *)&fragment_shader_post, NULL);
    glCompileShader(fsp);
 
    programPost = glCreateProgram();
    glAttachShader(programPost, vsp);
    glAttachShader(programPost, fsp);
    glLinkProgram(programPost);
    //glGetShaderiv(programPost, GL_INFO_LOG_LENGTH, programError);
   
    // ---

    glUseProgram(0);
    glUseProgram(programPost);
    setUniform1i(&programPost, "viewport_width", SCR_WIDTH);
    setUniform1i(&programPost, "viewport_height", SCR_HEIGHT);
    setUniform1i(&programPost, "samples", samples);
    
    setUniform1i(&programPost, "screenTexture", 0);

    free(programError);

    return 0;
}

// text is hud overlay, drawn last on the msaaFB (framebuffer)
char* vertex_shader_line;
char* fragment_shader_line;
// text is hud overlay, drawn last on the msaaFB (framebuffer)
char* vertex_shader_ui;
char* fragment_shader_ui;
// 3d text like ui but world
char* vertex_shader_t3d;
char* fragment_shader_t3d;
// text is hud overlay, drawn last on the msaaFB (framebuffer)
char* vertex_shader_text;
char* fragment_shader_text;
// main is the center rotating quad textured or colored
char* vertex_shader_main;
char* fragment_shader_main;
// main is the center rotating quad textured or colored
char* vertex_shader_cube;
char* fragment_shader_cube;
char* vertex_shader_terrain;
char* fragment_shader_terrain;
char* vertex_shader_water;
char* fragment_shader_water;
char* vertex_shader_water_depth;
char* fragment_shader_water_depth;
char* vertex_shader_water_height;
char* fragment_shader_water_height;
char* vertex_shader_stencil;
char* fragment_shader_stencil;
char* vertex_shader_depth;
char* fragment_shader_depth;
char* vertex_shader_fire;
char* fragment_shader_fire;
char* vertex_shader_fireball;
char* fragment_shader_fireball;
char* vertex_shader_vortex;
char* fragment_shader_vortex;
char* vertex_shader_lava;
char* fragment_shader_lava;
char* vertex_shader_sky;
char* fragment_shader_sky;
char* vertex_shader_sun;
char* fragment_shader_sun;
char* vertex_shader_moon;
char* fragment_shader_moon;
char* vertex_shader_clouds;
char* fragment_shader_clouds;
char* vertex_shader_post;
char* fragment_shader_post;
