#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 "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" // s
#include "shaders.h" // 
#include "geometry.h" // 
#include "sky.h" // 
#include "water.h" // 

#define nullptr ((void*)0)

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

//cJSON *json = NULL;
int hh,mm,ss;
int lapsedDays=0;
float totalTime=0;
int adjustStartTime = 6*60*60; // 6 hr after midnight
float timeSpeed=1000.0f;

unsigned int SKY_INDICES;
unsigned int skyVAO, skyVBO, skyEBO;
unsigned int SUN_INDICES;
unsigned int sunVAO, sunVBO, sunEBO;
unsigned int MOON_INDICES;
unsigned int moonVAO, moonVBO, moonEBO;
float sunTheta=90.0f;
float sunPhi=90.0f;
float moonTheta=270.0f;
float moonPhi=90.0f;
Vec3 sunpos, moonpos;
float daytime=0;
int InitializeSky() {
    // shaders initalized in other

    createSphere(36,18,1000,&skyVAO,&skyVBO,&skyEBO,&SKY_INDICES);
    
    createSphere(36,18,100,&sunVAO,&sunVBO,&sunEBO,&SUN_INDICES);

    createSphere(36,18,50,&moonVAO,&moonVBO,&moonEBO,&MOON_INDICES);
    return 0;
}

int RenderSun(mat4x4 *vc) {
    glUseProgram(0);
    glUseProgram(programSun);

    glDisable(GL_CULL_FACE);
	glEnable(GL_DEPTH_TEST);
    glDepthMask(GL_TRUE);
    // to be initiated by RenderSky--inherit matrices
   
    sunpos=convert_spherical_to_cartesian(10,degToRad(sunPhi),degToRad(sunTheta));
    //printf("sunpos %f, %f, %f\n",sunpos.x,sunpos.y,sunpos.z);
    mat4x4_translate_in_place(*vc,sunpos.x,sunpos.y,sunpos.z);

    sunTheta=180.0f - (totalTime*360.0f/86400);

   // sunTheta -= 0.01f;
    while(sunTheta<0.0f) sunTheta+=360.0f;
    while(sunTheta>360.0f) sunTheta-=360.0f;
    // rotation from direction to quart to model matrix apply
    //--Vec3 dir = {
    //--    player->transform->rotation->x,
    //--    0,
    //--    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);
    //mat4x4_rotate(vc,vc,0.0f,1.0f,0.0f,degToRad(45));
    //mat4x4_rotate_Z(m, m, (float) glfwGetTime());

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

    setUniform4m(&programSun,"MVP", mvp);
    
	glBindTexture(GL_TEXTURE_2D, 0);

    glBindVertexArray(sunVAO);

    glDrawElements(GL_TRIANGLES,SUN_INDICES,GL_UNSIGNED_INT,(void*)0);
    
    //RenderBoundBox(-2,-2,-2,2,2,2,1,mvp);

    glBindVertexArray(0);    
    return 0;
}

int RenderMoon(mat4x4 *vc) {
    // to be initiated by RenderSky--inherit matrices
    glUseProgram(0);
    glUseProgram(programMoon);

    glDisable(GL_CULL_FACE);
	glEnable(GL_DEPTH_TEST);
    glDepthMask(GL_TRUE);
    // to be initiated by RenderSky--inherit matrices
    
    moonpos=convert_spherical_to_cartesian(10,degToRad(moonPhi),degToRad(moonTheta));
    //printf("moonpos %f, %f, %f\n",moonpos.x,moonpos.y,moonpos.z);
    mat4x4_translate_in_place(*vc,moonpos.x,moonpos.y,moonpos.z);

    moonTheta=360.0f - ((totalTime)*360.0f/86400);

   // moonTheta -= 0.01f;
    while(moonTheta<0.0f) moonTheta+=360.0f;
    while(moonTheta>360.0f) moonTheta-=360.0f;
    // rotation from direction to quart to model matrix apply
    //--Vec3 dir = {
    //--    player->transform->rotation->x,
    //--    0,
    //--    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);
    //mat4x4_rotate(vc,vc,0.0f,1.0f,0.0f,degToRad(45));
    //mat4x4_rotate_Z(m, m, (float) glfwGetTime());

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

    setUniform4m(&programMoon,"MVP", mvp);
    
	glBindTexture(GL_TEXTURE_2D, 0);

    glBindVertexArray(moonVAO);

    glDrawElements(GL_TRIANGLES,MOON_INDICES,GL_UNSIGNED_INT,(void*)0);
    
    //RenderBoundBox(-2,-2,-2,2,2,2,1,mvp);

    glBindVertexArray(0);    
    return 0;
}

int RenderSky() {
    glUseProgram(0);
    glUseProgram(programSky);

    glDisable(GL_CULL_FACE);
	glEnable(GL_DEPTH_TEST);
    glDepthMask(GL_TRUE);
    
    setRenderModePerspective();
    //execRenderModePerspective(v);
    // 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 = {
    //--    player->transform->rotation->x,
    //--    0,
    //--    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, 0.7f, 0.7f, 0.7f);

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

 
    //setUniform4m(&programTerrain, "MVP", mvp);
    setUniform4m(&programSky,"MVP", mvp);
    setUniform3f(&programSky,"sunPos", sunpos.x, sunpos.y, sunpos.z);
    setUniform3f(&programSky,"moonPos", moonpos.x, moonpos.y, moonpos.z);

    //--daytime=fabs((86400*0.5f)-totalTime)/43200; // 1-0-1 night-day-night
    setUniform1f(&programSky,"daytime", daytime);
    //daytime*=.5f; // sky color, not lightpos
    //daytime+=1.0f;
    //--printf("totaltime=%f, daytime=%f\n",totalTime,daytime);
    setUniform3f(&programSky,"data", waterTime, 0.0, 0.0); // time
    
	glBindTexture(GL_TEXTURE_2D, 0);

    glBindVertexArray(skyVAO);

    glDrawElements(GL_TRIANGLES,SKY_INDICES,GL_UNSIGNED_INT,(void*)0);
    
    glBindVertexArray(0);

    //RenderBoundBox(-2,-2,-2,2,2,2,1,mvp);
    //
    mat4x4 vcsun;
    memcpy(vcsun,vc,sizeof(vcsun));
    mat4x4 vcmoon;
    memcpy(vcmoon,vc,sizeof(vcmoon));
    
    RenderSun(&vcsun);
    RenderMoon(&vcmoon);

    //printf("Sun Theta: %f\n",sunTheta);
    //printf("Moon Theta: %f\n",moonTheta);

    return 0;
}

float cloudAmp = 0.5f;
float cloudFreq = 1.0f;
float cloudNextAmp = 0.5;
//float cloudNextFreq = 1.0f;
float cloudLastAmp = 0.5;
//float cloudLastFreq = 1.0f;
float cloudNextTime = 0;
float cloudLastTime = 0;
int RenderClouds() {
    glUseProgram(0);
    glUseProgram(programClouds);

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

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
    setRenderModePerspective();
    //execRenderModePerspective(v);
    // transate position in view matrix (v) copy
    mat4x4 vc;
    memcpy(vc,v,sizeof(vc));

    float scale=0.7f;
    if(hiddenSky==2) {
        mat4x4_translate_in_place(vc,0.0f,-100.0f,0.0f);
        scale=0.5f;
    } else {
        mat4x4_translate_in_place(vc,player->transform->position->x,player->transform->position->y-100.0f,player->transform->position->z);
    }
    // rotation from direction to quart to model matrix apply
    //Vec3 dir = {
    //    0,
    //    .5,
    //    .5
    //};
    //Quaternion q = directionToQuaternion(dir);

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

    mat4x4_rotate_Z(vc, vc, (float)degToRad(90.0f));
    mat4x4_rotate_Y(vc, vc, (float)degToRad(180.0f));
    // apply scale to model matrix
    applyScalingToModel(m, scale, scale, scale);

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

 
    //setUniform4m(&programTerrain, "MVP", mvp);
    setUniform4m(&programClouds,"MVP", mvp);
    setUniform3f(&programClouds,"sunPos", sunpos.x, sunpos.y, sunpos.z);
    setUniform3f(&programClouds,"moonPos", moonpos.x, moonpos.y, moonpos.z);

    //daytime=fabs((86400*0.5f)-totalTime)/43200; // 1-0-1 night-day-night
    setUniform1f(&programClouds,"daytime", daytime);
    //setUniform3f(&programClouds,"data", waterTime, 1.0, 0.5); // time, freq=1.0, amp=0.5
    setUniform3f(&programClouds,"data", waterTime, cloudFreq, cloudAmp); // time, freq=1.0, amp=0.5
    //
    // cloudy = amp .5 freq 1.0
    // clear = amp .1 freq .5
    //
    // pick state and lerp before pick new state?
    // randomize weather
    if(updateCurrentTime>cloudNextTime) {
        cloudLastAmp = cloudNextAmp;
        //cloudLastFreq = cloudNextFreq;
        cloudLastTime = cloudNextTime;

        int newRandom = (rand() % 60) + 1; // 1-60
        cloudNextTime = updateCurrentTime + newRandom;
        newRandom = (rand() % 90) + 1; // 0-90
        cloudNextAmp = (float)newRandom / 100.0f; // 0.01-0.9
        //--newRandom = (rand() % 100) + 1; // 0-100
        //--cloudNextFreq = (float)newRandom / 100.0f; // 0.01-1.0
        if(cloudLastAmp<=0.1f) {
            newRandom = (rand() % 100) + 1; // 0-100
            cloudFreq = ((float)newRandom / 80.0f)+0.1f; // 0.11-0.9
        }
        printf("new cloud time: %f\n",cloudNextTime);
    } else {
        float max=cloudNextTime-cloudLastTime;
        float t=(updateCurrentTime-cloudLastTime)*1.0/max;
        cloudAmp = lerp(cloudLastAmp,cloudNextAmp,t);
        //--cloudFreq = lerp(cloudLastFreq,cloudNextFreq,t);
        //printf("cloudLastAmp=%f, cloudNextAmp=%f, t=%f/%f\n",cloudLastAmp,cloudNextAmp,t,max);
        //--printf("cloudLastFreq=%f, cloudNextFreq=%f\n",cloudLastFreq,cloudNextFreq);
        //printf("cloudAmp=%f, cloudFreq=%f\n",cloudAmp,cloudFreq);
    }

    //daytime*=.5f; // sky color, not lightpos
    //daytime+=1.0f;
    //--printf("totaltime=%f, daytime=%f\n",totalTime,daytime);
    
	glBindTexture(GL_TEXTURE_2D, 0);

    glBindVertexArray(skyVAO);

    glDrawElements(GL_TRIANGLES,SKY_INDICES,GL_UNSIGNED_INT,(void*)0);
    
    glBindVertexArray(0);


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

    return 0;
}


// box sky box stuff
int skytexloaded=0;

float skysize=1000.0f;
unsigned int skytex[6];
int skylen=6; // 5 if skipping bottom
int unloadSkyImages() {
    for(int i=0;i<skylen;++i) {
        glDeleteTextures(1, &skytex[i]);
    }
    skytexloaded=0;

    return 0;
}
int loadSkyImages() {
    if(skytexloaded==1) {
        unloadSkyImages();
    }
    skytexloaded=1;
    char skylist[6]={"neswtb"};
    char skyname[100];
    int width, height, nrChannels;
    for(int i=0;i<skylen;++i) {
        glGenTextures(1, &skytex[i]);
        glBindTexture(GL_TEXTURE_2D, skytex[i]);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

        sprintf(skyname,"data/textures/skybox/%c.png",skylist[i]);
        unsigned char *data = stbi_load(skyname,&width, &height, &nrChannels, 0);
        printf("loading file %s, idx %d\n",skyname,i);
        if(data) {
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
        } else {
            printf("Failed to load texture skybox");
        }
        stbi_image_free(data);

        //
    }
    
    return 0;
}

unsigned int sky_dirx[6]={1,1,0,0,0,0};
unsigned int sky_diry[6]={0,0,0,0,1,1};
unsigned int sky_dirz[6]={0,0,1,1,0,0};
int drawSkyBox() {
    glUseProgram(0);
    glUseProgram(program);

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

    //glBindVertexArray(quadVAO);
    glBindVertexArray(plane_array);
    
    glActiveTexture(GL_TEXTURE0);
        
    //daytime=fabs((86400*0.5f)-totalTime)/43200; // 1-0-1 night-day-night
    //--daytime=0.0f; // 1-0-1 night-day-night

    mat4x4 vc;
    float sx=0;
    float sz=0;
    float sy=0;
    float offset=(skysize/2)-25.0f;
    for(int i=0;i<skylen;++i) {
        
        setRenderModePerspective();
        
        if (i % 2 == 0) { // even
            sx = sky_dirx[i]*(skysize/2);
            sy = sky_diry[i]*(skysize/2);
            sz = sky_dirz[i]*(skysize/2);
        } else { // odd
            sx = sky_dirx[i]*(-skysize/2);
            sy = sky_diry[i]*(-skysize/2);
            sz = sky_dirz[i]*(-skysize/2);
        }

        memcpy(vc,v,sizeof(vc));
        mat4x4_translate_in_place(vc,sx,sy+offset,sz);
        //mat4x4_translate_in_place(vc,0.0f,offset,0.0f);
        //offset+=2.0f;
        // 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 = {
            sky_dirx[i],
            sky_diry[i],
            sky_dirz[i]
        };
        //Vec3 dir = {
        //    1,
        //    0,
        //    0
        //};
        Quaternion q = directionToQuaternion(dir);

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

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

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

        setUniform4m(&program, "MVP", mvp);
        //setUniform1i(&program, "sampler", 0);
        setUniform1i(&program, "texture1", 0); 
        setUniform1i(&program, "mix", 0);

        glBindTexture(GL_TEXTURE_2D, skytex[i]);
        //printf("drawing skybox %d, skytex=%d\n",i,skytex[i]);

        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
    }
    glBindVertexArray(0);
 
    
    sunpos=convert_spherical_to_cartesian(10,degToRad(90.0f),degToRad(0.0f));
    //printf("sunpos x:%f, y:%f, z:%f\n",sunpos.x,sunpos.y,sunpos.z);
    //printf("sunpos %f, %f, %f\n",sunpos.x,sunpos.y,sunpos.z);
    //mat4x4_translate_in_place(*vc,sunpos.x,sunpos.y,sunpos.z);

    //sunTheta=180.0f - (totalTime*360.0f/86400);

   //// sunTheta -= 0.01f;
    //while(sunTheta<0.0f) sunTheta+=360.0f;
    //while(sunTheta>360.0f) sunTheta-=360.0f;

    //mat4x4 vcsun;
    //memcpy(vcsun,vc,sizeof(vcsun));
    //mat4x4 vcmoon;
    //memcpy(vcmoon,vc,sizeof(vcmoon));
    //
    //RenderSun(&vcsun);
    //RenderMoon(&vcmoon);
    
    return 0;
}


