#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 4

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

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

GLuint pyrTex;

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 pyrTexCoords[36] =
    {  0.0f, 0.0f, 1.0f, 0.0f, 0.5f, 1.0f,  0.0f, 0.0f, 1.0f, 0.0f, 0.5f, 1.0f,    // top and right faces
        0.0f, 0.0f, 1.0f, 0.0f, 0.5f, 1.0f,  0.0f, 0.0f, 1.0f, 0.0f, 0.5f, 1.0f,    // back and left faces
        0.0f, 0.0f, 200.0f, 200.0f, 0.0f, 200.0f,  200.0f, 200.0f, 0.0f, 0.0f, 200.0f, 0.0f };  // base triangles
   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[2]);
   glBufferData(GL_ARRAY_BUFFER, sizeof(pyramidPositions), pyramidPositions, GL_STATIC_DRAW);

   glBindBuffer(GL_ARRAY_BUFFER, vbo[3]);
   glBufferData(GL_ARRAY_BUFFER, sizeof(pyrTexCoords), pyrTexCoords, GL_STATIC_DRAW);
}

void setupTextures(){ 
    pyrTex=utils::loadTexture("brick1.jpg");
    glBindTexture(GL_TEXTURE_2D,pyrTex);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
    glGenerateMipmap(GL_TEXTURE_2D);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}

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

double lastTime=0;

void DrawCube(){
    glUniformMatrix4fv(mvLoc,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 DrawTexturedSquarePyramid(){

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

    glUniformMatrix4fv(mvLoc,1,GL_FALSE,glm::value_ptr(mMat));
    glUniformMatrix4fv(projLoc,1,GL_FALSE,glm::value_ptr(pMat));
    glBindBuffer(GL_ARRAY_BUFFER,vbo[2]);
    glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,0);
    glBindBuffer(GL_ARRAY_BUFFER,vbo[3]);
    glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,0,0);

    glDrawArrays(GL_TRIANGLES,0,18);
}

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);

    mvLoc=glGetUniformLocation(renderingProgram,"mv_matrix");
    projLoc=glGetUniformLocation(renderingProgram,"proj_matrix");

    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::scale(transforms.top(),{200.f,1.f,200.f});
    mMat=glm::translate(mMat,{0.f,0.f,0.f});
    transforms.push(mMat);
    mMat=glm::rotate(mMat,float(utils::degToRad(90)*currentTime/100),{0.f,1.f,0.f});
    mMat=glm::rotate(mMat,float(utils::degToRad(12)),{0.f,0.f,1.f});
    DrawTexturedSquarePyramid();
    mMat=glm::translate(transforms.top(),{0.f,0.f,0.f});
    mMat=glm::rotate(mMat,float(utils::degToRad(90)*currentTime/100),{0.f,1.f,0.f});
    mMat=glm::rotate(mMat,float(utils::degToRad(192)),{0.f,0.f,1.f});
    mMat=glm::translate(mMat,{0.f,4.f,0.f});
    mMat=glm::rotate(mMat,float(utils::degToRad(270)),{0.f,1.f,0.f});
    DrawTexturedSquarePyramid();

    mMat=glm::translate(transforms.top(),{sin(currentTime*0.5)*6,sin(currentTime*0.5)*0.8-1,cos(currentTime*0.5)*6});
    transforms.push(mMat);
    mMat=glm::rotate(mMat,float(utils::degToRad(24)-28),{0,0,1});
    mMat=glm::rotate(mMat,float(utils::degToRad(24)*currentTime+28),{0,1,0});
    //DrawCube();

    mMat=glm::rotate(transforms.top(),utils::degToRad(90),{1,0,0});
    mMat=glm::translate(mMat,{sin(currentTime*3)*3,0,cos(currentTime*3)*3});
    mMat=glm::scale(mMat,{0.3,0.3,0.3});
    mMat=glm::rotate(mMat,float(-utils::degToRad(220)*currentTime+4),{0,1,0});
    //DrawCube();

    mMat=glm::rotate(transforms.top(),utils::degToRad(90),{1,1,0});
    mMat=glm::translate(mMat,{sin(currentTime*3)*3,0,cos(currentTime*3)*3});
    mMat=glm::scale(mMat,{0.3,0.3,0.3});
    mMat=glm::rotate(mMat,float(utils::degToRad(120)*currentTime),{0,1,0});
    //DrawCube();
}

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, "Texture Mapping!", NULL, NULL);
    glfwSetWindowPos(window,1400,180);
    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);
}