#include "ofxSurfaceManager.h"

ofxSurfaceManager::ofxSurfaceManager()
{
    
}

ofxSurfaceManager::~ofxSurfaceManager()
{
    clear();
}

void ofxSurfaceManager::draw()
{
    for ( int i=0; i<surfaces.size(); i++ ) {
        surfaces[i]->draw();
    }
}

void ofxSurfaceManager::addSurface(int surfaceType)
{
    if ( surfaceType == ofxSurfaceType::TRIANGLE_SURFACE ) {
        surfaces.push_back( new ofxTriangleSurface() );
    } 
    else if (surfaceType == ofxSurfaceType::QUAD_SURFACE ) {
        surfaces.push_back( new ofxQuadSurface() );
    }
    else {
        throw std::runtime_error("Attempt to add non-existing surface type.");
    }
}

void ofxSurfaceManager::addSurface(int surfaceType, ofTexture* texturePtr)
{
    if ( surfaceType == ofxSurfaceType::TRIANGLE_SURFACE ) {
        surfaces.push_back( new ofxTriangleSurface() );
        surfaces.back()->setTexture(texturePtr);
    } 
    else if (surfaceType == ofxSurfaceType::QUAD_SURFACE ) {
        surfaces.push_back( new ofxQuadSurface() );
        surfaces.back()->setTexture(texturePtr);
    }
    else {
        throw std::runtime_error("Attempt to add non-existing surface type.");
    }
}

void ofxSurfaceManager::addSurface(int surfaceType, vector<ofVec2f> vertices, vector<ofVec2f> texCoords)
{
    if ( surfaceType == ofxSurfaceType::TRIANGLE_SURFACE ) {
        
        if ( vertices.size() < 3 ) {
            throw std::runtime_error("There must be 3 vertices for a triangle surface.");
        } else if (texCoords.size() < 3) {
            throw std::runtime_error("There must be 3 texture coordinates for a triangle surface.");
        }
        
        surfaces.push_back( new ofxTriangleSurface() );
        
        for ( int i=0; i<3; i++ ) {
            surfaces.back()->setVertex(i, vertices[i]);
            surfaces.back()->setTexCoord(i, texCoords[i]);
        }
        
    } 
    else if (surfaceType == ofxSurfaceType::QUAD_SURFACE ) {
        if ( vertices.size() < 4 ) {
            throw std::runtime_error("There must be 4 vertices for a quad surface.");
        } else if (texCoords.size() < 4) {
            throw std::runtime_error("There must be 4 texture coordinates for a quad surface.");
        }
        
        surfaces.push_back( new ofxQuadSurface() );
        
        for ( int i=0; i<4; i++ ) {
            surfaces.back()->setVertex(i, vertices[i]);
            surfaces.back()->setTexCoord(i, texCoords[i]);
        }
    }
    else {
        throw std::runtime_error("Attempt to add non-existing surface type.");
    }

}

void ofxSurfaceManager::addSurface(int surfaceType, ofTexture* texturePtr, vector<ofVec2f> vertices, vector<ofVec2f> texCoords)
{
    if ( surfaceType == ofxSurfaceType::TRIANGLE_SURFACE ) {
        
        if ( vertices.size() < 3 ) {
            throw std::runtime_error("There must be 3 vertices for a triangle surface.");
        } else if (texCoords.size() < 3) {
            throw std::runtime_error("Thre must be 3 texture coordinates for a triangle surface.");
        }
        
        surfaces.push_back( new ofxTriangleSurface() );
        surfaces.back()->setTexture(texturePtr);
        
        for ( int i=0; i<3; i++ ) {
            surfaces.back()->setVertex(i, vertices[i]);
            surfaces.back()->setTexCoord(i, texCoords[i]);
        }
        
    } 
    else if (surfaceType == ofxSurfaceType::QUAD_SURFACE ) {
        if ( vertices.size() < 4 ) {
            throw std::runtime_error("There must be 4 vertices for a quad surface.");
        } else if (texCoords.size() < 4) {
            throw std::runtime_error("Thre must be 4 texture coordinates for a quad surface.");
        }
        
        surfaces.push_back( new ofxQuadSurface() );
        surfaces.back()->setTexture(texturePtr);
        
        for ( int i=0; i<4; i++ ) {
            surfaces.back()->setVertex(i, vertices[i]);
            surfaces.back()->setTexCoord(i, texCoords[i]);
        }
    }
    else {
        throw std::runtime_error("Attempt to add non-existing surface type.");
    }
}

void ofxSurfaceManager::removeSelectedSurface()
{
    if ( selectedSurface == NULL ) return;
    
    for ( int i=0; i<surfaces.size(); i++ ) {
        if ( surfaces[i] == selectedSurface ) {
            delete surfaces[i];
            surfaces.erase(surfaces.begin()+i);
            selectedSurface = NULL;
            break;
        }
    }
}

void ofxSurfaceManager::manageMemory()
{
    // check if each of the sources is assigned to a surface or not
    for ( int i=0; i<loadedImageSources.size(); i++ ) {
        bool bAssigned = false;
        
        for ( int j=0; j<surfaces.size(); j++ ) {
            if ( surfaces[j]->getTexture() == &loadedImageSources[i]->getTextureReference() ) {
                bAssigned = true;
                break;
            }
        }
        
        if ( !bAssigned ) {
            // purge the image source from memory
            delete loadedImageSources[i];
            loadedImageSources.erase(loadedImageSources.begin()+i);
            cout << "Deleting image source: " << loadedImageSourceNames[i] << endl;
            loadedImageSourceNames.erase(loadedImageSourceNames.begin()+i);
            i--;
        }
    }
}

void ofxSurfaceManager::clear()
{
    // delete all extra allocations from the heap
    while ( surfaces.size() ) {
        delete surfaces.back();
        surfaces.pop_back();
    }
    
    while ( loadedImageSources.size() ) {
        delete loadedImageSources.back();
        loadedImageSources.pop_back();
    }
    
    while ( loadedImageSourceNames.size() ) {
        loadedImageSourceNames.pop_back();
    }
}

// String getTypeString(ofxSurfaceType e)
// {
//   switch e
//   {
//       case TRINAGLE_SURFACE: return "TRINAGLE_SURFACE";
//       case QUAD_SURFACE: return "QUAD_SURFACE";
//       default: throw Exception("Bad MyEnum");
//   }
// }

void ofxSurfaceManager::saveXmlSettings(string fileName)
{
    xmlSettings.clear();
    
    // save surfaces
    xmlSettings.addTag("surfaces");
    xmlSettings.pushTag("surfaces");
    for ( int i=0; i<surfaces.size(); i++ ) {
        
        xmlSettings.addTag("surface");
        xmlSettings.pushTag("surface", i);
        ofxBaseSurface* surface = surfaces[i];

        xmlSettings.addTag("vertices");
        xmlSettings.pushTag("vertices");
        vector<ofVec3f>* vertices = &surface->getVertices();
        for ( int j=0; j<vertices->size(); j++ ) {
            xmlSettings.addTag("vertex");
            xmlSettings.pushTag("vertex", j);
            ofVec3f* vertex = &(*vertices)[j];
            xmlSettings.addValue("x", vertex->x);
            xmlSettings.addValue("y", vertex->y);
            
            // we don't need z as it will be 0 anyways
            
            xmlSettings.popTag(); // vertex
        }
        xmlSettings.popTag(); // vertices
        
        xmlSettings.addTag("texCoords");
        xmlSettings.pushTag("texCoords");
        vector<ofVec2f>* texCoords = &surface->getTexCoords();
        for ( int j=0; j<texCoords->size(); j++ ) {
            xmlSettings.addTag("texCoord");
            xmlSettings.pushTag("texCoord", j);
            ofVec2f* texCoord = &(*texCoords)[j];
            xmlSettings.addValue("x", texCoord->x);
            xmlSettings.addValue("y", texCoord->y);
            xmlSettings.popTag(); // texCoord
        }
        xmlSettings.popTag(); // texCoords
        
        xmlSettings.addTag("source");
        xmlSettings.pushTag("source");
        
        xmlSettings.addValue("source-type", "image");
        xmlSettings.addValue("source-name", getSurfaceSourceName(surface));
        //xmlSettings.addValue("source-path", "/root/etc/image.jpg");
        xmlSettings.popTag(); // source


        // xmlSettings.addTag("type");
        // xmlSettings.pushTag("type");
        // // surfaceType == ofxSurfaceType::TRIANGLE_SURFACE
        // ofxSurfaceType surfaceType = &surface->getType();
        // xmlSettings.addValue("surface-type", surfaceType);
        // xmlSettings.popTag(); // type

        xmlSettings.popTag(); // surface
    }
    xmlSettings.popTag(); // surfaces
    
    xmlSettings.save(fileName);
}

void ofxSurfaceManager::loadXmlSettings(string fileName)
{


    if (!xmlSettings.loadFile(fileName)){
        ofLog(OF_LOG_WARNING, "Could not load XML settings.");
        return;
    }
    
    if (!xmlSettings.tagExists("surfaces")){
        ofLog(OF_LOG_WARNING, "XML settings is empty or has wrong markup.");
        return;
    }
    
    xmlSettings.pushTag("surfaces");
    
    int numSurfaces = xmlSettings.getNumTags("surface");
    for ( int i=0; i<numSurfaces; i++ ) {
        xmlSettings.pushTag("surface", i);
        // attempt to load surface source
        xmlSettings.pushTag("source");
        string sourceType = xmlSettings.getValue("source-type", "image");
        string sourceName = xmlSettings.getValue("source-name", "none");
        ofTexture* sourceTexture = NULL;
        if ( sourceName != "none" ) {
            stringstream ss;
            ss << "sources/images/" << sourceName; // TODO: reuse constants here
            sourceTexture = loadImageSource(sourceName, ss.str());
        }
        xmlSettings.popTag(); // source

        // // attempt to load surface type
        // ofLog(OF_LOG_WARNING, "Attempt to load surface type.");
        // xmlSettings.pushTag("type");
        // string surfaceType = xmlSettings.getValue("surface-type", "TRIANGLE_SURFACE");
        // xmlSettings.popTag(); // type


        xmlSettings.pushTag("vertices");
        vector<ofVec2f> vertices;

        int vertexCount = xmlSettings.getNumTags("vertex");
        

        //it's a triangle ?
        if (vertexCount == 3)
        {
            ofLog(OF_LOG_NOTICE, "create Triangle");
            xmlSettings.pushTag("vertex", 0);
            vertices.push_back( ofVec2f( xmlSettings.getValue("x", 0.0f), xmlSettings.getValue("y", 0.0f) ) );
            xmlSettings.popTag();
            
            xmlSettings.pushTag("vertex", 1);
            vertices.push_back( ofVec2f( xmlSettings.getValue("x", 100.0f), xmlSettings.getValue("y", 0.0f) ) );
            xmlSettings.popTag();
            
            xmlSettings.pushTag("vertex", 2);
            vertices.push_back( ofVec2f( xmlSettings.getValue("x", 0.0f), xmlSettings.getValue("y", 100.0f) ) );
            xmlSettings.popTag();
            
            xmlSettings.popTag(); // vertices
            
            xmlSettings.pushTag("texCoords");
            
            vector<ofVec2f> texCoords;
            
            xmlSettings.pushTag("texCoord", 0);
            texCoords.push_back( ofVec2f( xmlSettings.getValue("x", 0.0f), xmlSettings.getValue("y", 0.0f) ) );
            xmlSettings.popTag();
            
            xmlSettings.pushTag("texCoord", 1);
            texCoords.push_back( ofVec2f( xmlSettings.getValue("x", 1.0f), xmlSettings.getValue("y", 0.0f) ) );
            xmlSettings.popTag();
            
            xmlSettings.pushTag("texCoord", 2);
            texCoords.push_back( ofVec2f( xmlSettings.getValue("x", 0.0f), xmlSettings.getValue("y", 1.0f) ) );
            xmlSettings.popTag();
            
            xmlSettings.popTag(); // texCoords
            
            
            // now we have variables sourceName and sourceTexture
            // by checking those we can use one or another addSurface method
            if ( sourceName != "none" && sourceTexture != NULL ) {
                addSurface(ofxSurfaceType::TRIANGLE_SURFACE, sourceTexture, vertices, texCoords);
            } else {
                addSurface(ofxSurfaceType::TRIANGLE_SURFACE, vertices, texCoords);
            }
        }
        // it's a quad ?
        else if (vertexCount == 4)  
        // if (surface-type == QUAD_SURFACE)
        {
            xmlSettings.pushTag("vertex", 0);
            vertices.push_back( ofVec2f( xmlSettings.getValue("x", 0.0f), xmlSettings.getValue("y", 0.0f) ) );
            xmlSettings.popTag();
            
            xmlSettings.pushTag("vertex", 1);
            vertices.push_back( ofVec2f( xmlSettings.getValue("x", 100.0f), xmlSettings.getValue("y", 0.0f) ) );
            xmlSettings.popTag();
            
            xmlSettings.pushTag("vertex", 2);
            vertices.push_back( ofVec2f( xmlSettings.getValue("x", 100.0f), xmlSettings.getValue("y", 100.0f) ) );
            xmlSettings.popTag();

            xmlSettings.pushTag("vertex", 3);
            vertices.push_back( ofVec2f( xmlSettings.getValue("x", 0.0f), xmlSettings.getValue("y", 100.0f) ) );
            xmlSettings.popTag();
            
            xmlSettings.popTag(); // vertices
            
            xmlSettings.pushTag("texCoords");
            
            vector<ofVec2f> texCoords;
            
            xmlSettings.pushTag("texCoord", 0);
            texCoords.push_back( ofVec2f( xmlSettings.getValue("x", 0.0f), xmlSettings.getValue("y", 0.0f) ) );
            xmlSettings.popTag();
            
            xmlSettings.pushTag("texCoord", 1);
            texCoords.push_back( ofVec2f( xmlSettings.getValue("x", 1.0f), xmlSettings.getValue("y", 0.0f) ) );
            xmlSettings.popTag();
            
            xmlSettings.pushTag("texCoord", 2);
            texCoords.push_back( ofVec2f( xmlSettings.getValue("x", 1.0f), xmlSettings.getValue("y", 1.0f) ) );
            xmlSettings.popTag();

            xmlSettings.pushTag("texCoord", 3);
            texCoords.push_back( ofVec2f( xmlSettings.getValue("x", 0.0f), xmlSettings.getValue("y", 1.0f) ) );
            xmlSettings.popTag();
            
            xmlSettings.popTag(); // texCoords
            
            
            // now we have variables sourceName and sourceTexture
            // by checking those we can use one or another addSurface method
            if ( sourceName != "none" && sourceTexture != NULL ) {
                addSurface(ofxSurfaceType::QUAD_SURFACE, sourceTexture, vertices, texCoords);
            } else {
                addSurface(ofxSurfaceType::QUAD_SURFACE, vertices, texCoords);
            }
            
            
        }

        xmlSettings.popTag(); // surface
    }
    
    xmlSettings.popTag(); // surfaces
}

ofxBaseSurface* ofxSurfaceManager::selectSurface(int index)
{
    if ( index >= surfaces.size() ) {
        throw std::runtime_error("Surface index out of bounds.");
    }
    
    selectedSurface = surfaces[index];
    
    // notify that a new surface has been selected
    ofSendMessage("surfaceSelected");
}

ofxBaseSurface* ofxSurfaceManager::getSelectedSurface()
{    
    return selectedSurface;
}

void ofxSurfaceManager::deselectSurface()
{
    selectedSurface = NULL;
}

ofTexture* ofxSurfaceManager::loadImageSource(string name, string path)
{
    // check if it is loaded
    for ( int i=0; i<loadedImageSourceNames.size(); i++ ) {
        if ( loadedImageSourceNames[i] == name ) {
            // this image is already loaded
            return &loadedImageSources[i]->getTextureReference();
        }
    }
    
    // not loaded - load
    ofImage* image = new ofImage();
    if ( !image->loadImage(path) ){
        return NULL;
    }
    loadedImageSources.push_back(image);
    loadedImageSourceNames.push_back(name);
    return &image->getTextureReference();
}

string ofxSurfaceManager::getSelectedSurfaceSourceName()
{
    if ( selectedSurface == NULL ) {
        return "none";
    }
    
    return getSurfaceSourceName( selectedSurface );
}

string ofxSurfaceManager::getSurfaceSourceName(ofxBaseSurface *surface)
{
    ofTexture* tex = surface->getTexture();
    for ( int i=0; i<loadedImageSources.size(); i++ ) {
        if (tex == &loadedImageSources[i]->getTextureReference()) {
            return loadedImageSourceNames[i];
        }
    }
    
    return "none";
}

ofxBaseSurface* ofxSurfaceManager::getSurface(int index)
{
    if ( index >= surfaces.size() ) {
        throw std::runtime_error("Surface index out of bounds.");
        return NULL;
    }
    
    return surfaces[index];
}

int ofxSurfaceManager::size()
{
    return surfaces.size();
}