#include "Export.h"

#include "RTMSimulator.h"
#include "SimNode.h"
#include "SimTriangle.h"

#include <QFile>
#include <QDataStream>

#include <Inventor/nodes/SoCamera.h>
#include <Inventor/nodes/SoPerspectiveCamera.h>

const char* ExportRTMS::CURRENT_VERSION = "20080927";

/**
 * Stores the whole scene graph including all simulation settings to a file.
 * @param strFile Fully qualified path where to store the data
 * @param pSimulator RTMSimulator object that provides the scene graph
 */
void ExportRTMS::writeToRTMSFile(QString strFile, RTMSimulator* pSimulator)
{
    QFile f(strFile);
    f.open( QIODevice::WriteOnly);
    QDataStream out(&f);
    out.writeRawData("RTMSimulatorScene FormatVersion:",32);
    out.writeRawData(CURRENT_VERSION, 8);
    out.writeRawData("\r\n",2);
    writeCameraPosition(pSimulator, out);
    writeSettings(pSimulator, out);
    writeNodes(pSimulator->activeNodes(), out);
    writeNodes(pSimulator->sweptNodes(), out);
    writeNodesNeighbors(pSimulator->activeNodes(), out);
    writeNodesNeighbors(pSimulator->sweptNodes(), out);
    writeTriangles(pSimulator, out);
    writeTriangleVertices(pSimulator, out);
    f.close();
}

/**
 * Writes the current camera position to the provided data stream.
 * @param pSimulator Pointer to the RTMSimulator object that provides the camera.
 * @param out QDataStream object to write to.
 */
void ExportRTMS::writeCameraPosition(RTMSimulator* pSimulator, QDataStream& out)
{
    SoCamera* pCamera = pSimulator->camera();
    SbVec3f camVec = pCamera->position.getValue();
    SbRotation camOrientation = pCamera->orientation.getValue();
    CameraPosition cp;
    camOrientation.getValue(cp.val0,cp.val1,cp.val2,cp.val3);
    cp.x = camVec[0];
    cp.y = camVec[1];
    cp.z = camVec[2];
    out.writeRawData((const char*)&cp,sizeof(CameraPosition));
}

/**
 * Writes simulation settings to the provided data stream.
 * @param pSimulator Pointer to the RTMSimulator object with the settings to store.
 * @param out QDataStream object to write to.
 */
void ExportRTMS::writeSettings(RTMSimulator* pSimulator, QDataStream& out)
{
    SceneSettings settings;
    settings.q = pSimulator->q();
    settings.r = pSimulator->r();
    settings.s = pSimulator->s();
    settings.viscosity              = pSimulator->viscosity();
    settings.pictureTimeInterval    = pSimulator->pictureTimeInterval();
    settings.timeOfLastSnapshot     = pSimulator->timeOfLastSnapShot();
    settings.showSweptNodes         = pSimulator->isSetShowSweptNodes();
    settings.markNeighbors          = pSimulator->isSetMarkNeighbors();
    settings.markDirectNeighbors    = pSimulator->isSetMarkDirectNeighbors();
    settings.takePictures           = pSimulator->isSetTakePictures();
    settings.numRelevantNeighbors   = pSimulator->numRelevantNeighbors();
    settings.currentCycle           = pSimulator->currentCycle();
    settings.currentTime            = pSimulator->currentTime();
    settings.vacuumPressure         = pSimulator->vacuumPressure();
    settings.cellSize               = pSimulator->cellSize();
    settings.translationFactor      = pSimulator->translationFactor();

    out.writeRawData((const char*)&settings,sizeof(SceneSettings));
}

/**
 * Writes all the simulation node data to the provided data stream.
 * @param nodes Map that holds all the node data to write.
 * @param out QDataStream object to write to.
 */
void ExportRTMS::writeNodes(std::map<int,SimNode*>* nodes, QDataStream& out)
{
    int numNodes = nodes->size();

    out.writeRawData((const char*)&numNodes,sizeof(int));
    for(std::map<int,SimNode*>::iterator it = nodes->begin(); it != nodes->end(); it++)
    {
        SimNode* node = (*it).second;
        NodeSettings settings;

        settings.nodeId             = (*it).first;
        settings.x                  = node->getX();
        settings.y                  = node->getY();
        settings.z                  = node->getZ();
        settings.permeability       = node->getPermeability();
        settings.cavityHeight       = node->getCavityHeight();
        settings.porosity           = node->getPorosity();
        settings.pressure           = node->getPressure();
        settings.injectionPressure  = node->getInjectionPressure();
        settings.injectionDiameter  = node->getInjectionDiameter();
        settings.injectionPoint     = node->isInjectionPoint();
        settings.markedForSweep     = node->isMarkedForSweep();
        settings.markedGreen        = node->isMarkedGreen();

        out.writeRawData((const char*)&settings,sizeof(NodeSettings));
    }
}

/**
 * Writes all neighbor information to the provided data stream.
 * @param nodes Map that holds all the nodes of which the neighbor data should be collected and written to the data stream.
 * @param out QDataStream object to write to.
 */
void ExportRTMS::writeNodesNeighbors(std::map<int,SimNode*>* nodes, QDataStream& out)
{
    std::vector<Neighborhood> directNeighborhood;
    std::vector<Neighborhood> neighborhood;
    std::vector<Neighborhood> sweptNeighborhood;
    std::vector<Neighborhood> adoptedNeighborhood;

    for(std::map<int,SimNode*>::iterator it = nodes->begin(); it != nodes->end(); it++)
    {
        SimNode* node = (*it).second;
        int nodeId = (*it).second->id();
        // collect direct neighbors
        std::set<SimNode*>* directNeighbors = node->getDirectNeighbors();
        for(std::set<SimNode*>::iterator nit = directNeighbors->begin(); nit != directNeighbors->end(); nit++)
        {
            Neighborhood n;
            n.node1 = nodeId;
            n.node2 = (*nit)->id();
            directNeighborhood.push_back(n);
        }

        // collect neighbors
        std::multimap<double, SimNode*>* neighbors = node->getNeighbors();
        for(std::multimap<double, SimNode*>::iterator nit = neighbors->begin(); nit != neighbors->end(); nit++)
        {
            Neighborhood n;
            n.node1 = nodeId;
            n.node2 = (*nit).second->id();
            neighborhood.push_back(n);
        }

        // collect swept neighbors
        std::set<SimNode*>* sweptNeighbors = node->getSweptNeighbors();
        for(std::set<SimNode*>::iterator nit = sweptNeighbors->begin(); nit != sweptNeighbors->end(); nit++)
        {
            Neighborhood n;
            n.node1 = nodeId;
            n.node2 = (*nit)->id();
            sweptNeighborhood.push_back(n);
        }

        // collect adopted neighbors
        std::set<SimNode*>* adoptedNeighbors = node->getAdoptedDirectNeighbors();
        for(std::set<SimNode*>::iterator nit = adoptedNeighbors->begin(); nit != adoptedNeighbors->end(); nit++)
        {
            Neighborhood n;
            n.node1 = nodeId;
            n.node2 = (*nit)->id();
            adoptedNeighborhood.push_back(n);
        }
    }

    int numDirectNeighborhoods = directNeighborhood.size();
    int numNeighborhoods = neighborhood.size();
    int numSweptNeighborhoods = sweptNeighborhood.size();
    int numAdoptedNeighborhoods = adoptedNeighborhood.size();

    out.writeRawData((const char*)&numDirectNeighborhoods, sizeof(int));
    out.writeRawData((const char*)&numNeighborhoods, sizeof(int));
    out.writeRawData((const char*)&numSweptNeighborhoods, sizeof(int));
    out.writeRawData((const char*)&numAdoptedNeighborhoods, sizeof(int));

    //write direct neighborhood
    for(std::vector<Neighborhood>::iterator it = directNeighborhood.begin(); it != directNeighborhood.end(); it++)
    {
        out.writeRawData((const char*)&(*it),sizeof(Neighborhood));
    }

    //write neighborhood
    for(std::vector<Neighborhood>::iterator it = neighborhood.begin(); it != neighborhood.end(); it++)
    {
        out.writeRawData((const char*)&(*it),sizeof(Neighborhood));
    }

    //write swept neighborhood
    for(std::vector<Neighborhood>::iterator it = sweptNeighborhood.begin(); it != sweptNeighborhood.end(); it++)
    {
        out.writeRawData((const char*)&(*it),sizeof(Neighborhood));
    }

    //write adopted neighborhood
    for(std::vector<Neighborhood>::iterator it = adoptedNeighborhood.begin(); it != adoptedNeighborhood.end(); it++)
    {
        out.writeRawData((const char*)&(*it),sizeof(Neighborhood));
    }
}

/**
 * Writes all triangle data to the provided data stream.
 * @param pSimulator RTMSimulator object which provides the triangle data.
 * @param out QDataStream object to write to.
 */
void ExportRTMS::writeTriangles(RTMSimulator* pSimulator, QDataStream& out)
{
    std::map<int,SimTriangle*>* triangles = pSimulator->triangles();

    int numTriangles = triangles->size();
    out.writeRawData((const char*)&numTriangles,sizeof(int));

    for(std::map<int,SimTriangle*>::iterator it = triangles->begin(); it != triangles->end(); it++)
    {
        SimTriangle* triangle = (*it).second;
        TriangleSettings settings;
        triangle->getTriangleSettings(settings);

        out.writeRawData((const char*)&settings,sizeof(TriangleSettings));
    }
}

/**
 * Writes the vertices for all the available triangles to the provided data stream.
 * @param pSimulator RTMSimulator object that holds all the triangle information.
 * @param out QDataStream to write to.
 */
void ExportRTMS::writeTriangleVertices(RTMSimulator* pSimulator, QDataStream& out)
{
    std::map<int,SimTriangle*>* triangles = pSimulator->triangles();

    int numTriangles = triangles->size();
    out.writeRawData((const char*)&numTriangles,sizeof(int));

    for(std::map<int,SimTriangle*>::iterator it = triangles->begin(); it != triangles->end(); it++)
    {
        SimTriangle* triangle = (*it).second;
        std::set<SimNode*>* vertexNodes = triangle->getVertexNodes();

        int triangleId = triangle->id();
        out.writeRawData((const char*)&triangleId,sizeof(int));

        int numVertexNodes = vertexNodes->size();
        out.writeRawData((const char*)&numVertexNodes,sizeof(int));

        for(std::set<SimNode*>::iterator it = vertexNodes->begin(); it != vertexNodes->end(); it++)
        {
            int nodeId = (*it)->id();
            out.writeRawData((const char*)&nodeId,sizeof(int));
        }
    }
}

