// all needed include files
#include <OSGGLUT.h>
#include <OSGConfig.h>
#include <OSGSimpleGeometry.h>
#include <OSGGLUTWindow.h>
#include <OSGSimpleSceneManager.h>

#include <OSGThreadManager.h>

#include <OSGGeometry.h>

#include <OSGTypedGeoIntegralProperty.h>
#include <OSGTypedGeoVectorProperty.h>

// this will specify the resolution of the mesh
#define N   100

//the two dimensional array that will store all height values
OSG::Real32 wMesh[N][N];

//the origin of the water mesh
OSG::Pnt3f wOrigin = OSG::Pnt3f(0,0,0);

//width and length of the mesh
OSG::UInt16 width = 100;
OSG::UInt16 length = 100;

OSG::SimpleSceneManager *mgr;

// we will store the transformation globally - this
// is not necessary, but comfortable
// Note that these global objects are accessed from different aspects,
// therefore you need to use MTRecPtr here, so that you get a pointer to the
// correct aspect copy of the object.

OSG::TransformMTRecPtr  trans;
OSG::NodeMTRecPtr       scene;
OSG::ThreadRefPtr       animationThread;
OSG::ThreadRefPtr       applicationThread;
OSG::BarrierRefPtr      syncBarrier;

int setupGLUT(int *argc, char *argv[]);

void updateMesh(OSG::Real32 time)
{
    for (int x = 0; x < N; x++)
        for (int z = 0; z < N; z++)
            wMesh[x][z] = 10*cos(time/1000.f + (x+z)/10.f);
}

OSG::NodeTransitPtr createScenegraph(void)
{
    // the scene must be created here
    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++)
            wMesh[i][j] = 0;
    
    // the types of primitives that are used - an integerer propery
    OSG::GeoUInt8PropertyRecPtr types = OSG::GeoUInt8Property::create();
    
    // we want to use quads ONLY 
    types->addValue(GL_QUADS);
    
    // the number of vertices (or indices) we want to use with the primitive
    // type; types and lengths always have the same number of elements
    // (here both have just one)
    OSG::GeoUInt32PropertyRecPtr lengths = OSG::GeoUInt32Property::create();
    // the length of our quads is four ;-)
    lengths->addValue(4 * (N - 1) * (N - 1));

    // GeoPnt3fProperty stores the positions of all vertices used in
    // this specific geometry core
    OSG::GeoPnt3fPropertyRecPtr pos = OSG::GeoPnt3fProperty::create();
    // here they all come
    for (int x = 0; x < N; x++)
        for (int z = 0; z < N; z++)
            pos->addValue(OSG::Pnt3f(x, wMesh[x][z], z));

    // GeoColor3fProperty stores all color values that will be used
    OSG::GeoColor3fPropertyRecPtr colors = OSG::GeoColor3fProperty::create();
    for (int x = 0; x < N; x++)
        for (int z = 0; z < N; z++)
            colors->addValue(OSG::Color3f(0,0,1));
    
    // and finally the normals are stored in a GeoVec3fProperty
    OSG::GeoVec3fPropertyRecPtr norms = OSG::GeoVec3fProperty::create();
    for (int x = 0; x < N; x++)
        for (int z = 0; z < N; z++)
            // As initially all heights are set to zero thus yielding a plane,
            // we set all normals to (0,1,0) parallel to the y-axis
            norms->addValue(OSG::Vec3f(0,1,0));
    
    OSG::SimpleMaterialRecPtr mat = OSG::SimpleMaterial::create();
    
    // Indices define the order in which the entries in the above properties
    // are used
    OSG::GeoUInt32PropertyRecPtr indices = OSG::GeoUInt32Property::create();
    for (int x = 0; x < N-1; x++)
    {
        for (int z = 0; z < N-1; z++)
        {
            // points to four vertices that will
            // define a single quad
            indices->addValue( z    * N + x    );
            indices->addValue((z+1) * N + x    );
            indices->addValue((z+1) * N + x + 1);
            indices->addValue( z    * N + x + 1);
        }
    }

    OSG::GeometryRecPtr geo = OSG::Geometry::create();

    geo->setTypes    (types  );
    geo->setLengths  (lengths);
    geo->setIndices  (indices);
    geo->setPositions(pos    );
    geo->setNormals  (norms  );
    geo->setMaterial (mat    );
    geo->setColors   (colors );
    
    // Turn off creation of display lists, since the geometry changes each
    // frame
    geo->setDlistCache(false);
    
    OSG::NodeRecPtr root = OSG::Node::create();
    root->setCore(geo);

    return OSG::NodeTransitPtr(root);
}

/*
OSG::NodeTransitPtr createScenegraph(void)
{
    // the scene must be created here
    OSG::NodeRecPtr n = OSG::makeTorus(.5,2,16,16);
    
    //add a simple Transformation
    trans = OSG::Transform::create();
    OSG::Matrix m;
    m.setIdentity();
    trans->setMatrix(m);
    
    OSG::NodeRecPtr transNode = OSG::Node::create();
    transNode->setCore(trans);
    transNode->addChild(n);

    
    
    return OSG::NodeTransitPtr(transNode);
}*/

//this function will run in a thread and simply will
//rotate the cube by setting a new transformation matrix
void rotate(void *args)
{
    // sync this thread to the main thread, i.e. pull in all changes done
    // during scene construction
    syncBarrier->enter(2);
    applicationThread->getChangeList()->applyAndClear();
    syncBarrier->enter(2);

    // clear the local changelist as we only want to sync the
    // real changes we make back.
    OSG::commitChanges();
    animationThread->getChangeList()->clear();


    // we won't stop calculating new matrices....
    while(true)
    {

      OSG::Real32 time = glutGet(GLUT_ELAPSED_TIME);
      updateMesh(time);

      // we extract the core out of the root node
      // as we now this is a geometry node
      OSG::GeometryMTRecPtr geo = dynamic_cast<OSG::Geometry *>(scene->getCore());

      //now modify it's content

      // first we need a pointer to the position data field
      OSG::GeoPnt3fPropertyMTRecPtr pos = 
        dynamic_cast<OSG::GeoPnt3fProperty *>(geo->getPositions());

      //get the data field the pointer is pointing at
      OSG::GeoPnt3fProperty::StoredFieldType *posfield = pos->editFieldPtr();
      //get some iterators
      OSG::GeoPnt3fProperty::StoredFieldType::iterator last, it;

      // set the iterator to the first data
      it = posfield->begin();

      //now simply run over all entires in the array
      for (int x = 0; x < N; x++)
      {
        for (int z = 0; z < N; z++)
        {
          (*it) = OSG::Pnt3f(x, wMesh[x][z], z);
          it++;
        }
      }

        //well that's new...
        
        //wait until two threads are cought in the
        //same barrier
        syncBarrier->enter(2);    // barrier (1)
        
        //just the same again
        syncBarrier->enter(2);    // barrier (2)
    }
}

int main(int argc, char **argv)
{
    OSG::ChangeList::setReadWriteDefault(true);
    OSG::osgInit(argc,argv);
    
    {
        int winid = setupGLUT(&argc, argv);
        OSG::GLUTWindowRecPtr gwin = OSG::GLUTWindow::create();
        gwin->setGlutId(winid);
        gwin->init();
        
        scene = createScenegraph();

        //OSG::commitChangesAndClear();
        
        //create the barrier, that will be used to
        //synchronize threads
        
        //instead of NULL you could provide a name
        syncBarrier = OSG::Barrier::get("syncBarrier", true);
        
        mgr = new OSG::SimpleSceneManager;
        mgr->setWindow(gwin );
        mgr->setRoot  (scene);
        mgr->showAll();
        
        // store a pointer to the application thread
        applicationThread = 
            dynamic_cast<OSG::Thread *>(OSG::ThreadManager::getAppThread());
        
        //create the thread that will run generation of new matrices
        animationThread =
            OSG::dynamic_pointer_cast<OSG::Thread>(
                OSG::ThreadManager::the()->getThread("anim", true));
        
        //do it...
        animationThread->runFunction(rotate, 1, NULL);
        
        // wait for animationThread to complete its sync
        syncBarrier->enter(2);
        syncBarrier->enter(2);
        
        OSG::commitChanges();
    }
    
    glutMainLoop();

    return 0;
}

void reshape(int w, int h)
{
    mgr->resize(w, h);
    glutPostRedisplay();
}

void display(void)
{
    // if the animation thread has computed an update to the scene
    // it waits at the barrier and we copy over the changes
    // otherwise we just keep rendering

    if(syncBarrier->getNumWaiting() > 0)
    {
        // we wait here until the animation thread enters
        // barrier (1)
        syncBarrier->enter(2);

        //now we sync data
        animationThread->getChangeList()->applyAndClear();

        // update dependend data
        OSG::commitChanges();

        // now wait for animation thread to enter barrier (2)
        syncBarrier->enter(2);
    }
    
    // !!!! Attention
    // you will find a more detailed description
    // of what's going on here in the documentation
    // itself!
    
    // now render...
    mgr->redraw();
}

void mouse(int button, int state, int x, int y)
{
    if (state)
        mgr->mouseButtonRelease(button, x, y);
    else
        mgr->mouseButtonPress(button, x, y);
        
    glutPostRedisplay();
}

void motion(int x, int y)
{
    mgr->mouseMove(x, y);
    glutPostRedisplay();
}

int setupGLUT(int *argc, char *argv[])
{
    glutInit(argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);
    
    int winid = glutCreateWindow("OpenSG First Application");
    
    glutDisplayFunc(display);
    glutMouseFunc(mouse);
    glutMotionFunc(motion);
    glutReshapeFunc(reshape);
    glutIdleFunc(display);
    
    return winid;
}
