#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include "utils.h"
#include <algorithm>
#include <vector>
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <array>
#include <stack>

#define numVAOs 1
#define numVBOs 3

GLuint renderingProgram,expandingPlaneProgram;
GLuint vao[numVAOs];
GLuint vbo[numVBOs];

glm::vec3 camera;
GLuint vLoc,projLoc;
int width, height;
float aspect;
glm::mat4 mMat, pMat, vMat, mvMat;
std::stack<glm::mat4>transforms;

GLuint cupYayTex,iceTex;

void setupTextures(void){
    glActiveTexture(GL_TEXTURE0);
}

void setupVertices(void) { // 36 vertices, 12 triangles, makes 2x2x2 cube placed at origin
   float cubePositions[108] = {
      -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f,
      1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f,
      1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f,
      1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f,
      1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
      -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
      -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f,
      -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f,
      -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f,
      1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f,
      -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f,
      1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f
   };
   float pyramidPositions[54]={
        -1.0f, -1.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 0.0f, 1.0f, 0.0f,
        1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 1.0f, 0.0f,
        1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 1.0f, 0.0f,
        -1.0f, -1.0f, - 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 0.0f,
        -1.0f, -1.0f, - 1.0f, 1.0f, - 1.0f, 1.0f, -1.0f, -1.0f, 1.0f,
        1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f
   };
   float plane[6*3]={
        -1,1,0,1,1,0,-1,-1,0,
        -1,-1,0,1,1,0,1,-1,0
   };
   float planeUVs[6*2]={
        0,1,1,1,0,0,
        0,0,1,1,1,0
   };
   glGenVertexArrays(1, vao);
   glBindVertexArray(vao[0]);
   glGenBuffers(numVBOs, vbo);

   glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
   glBufferData(GL_ARRAY_BUFFER, sizeof(cubePositions), cubePositions, GL_STATIC_DRAW);

   glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
   glBufferData(GL_ARRAY_BUFFER, sizeof(plane), plane, GL_STATIC_DRAW);
   glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
   glBufferData(GL_ARRAY_BUFFER, sizeof(planeUVs), planeUVs, GL_STATIC_DRAW);
}

void init(GLFWwindow* window) {
    renderingProgram=utils::createShaderProgram("vertShader.glsl","fragShader.glsl");
    expandingPlaneProgram=utils::createShaderProgram("vertShader2.glsl","fragShader2.glsl");
    camera={0.0f,0.0f,-10.0f};
    setupVertices();

    cupYayTex=utils::loadTexture("cupyay4.png");
    iceTex=utils::loadTexture("ice.jpg");
    setupTextures();
}

double lastTime=0;

void DrawCube(){
    glUniformMatrix4fv(vLoc,1,GL_FALSE,glm::value_ptr(mMat));
    glUniformMatrix4fv(projLoc,1,GL_FALSE,glm::value_ptr(pMat));
    glBindBuffer(GL_ARRAY_BUFFER,vbo[0]);
    glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,0);
    glDrawArrays(GL_TRIANGLES,0,36);
}
void DrawSquarePyramid(){
    glUniformMatrix4fv(vLoc,1,GL_FALSE,glm::value_ptr(mMat));
    glUniformMatrix4fv(projLoc,1,GL_FALSE,glm::value_ptr(pMat));
    glBindBuffer(GL_ARRAY_BUFFER,vbo[1]);
    glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,0);
    glDrawArrays(GL_TRIANGLES,0,18);
}

GLuint timeLoc;

GLuint mvLoc;
float size=0;
GLuint texLoc;

void display(GLFWwindow* window, double currentTime) {
    double elapsedTime=currentTime-lastTime;
    lastTime=currentTime;

    glClear(GL_DEPTH_BUFFER_BIT);
    glClearColor(0,0,0.2,1);
    glClear(GL_COLOR_BUFFER_BIT);

    glUseProgram(renderingProgram);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    vLoc=glGetUniformLocation(renderingProgram,"v_matrix");
    projLoc=glGetUniformLocation(renderingProgram,"proj_matrix");
    timeLoc=glGetUniformLocation(renderingProgram,"time");

    glfwGetFramebufferSize(window,&width,&height);
    aspect=(float)width/(float)height;
    pMat=glm::perspective(utils::degToRad(60),aspect,0.1f,1000.f);

    vMat=glm::translate(glm::mat4(1.0f),glm::vec3{camera.x,camera.y,camera.z});
    transforms.push(vMat);
    
    mMat=glm::translate(transforms.top(),{0.f,0.f,0.f});
    glUniformMatrix4fv(vLoc,1,GL_FALSE,glm::value_ptr(vMat));
    glUniformMatrix4fv(projLoc,1,GL_FALSE,glm::value_ptr(pMat));
    glUniform1f(timeLoc,currentTime);
    glBindBuffer(GL_ARRAY_BUFFER,vbo[0]);
    glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,0);
    glDrawArraysInstanced(GL_TRIANGLES,0,36,100000);
    
    size+=currentTime/1000;
    glUseProgram(expandingPlaneProgram);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);  
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
    glfwGetFramebufferSize(window,&width,&height);
    aspect=(float)width/(float)height;
    pMat=glm::perspective(utils::degToRad(60),aspect,0.1f,1000.f);
    vMat=glm::translate(glm::mat4(1.0f),glm::vec3{camera.x,camera.y,camera.z});

    mMat=glm::translate(glm::mat4{1.0},{0,0,0});
    mMat=glm::scale(mMat,{size,size,size});

    mvMat=vMat*mMat;
    
    mvLoc=glGetUniformLocation(expandingPlaneProgram,"mv_matrix");
    projLoc=glGetUniformLocation(expandingPlaneProgram,"proj_matrix");
    texLoc=glGetUniformLocation(expandingPlaneProgram,"texture");
    glUniformMatrix4fv(mvLoc,1,GL_FALSE,glm::value_ptr(mvMat));
    glUniformMatrix4fv(projLoc,1,GL_FALSE,glm::value_ptr(pMat));
    glBindBuffer(GL_ARRAY_BUFFER,vbo[1]);
    glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,0);
    glBindBuffer(GL_ARRAY_BUFFER,vbo[2]);
    glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,0,0);
    
    glBindTexture(GL_TEXTURE_2D,iceTex);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);

    glDrawArrays(GL_TRIANGLES,0,6);
    mMat=glm::translate(glm::mat4{1.0},{10,0,0});
    mMat=glm::scale(mMat,{size,size,size});

    mvMat=vMat*mMat;
    
    mvLoc=glGetUniformLocation(expandingPlaneProgram,"mv_matrix");
    projLoc=glGetUniformLocation(expandingPlaneProgram,"proj_matrix");
    texLoc=glGetUniformLocation(expandingPlaneProgram,"texture");
    glUniformMatrix4fv(mvLoc,1,GL_FALSE,glm::value_ptr(mvMat));
    glUniformMatrix4fv(projLoc,1,GL_FALSE,glm::value_ptr(pMat));
    glBindBuffer(GL_ARRAY_BUFFER,vbo[1]);
    glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,0);
    glBindBuffer(GL_ARRAY_BUFFER,vbo[2]);
    glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,0,0);
    glBindTexture(GL_TEXTURE_2D,cupYayTex);
    
    glDrawArrays(GL_TRIANGLES,0,6);
}

int main(void) {
    if (!glfwInit()) { exit(EXIT_FAILURE); }
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    GLFWwindow* window = glfwCreateWindow(1920, 1080, "Hello Cubes!", NULL, NULL);
    glfwMakeContextCurrent(window);
    if (glewInit() != GLEW_OK) { exit(EXIT_FAILURE); }
    glfwSwapInterval(1);

    init(window);

    while (!glfwWindowShouldClose(window)) {
        display(window,glfwGetTime());
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwDestroyWindow(window);
    glfwTerminate();
    exit(EXIT_SUCCESS);
}