#include "cStaticTriangleMesh.h"
 
#include "cMS3DFileLoader.h"
 
cStaticTriangleMesh::~cStaticTriangleMesh()
{
    glDeleteLists(displayListID_, 1);
    
    for(int i = 0; i < numVertices_; ++i)
        delete vertices_[i];
        
    for(int i = 0; i < numTriangles_; ++i)
        delete triangles_[i];
        
    for(int i = 0; i < numEdges_; ++i)
        delete edges_[i];
        
    for(int i = 0; i < textures_.size(); ++i)
        delete textures_[i];
}

void cStaticTriangleMesh::draw()
{ 
    // without this, things are drawn tinted in the color of the ball
    glColor3f(1,1,1); 
    glCallList(displayListID_);
}

void cStaticTriangleMesh::generateDisplayList()
{
    displayListID_ = glGenLists(1);
    
    glNewList(displayListID_, GL_COMPILE);
    
    // draw all triangles with the same texture at the same time
    for( int i = 0; i < textures_.size(); ++i)
    {
        textures_[i]->bindGLTexture();
        
        for( int j = 0; j < textures_[i]->trianglesWithThisTexture_.size(); ++j)
        {
            textures_[i]->trianglesWithThisTexture_[j]->draw();
        }
            
        textures_[i]->unbindGLTexture();
    }
    
    //octree_.draw();
    
   // End the list creation.
   glEndList();

}

void cStaticTriangleMesh::preprocessMesh()
{
    for(int i = 0; i < numTriangles_; ++i)
    {
        if(i%100 == 0) cGame::instance()->updateParticleSystem();
        triangles_[i]->computeNormal();
        triangles_[i]->standardizeVertices();
        triangles_[i]->addToTextureList(textures_);
    }
	
    for(int i = 0; i < numEdges_; ++i)
    {
        if(i%100 == 0) cGame::instance()->updateParticleSystem();
        edges_[i]->standardizeEdge();
    }
    
    for(int i = 0; i < numVertices_; ++i)
    {
        if(i%100 == 0) cGame::instance()->updateParticleSystem();
        vertices_[i]->computeVertexNormal();
    }
}

cVertex* cStaticTriangleMesh::addVertexIfDoesntExist(cVector& pos)
{
    cVertex* p = vertexMap[pos];
    if(p == NULL)
    {
        ++numVertices_;
        cVertex *v = new cVertex(pos);        vertices_.push_back(v);
        p = v;
        vertexMap[pos] = p;
    }
    return p;
}

// it seems almost impossible to create files that dont break this in milkshape,
// so as a dirty hack to make this work, we will just duplicate every edge.
cEdge* cStaticTriangleMesh::addEdgeIfDoesntExist(cEdge& edge)
{
    cEdge* p = edgeMap[edge];
    
    cEdge reversed = cEdge(edge.v1_, edge.v0_);
    cEdge* q = edgeMap[reversed];
    if(p == NULL)// && q == NULL)
    {
        ++numEdges_;
        cEdge *e = new cEdge(edge);
        edges_.push_back(e);
        //edgeMap[edge] = e;
        return e;
    }
    else if(p == NULL && q != NULL) return q;
    
    return p;
}


cStaticTriangleMesh::cStaticTriangleMesh(string filename, int level)
{
    numVertices_ = 0;
    numTriangles_ = 0;
    numEdges_ = 0;
    
    // lets see if ms3dloader crashes
    cMS3DFileLoader ms3dfl;
    ms3dfl.load(filename.c_str());
    ms3dfl.buildStaticTriangleMesh(*this, level);
    
    /*
    // load a scene
    cout << "loading file" << endl;
    loadTextFile("test.txt");
    
    // some phoney texture loadishness
    cTextureImage *grassImage = new cTextureImage();
    grassImage->loadTGA("grass.tga");
    for(int i = 0; i < numTriangles_; ++i)
        grassImage->trianglesWithThisTexture_.push_back(triangles_[i]);
    textures_.push_back(grassImage);
    triangles_[0]->v0TC = cTextureCoord(0,0);
    triangles_[0]->v1TC = cTextureCoord(0,1);
    triangles_[0]->v2TC = cTextureCoord(1,0);
    
    triangles_[1]->v0TC = cTextureCoord(1,0);
    triangles_[1]->v1TC = cTextureCoord(0,1);
    triangles_[1]->v2TC = cTextureCoord(1,1);
    */
    
    // clean up after adding triangles
    vertexMap.clear();
    edgeMap.clear();
    
    // standardize triangles and edges, compute normals
    cout << "preprocessing mesh" << endl;
    preprocessMesh();
    
    // build octree
    cout << "building octree" << endl;
    float maxDist = 1000;
    cBoundingBox worldBox(-maxDist, maxDist, -maxDist, maxDist, -maxDist, maxDist);
    cGame::instance()->octreeProgress_ = 0;
    octree_.build(triangles_, worldBox);
    
    // precompile opengl display list
    cout << "generating display list" << endl;
    generateDisplayList();
}

void cStaticTriangleMesh::buildCollisionLists(cVector start, cVector end, float radius, 
        vector<cTriangle*>& tlist, vector<cVertex*>& vlist, vector<cEdge*>& elist)
{
    // compute a bounding box that contains 
    // the sphere throughout its entire path
    cVector path = end - start;
    float pathLength = path.length();
    float halfWidth = pathLength / 2.0 + radius;
    float xAvg = (start.x_ + end.x_) / 2.0;
    float yAvg = (start.y_ + end.y_) / 2.0;
    float zAvg = (start.z_ + end.z_) / 2.0;
    
    cBoundingBox sphereBound(	xAvg - halfWidth, xAvg + halfWidth, 
                                yAvg - halfWidth, yAvg + halfWidth,
                                zAvg - halfWidth, zAvg + halfWidth);
    
    octree_.buildCollisionList(tlist, sphereBound);
    
    for(unsigned int i = 0; i < tlist.size(); ++i)
    {
        // vertices
        if(!tlist[i]->v0_->hasBeenAddedToCollisionList_)
        {
            vlist.push_back(tlist[i]->v0_);
            tlist[i]->v0_->hasBeenAddedToCollisionList_ = true;
        }
        
        if(!tlist[i]->v1_->hasBeenAddedToCollisionList_)
        {
            vlist.push_back(tlist[i]->v1_);
            tlist[i]->v1_->hasBeenAddedToCollisionList_ = true;
        }
        
        if(!tlist[i]->v2_->hasBeenAddedToCollisionList_)
        {
            vlist.push_back(tlist[i]->v2_);
            tlist[i]->v2_->hasBeenAddedToCollisionList_ = true;
        }
        
        // edges
        if(!tlist[i]->e0_->hasBeenAddedToCollisionList_)
        {
            elist.push_back(tlist[i]->e0_);
            tlist[i]->e0_->hasBeenAddedToCollisionList_ = true;
        }
        
        if(!tlist[i]->e1_->hasBeenAddedToCollisionList_)
        {
            elist.push_back(tlist[i]->e1_);
            tlist[i]->e1_->hasBeenAddedToCollisionList_ = true;
        }
        
        if(!tlist[i]->e2_->hasBeenAddedToCollisionList_)
        {
            elist.push_back(tlist[i]->e2_);
            tlist[i]->e2_->hasBeenAddedToCollisionList_ = true;
        }
    }
    
    // reset flags, note that triangle flags were set by octree
    for(unsigned int i = 0; i < tlist.size(); ++i)
        tlist[i]->hasBeenAddedToCollisionList_ = false;
    
    for(unsigned int i = 0; i < vlist.size(); ++i)
        vlist[i]->hasBeenAddedToCollisionList_ = false;
    
    for(unsigned int i = 0; i < elist.size(); ++i)
        elist[i]->hasBeenAddedToCollisionList_ = false;
        
    //cout << "collision list contains: " << tlist.size() << " triangles, " << vlist.size() << " vertices, " << elist.size() << " edges" << endl;

}


int cStaticTriangleMesh::addTexture(char* filename, bool envMap)
{
    cTextureImage* image = new cTextureImage();
    image->loadTGA(filename, envMap);
    textures_.push_back(image);
    return textures_.size() - 1;
}

int cStaticTriangleMesh::addVertex(float x, float y, float z, float u, float v)
{
    ++numVertices_;
    cVertex* vert = new cVertex(x, y, z, u, v);
    vertices_.push_back(vert);
    return vertices_.size() - 1;
}

int cStaticTriangleMesh::addTriangle(int v0, int v1, int v2, int materialIndex)
{
    ++numTriangles_;
    cTriangle* newTriangle = new cTriangle();
    newTriangle->materialIndex_ = materialIndex;
    
    cVertex* pV0 = vertices_[v0];
    cVertex* pV1 = vertices_[v1];
    cVertex* pV2 = vertices_[v2];
    
    cEdge e0 = cEdge(pV0,pV1);
    cEdge e1 = cEdge(pV1,pV2);
    cEdge e2 = cEdge(pV2,pV0);
    
    cEdge* pe0 = addEdgeIfDoesntExist(e0);
    cEdge* pe1 = addEdgeIfDoesntExist(e1);
    cEdge* pe2 = addEdgeIfDoesntExist(e2);
    
    newTriangle->v0_ = pV0;
    newTriangle->v1_ = pV1;
    newTriangle->v2_ = pV2;
    
    newTriangle->e0_ = pe0;
    newTriangle->e1_ = pe1;
    newTriangle->e2_ = pe2;
    
    pV0->numFaces_++; pV1->numFaces_++; pV2->numFaces_++;
    pV0->triangles_.push_back(newTriangle);
    pV1->triangles_.push_back(newTriangle);
    pV2->triangles_.push_back(newTriangle);
    
    pe0->setAvailableFace(newTriangle);
    pe1->setAvailableFace(newTriangle);
    pe2->setAvailableFace(newTriangle);
    
    triangles_.push_back(newTriangle);
}