#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>
#include "Sphere.h"
#include "Torus.h"
#include "ImportedModel.h"

#define numVAOs 1
#define numVBOs 11

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;

Sphere sphere(48);
Torus torus(1.9,0.4,48);
ImportedModel shuttle("shuttle.obj");

void setupVertices(void) {
    glGenVertexArrays(1, vao);
    glBindVertexArray(vao[0]);
    glGenBuffers(numVBOs, vbo);
   
    std::vector<int>indices=sphere.getIndices();
    std::vector<glm::vec3>vertices=sphere.getVertices();
    std::vector<glm::vec2>uvs=sphere.getTexCoords();
    std::vector<glm::vec3>normals=sphere.getNormals();

    std::vector<float>pvalues;
    std::vector<float>tvalues;
    std::vector<float>nvalues;
    for(int i=0;i<sphere.getNumVertices();i++){
        pvalues.push_back(vertices[i].x);
        pvalues.push_back(vertices[i].y);
        pvalues.push_back(vertices[i].z);
        tvalues.push_back(uvs[i].s);
        tvalues.push_back(uvs[i].t);
        nvalues.push_back(normals[i].x);
        nvalues.push_back(normals[i].y);
        nvalues.push_back(normals[i].z);
    }

    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
    glBufferData(GL_ARRAY_BUFFER, pvalues.size()*sizeof(float), pvalues.data(), GL_STATIC_DRAW);

    glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
    glBufferData(GL_ARRAY_BUFFER, tvalues.size()*sizeof(float), tvalues.data(),GL_STATIC_DRAW);

    glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
    glBufferData(GL_ARRAY_BUFFER, nvalues.size()*sizeof(float), nvalues.data(), GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo[3]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size()*sizeof(float), indices.data(), GL_STATIC_DRAW);

    indices=torus.getIndices();
    vertices=torus.getVertices();
    uvs=torus.getTexCoords();
    normals=torus.getNormals();

    pvalues.clear();
    tvalues.clear();
    nvalues.clear();
    for(int i=0;i<torus.getNumVertices();i++){
        pvalues.push_back(vertices[i].x);
        pvalues.push_back(vertices[i].y);
        pvalues.push_back(vertices[i].z);
        tvalues.push_back(uvs[i].s);
        tvalues.push_back(uvs[i].t);
        nvalues.push_back(normals[i].x);
        nvalues.push_back(normals[i].y);
        nvalues.push_back(normals[i].z);
    }

    glBindBuffer(GL_ARRAY_BUFFER, vbo[4]);
    glBufferData(GL_ARRAY_BUFFER, pvalues.size()*sizeof(float), pvalues.data(), GL_STATIC_DRAW);

    glBindBuffer(GL_ARRAY_BUFFER, vbo[5]);
    glBufferData(GL_ARRAY_BUFFER, tvalues.size()*sizeof(float), tvalues.data(),GL_STATIC_DRAW);

    glBindBuffer(GL_ARRAY_BUFFER, vbo[6]);
    glBufferData(GL_ARRAY_BUFFER, nvalues.size()*sizeof(float), nvalues.data(), GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo[7]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size()*sizeof(float), indices.data(), GL_STATIC_DRAW);

    vertices=shuttle.getVertices();
    uvs=shuttle.getTextureCoords();
    normals=shuttle.getNormals();

    pvalues.clear();
    tvalues.clear();
    nvalues.clear();
    for(int i=0;i<shuttle.getNumVertices();i++){
        pvalues.push_back(vertices[i].x);
        pvalues.push_back(vertices[i].y);
        pvalues.push_back(vertices[i].z);
        tvalues.push_back(uvs[i].s);
        tvalues.push_back(uvs[i].t);
        nvalues.push_back(normals[i].x);
        nvalues.push_back(normals[i].y);
        nvalues.push_back(normals[i].z);
    }

    glBindBuffer(GL_ARRAY_BUFFER, vbo[7]);
    glBufferData(GL_ARRAY_BUFFER, pvalues.size()*sizeof(float), pvalues.data(), GL_STATIC_DRAW);

    glBindBuffer(GL_ARRAY_BUFFER, vbo[8]);
    glBufferData(GL_ARRAY_BUFFER, tvalues.size()*sizeof(float), tvalues.data(),GL_STATIC_DRAW);

    glBindBuffer(GL_ARRAY_BUFFER, vbo[9]);
    glBufferData(GL_ARRAY_BUFFER, nvalues.size()*sizeof(float), nvalues.data(), GL_STATIC_DRAW);
}

void setupTextures(){ 
    pyrTex=utils::loadTexture("spstob_1.jpg");
    glActiveTexture(GL_TEXTURE0);
    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,5.0f,-10.0f};
    setupVertices();
    setupTextures();
}

void DrawSphere(){
    glUniformMatrix4fv(mvLoc,1,GL_FALSE,glm::value_ptr(mvMat));
    glUniformMatrix4fv(projLoc,1,GL_FALSE,glm::value_ptr(pMat));
    glBindBuffer(GL_ARRAY_BUFFER,vbo[0]);
    glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,0);
    glBindBuffer(GL_ARRAY_BUFFER,vbo[1]);
    glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,0,0);
    glBindBuffer(GL_ARRAY_BUFFER,vbo[2]);
    glVertexAttribPointer(2,3,GL_FLOAT,GL_FALSE,0,0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,vbo[3]);

    glDrawElements(GL_TRIANGLES,sphere.getNumIndices(),GL_UNSIGNED_INT,0);
}

void DrawTorus(){
    glUniformMatrix4fv(mvLoc,1,GL_FALSE,glm::value_ptr(mvMat));
    glUniformMatrix4fv(projLoc,1,GL_FALSE,glm::value_ptr(pMat));
    glBindBuffer(GL_ARRAY_BUFFER,vbo[4]);
    glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,0);
    glBindBuffer(GL_ARRAY_BUFFER,vbo[5]);
    glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,0,0);
    glBindBuffer(GL_ARRAY_BUFFER,vbo[6]);
    glVertexAttribPointer(2,3,GL_FLOAT,GL_FALSE,0,0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,vbo[7]);

    glDrawElements(GL_TRIANGLES,torus.getNumIndices(),GL_UNSIGNED_INT,0);
}

void DrawShuttle(){
    glUniformMatrix4fv(mvLoc,1,GL_FALSE,glm::value_ptr(mvMat));
    glUniformMatrix4fv(projLoc,1,GL_FALSE,glm::value_ptr(pMat));
    glBindBuffer(GL_ARRAY_BUFFER,vbo[7]);
    glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,0);
    glBindBuffer(GL_ARRAY_BUFFER,vbo[8]);
    glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,0,0);
    glBindBuffer(GL_ARRAY_BUFFER,vbo[9]);
    glVertexAttribPointer(2,3,GL_FLOAT,GL_FALSE,0,0);

    glDrawArrays(GL_TRIANGLES,0,shuttle.getNumVertices());
}

double lastTime=0;

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

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

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

    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});
    vMat=glm::lookAt(glm::vec3{camera.x,camera.y,camera.z},{0,-3,0},{0,1,0});
    
    mMat=glm::translate(glm::mat4(1.0f),{0.f,0.f,0.f});
    mMat=glm::rotate(mMat,float(currentTime/4),{0.f,1.f,-0.2f});
    mMat=glm::scale(mMat,{8,8,8});
    mvMat=vMat*mMat;

    //DrawSphere();

    DrawShuttle();
    
    mMat=glm::translate(glm::mat4(1.0f),{0.f,-3.f,0.f});
    mMat=glm::rotate(mMat,float(currentTime/4),{0.f,1.f,0.4f});
    mMat=glm::scale(mMat,{3,0.1,3});
    mvMat=vMat*mMat;
    
    //DrawTorus();
}

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