#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 "shaders.h" //

#define nullptr ((void*)0)

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

int createSphere(int sectorCount, int stackCount, float radius, unsigned int *sphereVAO, unsigned int *sphereVBO, unsigned int *sphereEBO, unsigned int *NUM_INDICES) {
    // draw sphere move to geometry with cube and quad later?
    float x,y,z,xz;
    float nx,ny,nz;
    float s,t;
    
    //--float radius = 1000.0f;
    float lengthInv=1.0f/radius;

    //--int sectorCount = 36;
    //--int stackCount = 18;
    //int sectorCount = 8;
    //int stackCount = 6;
    //int sectorCount = 4;
    //int stackCount = 2;
    float sectorStep = 2 * M_PI / sectorCount;
    float stackStep = M_PI / stackCount;
    float sectorAngle, stackAngle; // theta phi?
    
    int vertexLen=(sectorCount+1)*(stackCount+1)*8;
    int indicesLen=sectorCount*(stackCount+(stackCount-2))*3;

    float *vertexData = malloc(sizeof(float)*vertexLen); // x y z nx ny nz
    unsigned int *indicesData = malloc(sizeof(unsigned int)*indicesLen); // stack x2 except top/bot
  //  unsigned int *lineIndicesData = malloc(sizeof(unsigned int)*sectorCount*(stackCount+(stackCount-1))*2);
                                                                        //printf("sectorCount*(stackCount+(stackCount-2))*3=%d\n",sectorCount*(stackCount+(stackCount-2))*3);

    *NUM_INDICES = 0;
    int c=0;
    //int len=sectorCount * stackCount;
    //for(int c=0;c<len;++c) {
    //    int i = c / stackCount;
    //    int j = c % sectorCount;
    for(int i=0;i <= stackCount;++i) { // <= +1
        stackAngle = M_PI / 2 - i * stackStep;// starting from pi/2 to -pi/2
        xz=radius * cosf(stackAngle);         // r * cos(u)
        y=radius * sinf(stackAngle);          // r * sin(u)

        for(int j=0;j <= sectorCount;++j,++c) { // <= +1
            sectorAngle = j * sectorStep; // starting from 0 to 2pi
            x = xz * cosf(sectorAngle); // r * cos(u) * cos(v)
            z = xz * sinf(sectorAngle); // r * cos(u) * sin(v)

            // vertex position (x, y, z)
            vertexData[c*8] = x;
            vertexData[c*8 + 1] = y; 
            vertexData[c*8 + 2] = z;

            // normalized vertex normal (nx, ny, nz)
            nx = x * lengthInv;
            ny = y * lengthInv;
            nz = z * lengthInv;
            vertexData[c*8 + 3] = nx;
            vertexData[c*8 + 4] = ny;
            vertexData[c*8 + 5] = nz;

            // vertex tex coord (s, t) range between [0, 1]
            s=(float)j / sectorCount;
            t=(float)i / stackCount;
            vertexData[c*8 + 6] = s;
            vertexData[c*8 + 7] = t;

            //--printf("creating sphere vertices: c=%d, i=%d, j=%d, [x=%f, y=%f, z=%f], [nx=%f, ny=%f, nz=%f], phi=%f, theta=%f\n",c,i,j,x,y,z,nx,ny,nz,radToDeg(stackAngle),radToDeg(sectorAngle));
        }
    }

    // indices
    //  k1--k1+1
    //  |  / |
    //  | /  |
    //  k2--k2+1
    // tri 1
    //   k1 -> k2 -> k1+1
    // tri 2
    //   k1+1 -> k2 -> k2+1
    int k1, k2;
    //for(int c=0;c<len;++c) {
    //    int i = c / stackCount;
    //    int j = c % sectorCount;
    c=0;
  //  int d=0;
    int addedtris=0;
    for(int i=0;i < stackCount;++i) {
        k1 = i * (sectorCount + 1);
        k2 = k1 + sectorCount + 1;
        //k1 = k1 % sectorCount;
        //k2 = k2 / sectorCount;

        // 2 triangles per sector excluding first and last stacks
        // k1 => k2 => k1+1
        for(int j=0;j < sectorCount;++j,++k1,++k2) {
            addedtris=0;
            if(i != 0) {
                indicesData[c*3] = k1;
                indicesData[c*3 + 1] = k2;
                indicesData[c*3 + 2] = k1 + 1;
                ++addedtris; // 1
                //--printf("creating sphere up: c=%d, i=%d, j=%d, [id1=%d, id2=%d, id3=%d]\n",c,i,j,indicesData[c*3],indicesData[c*3 + 1],indicesData[c*3 + 2]);
                ++c;
            }

            // k1+1 => k2 => k2+1
            if(i != (stackCount-1)) {
                indicesData[c*3] = k1 + 1;
                indicesData[c*3 + 1] = k2;
                indicesData[c*3 + 2] = k2 + 1;
                ++addedtris; // 1 | 2
                //--printf("creating sphere down: c=%d, i=%d, j=%d, [id1=%d, id2=%d, id3=%d]\n",c,i,j,indicesData[c*3],indicesData[c*3 + 1],indicesData[c*3 + 2]);
                ++c;
            }
            
            // store indices for lines
            // vertical liens for all stacks k1 => k2
          //  lineIndicesData[d*2] = k1;
          //  lineIndicesData[d*2 + 1] = k2;
          //  printf("adding line: d=%d, [id1=%d, id2=%d]\n",d,lineIndicesData[d*2],lineIndicesData[d*2 + 1]);
          //  ++d;
          //  if(i != 0 ) {
          //      lineIndicesData[d*2] = k1;
          //      lineIndicesData[d*2 + 1] = k1+1;
          //      printf("adding line inner: d=%d, [id1=%d, id2=%d]\n",d,lineIndicesData[d*2],lineIndicesData[d*2 + 1]);
          //      ++d;
          //  }

            //--printf("added tris:%d\n",addedtris);
        }
    }
    //NUM_INDICES=c*3;
    *NUM_INDICES=indicesLen;
    
    //printf("sector*stack*3 = %d\n",sectorCount*stackCount*3);
    //printf("c*3 = %d\n",c*3);
   // printf("d* = %d\n",d*2);

    glGenVertexArrays(1, sphereVAO);
    glBindVertexArray(*sphereVAO);

    glGenBuffers(1, sphereVBO);
    glBindBuffer(GL_ARRAY_BUFFER, *sphereVBO);
    glBufferData(GL_ARRAY_BUFFER,sizeof(float)*vertexLen,&vertexData[0],GL_STATIC_DRAW);

    glGenBuffers(1, sphereEBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, *sphereEBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(float)*indicesLen,&indicesData[0],GL_STATIC_DRAW);

    //int stride = 8 * sizeof(float); // float size = 4 = 32bytes
    int stride = 8 * sizeof(float);
    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);

    // unbind VAO and VBOs
    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    
    free(vertexData);
    free(indicesData);
   // free(lineIndicesData);
    return 0;
}

unsigned int texture1;
unsigned int plane_elements, plane_buffer, plane_array;
int InitializeSpinningQuad() {
    glUseProgram(0);
    glUseProgram(program);
    setUniform1i(&program, "texture1", 0); 

    // rotating textured or colored quad made of 2 triangles
    // centered in view of camera, connected points
    static const struct
    {
        float x, y, z;
        float r, g, b;
        float u, v;
    } vertices[] = {
        // position           // color           // texture coords
        //{  .66f,  0.5f, 0.0,  1.0f, 0.0f, 0.0f,  1.0f, 1.0f }, // top right
        //{  .66f, -0.5f, 0.0,  0.0f, 1.0f, 0.0f,  1.0f, 0.0f }, // bottom right
        //{ -.66f, -0.5f, 0.0,  0.0f, 0.0f, 1.0f,  0.0f, 0.0f }, // bottom left
        //{ -.66f,  0.5f, 0.0,  1.0f, 1.0f, 0.0f,  0.0f, 1.0f } // top left
        {  0.5f,  0.5f, 0.0,  1.0f, 0.0f, 0.0f,  1.0f, 1.0f }, // top right
        {  0.5f, -0.5f, 0.0,  0.0f, 1.0f, 0.0f,  1.0f, 0.0f }, // bottom right
        { -0.5f, -0.5f, 0.0,  0.0f, 0.0f, 1.0f,  0.0f, 0.0f }, // bottom left
        { -0.5f,  0.5f, 0.0,  1.0f, 1.0f, 0.0f,  0.0f, 1.0f } // top left
    };
    unsigned int indices[] = {
        0, 1, 3, // first triangle
        1, 2, 3 // second triangle
    };
    //red(0)    yellow(3)
    //|---------|
    //|       / |
    //|     /   |
    //|   /     |
    //| /       |
    //|---------|
    //green(1)  blue(2)
    //
    //

    int width, height, nrChannels;
    // --------- prepare texture1 
    glGenTextures(1, &texture1);
    glBindTexture(GL_TEXTURE_2D, texture1);
     // set the texture wrapping parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);	// set texture wrapping to GL_REPEAT (default wrapping method)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    // set texture filtering parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    // load image, create texture and generate mipmaps
    stbi_set_flip_vertically_on_load(true); // tell stb_image.h to flip loaded texture's on the y-axis.
    
    unsigned char *data = stbi_load("data/textures/analie.jpg", &width, &height, &nrChannels, 0);
    if (data)
    {
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
        //glGenerateMipmap(GL_TEXTURE_2D); // do not use with framebuffer? "otherwise you'll use a ton of memory." unconfirmed but not needed for now
    }
    else
    {
        printf("Failed to load texture spinning quad");
    }
    stbi_image_free(data);

    glGenVertexArrays(1, &plane_array);
    glGenBuffers(1, &plane_buffer);
    glGenBuffers(1, &plane_elements);
    glBindVertexArray(plane_array);

    glBindBuffer(GL_ARRAY_BUFFER, plane_buffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, plane_elements);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    // position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // color attribute
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    // texture coord attribute
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
    glEnableVertexAttribArray(2);

    return 0;
}

mat4x4 cube_mat4;
unsigned int cube_buffer, cube_elements, cube_array;
int InitializeCube() { // init player, or the first existence of anyway, struct Player defined in custom.h, player malloc in main.c
    //memcpy(cube_mat4,mvp,sizeof(mvp));
    mat4x4_identity(cube_mat4);

    static const struct {
       float x, y, z;
       float r, g, b;
       float u,v;
       float nx, ny, nz;
    } cube2_vertices[] = {
        { -1.0f,-1.0f,-1.0f,   0.583f,  0.771f,  0.014f,  0.000059f, 1.0f-0.000004f,  0.0f, 0.0f, 1.0f },
        { -1.0f,-1.0f, 1.0f,   0.609f,  0.115f,  0.436f,  0.000103f, 1.0f-0.336048f,  0.0f, 0.0f, 1.0f },
        { -1.0f, 1.0f, 1.0f,   0.327f,  0.483f,  0.844f,  0.335973f, 1.0f-0.335903f,  0.0f, 0.0f, 1.0f },
        {  1.0f, 1.0f,-1.0f,   0.822f,  0.569f,  0.201f,  1.000023f, 1.0f-0.000013f,  0.0f, -1.0f, 0.0f },
        { -1.0f,-1.0f,-1.0f,   0.435f,  0.602f,  0.223f,  0.667979f, 1.0f-0.335851f,  0.0f, -1.0f, 0.0f },
        { -1.0f, 1.0f,-1.0f,   0.310f,  0.747f,  0.185f,  0.999958f, 1.0f-0.336064f,  0.0f, -1.0f, 0.0f },

        {  1.0f,-1.0f, 1.0f,   0.597f,  0.770f,  0.761f,  0.667979f, 1.0f-0.335851f,  1.0f, 0.0f, 0.0f },
        { -1.0f,-1.0f,-1.0f,   0.559f,  0.436f,  0.730f,  0.336024f, 1.0f-0.671877f,  1.0f, 0.0f, 0.0f },
        {  1.0f,-1.0f,-1.0f,   0.359f,  0.583f,  0.152f,  0.667969f, 1.0f-0.671889f,  1.0f, 0.0f, 0.0f },
        {  1.0f, 1.0f,-1.0f,   0.483f,  0.596f,  0.789f,  1.000023f, 1.0f-0.000013f,  0.0f, -1.0f, 0.0f },
        {  1.0f,-1.0f,-1.0f,   0.559f,  0.861f,  0.639f,  0.668104f, 1.0f-0.000013f,  0.0f, -1.0f, 0.0f },
        { -1.0f,-1.0f,-1.0f,   0.195f,  0.548f,  0.859f,  0.667979f, 1.0f-0.335851f,  0.0f, -1.0f, 0.0f },

        { -1.0f,-1.0f,-1.0f,   0.014f,  0.184f,  0.576f,  0.000059f, 1.0f-0.000004f,  0.0f, 0.0f, 1.0f },
        { -1.0f, 1.0f, 1.0f,   0.771f,  0.328f,  0.970f,  0.335973f, 1.0f-0.335903f,  0.0f, 0.0f, 1.0f },
        { -1.0f, 1.0f,-1.0f,   0.406f,  0.615f,  0.116f,  0.336098f, 1.0f-0.000071f,  0.0f, 0.0f, 1.0f },
        {  1.0f,-1.0f, 1.0f,   0.676f,  0.977f,  0.133f,  0.667979f, 1.0f-0.335851f,  1.0f, 0.0f, 0.0f },
        { -1.0f,-1.0f, 1.0f,   0.971f,  0.572f,  0.833f,  0.335973f, 1.0f-0.335903f,  1.0f, 0.0f, 0.0f },
        { -1.0f,-1.0f,-1.0f,   0.140f,  0.616f,  0.489f,  0.336024f, 1.0f-0.671877f,  1.0f, 0.0f, 0.0f },

        { -1.0f, 1.0f, 1.0f,   0.997f,  0.513f,  0.064f,  1.000004f, 1.0f-0.671847f,  0.0f, 1.0f, 0.0f },
        { -1.0f,-1.0f, 1.0f,   0.945f,  0.719f,  0.592f,  0.999958f, 1.0f-0.336064f,  0.0f, 1.0f, 0.0f },
        {  1.0f,-1.0f, 1.0f,   0.543f,  0.021f,  0.978f,  0.667979f, 1.0f-0.335851f,  0.0f, 1.0f, 0.0f },
        {  1.0f, 1.0f, 1.0f,   0.279f,  0.317f,  0.505f,  0.668104f, 1.0f-0.000013f,  0.0f, 0.0f, -1.0f }, 
        {  1.0f,-1.0f,-1.0f,   0.167f,  0.620f,  0.077f,  0.335973f, 1.0f-0.335903f,  0.0f, 0.0f, -1.0f },
        {  1.0f, 1.0f,-1.0f,   0.347f,  0.857f,  0.137f,  0.667979f, 1.0f-0.335851f,  0.0f, 0.0f, -1.0f },

        {  1.0f,-1.0f,-1.0f,   0.055f,  0.953f,  0.042f,  0.335973f, 1.0f-0.335903f,  0.0f, 0.0f, -1.0f },
        {  1.0f, 1.0f, 1.0f,   0.714f,  0.505f,  0.345f,  0.668104f, 1.0f-0.000013f,  0.0f, 0.0f, -1.0f },
        {  1.0f,-1.0f, 1.0f,   0.783f,  0.290f,  0.734f,  0.336098f, 1.0f-0.000071f,  0.0f, 0.0f, -1.0f },
        {  1.0f, 1.0f, 1.0f,   0.722f,  0.645f,  0.174f,  0.000103f, 1.0f-0.336048f,  -1.0f, 0.0f, 0.0f },
        {  1.0f, 1.0f,-1.0f,   0.302f,  0.455f,  0.848f,  0.000004f, 1.0f-0.671870f,  -1.0f, 0.0f, 0.0f },
        { -1.0f, 1.0f,-1.0f,   0.225f,  0.587f,  0.040f,  0.336024f, 1.0f-0.671877f,  -1.0f, 0.0f, 0.0f },

        {  1.0f, 1.0f, 1.0f,   0.517f,  0.713f,  0.338f,  0.000103f, 1.0f-0.336048f,  -1.0f, 0.0f, 0.0f },
        { -1.0f, 1.0f,-1.0f,   0.053f,  0.959f,  0.120f,  0.336024f, 1.0f-0.671877f,  -1.0f, 0.0f, 0.0f },
        { -1.0f, 1.0f, 1.0f,   0.393f,  0.621f,  0.362f,  0.335973f, 1.0f-0.335903f,  -1.0f, 0.0f, 0.0f },
        {  1.0f, 1.0f, 1.0f,   0.673f,  0.211f,  0.457f,  0.667969f, 1.0f-0.671889f,  0.0f, 1.0f, 0.0f },
        { -1.0f, 1.0f, 1.0f,   0.820f,  0.883f,  0.371f,  1.000004f, 1.0f-0.671847f,  0.0f, 1.0f, 0.0f },
        {  1.0f,-1.0f, 1.0f,   0.982f,  0.099f,  0.879f,  0.667979f, 1.0f-0.335851f,  0.0f, 1.0f, 0.0f }
    };

    glGenVertexArrays(1, &cube_array);
    glGenBuffers(1, &cube_buffer);
    glGenBuffers(1, &cube_elements);
    glBindVertexArray(cube_array);
 
    glBindBuffer(GL_ARRAY_BUFFER, cube_buffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(cube2_vertices), cube2_vertices, GL_STATIC_DRAW);

    //--glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cube_elements);
    //--glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(cube2_indices), cube2_indices, GL_STATIC_DRAW);

    // position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 11 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // color attribute
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 11 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    // uv coords
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 11 * sizeof(float), (void*)(6 * sizeof(float)));
    glEnableVertexAttribArray(2);

    glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 11 * sizeof(float), (void*)(8 * sizeof(float)));
    glEnableVertexAttribArray(3);

    return 0;
}
