//
//  Mesh.cpp
//  prog2
//
//  Created by Neil Killeen on 5/15/13.
//  Copyright (c) 2013 Neil Killeen. All rights reserved.
//

#include "Mesh.h"
#include "Angel.h"
#include <string>
#include <fstream>
#include <sstream>

using namespace Angel;


void Mesh::vRotate(GLfloat x, GLfloat y, GLfloat z) {
    for (auto itor = allVertices.begin(); itor != allVertices.end(); ++itor) 
        *itor = RotateX(x) * RotateY(y) * RotateZ(z) * (*itor);
}

void Mesh::vScale(GLfloat x, GLfloat y, GLfloat z) {
    for (auto itor = allVertices.begin(); itor != allVertices.end(); ++itor)
        *itor = Scale(x, y, z) * (*itor);
}

void Mesh::vTranslate(GLfloat x, GLfloat y, GLfloat z) {
    for (auto itor = allVertices.begin(); itor != allVertices.end(); ++itor)
        *itor = Translate(x, y, z) * (*itor);
}

void Mesh::mRotate(GLfloat x, GLfloat y, GLfloat z) {
    m_view *= RotateX(x) * RotateY(y) * RotateZ(z);
    m_pitch += x;
    m_yaw += y;
    m_roll += z;
}

void Mesh::mScale(GLfloat x, GLfloat y, GLfloat z) {
    m_view *= Scale(x, y, z);
}

void Mesh::mTranslate(GLfloat x, GLfloat y, GLfloat z) {
    m_view *= RotateX(-m_pitch) * RotateY(-m_yaw) * RotateZ(-m_roll);
    m_view *= Translate(x, y, z);
    m_view *= RotateX(m_pitch) * RotateY(m_yaw) * RotateZ(m_roll);
    m_center += vec3(x, y, z);
}


Mesh::Mesh() : m_color(vec4(0.0, 0.0, 1.0, 1.0)), m_sel_color(vec4(1.0, 0.0, 0.0, 1.0)),
                m_pitch(0.0), m_yaw(0.0), m_roll(0.0) {
    
}

void Mesh::draw(const GLuint &vPos, const GLuint &vNormal, GLuint &ModelView, int SEL_ID, bool solid) {
    glUniformMatrix4fv( ModelView, 1, GL_TRUE, m_view );
    
    // Create and initialize a buffer object
    
    glBufferData( GL_ARRAY_BUFFER, allVertices.size()*sizeof(vec4) + allNormals.size()*sizeof(vec3),
                 NULL, GL_DYNAMIC_DRAW);
    glBufferSubData(GL_ARRAY_BUFFER, 0, allVertices.size()*sizeof(vec4), allVertices.data());
    if (!allNormals.empty())
        glBufferSubData(GL_ARRAY_BUFFER, allVertices.size()*sizeof(vec4),
                        allNormals.size()*sizeof(vec3), allNormals.data());
    glVertexAttribPointer(vPos, 4, GL_FLOAT, GL_TRUE, 0, BUFFER_OFFSET(0) );
    if (!allNormals.empty())
        glVertexAttribPointer(vNormal, 3, GL_FLOAT, GL_FALSE, 0,
                          BUFFER_OFFSET(allVertices.size()*sizeof(vec4)));
    
	glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
    glStencilFunc(GL_ALWAYS, SEL_ID + 1, -1);
    
    for (int i = 0; i < allPolys.size(); ++i)
        glDrawElements((solid) ? GL_TRIANGLE_FAN : GL_LINE_LOOP, GLsizei(allPolys[i].indices.size()),
                       GL_UNSIGNED_INT, allPolys[i].indices.data());
}

// Bounding Volume

/*
void Mesh::drawboundingBox() {
    if (allVertices.empty())
        return;
    double xmin, ymin, zmin, xmax, ymax, zmax;
    xmin = ymin = zmin = xmax = ymax = zmax = 0.0;
    for (auto itor = allVertices.begin(); itor != allVertices.end(); ++itor) {
        if (itor->x < xmin)
            xmin = itor->x;
        else if (itor->x > xmax)
            xmax = itor->x;
        if (itor->y < ymin)
            ymin = itor->y;
        else if (itor->y > ymax)
            ymax = itor->y;
        if (itor->z < zmin)
            zmin = itor->z;
        else if (itor->z > zmax)
            zmax = itor->z;
    }
    
    //        glScalef(xmax-xmin, ymax-ymin, zmax-zmin);
    //        glMatrixMode(GL_MODELVIEW);
    //        glLoadIdentity();
    //        glPushMatrix();
    //            glColor3f(0.05, 0.0375, 0.025);
    //            glScalef(10.0, 10.0, 10.0);
    //            glutWireCube(1.0);
    //        glPopMatrix();
    glPushMatrix();
    glColor3f(1.0, 1.0, 1.0);
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE_LOOP);
    glBegin(GL_LINE_LOOP); // Start drawing a quad primitive
    glVertex3f(xmin, ymin, zmin);
    glVertex3f(xmax, ymin, zmin);
    glVertex3f(xmax, ymax, zmin);
    glVertex3f(xmin, ymax, zmin);
    glEnd();
    
    glBegin(GL_LINE_LOOP);
    glVertex3f(xmin, ymin, zmax);
    glVertex3f(xmax, ymin, zmax);
    glVertex3f(xmax, ymax, zmax);
    glVertex3f(xmin, ymax, zmax);
    glEnd();
    
    glBegin(GL_LINE_STRIP);
    glVertex3f(xmin, ymin, zmin);
    glVertex3f(xmin, ymin, zmax);
    glEnd();
    glBegin(GL_LINE_STRIP);
    glVertex3f(xmax, ymin, zmin);
    glVertex3f(xmax, ymin, zmax);
    glEnd();
    glBegin(GL_LINE_STRIP);
    glVertex3f(xmin, ymax, zmin);
    glVertex3f(xmin, ymax, zmax);
    glEnd();
    glBegin(GL_LINE_STRIP);
    glVertex3f(xmax, ymax, zmin);
    glVertex3f(xmax, ymax, zmax);
    glEnd();
    glPopMatrix();
}
 */

// Normals

void Mesh::addNormal(const point3& pt1, const point3& pt2, const point3& pt3, const vec3& normal) {
    VertexMap[pt1].push_back(normal);
    VertexMap[pt2].push_back(normal);
    VertexMap[pt3].push_back(normal);
}

void Mesh::addVertex(const point3& point) {
    addVertex(point4(point, 1.0));
}
void Mesh::addVertex(const point4& point) {
    allVertices.push_back(point);
}

vec3 Mesh::calcNormal(const point3& vertex) const {
    vec3 result;
    if (VertexMap.find(vertex) == VertexMap.end())
        return vec3();
    for(auto itor = VertexMap.find(vertex)->second.begin();
        itor != VertexMap.find(vertex)->second.end(); ++itor)
        result += *itor;
    return normalize(result);
}

// Model input

void Mesh::calcNormals() {
    for (auto itor = allVertices.begin(); itor != allVertices.end(); ++itor)
        allNormals.push_back(calcNormal(vec3(itor->x, itor->y, itor->z)));
}

void Mesh::readVertices(const char *filename) {
    std::ifstream myfile(filename);
    std::string line;
    int temp;
    char comma;
    if (!myfile) {
        std::cout << filename << " does not exist\n";
        return;
    }
    int lineCount = 0;
    float coord[3];
    getline(myfile, line);
    std::istringstream iss(line);
    iss >> lineCount;
    for (int i = 0; getline(myfile, line) && i < lineCount; ++i) {
//        std::cout << line << std::endl;
        std::istringstream iss(line);
        iss >> temp;
        iss >> comma;
        iss >> coord[0];
        iss >> comma;
        iss >> coord[1];
        iss >> comma;
        iss >> coord[2];
        //std::cout << coord[0] << "," << coord[1] << "," << coord[2] << std::endl;
        allVertices.push_back(point4(GLfloat(coord[0]), GLfloat(coord[1]),
                                     GLfloat(coord[2]), 1.0));
    }
}

void Mesh::readPolygons(const char *filename) { // Must run readVertices beforehand
    if (allVertices.empty())
        return;
    std::ifstream myfile(filename);
    std::string line;
    int poly;
    if (!myfile) {
        std::cout << filename << " does not exist\n";
        return;
    }
    int lineCount = 0;
    getline(myfile, line);
    std::istringstream iss(line);
    iss >> lineCount;
    for (int i = 0; getline(myfile, line) && i < lineCount; ++i) {
        line = line.substr(line.find(' ')+1);
        std::istringstream iss(line);
        int size = 0;
        polygon newPoly;
        for (;iss >> poly; ++size)
            newPoly.indices.push_back(poly-1);
        if (newPoly.indices.size() < 3)
            continue;
        
        // Calc normal and store
        vec3 a(allVertices[newPoly.indices[0]].x, allVertices[newPoly.indices[0]].y,
               allVertices[newPoly.indices[0]].z);
        vec3 b(allVertices[newPoly.indices[1]].x, allVertices[newPoly.indices[1]].y,
               allVertices[newPoly.indices[1]].z);
        vec3 c(allVertices[newPoly.indices[2]].x, allVertices[newPoly.indices[2]].y,
               allVertices[newPoly.indices[2]].z);
        
        vec3 normal = cross(b-a, c-a);
        newPoly.normal = normal;
        for (auto itor = newPoly.indices.begin(); itor != newPoly.indices.end(); ++itor)
            VertexMap[vec3(allVertices[*itor].x, allVertices[*itor].y, allVertices[*itor].z)].push_back(normal);
        
        allPolys.push_back(newPoly);
    }
}
