/* Author: Jason Leigh
 * (C) 1997 Electronic Visualization Laboratory - Univ Illinois at Chicago
 */
// Begin by reading main() and in particular the sections marked CAVERN.
// This program is based on Dave Pape's Performer flame demo.
// This version of the flame demo will attempt to re-establish broken network connections.
// Also this version is the same as flame2.c++ except that this version
// also provides multicast support.

#include "CAVERN.h++"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <gl/device.h>
#include <Performer/pf/pfChannel.h>
#include <Performer/pf/pfLightSource.h>
#include <Performer/pf/pfDCS.h>
#include <Performer/pf/pfGeode.h>
#include <Performer/pfutil.h>
#include <Performer/pr/pfTexture.h>
#include <pfcave.h>

#include <pthread.h>
 

pfuSmoke *smoke;
pfVec3 origin, dir;
pfGeode *node;
pfSphere bsph;
 

pfDCS * create_scene(pfChannel *chan);
void create_smoke(pfGroup *parent);
int smoke_draw(pfTraverser *trav,void *data);

CAVERN_irbKey_c *aKey;
CAVERN_irbChannel_c *aChannel;
CAVERN_irbLink_c *aLink=NULL;
 
 

/// -------------------------------------------------------------
/// CAVERN CAVERN CAVERN CAVERN CAVERN CAVERN CAVERN CAVERN CAVERN
/// -------------------------------------------------------------
/// Callback that fires when A_KEY gets filled with new data.

void A_keyCB(CAVERN_irbKey_c::CAVERN_irbKeyEvent_t event,
      CAVERN_irbKey_c *thisKey,
      void* userData)
{
         int size;
         CAVERN_irbKey_c::status_t status;

         // Grab the data from the key.
         // Recall userData is really a reference to the "movement" variable
         // declared in main(). You passed it in as a parameter to trigger().

         // Fill size with the amount of data we want to grab from the key.
        size = sizeof(float);

         // Get the data. userData is filled with the data. size returns
         // with the amount of data grabbed.
         thisKey->get((char*) userData, &size, &status);
}

/// -------------------------------------------------------------
/// CAVERN CAVERN CAVERN CAVERN CAVERN CAVERN CAVERN CAVERN CAVERN
/// -------------------------------------------------------------
/// Make a channel and a link over which the data in akey can be sent

int makeChannelAndLink(char *remoteHost)
{
         CAVERN_irbId_c remoteIRBId;
         CAVERN_irb_c::status_t irbStatus;
         CAVERN_irbChannel_c::status_t channelRetStatus;
         CAVERN_irbKey_c::status_t keyStatus;

         /// Open the channel to a remote IRB specified on the command line.
         remoteIRBId.setAddress(remoteHost);

         do {
                  cvrnPrintf("Attempting to Connect...\n");
                  aChannel->open(&remoteIRBId,NULL,CAVERN_irbChannel_c::UNRELIABLE, &channelRetStatus);
                  sleep(2);
             } while(channelRetStatus != CAVERN_irbChannel_c::OK);

         cvrnPrintf("Connected\n");

         if (aLink) delete aLink;
         aLink = NULL;

         /// Create a link to link the local key named A_KEY with the remote key
         /// also named A_KEY.

         CAVERN_irbKeyId_c aKeyId, remote_aKeyId;
         aKeyId.setName("A_KEY");
         remote_aKeyId.setName("A_KEY");

         CAVERN_linkAttrib_c linkAttribute;
         aLink = aChannel->link(aKey, &remote_aKeyId,&linkAttribute);
         if (aLink == NULL) {
                  printf("Yo dude! The link didn't work!\n");
                  fflush(stdout);
                  return 0;
             }

         return 1;

}

/// -------------------------------------------------------------
/// CAVERN CAVERN CAVERN CAVERN CAVERN CAVERN CAVERN CAVERN CAVERN
/// -------------------------------------------------------------
/// Callback that gets fired when a channel breaks.
/// This attempts to re-establish the channel and rebind the link.

void channelCB(CAVERN_irbChannel_c::CAVERN_channelEvent_t event,
   CAVERN_irbChannel_c *thisChannel,
   void* userData)
{
         if (event == CAVERN_irbChannel_c::BROKEN_CHANNEL) {
          cvrnMesg(NULL,"CHANNEL BROKEN!");
             }

         makeChannelAndLink((char*) userData);
}

int main(int argc,char **argv)
{

         if (argc < 2) {
                  printf("usage: %s IP_Address | \"mcast\">\n",argv[0]);
                  exit(1);
             }
         pfDCS *nav_dcs;
         pfInit();
 
         pfCAVEConfig(&argc,argv,NULL);

         int arg;

         CAVEFar = 1000;
         pfuInitSmokes();
         pfConfig();
 
         pfCAVEInitChannels();
 
         nav_dcs = create_scene(pfCAVEMasterChan());
 

    /// -------------------------------------------------------------
    /// CAVERN CAVERN CAVERN CAVERN CAVERN CAVERN CAVERN CAVERN CAVERN
    /// -------------------------------------------------------------

         CAVERN_irb_c::status_t irbStatus;
         CAVERN_irbId_c remoteIRBId;
         CAVERN_irbChannel_c::status_t channelRetStatus;
         CAVERN_irbKey_c::status_t keyStatus;

         /// Startup CAVERN.
         CAVERN_irb_c *personalIRB = CAVERNInit(&argc, &argv, NULL);
         if (!personalIRB) {
                  CAVEHalt();
                  pfExit();
                  return 0;
             }

         /// Define the local key (called A_KEY)
         CAVERN_irbKeyId_c aKeyId, remote_aKeyId;
         aKeyId.setName("A_KEY");
         remote_aKeyId.setName("A_KEY");

         aKey = personalIRB->define(&aKeyId, NULL, &irbStatus);

         /// Notify me when A_KEY gets changed by a remote client.
         float movement = 0;
         aKey->trigger(A_keyCB,(void*) &movement);

         /// If "mcast" is chosen then listen to the same mcast channel that
         /// throwerm sends data on.
         if (strcmp(argv[1],"mcast") == 0) {
                  CAVERN_irbMcastChannel_c *mcastChannel = personalIRB->createMcastChannel();
                  CAVERN_irbMcastChannel_c::status_t mcastStatus;

                  mcastChannel->open("224.100.100.100",9123,64,NULL, &mcastStatus);
                  if (mcastStatus != CAVERN_irbMcastChannel_c::OK) exit(1);

                  CAVERN_irbMcastLink_c *mcastLink = mcastChannel->link(aKey);
                  cvrnPrintf("%s: Listening to Multicast channel 224.100.100.100:9123\n",argv[0]);

             }
        else {
                  /// Create a channel over which communication will occur.
                  aChannel = personalIRB->createChannel();

                  /// Open the channel to a remote IRB specified on the command line.
                  makeChannelAndLink(argv[1]);
                  aChannel->trigger(channelCB,(void*) argv[1]);
         }
 

 /// -------------------------------------------------------------

 
         while (!CAVEgetbutton(CAVE_ESCKEY)){

                  pfSync();
                  pfCAVEPreFrame();
                  pfFrame();
                  pfCAVEPostFrame();

                  pfVec3 eye;

                  pfCAVEGetPosition(CAVE_WAND,eye.vec);
                  eye.vec[0] = movement;
                  pfuSmokeOrigin(smoke,eye,0.25f);

             }

         CAVEHalt();
         pfExit();
         return 0;
}
 

int smoke_draw(pfTraverser *trav,void *data)
{
         pfVec3 eye;

         // pfCAVEGetPosition(CAVE_WAND,eye.vec);
         CAVEGetPosition(CAVEEye,eye.vec);
         pfuDrawSmokes(eye);
         return PFTRAV_CONT;
}

#define SPEED 2.0f
 

pfDCS * create_scene(pfChannel *chan)
{
         pfScene *scene = new pfScene;
         pfGeoState *gstate = new pfGeoState;
         pfDCS *dcs = new pfDCS;
         gstate->setMode(PFSTATE_ENLIGHTING, PF_ON);
         gstate->setMode(PFSTATE_CULLFACE, PFCF_OFF);
         scene->setGState(gstate);
         scene->addChild(new pfLightSource);
         scene->addChild(dcs);
         chan->setScene(scene);
         create_smoke(dcs);
         return dcs;
}

void create_smoke(pfGroup *parent)
{
         origin[0] = origin[2] = 0; origin[1] = 5;
         dir[0] = dir[2] = 1; dir[1] = 0;
         smoke = pfuNewSmoke();
         pfuSmokeType(smoke,PFUSMOKE_SMOKE);
         pfuSmokeOrigin(smoke,origin,1.0f);
         pfuSmokeVelocity(smoke,3.0f,0.2f);
         pfuSmokeDir(smoke,dir);
         pfuSmokeMode(smoke,PFUSMOKE_START);
         pfuSmokeDuration(smoke,50000.0f);

         pfuSmokeDensity(smoke,1,1,0.5);

         node = new pfGeode;
         parent->addChild(node);
         bsph.radius = 100.0f;
         bsph.center = origin;
         node->setBound(&bsph,PFBOUND_STATIC);
         node->setTravFuncs(PFTRAV_DRAW,smoke_draw,NULL);
         node->setTravData(PFTRAV_DRAW,smoke);
}