/*
    This is a small performer program that demonstrates a strange behaviour
    in the intersection process.  For each command line argument, one set of
    three nodes is created.  The set contains a pfSwitch, a pfDCS, and a
    pfGeode, where the pfDCS is a child of the pfSwitch, the pfGeode is a child
    of the pfDCS, and the sets of nodes are connected together at the DCS level
    ie. the pfSwitch node for one set of nodes is a child of the pfDCS of the
    previous set of nodes.  Each node in a set is given the name of the
    corresponding command line argument.
    
    As the scene is created, each node is given a pre and post traversal
    function that prints out its name, so that the user can 'see' the 
    traversal as it happens. Traversal is triggered by calling pfChannel::pick
    on the scene. **IMPORTANT** for pfChannel::pick to work, pfNodePickSetup
    must be called with the root node for the scene, prior to the pick call.
    
    Here's the weird part: The post traversal function for the pfDCS's in the
    scene NEVER get called, and it seems that instead, the post traversal
    functions for the pfGeodes get called twice?  A couple of different
    behaviours can be observed depending on which nodes are assigned traversal
    functions, but the common theme is that pfDCS's seem to be 'special' and
    quite often a traversal function gets called on a node that did not have
    a traversal function assigned to it.  We have never seen a post traversal
    function be called for a pfDCS.
    
    Emily Nichols
    May 11, 2001

*/


#include <stdlib.h>

#include <Performer/pf/pfChannel.h>
#include <Performer/pf/pfLightSource.h>
#include <Performer/pf/pfNode.h>
#include <Performer/pf/pfSwitch.h>
#include <Performer/pf/pfDCS.h>
#include <Performer/pf/pfGeode.h>
#include <Performer/pf/pfScene.h>
#include <Performer/pf/pfTraverser.h>


#include <Performer/pfutil.h>
#include <Performer/pfdu.h>
#include <Performer/pfui.h>


static void
usage (void)
{
    pfNotify(PFNFY_FATAL, PFNFY_USAGE, "Usage: test name1 name2 ...\n");
    exit(1);
}

static int preIsectFunc(pfTraverser* trav, void* data)
{
    pfNotify(PFNFY_NOTICE, PFNFY_PRINT, "preFunc %s %s\n", trav->getNode()->getName(), trav->getNode()->getTypeName());
    return PFTRAV_CONT;
}

static int postIsectFunc(pfTraverser* trav, void* data)
{
    pfNotify(PFNFY_NOTICE, PFNFY_PRINT, "postFunc %s %s\n", trav->getNode()->getName(), trav->getNode()->getTypeName());
    return PFTRAV_CONT;
}

static int preFunc(pfuTraverser* trav)
{
    pfNotify(PFNFY_NOTICE, PFNFY_PRINT, "preFunc %s %s\n", trav->node->getName(), trav->node->getTypeName());
    return PFTRAV_CONT;
}

static int postFunc(pfuTraverser* trav)
{
    pfNotify(PFNFY_NOTICE, PFNFY_PRINT, "postFunc %s %s\n", trav->node->getName(), trav->node->getTypeName());
    return PFTRAV_CONT;
}

void update(pfNode* node, pfuMouse* mouse, pfChannel* channel)
{
    static bool downLastFrame = false;
    bool clicked = false;

    bool down = mouse->flags & PFUDEV_MOUSE_LEFT_DOWN;

    if (down && !downLastFrame)
        clicked = true;
    else clicked = false;

    downLastFrame = down;

    if (clicked)
    {
        pfNotify(PFNFY_NOTICE, PFNFY_PRINT, "received a click\n");
        float nx, ny;
        pfuCalcNormalizedChanXY(&nx, &ny, channel, mouse->xpos, mouse->ypos);
      
        //is mouse in channel ?
        if ( (nx > 0.0f) || (nx < 1.0f) || (ny > 0.0f) || (ny < 1.0f))
        {
            pfHit **hits[32];

            int intersections = channel->pick(PFTRAV_IS_PRIM, nx, ny, 0.0f, hits);
            pfNotify(PFNFY_NOTICE, PFNFY_PRINT, "#hits:%d  \n", intersections);
        }
    }
}

int
main (int argc, char *argv[])
{
    if (argc < 2)
	usage();
    
    pfInit();
    
    pfMultiprocess( PFMP_DEFAULT );			
    
    pfConfig();
    pfuInit();	
        
    //create the root node for the scene and a DCS for the geometry
    pfScene* scene = new pfScene;
    pfDCS* baseDCS = new pfDCS;
    baseDCS->setName("baseDCS");
    scene->addChild(baseDCS);
    
    // Create a lit scene pfGeoState for the scene
    pfGeoState *gstate = new pfGeoState;
    gstate->setMode(PFSTATE_ENLIGHTING, PF_ON);
    // attach the pfGeoState to the scene
    scene->setGState(gstate);
    
    // put a default light source in the scene
    scene->addChild(new pfLightSource);
    
    // Configure and open GL window
    pfPipe *p = pfGetPipe(0);
    pfPipeWindow *pw = new pfPipeWindow(p);
    pw->setWinType(PFPWIN_TYPE_X);
    pw->setName("ISECT traversal test");
    pw->setOriginSize(0,0,500,500);
    pw->open();
    
    pfuInitInput(pw, PFUINPUT_X);
    
    // Create and configure a pfChannel.
    pfChannel *chan = new pfChannel(p);
    chan->setScene(scene);
    //chan->setFOV(45.0f, 0.0f);
    
    //add some context
    pfEarthSky *esky = new pfEarthSky();
    esky->setMode(PFES_BUFFER_CLEAR, PFES_FAST);
    esky->setColor(PFES_CLEAR, 0.0, 0.0, 1.0, 1.0);
    chan->setESky(esky);
        
    //create the first "treeItem"
    pfSwitch* currentSwitch = new pfSwitch;
    pfDCS* currentDCS = new pfDCS;
    pfGeode* currentGeode = new pfGeode;
    
    //link the parts of the first treeItem together
    currentSwitch->addChild(currentDCS);
    currentDCS->addChild(currentGeode);
    
    //add the first treeItem to the baseDCS
    baseDCS->addChild(currentSwitch);
    
    //give the first treeItem a name
    currentSwitch->setName("top");
    currentDCS->setName("top");
    currentGeode->setName("top");
    baseDCS->setName("baseDCS");
    
    //set the traversal functions on the treeItem parts
    currentSwitch->setTravFuncs(PFTRAV_ISECT, preIsectFunc, postIsectFunc);
    currentDCS->setTravFuncs(PFTRAV_ISECT, preIsectFunc, postIsectFunc);
    /*
        even when the line below is commented out, the post traversal function 
        on the geode called "top" gets called, and it looks like it gets called
        instead of the post traversal function for the DCS called "top"
     */
    //currentGeode->setTravFuncs(PFTRAV_ISECT, preIsectFunc, postIsectFunc);
  
    //add a treeItem for each command line node name
    for (int i = 1; i < argc; i++)
    {
            pfNotify(PFNFY_NOTICE, PFNFY_PRINT, "%d  \n", i);
        //create the three nodes required for a treeItem
        pfSwitch* newSwitch = new pfSwitch;
        pfDCS* newDCS = new pfDCS;
        pfGeode* newGeode = new pfGeode;
        
        //link the parts of the treeItem together and give the geode some 
        //geometry
        newSwitch->addChild(newDCS);
        newDCS->addChild(newGeode);
        pfGeoSet* geoSet = pfdNewCylinder(60, pfGetSharedArena());

        //pfdNewCylinder creates a new cylinder along the Z axis from -1 to 1
        //with a radius of 1.

        pfdGSetColor(geoSet, 1.0,0.0,0.0,0.0);
        newGeode->addGSet(geoSet);
        
        //give each part of the treeItem its name (from the command line)
        newSwitch->setName(argv[i]);
        newDCS->setName(argv[i]);
        newGeode->setName(argv[i]);
        
        //give the DCS a transformation so this sphere doesn't coincide with
        //the previous one
        pfMatrix m;
        m.makeIdent();
        m.preTrans(0, 20, -i*5, m);
        m.scale(0.8,m);
        newDCS->setMat(m);
        
        //set the traversal functions on the treeItem parts
        newSwitch->setTravFuncs(PFTRAV_ISECT, preIsectFunc, postIsectFunc);
        newDCS->setTravFuncs(PFTRAV_ISECT, preIsectFunc, postIsectFunc);
        newGeode->setTravFuncs(PFTRAV_ISECT, preIsectFunc, postIsectFunc);
           
        //link this new treeItem to the current one by way of the current DCS
        currentDCS->addChild(newSwitch);
        currentDCS = newDCS;
        currentSwitch = newSwitch;
        currentGeode = newGeode;
    }
       
    // determine extent of scene's geometry
    pfSphere bsphere;
    baseDCS->getBound(&bsphere);
    chan->setNearFar(0.01f, 1000.0f * bsphere.radius);
       
    //try a test traversal
    pfuTraverser trav;
    pfuInitTraverser(&trav);

    trav.preFunc = preFunc;
    trav.postFunc = postFunc;

    // Perform the traversal
    pfuTraverse(baseDCS, &trav);
        
    float t = 0.0;
    pfuMouse* mouse;
    
    pfCoord view;
    view.hpr.set(0.0,-20.0,0.0);
    view.xyz.set(bsphere.center[0],bsphere.center[1],-2*bsphere.center[2]);
    view.xyz.set(0.0,0.0,0.0);
    chan->setView(view.xyz, view.hpr);
    pfNodePickSetup(scene);
    
    // Simulate for ten seconds.
    while (t < 10.0f)
    {
	// Go to sleep until next frame time.
	pfSync();		
	
	// Initiate cull/draw for this frame.
	pfFrame();
	
	// Compute new view position.
	t = pfGetTime();
        
        pfuGetMouse(mouse);
        update(baseDCS,mouse,chan);
    }
    
    // Terminate parallel processes and exit
    pfExit();
    return 0;
}

