#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

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

#define nullptr ((void*)0)

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



float *keepLines;
unsigned int keepLinesLen=0;
unsigned int NUM_STRIPS;
unsigned int NUM_VERTS_PER_STRIP;
unsigned int terrainVAO, terrainVBO, terrainEBO;
Vec3 terrainMin,terrainMax;
float *terrainCollider; // collider data
float gridWidth,gridHeight; // size
float gridTop,gridLeft; // offset
float terrainScale = 4.0f;
int InitializeTerrain() { // buffer only needs to be made once?
                        // updating buffers means transfering
                        // the data back again to the GPU,
                        // create the buffer once and simply
                        // rebind before every draw = no transfer
                        // between cpu/gpu, and make modification
                        // with the translate of m * v * p matrices
    // flip is already set in spinningquad once
    //--int width, height, nChannels;
    //unsigned char *data = stbi_load("data/textures/iceland_heightmap.png", &width, &height, &nChannels, 0);
    //unsigned char *data = stbi_load("data/textures/mini_island.png", &width, &height, &nChannels, 0);
    int width, height, nChannels;
    unsigned char *data = stbi_load("data/textures/awv6.png", &width, &height, &nChannels, 0);
    //unsigned char *data = stbi_load("data/textures/mini_island2.png", &width, &height, &nChannels, 0);
    
    gridWidth = width;
    gridHeight = height;
    gridTop = -height/2.0f;
    gridLeft = -width/2.0f;

    //float yScale = 64.0f / 256.0f;
    float yScale = 0.25f;
    //float yShift = 16.0f;
    float yShift = 8.0f;

    // Example rendering setup in plain C
    // Assume you have a pointer to vertex data structure, which includes x, y, and z (height)
    // malloc size 3 + 3 + 2 position,color,uv
    //uint8_t *colorData = malloc(sizeof(float) * width * height * 3); // Each point has x,y,z
    float *vertexData = malloc(sizeof(float) * width * height * 7); // Each point has x,y,z
    terrainCollider = malloc(sizeof(float) * width * height); // keeing for calculations after - heights only(Y)
    // scaling 1:1 pixel to float position, offset -width/2
    unsigned int *indicesData = malloc(sizeof(unsigned int) * width * (height - 1) * 2); // Each side of strip indices
   
    printf("\nTerrain loading\n*vertexData=[");
    printf("_redacted_for_speed_");
    //int lastZ=-1; 
    for (int c = 0; c < width * height; ++c) {
        int j = c % width; // width cycles | equal j in learnopengl
        int i = c / height; // height // equal i in learnopengl
        //int z1 = z-1; // height-1
        //int x1 = x-1; // height-1
        //int modUV = c % 4; // wrap 0-1-2-3
        
        //if(lastZ!=z) {
        //    printf("new strip start. z=%d\n",z);
        //}
        //lastZ=z;
         
        // retrieve texel for (i,j) tex coord
        unsigned char* texel = data + (j + width * i) * nChannels;
        // raw height at coordinate
        unsigned char y = texel[0];
        //const stbi_us y = texel[0];
        float heightMap = (int)y * yScale - yShift;

        float realZ = gridTop + i;
        float realX = gridLeft + j;
        //float colorY = (heightMap+(yShift*2))/(256.0f-yShift*10);

        vertexData[c*7] = realX;
        vertexData[c*7 + 1] = heightMap;
        vertexData[c*7 + 2] = realZ;
        //vertexData[c*6 + 3] = 0.0f;
        //vertexData[c*6 + 4] = colorY;
        //vertexData[c*6 + 5] = 0.0f;
        //terrainCollider[c*3] = realX;
        terrainCollider[c] = heightMap;
        //terrainCollider[c*3 + 2] = realZ;

        //colorData[i*3] = colorY; // color
        //colorData[i*3 + 1] = colorY; // color
        //colorData[i*3 + 2] = colorY; // color
        
        vertexData[c*7 + 6] = realX; // uv
        vertexData[c*7 + 7] = realZ; // uv
        //vertexData[c*7 + 6] = 1.0f; // uv
        //vertexData[c*7 + 7] = 0.0f; // uv
        //--printf("%f,0.0f,%f, ",realX,realZ);
        //--printf("colorY = %f\n",colorY);

        if(heightMap<terrainMin.y) { // for bounds
            terrainMin.y = heightMap;
        }
        if(heightMap>terrainMax.y) {
            terrainMax.y = heightMap;
        }
        if(c==0) {
            terrainMin.x = realX;
            terrainMin.z = realZ;
        } else if(c==(width*height)-1) {
            terrainMax.x = realX;
            terrainMax.z = realZ;
        }
        //printf("i=%d, x=%d, z=%d [x=%f, y=%f, z=%f]\n",i,x,z,realX,heightMap,realZ);
    }
    keepLines = malloc(sizeof(float)*(width * (height - 1))*7);
    printf("]\n*indicesData=[");
    printf("_redacted_for_speed_");
    unsigned int count = 0;
    for (int c = 0; c < width * (height - 1); ++c) {
        int j = c % width; // width cycles | equal j in learnopengl
        int i = c / height; // height // equal i in learnopengl
        
        //int cN=((i - 1) * j) + j;
        //int cE=(i * j) + j + 1;
        //int cW=(i * j) + j - 1;
        //int cS=((i + 1) * j) + j - 1;
        //-0int cN=((j+width)*6)*(i-1);
        //-0int cE=((j+1+width)*6)*i;
        //-0int cW=((j-1+width)*6)*i;
        //-0int cS=((j+width)*6)*(i+1);
        int cpos=c * 7;

        int left=cpos-7;
        int right=cpos+7;
        //int top=cpos-(width*6);
        int bottom=cpos+(width*7);

        //int cN=top;
        int cE=right;
        int cW=left;
        int cS=bottom;
        //--printf("cN: %d, cE: %d, cW: %d, cS: %d\n",cN,cE,cW,cS);
        //--Vec3 vN={0.0f,0.0f,0.0f};
        Vec3 vE={0.0f,0.0f,0.0f};
        Vec3 vW={0.0f,0.0f,0.0f};
        Vec3 vS={0.0f,0.0f,0.0f};

        int cLen = width * height * 7;
        int row = width * 7;

        //--if(cN>=0 && i>0) {
        //--    vN.x=vertexData[cN];
        //--    vN.y=vertexData[cN+1];
        //--    vN.z=vertexData[cN+2];
        //--}
        if(cE<=cLen && j<width-1) {
            vE.x=vertexData[cE];
            vE.y=vertexData[cE+1];
            vE.z=vertexData[cE+2];
        }
        if(cW>=0 && j>0) {
            vW.x=vertexData[cW];
            vW.y=vertexData[cW+1];
            vW.z=vertexData[cW+2];
        }
        if(cS<=cLen-row && i<height-1) {
            vS.x=vertexData[cS];
            vS.y=vertexData[cS+1];
            vS.z=vertexData[cS+2];
        }

        //--Vec3 vC={vertexData[c*6],vertexData[c*6+1],vertexData[c*6+2]};

        //--Vec3 measure={0.0f,2.0f,0.0f};
        //Vec3 up={0.0f,1.0f,0.0f};
        //measure.y=((vS.y-vN.y)+(vW.y-vE.y))/2;
        //Vec3 test={0.0f,0.0f,0.0f};
        //i//test.
        //Vec3 crossy=cross(vW,vN);
        //measure.z=vS.x-vN.x;
        //measure.x=vW.z-vE.z;

//Vec3 n0 = normalize(cross(Vec3_sub(vC,vN), Vec3_sub(vW,vC)));
//Vec3 n1 = normalize(cross(Vec3_sub(vE,vC), Vec3_sub(vW,vC)));
//Vec3 n2 = normalize(cross(Vec3_sub(vC,vW), Vec3_sub(vN,vC)));
//Vec3 n3 = normalize(cross(Vec3_sub(vW,vC), Vec3_sub(vE,vC)));
    
        //Vec3 n0 = Vec3_sub(vS,vC);
        //Vec3 n1 = Vec3_sub(vC,vN);
        //Vec3 n2 = Vec3_sub(vW,vC);
        //Vec3 n3 = Vec3_sub(vC,vE);
        Vec3 uVec = Vec3_sub(vW,vE);
        Vec3 vVec = Vec3_sub(vS,vE);
        Vec3 normal = normalize(cross(uVec,vVec));

        //measure.z=n0.x;
        //measure.x=n2.z;
        //measure.y=-n0.y;
        //measure.z=vS.x-vN.x;
        //measure.x=vW.z-vE.z;

        vertexData[c*7 + 3] = normal.x;
        vertexData[c*7 + 4] = normal.y;
        vertexData[c*7 + 5] = normal.z;


        // keepLines for mesh normals that are generated once
        // but want lines to keep rendering indefinitely--
        //--keepLines[c*6] = vertexData[c*6];
        //--keepLines[c*6+1] = vertexData[c*6+1];
        //--keepLines[c*6+2] = vertexData[c*6+2];
        //--keepLines[c*6+3] = vertexData[c*6]+normal.x;
        //--keepLines[c*6+4] = vertexData[c*6+1]+normal.y;
        //--keepLines[c*6+5] = vertexData[c*6+2]+normal.z;
        //--keepLinesLen++;
        
        //--printf("c=%d, i=%d, j=%d, [nX: %f, nY: %f, nZ: %f]\n",c,i,j,vertexData[c*6 + 3],vertexData[c*6 + 4],vertexData[c*6 + 5]);

        for(int k=0;k<2;k++) { // alternate index k=h
            unsigned int indice=j + width * (i + k);
            indicesData[count] = indice;
            count++;

            //if(k==0) {
            //    printf("k0: %d\n",indice);
            //    // |----/ 
            //    // |  _/   
            //    // | /     
            //    // |/     
            //} else {
            //    printf("k1: %d\n",indice);
            //    //      /|
            //    //    _/ |
            //    //   /   |
            //    //  /----|
            //}
        }
    }
    printf("]\n");

    printf("\nterrainMin.x=%f, terrainMin.y=%f, terrainMin.z=%f\n",terrainMin.x,terrainMin.y,terrainMin.z);
    printf("terrainMax.x=%f, terrainMax.y=%f, terrainMax.z=%f\n\n",terrainMax.x,terrainMax.y,terrainMax.z);

    stbi_image_free(data); // free after building vertex array

    NUM_STRIPS = height-1;
    NUM_VERTS_PER_STRIP = width*2;

    glGenVertexArrays(1,&terrainVAO);
    glBindVertexArray(terrainVAO);
    
    glGenBuffers(1,&terrainVBO);
    glBindBuffer(GL_ARRAY_BUFFER,terrainVBO);
    glBufferData(GL_ARRAY_BUFFER,sizeof(float)*width*height*7,&vertexData[0],GL_STATIC_DRAW);
    //glBufferData(GL_ARRAY_BUFFER,0,NULL,GL_STATIC_DRAW);
    //glBufferSubData(GL_ARRAY_BUFFER,0,sizeof(float)*width*height*3,&vertexData[0]);
    //glBufferSubData(GL_ARRAY_BUFFER,sizeof(float)*width*height*3,sizeof(float)*width*height*3,&colorData[0]);
    //glBindBuffer(GL_ARRAY_BUFFER, 0);
    
    // element buffer add if using indices--
    glGenBuffers(1,&terrainEBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,terrainEBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(unsigned int)*(width*2)*(height-1),&indicesData[0],GL_STATIC_DRAW);
    //glBindBuffer(GL_ARRAY_BUFFER, 0);

 // position attribute
    int stride = 7 * sizeof(float); // float size = 4
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, 0);
    glEnableVertexAttribArray(0);
    
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, stride, (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, stride, (void*)(6 * sizeof(float)));
    glEnableVertexAttribArray(2);
   // // texture coord attribute
   // glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
   // glEnableVertexAttribArray(2);
    //glDisableVertexAttribArray(2);

    // fine to free malloc after glBufferData is pushed to gpu
    //free(colorData);
    free(vertexData);
    free(indicesData);
    return 0;
}

int RenderTerrain() {
    // draw main rotating quad in mbp
    glUseProgram(0);
    //glUseProgram(programTerrain);
    glUseProgram(programTerrain);

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

    mat4x4_invert(ip,p); // invert projection for underwater frag post

    applyScalingToModel(m, terrainScale, 1.0f, terrainScale);

    execRenderModePerspective(v);
 
    //setUniform4m(&programTerrain, "MVP", mvp);
    setUniform4m(&programTerrain,"MVP", mvp);
    setUniform3f(&programTerrain,"sunPos", sunpos.x, sunpos.y, sunpos.z);
    setUniform3f(&programTerrain,"moonPos", moonpos.x, moonpos.y, moonpos.z);
    setUniform3f(&programTerrain,"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);
    setUniform3f(&programTerrain,"data", waterTime, cloudFreq, cloudAmp); // time, freq=1.0, amp=0.5

    setUniform1f(&programTerrain,"daytime", daytime);
    setUniform4m(&programTerrain, "invProjection", ip);
    setUniform4m(&programTerrain, "invView", iv);

    setUniform1i(&programTerrain, "splat1", 0);
    setUniform1i(&programTerrain, "texture1", 1); // default black/sand/underwater
    setUniform1i(&programTerrain, "texture2", 2); // grass1 (green)
    setUniform1i(&programTerrain, "texture3", 3); // stone1 norm.y (green)
    setUniform1i(&programTerrain, "texture4", 4); // dirt (red)
    setUniform1i(&programTerrain, "texture5", 5); // road (blue)
    //setUniform1i(&programTerrain, "texture6", 6);
    //setUniform1i(&programTerrain, "texture7", 7);
    //setUniform1i(&programTerrain, "texture8", 8);
	
    glBindVertexArray(terrainVAO);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, terrainMasks[0]); // splat
    
    glActiveTexture(GL_TEXTURE0+1);
    glBindTexture(GL_TEXTURE_2D, terrainTextures[6]); // sand

    glActiveTexture(GL_TEXTURE0+2);
    glBindTexture(GL_TEXTURE_2D, terrainTextures[2]); // grass1

    glActiveTexture(GL_TEXTURE0+3);
    glBindTexture(GL_TEXTURE_2D, terrainTextures[3]); // stone1

    glActiveTexture(GL_TEXTURE0+4);
    glBindTexture(GL_TEXTURE_2D, terrainTextures[5]); // dirt
    
    glActiveTexture(GL_TEXTURE0+5);
    glBindTexture(GL_TEXTURE_2D, terrainTextures[4]); // road

	//glBindTexture(GL_TEXTURE_2D, texture1);

    for(unsigned int strip=0;strip<NUM_STRIPS;++strip) {
        glDrawElements(GL_TRIANGLE_STRIP,NUM_VERTS_PER_STRIP,GL_UNSIGNED_INT,(void*)(sizeof(unsigned int) * NUM_VERTS_PER_STRIP * strip));
    }

    RenderBoundBox(terrainMin.x,terrainMin.y,terrainMin.z,terrainMax.x,terrainMax.y,terrainMax.z,1,mvp);

    // render normal direction aligned lines (heavy)
    //--for(unsigned int i=0;i<keepLinesLen;i++) {
    //--    RenderLine(keepLines[i*6],keepLines[i*6+1],keepLines[i*6+2],keepLines[i*6+3],keepLines[i*6+4],keepLines[i*6+5],1,mvp);
    //--}

    // starting +5
	glBindTexture(GL_TEXTURE_2D, 0);

    glActiveTexture(GL_TEXTURE0+4);
	glBindTexture(GL_TEXTURE_2D, 0);

    glActiveTexture(GL_TEXTURE0+3);
	glBindTexture(GL_TEXTURE_2D, 0);

    glActiveTexture(GL_TEXTURE0+2);
	glBindTexture(GL_TEXTURE_2D, 0);

    glActiveTexture(GL_TEXTURE0+1);
	glBindTexture(GL_TEXTURE_2D, 0);

    glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, 0);

    glBindVertexArray(0);
    return 0;
}
