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

#define nullptr ((void*)0)

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

unsigned int spellVAO, spellVBO, spellEBO, SPELL_INDICES;
int InitializeFireBall() {
    createSphere(36,18,10,&spellVAO,&spellVBO,&spellEBO,&SPELL_INDICES);
    return 0;
}

int RenderFireBall(int depth) {
    glUseProgram(0);
    if(depth==-1) {
        glUseProgram(programStencil);
    } else {
        glUseProgram(programFireBall);
    }

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

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    setRenderModePerspective();

    mat4x4 vc;
    memcpy(vc,v,sizeof(vc));
    //
    // x = blue, z = red on grid!! y is normal up+/down-
    //mat4x4_translate_in_place(vc,-111.29f,9.0f,49.68f);
    mat4x4_translate_in_place(vc,-68.29f,64.0f,-110.68f);
    //Vec3 dir = {
    //    1,
    //    0,
    //    0,
    //};
    //Quaternion q = directionToQuaternion(dir);
    //mat4x4 RotationMatrix;
    //QuaternionToMat4(RotationMatrix,&q);

    //mat4x4_mul(m, m, RotationMatrix);

    // 
    applyScalingToModel(m, 2.0f, 2.0f, 2.0f);

    execRenderModePerspective(vc);

    if(depth==-1) {
        setUniform4m(&programStencil,"MVP", mvp);
    } else {
        setUniform4m(&programFireBall,"MVP", mvp);
        setUniform3f(&programFireBall,"data", waterTime, -1.0f, 2.0f);
    }
    
    glBindVertexArray(spellVAO);

    glBindTexture(GL_TEXTURE_2D, 0);

    glDrawElements(GL_TRIANGLES,SPELL_INDICES,GL_UNSIGNED_INT,(void*)0);

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

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

    glBindVertexArray(0);
    return 0;
}

int RenderFireQuad(int depth) {
    glUseProgram(0);
    if(depth == -1) {
        glUseProgram(programStencil);
    } else {
        glUseProgram(programFire);
    }

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

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    setRenderModePerspective();

    mat4x4 vc;
    memcpy(vc,v,sizeof(vc));
    //
    // x = blue, z = red on grid!! y is normal up+/down-
    mat4x4_translate_in_place(vc,-111.29f,9.0f,19.68f);
    Vec3 dir = {
        1,
        0,
        0,
    };
    Quaternion q = directionToQuaternion(dir);
    mat4x4 RotationMatrix;
    QuaternionToMat4(RotationMatrix,&q);

    mat4x4_mul(m, m, RotationMatrix);

    // 
    applyScalingToModel(m, 1.0f, 10.0f, 10.0f);

    execRenderModePerspective(vc);

    if(depth == -1) {
        setUniform4m(&programStencil,"MVP", mvp);
    } else {
        setUniform4m(&programFire,"MVP", mvp);
        setUniform3f(&programFire,"data", waterTime, -1.0f, 2.0f);
    }

    glBindTexture(GL_TEXTURE_2D, 0);
    glBindVertexArray(quadVAO);

    glDrawArrays(GL_TRIANGLES, 0, 6);

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

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

    glBindVertexArray(0);

    return 0;
}

int RenderVortexQuad(int depth) {
    glUseProgram(0);
    if(depth == -1) {
        glUseProgram(programStencil);
    } else {
        glUseProgram(programVortex);
    }

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

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    setRenderModePerspective();

    mat4x4 vc;
    memcpy(vc,v,sizeof(vc));
    //
    // x = blue, z = red on grid!! y is normal up+/down-
    mat4x4_translate_in_place(vc,-111.29f,9.0f,56.68f);
    Vec3 dir = {
        1,
        0,
        0,
    };
    Quaternion q = directionToQuaternion(dir);
    mat4x4 RotationMatrix;
    QuaternionToMat4(RotationMatrix,&q);

    mat4x4_mul(m, m, RotationMatrix);

    // 
    applyScalingToModel(m, 1.0f, 10.0f, 10.0f);

    execRenderModePerspective(vc);

    if(depth == -1) {
        setUniform4m(&programStencil,"MVP", mvp);
    } else {
        setUniform4m(&programVortex,"MVP", mvp);
        setUniform3f(&programVortex,"data", waterTime, -1.0f, 2.0f);
    }

    glBindTexture(GL_TEXTURE_2D, 0);
    glBindVertexArray(quadVAO);

    glDrawArrays(GL_TRIANGLES, 0, 6);

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

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

    glBindVertexArray(0);

    return 0;
}
