#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>

//#define STB_IMAGE_IMPLEMENTATION
//#include "stb_image.h"
//#include "lodepng.h" // alternative to stb_image, may be slower

//#define STB_IMAGE_WRITE_IMPLEMENTATION
//#include "stb_image_write.h" //Just for test purposes
 
#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)

#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))
#define CLAMP(x, upper, lower) (MIN(upper, MAX(x, lower)))
 
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#include "custom.h" // early declarations

#include "input.h" // input glfw callbacks/mouse
#include "shaders.h" // shader code texts
#include "water.h" // 
#include "terrain.h" // 
#include "sky.h" // sky for sun/moonpos
//#include "text.h" // all related text/glyph/font

#define nullptr ((void*)0)

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

// start terrain collider
float get_terrain_height(float x, float z, int width, int height, float grid_size) {
    // Step 2: Grid indices
    int i = (int)(x / grid_size);
    int j = (int)(z / grid_size);
    if (i < 0 || i >= width-1 || j < 0 || j >= height-1) return 0.0f; // Boundary handjling

    i=(int)z;
    j=(int)x;
    // Step 3: Fractional coordinates
    //float x0 = i * grid_size;
    //float z0 = j * grid_size;
    float x0 = i;
    float z0 = j;
    float fx = (x - x0) / grid_size;
    float fz = (z - z0) / grid_size;

    // Step 4-6: Triangle selection and barycentric coordinates
    Vec3 v0, v1, v2;
    float a, b, c;
    if (fz <= 1.0f - fx) { // Triangle 1
        v0 = (Vec3){x0, terrainCollider[i * height + j], z0};
        v1 = (Vec3){x0 + grid_size, terrainCollider[(i+1) * height + j], z0};
        v2 = (Vec3){x0, terrainCollider[i * height + j+1], z0 + grid_size};
        a = 1.0f - fx - fz;
        b = fx;
        c = fz;
    } else { // Triangle 2
        v0 = (Vec3){x0 + grid_size, terrainCollider[(i+1) * height + j], z0};
        v1 = (Vec3){x0 + grid_size, terrainCollider[(i+1) * height + j+1], z0 + grid_size};
        v2 = (Vec3){x0, terrainCollider[i * height + j+1], z0 + grid_size};
        a = 1.0f - fx;
        b = fx + fz - 1.0f;
        c = 1.0f - fz;
    }

    //printf("x:%f, z:%f, x0:%f, z0:%f, fx=%f, fz=%f, a:%f\n",x,z,x0,z0,fx,fz,a);

    // Step 7: Interpolate height
    return a * v0.y + b * v1.y + c * v2.y;
}

int check_collision(float x, float y, float z, int width, int height, float grid_size, float *retHeight) {
    float y_terrain = get_terrain_height(x, z, width, height, grid_size);
    int collided=y<=y_terrain;
    //printf("collided:%d, height:%f\n",collided,y_terrain);
    *retHeight=y_terrain;
    return collided;
    //return y <= y_terrain; // 1 if collision, 0 if above
}
// end terrain collider


// start box collider
typedef struct {
    float min_x, min_y, min_z;
    float max_x, max_y, max_z;
} AABB;

int aabb_collide(AABB a, AABB b) {
    return (a.min_x < b.max_x && a.max_x > b.min_x) &&
           (a.min_y < b.max_y && a.max_y > b.min_y) &&
           (a.min_z < b.max_z && a.max_z > b.min_z);
}

// Function to check collision and get the normal
int aabb_normal(AABB player, AABB block, float* normal_x, float* normal_y, float* normal_z) {
    float overlap_x = fmin(player.max_x, block.max_x) - fmax(player.min_x, block.min_x);
    float overlap_y = fmin(player.max_y, block.max_y) - fmax(player.min_y, block.min_y);
    float overlap_z = fmin(player.max_z, block.max_z) - fmax(player.min_z, block.min_z);
    float epsilon = 1.0;
    if(player.min_y - block.max_y < epsilon) {
        *normal_y = (player.min_y < block.min_y) ? 1.0f : -1.0f;
        *normal_x = 0.0f;
        *normal_z = 0.0f;
        return 1; // Collision detected
    } else {
        if (overlap_x > 0 && overlap_y > 0 && overlap_z > 0) {
            // Collision occurred, find smallest overlap
            if (overlap_x < overlap_y && overlap_x < overlap_z) {
                *normal_x = (player.min_x < block.min_x) ? 1.0f : -1.0f;
                *normal_y = 0.0f;
                *normal_z = 0.0f;
            } else if (overlap_y < overlap_z) {
                *normal_y = (player.min_y < block.min_y) ? 1.0f : -1.0f;
                *normal_x = 0.0f;
                *normal_z = 0.0f;
            } else {
                *normal_z = (player.min_z < block.min_z) ? 1.0f : -1.0f;
                *normal_x = 0.0f;
                *normal_y = 0.0f;
            }
            return 1; // Collision detected
        }
        return 0; // No collision
    }
    return 0;
}

int initStairs=0;
float *block_colliders;
int stairSteps=25;
int checkBlockColliders(AABB player_aabb, float *rh) {
    if(initStairs!=1) return 1;

    float dist=0.0f;
    Vec3 *p1=malloc(sizeof(Vec3));
    Vec3 *p2=malloc(sizeof(Vec3));
    int collide=0;
    for(int i=0;i<stairSteps;++i) {
        p1->x=block_colliders[i*6];
        p1->y=block_colliders[i*6+1];
        p1->z=block_colliders[i*6+2];
        p2->x=player->transform->position->x;
        p2->y=player->transform->position->y;
        p2->z=player->transform->position->z;

        //dist=vector3_distance(p1,p2);
        dist=calculate_distance(p1,p2); // correct measure return
        //printf("dist block %d = %f\n",i,dist);
        if(dist<block_colliders[i*6+5]) { // or +3 should be equal
            //within radius of longest point, check intersections
            // AABB ?
            float minx=block_colliders[i*6]-(block_colliders[i*6+3]);
            float miny=block_colliders[i*6+1]-(block_colliders[i*6+4]);
            float minz=block_colliders[i*6+2]-(block_colliders[i*6+5]);
            float maxx=block_colliders[i*6]+(block_colliders[i*6+3]);
            float maxy=block_colliders[i*6+1]+(block_colliders[i*6+4]);
            float maxz=block_colliders[i*6+2]+(block_colliders[i*6+5]);
            //printf("within range of block %d, dist=%f\n",i,dist);

            AABB block_aabb = {minx, miny, minz,
                               maxx, maxy, maxz};
            if (aabb_collide(player_aabb, block_aabb)) {
                //player.min_y - ground.max_y < epsilon
                float nx, ny, nz;
                if (aabb_normal(player_aabb, block_aabb, &nx, &ny, &nz)) {
                    //printf("nx: %f, ny: %f, nz: %f\n",nx,ny,nz);
                    if (ny != 0) { // Normal points up, surface is below
                        //printf("Player is on the ground!\n");
                        // Set ground state to true
                        *rh=block_colliders[i*6+1]+block_colliders[i*6+4];
                        collide=2;
                        break;
                    }
                }

                collide=1;
                break;
            }

        }
    }
    free(p1);
    free(p2);

    return collide;
}

// end box collider

float speed = 4.0f;
float strifeSpeed = 4.0f;
float rotSpeed = 120.0f*1.0f; // deg per sec
float speedMulti = 1.0f;
float addSpeed = 0.0f;

float velocityY=0.0f; // up/down
float velocityX=0.0f; // sides // not in use yet
float velocityZ=0.0f; // forward/back // not in use yet

bool isGrounded = false;
float gravityCalc = 9.53f; // 9.53?
float isJumping = 0.0f;
int isSwimming = 0;
int moveMode = 0; // 0 = free mode, 1 = player mode
int applyXoffset = 0;
bool readyXoffset = false;

bool swapStrife = true;
void handle_move() { // fixed movement ticks
    velocityX=0;
    velocityY=0;
    velocityZ=0;

    float last_x = player->transform->position->x;
    float last_y = player->transform->position->y;
    float last_z = player->transform->position->z;
    AABB player_aabb = {last_x - 0.5f, last_y - 1.0f, last_z - 0.5f,
                        last_x + 0.5f, last_y + 1.0f, last_z + 0.5f};

    if(LMB_press && RMB_press) is_moving=1; // others in input.c

    if(moveMode == 1) { // player mode
        if(isJumping<=0.0f) {
            if(is_moving==1) { // W | ArrowUp
                is_moving=0;
                // forward relative to world xz
                // rotation player equal camera direction forward
                velocityX += -player->transform->rotation->x * speed * speedMulti;
                velocityY += -player->transform->rotation->y * speed * speedMulti;
                velocityZ += -player->transform->rotation->z * speed * speedMulti;
                
                if(!mouse_drag_started) {
                    readyXoffset=true;
                    applyXoffset=1; // camera to player
                }
            } else if(is_moving==-1) { // S | ArrowDown
                is_moving=0;

                velocityX += player->transform->rotation->x * speed * speedMulti;
                velocityY += player->transform->rotation->y * speed * speedMulti;
                velocityZ += player->transform->rotation->z * speed * speedMulti;

                if(!mouse_drag_started) {
                    readyXoffset=true;
                    applyXoffset=1; // camera to player
                }
            }
        }
    } else if(moveMode == 0) { // free mode
        if(addSpeed>0.0f) {
            //addSpeed -= updateDeltaTime;
            --addSpeed;

            velocityX += -camera->dir->forward->x * addSpeed * speedMulti;
            velocityY += -camera->dir->forward->y * addSpeed * speedMulti;
            velocityZ += -camera->dir->forward->z * addSpeed * speedMulti;
        }
        if(readyBlink) {
            addSpeed = 50.0f;
            //addSpeed += 3.0f;
            //addSpeed=CLAMP(addSpeed,255.0f,0.0f);
            readyBlink=false;
        }

        if(is_moving==1) { // W | ArrowUp
            is_moving=0;
            // forward relative to camera look
            velocityX += -camera->dir->forward->x * speed * speedMulti;
            velocityY += -camera->dir->forward->y * speed * speedMulti;
            velocityZ += -camera->dir->forward->z * speed * speedMulti;
            
            if(!mouse_drag_started) {
                readyXoffset=true;
                applyXoffset=1; // camera to player
            }
        } else if(is_moving==-1) { // S | ArrowDown
            is_moving=0;
            if(addSpeed>0.0f) {
                addSpeed -= 2.0f; // break
            }

            velocityX += camera->dir->forward->x * speed * speedMulti;
            velocityY += camera->dir->forward->y * speed * speedMulti;
            velocityZ += camera->dir->forward->z * speed * speedMulti;
            
            if(!mouse_drag_started) {
                readyXoffset=true;
                applyXoffset=1; // camera to player
            }
        //} else if(addSpeed>0.0f) {
        //    addSpeed -= 2.0f; // break
        }
    }

    // Jumping / Flying / swimming
    if(moveMode == 1) { // player mode swim fall gravity
        float waterDiff=player->transform->position->y-waterHeightSample;
        float heightChange=waterHeightSample-lastWaterHeightSample;
        lastWaterHeightSample=waterHeightSample;

        //printf("waterDiff = %f, waterHeightSample = %f, playerY = %f\n",waterDiff,waterHeightSample,player->transform->position->y);
        //printf("isSwim: %d, isJump: %f, isGround: %d\n",isSwimming,isJumping,isGrounded);
        if(waterDiff<0.0f) {
            if((isSwimming==0 || isSwimming==2) && isJumping<=0.0f) {
                isSwimming = 1;
            }
        }
        if(isSwimming>0) {
            if(isJumping>0.0f) { // still going up
                velocityY += (gravityCalc)*1.2; // upward

                isJumping-=fixedDeltaTime;
            } else {
                if(waterDiff >= -0.5f && waterDiff <= 1.0f) {
                    // follow water up/down
                    player->transform->position->y+=heightChange;
                }
                if(waterDiff>0.0f && isJumping<=0.0f) {
                    velocityY += -gravityCalc*3; // fall
                    //printf("swim falling\n");
                } else {
                    if(is_elevating==1) {
                        is_elevating = 0;

                        if(isSwimming!=2) {
                            velocityY += 1.0f * speed * speedMulti;
                            if(isJumping<=0.0f) {
                                //printf("swim jump\n");
                                isJumping=0.2f;
                            }
                            isSwimming = 2;
                        }
                    } else if(is_elevating==-1) {
                        is_elevating = 0;

                        velocityY += -1.0f * speed * speedMulti;
                    }
                }
            }
        } else {
            if(isJumping>0.0f) { // still going up
                velocityY += (gravityCalc)*1.2; // upward

                isJumping-=fixedDeltaTime;
            } else {
                if(!isGrounded) { // falling
                    velocityY += -gravityCalc*3;
                } else if(is_elevating==1) {
                    if(isJumping<=0.0f && isGrounded) {
                        isJumping=0.2f;
                        isGrounded=false;
                        is_elevating=0;
                    }
                } else {
                    isJumping=0.0f;
                }
            }

            float fullFall=1.0/fixedDeltaTime;
            velocityY=CLAMP(velocityY,gravityCalc*fullFall,-gravityCalc*fullFall); // fall,jump
        }
    } else if(moveMode == 0) { // free mode
        if(is_elevating==1) {
            is_elevating = 0;

            velocityY += 1.0f * speed * speedMulti;
        } else if(is_elevating==-1) {
            is_elevating = 0;

            velocityY += -1.0f * speed * speedMulti;
        }
    }

    // Turning/Strifing
    if(is_turning!=0) { // all modes
        bool applyStrife = false;
        if(is_turning==1) { // A | ArrowLeft
            if((mouse_drag_started || shiftDown) && (RMB_press || moveMode==0)) {
                applyStrife = true;
            } else {
                if(mouse_drag_started && !RMB_press) {
                    // lock camera but keep rotating player
                    camera->xOffset -= rotSpeed * fixedDeltaTime;
                } else {
                    camera->theta -= rotSpeed * fixedDeltaTime;
                }
            }
            if(applyStrife) {
                if(moveMode == 1 || shiftDown) swapStrife = false;

                if(swapStrife && !mouse_drag_started) {
                    velocityX += -player->dir->right->x * strifeSpeed * speedMulti;
                    velocityZ += -player->dir->right->z * strifeSpeed * speedMulti;
                } else {
                    velocityX += player->dir->right->x * strifeSpeed * speedMulti;
                    velocityZ += player->dir->right->z * strifeSpeed * speedMulti;
                }

                if(!mouse_drag_started) {
                    readyXoffset=true;
                    applyXoffset=1; // camera to player
                }
            }
            is_turning=0;
        } else if(is_turning==-1) { // D | ArrowLeft
            if((mouse_drag_started || shiftDown) && (RMB_press || moveMode==0)) {
                applyStrife = true;
            } else {
                if(mouse_drag_started && !RMB_press) {
                    // lock camera but keep rotating player
                    camera->xOffset += rotSpeed * fixedDeltaTime;
                } else {
                    camera->theta += rotSpeed * fixedDeltaTime;
                }
            }
            if(applyStrife) {
                if(moveMode == 1 || shiftDown) swapStrife = false;

                if(swapStrife && !mouse_drag_started) {
                    velocityX += player->dir->right->x * strifeSpeed * speedMulti;
                    velocityZ += player->dir->right->z * strifeSpeed * speedMulti;
                } else {
                    velocityX += -player->dir->right->x * strifeSpeed * speedMulti;
                    velocityZ += -player->dir->right->z * strifeSpeed * speedMulti;
                }
            
                if(!mouse_drag_started) {
                    readyXoffset=true;
                    applyXoffset=1; // camera to player
                }
            }
            is_turning=0;
        }
    }

    // Mouse actions / drag / rotate / colliders
    if(moveMode == 0) { // free mode
        if(mouse_drag_started) { // MouseLMB=MouseRMB
            bool dragXZ=false;
            bool dragY=false;
            float RMBstrifeS = x_input * 10.0f * fixedDeltaTime;
            float RMBstrifeU = y_input * 10.0f * fixedDeltaTime;
            // left/right

            if(RMB_press && is_moving==0) { // Mouse Move
                if(!shiftDown) { // up/down
                    dragXZ=true;
                } else { // forward/back
                    dragY=true;
                }
                
                readyXoffset=true;
                applyXoffset=2; // player to camera
                is_turning=0;
            } else { // Mouse Rotate/orient set
                //if(!shiftDown) { // up/down
                    camera->phi -= y_input * 5.0f * fixedDeltaTime;
                    camera->theta += x_input * 5.0f * fixedDeltaTime;
                //} else {
                //    dragXZ=true;
                //}
                
                readyXoffset=true;
                applyXoffset=2; // player to camera in free mode, not to be confused with future but non existent 'fly' mode
            }
            if(dragXZ) {
                velocityX += camera->dir->right->x * RMBstrifeS * speedMulti;
                velocityY += camera->dir->right->y * RMBstrifeS * speedMulti;
                velocityZ += camera->dir->right->z * RMBstrifeS * speedMulti;

                velocityX += camera->dir->up->x * RMBstrifeU * speedMulti;
                velocityY += camera->dir->up->y * RMBstrifeU * speedMulti;
                velocityZ += camera->dir->up->z * RMBstrifeU * speedMulti;
            } else if(dragY) {
                velocityX += camera->dir->forward->x * RMBstrifeU * speedMulti;
                velocityY += camera->dir->forward->y * RMBstrifeU * speedMulti;
                velocityZ += camera->dir->forward->z * RMBstrifeU * speedMulti;
            }
        }
    } else if(moveMode == 1) { // player mode
        if(mouse_drag_started) { // MouseLMB=MouseRMB
            if(RMB_press && is_moving==0) { // Mouse Move
                // line player to camera first here
                camera->phi -= y_input * 5.0f * fixedDeltaTime;
                camera->theta += x_input * 5.0f * fixedDeltaTime;

                player->phi -= y_input * 5.0f * fixedDeltaTime;

                readyXoffset=true;
                applyXoffset=2; // player to camera
                is_turning=0;
            } else { // Mouse Rotate/orient set
                camera->phi -= y_input * 5.0f * fixedDeltaTime;
                camera->theta += x_input * 5.0f * fixedDeltaTime;

                camera->xOffset -= x_input * 5.0f * fixedDeltaTime; // separate alignment from player
                readyXoffset=true;
            }
        }
    }

    // apply velocity
    player->transform->position->x += velocityX * fixedDeltaTime;
    player->transform->position->y += velocityY * fixedDeltaTime;
    player->transform->position->z += velocityZ * fixedDeltaTime;

    float cO=cubeScale*2;
    // Update player AABB after movement
    player_aabb.min_x = player->transform->position->x - cO; player_aabb.max_x = player->transform->position->x + cO;
    player_aabb.min_y = player->transform->position->y - cO; player_aabb.max_y = player->transform->position->y + cO;
    player_aabb.min_z = player->transform->position->z - cO; player_aabb.max_z = player->transform->position->z + cO;

    //printf("velocityX=%f, velocityY=%f, velocityZ=%f, isGrounded=%d, isJumping:%f\n",velocityX,velocityY,velocityZ,isGrounded,isJumping);

    // Check collisions
    if(moveMode == 1) { // player mode
        float retHeight=0;
        int bcollided=checkBlockColliders(player_aabb,(void *)&retHeight);
        if(bcollided==1) {
            player->transform->position->x=last_x;
            player->transform->position->y=last_y;
            player->transform->position->z=last_z;
        } else if(bcollided==2) {
            isGrounded=true; // block ground
            if(player->transform->position->y<retHeight+cO) {
                player->transform->position->y=retHeight+cO;
            }
        }
        if(bcollided!=2) {
            //terrain collider
            float halfBlock = terrainScale/2; // 2f alignment fix
            if(check_collision(
                ((player->transform->position->x+halfBlock)/terrainScale)-gridLeft,
                player->transform->position->y-cO,
                ((player->transform->position->z+halfBlock)/terrainScale)-gridTop
                , gridWidth, gridHeight, gridHeight,&retHeight)==1) {
                    isGrounded=true;
                    if(isSwimming>0) {
                        isSwimming=0;
                    }
            } else {
                isGrounded=false;
            }
            if(retHeight!=0) {
                if(player->transform->position->y<retHeight+cO) {
                    player->transform->position->y=retHeight+cO;
                }
            }
        }
    }
    x_input = 0.0f;
    y_input = 0.0f;
    //printf("player offset x:%f, z:%f\n",player->transform->position->x-gridLeft,player->transform->position->z-gridTop);
}

int RenderSpinningQuad() {
    // draw main rotating quad in mbp
    glUseProgram(0);
    glUseProgram(program);

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

    //Vec3 dir = {
    //    player->transform->rotation->x,
    //    0,
    //    //player->transform->rotation->y,
    //    player->transform->rotation->z,
    //};
    //Quaternion q = directionToQuaternion(dir);

    //Vec3 rot={0.0f, glfwGetTime(), 0.0f};
    //Quaternion q = ToQuaternion(rot);

    //mat4x4 RotationMatrix;
    //QuaternionToMat4(RotationMatrix,&q);
    //mat4x4_mul(m, m, RotationMatrix);

    mat4x4 vc;
    memcpy(vc,v,sizeof(vc));
    mat4x4_rotate_Z(vc, vc, (float) glfwGetTime());
    applyScalingToModel(m, 1.43,1.0f,1.0f);
    execRenderModePerspective(vc);
 
    setUniform4m(&program, "MVP", mvp);
    setUniform1i(&program, "texture1", 0);
    setUniform1i(&program, "mix", 1);
    
	glBindTexture(GL_TEXTURE_2D, texture1);
    glBindVertexArray(plane_array);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
    //printf("about to fail 2\n");
    glBindVertexArray(0);

    RenderBoundBox(-0.66f,0.5f,0.0f,0.66*2,1.0f,0.0f,1,mvp);

    return 0;
}

int RenderCube() {
    glUseProgram(0);
    glUseProgram(programCube);

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

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

    //setUniform1i(&programCube, "texture1", 0); 
    //
    setRenderModePerspective();

    // transate position in view matrix (v) copy
    mat4x4 vc;
    memcpy(vc,v,sizeof(vc));
    mat4x4_translate_in_place(vc,player->transform->position->x,player->transform->position->y,player->transform->position->z);
    // rotation from direction to quart to model matrix apply
    //--Vec3 dir = eulerToDirection(player->transform->rotation);
    // rotation is already set to dir from camera
    // rotation as direction is calculated here and in main, getdirection in custom.c only used once in main.c
    Vec3 dir = {
        player->transform->rotation->x,
        0,
        //player->transform->rotation->y,
        player->transform->rotation->z,
    };
    Quaternion q = directionToQuaternion(dir);

    mat4x4 RotationMatrix;
    QuaternionToMat4(RotationMatrix,&q);
    
    mat4x4_mul(m, m, RotationMatrix);

    // apply scale to model matrix
    applyScalingToModel(m, cubeScale, cubeScale, cubeScale);

    execRenderModePerspective(vc); // custom view for position sets mvp

    //memcpy(cube_mat4,mvp,sizeof(mat4x4));

    // position
    //Vec3 EulerAngles = {90, 45, 0};
    //Quaternion q = ToQuaternion(EulerAngles);
    
    //mat4x4_scale(cube_mat4, cube_mat4, .25f);
    

    
    //printf("dir x:%f, y:%f, z:%f\n",dir.x,dir.y,dir.z);

    RenderBoundBox(-1.0f,-1.0f,-1.0f,1.0f,1.0f,1.0f,1,mvp);
    //RenderBoundBox(-1.0f,-1.0f,-1.0f,1.0f,1.0f,1.0f,1,cube_mat4);

    if(zoomDist<= 0.01f)
        return 0; // hide first person mode



    //setUniform4m(&programCube, "MVP", mvp);
    setUniform4m(&programCube, "MVP", mvp);
    setUniform3f(&programCube,"sunPos", sunpos.x, sunpos.y, sunpos.z);
    setUniform3f(&programCube,"moonPos", moonpos.x, moonpos.y,moonpos.z);
    setUniform1i(&programCube,"colorOnly", 1);
    setUniform3f(&programCube,"viewPos", camera->eye->position->x + player->transform->position->x, camera->eye->position->y + player->transform->position->y, camera->eye->position->z + player->transform->position->z);


	glBindTexture(GL_TEXTURE_2D, 0);

    
    glBindVertexArray(cube_array);
    
    //glBindBuffer(GL_ARRAY_BUFFER, cube_buffer);
    // Draw the triangle !
	//glBindTexture(GL_TEXTURE_2D, texture1);
    glDrawArrays(GL_TRIANGLES, 0, 12*3); // 12*3 indices starting at 0 -> 12 triangles -> 6 squares 
    //--glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
    //printf("about to fail\n");
    
    glBindVertexArray(0);

    return 0;
}

int RenderBlock(Vec3 *pos, Vec3 *scale) {
    //setUniform1i(&programCube, "texture1", 0); 
    //
    setRenderModePerspective();

    // transate position in view matrix (v) copy
    mat4x4 vc;
    memcpy(vc,v,sizeof(vc));
    mat4x4_translate_in_place(vc,pos->x, pos->y, pos->z);
    // rotation from direction to quart to model matrix apply
    //--Vec3 dir = eulerToDirection(player->transform->rotation);
    // rotation is already set to dir from camera
    // rotation as direction is calculated here and in main, getdirection in custom.c only used once in main.c
    Vec3 dir = {
        0,
        1,
        0,
    };
    Quaternion q = directionToQuaternion(dir);

    mat4x4 RotationMatrix;
    QuaternionToMat4(RotationMatrix,&q);
    
    mat4x4_mul(m, m, RotationMatrix);

    // apply scale to model matrix
    applyScalingToModel(m, scale->x, scale->y, scale->z);

    execRenderModePerspective(vc); // custom view for position sets mvp

    setUniform4m(&programCube, "MVP", mvp);
    //memcpy(cube_mat4,mvp,sizeof(mat4x4));

    // position
    //Vec3 EulerAngles = {90, 45, 0};
    //Quaternion q = ToQuaternion(EulerAngles);
    
    //mat4x4_scale(cube_mat4, cube_mat4, .25f);
    

    //printf("dir x:%f, y:%f, z:%f\n",dir.x,dir.y,dir.z);

    RenderBoundBox(-1.0f,-1.0f,-1.0f,1.0f,1.0f,1.0f,1,mvp);
    //RenderBoundBox(-1.0f,-1.0f,-1.0f,1.0f,1.0f,1.0f,1,cube_mat4);

    //glBindBuffer(GL_ARRAY_BUFFER, cube_buffer);
    // Draw the triangle !
	//glBindTexture(GL_TEXTURE_2D, texture1);
    glDrawArrays(GL_TRIANGLES, 0, 12*3); // 12*3 indices starting at 0 -> 12 triangles -> 6 squares 
    //--glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
    //printf("about to fail\n");
    //glBindBuffer(GL_ARRAY_BUFFER, cube_buffer);
    return 0;
}

int RenderStair() {
    int stairSteps=25;
    float stepsize=5.0f;
    float stepheight=2.0f;

    if(initStairs==0) { // first run
        // min x, min y, min z, max x, max y, max z, radius
        // x y x, w, h, d
        block_colliders=malloc(sizeof(float)*stairSteps*6);
    }

    Vec3 *pos=malloc(sizeof(Vec3));
    Vec3 *scale=malloc(sizeof(Vec3));

    scale->x = stepsize;
    scale->y = stepheight/2;
    scale->z = stepsize;

    glUseProgram(0);
    glUseProgram(programCube);

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

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

    //setUniform4m(&programCube, "MVP", mvp);
    setUniform3f(&programCube,"sunPos", sunpos.x, sunpos.y, sunpos.z);
    setUniform3f(&programCube,"moonPos", moonpos.x, moonpos.y,moonpos.z);
    setUniform1i(&programCube, "texture1", 0);
    setUniform1i(&programCube,"colorOnly", 0);
    setUniform3f(&programCube,"viewPos", camera->eye->position->x + player->transform->position->x, camera->eye->position->y + player->transform->position->y, camera->eye->position->z + player->transform->position->z);


	glBindTexture(GL_TEXTURE_2D, 0);

    glBindVertexArray(cube_array);
   
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, terrainTextures[0]);
    //glBindTexture(GL_TEXTURE_2D, textureGrass1);

    for(int i=0;i<stairSteps;++i) {
        pos->x = -((stairSteps/2)*(stepsize*2))+(i*(stepsize*2)); // center
        pos->y = 8.0f + (i*stepheight);
        pos->z = 192.0f; // direction RED in grid
        RenderBlock(pos,scale);

        if(initStairs==0) { // first run
            //--block_colliders[i*7] = pos->x - stepsize; //min x
            //--block_colliders[i*7+1] = pos->y - stepsize; //min y
            //--block_colliders[i*7+2] = pos->z - stepsize; //min z
            //--block_colliders[i*7+3] = pos->x + stepsize; //max x
            //--block_colliders[i*7+4] = pos->y + stepsize; //max y
            //--block_colliders[i*7+5] = pos->z + stepsize; //maz z
            block_colliders[i*6] = pos->x;
            block_colliders[i*6+1] = pos->y;
            block_colliders[i*6+2] = pos->z;
            block_colliders[i*6+3] = stepsize;
            block_colliders[i*6+4] = stepheight/2;
            block_colliders[i*6+5] = stepsize;
            //printf("adding block data stair\n");
        }
    }
    glBindTexture(GL_TEXTURE_2D, 0);
    
    glBindVertexArray(0);


    free(pos);
    free(scale);

    if(initStairs==0) { // first run
        initStairs=1;
    }

    return 0;
}
