/*
 * Syzygy-related functions for szgPartiview, including main program.
 */

static char local_id[] = "$Id$";

/*
 * $Log$
 * Revision 1.12  2009/03/14 21:32:02  slevy
 * Initialize the cmdbuf transfer-buffer size to 1 byte.
 * szg gets mad if we try to select size 0.
 *
 * Revision 1.11  2008/07/28 09:22:58  slevy
 * Add CMDTEST hook for testing PpCmd packing/unpacking.  Looks like it might work.
 *
 * Revision 1.10  2008/07/28 08:49:48  slevy
 * Scrap pp_ui_postinit().  It just overwrites settings that (a) were initialized
 * properly earlier and (b) might have been changed by command-line I/O.
 * I.e. let the "run" command work if included in a .cf file.
 *
 * Revision 1.9  2008/07/28 08:39:41  slevy
 * onPreExchange: Point sockp at a new bufferedSocket before passing to ar_accept()!
 * Tidy list insert/deletion.
 * Call clock_tick() periodically in master to allow time to pass.
 * onPostExchange: Add more error checking.
 * main: Hack to allow running under Linux+freeglut in standalone mode.
 *
 * Revision 1.8  2008/07/25 16:10:16  slevy
 * Lots of changes from Will Davis: new socket stuff
 * for listening for commands from network;
 * transfer fields; attempts to get "play" to play, etc.
 * Some from Stuart: PpCmd uses string object;
 * passing PpCmd commands by transfer-field
 * from master to slaves.
 *
 * Revision 1.7  2008/07/22 23:20:07  slevy
 * Need to resize() when adding new elements, not just reserve().
 *
 * Revision 1.6  2008/07/22 18:46:14  slevy
 * Use ar_log_<whatever>() for msg() logging.  Send to either _remark or _warning
 * according to whether msg() or warn() was called.
 *
 * Revision 1.5  2008/07/22 17:49:58  slevy
 * From Will Davis: globalize argc/argv for use in callback later.
 * onWindowStartGL() returns void.
 *
 * Implement PpCmd interface for serializing commands for network transport.
 *
 * Revision 1.4  2008/07/15 08:40:17  slevy
 * Don't mention "virtual" on *implementation* methods.
 *
 * Revision 1.3  2008/07/14 17:17:48  slevy
 * Attempt to glue in some arMasterSlaveFramework methods -- maybe enough to draw,
 * though not to process any commands after command-line startup yet.
 *
 */

#include "arPrecompiled.h"
#include "arMasterSlaveFramework.h"

#include <stdio.h>
#include <stdlib.h>
#include "config.h"

#ifdef AR_USE_WIN_32
# include "winjunk.h"
#endif

#include "plugins.h"
 
#include "specks.h"
#include "szgPartiview.h"
#include "shmem.h"
#include <string.h>
#include <stdarg.h>
#include <ctype.h>
#include <math.h>
#include <errno.h>

#include "partiviewc.h"
#include "findfile.h"


struct Ppszg  ppszg;

static int specks_commandstr( struct stuff **stp, const char *str ) {
  int result;
  if(stp == NULL || *stp == NULL || str == NULL)
    return 0;

#define MAXARGS 128
  char *av[MAXARGS];
  int ac;
  char *txt = (char *)malloc( strlen(str) + 1 );
  char *s = txt;
  strcpy(txt, str);
  for(ac = 0; ac < MAXARGS-1; ac++) {
    av[ac] = strtok(s, " \t\n");
    if(av[ac] == NULL) break;
    s = NULL;
  }
  av[ac] = NULL;
  result = specks_parse_args( stp, ac,av);
  free(txt);
  return result;
}

int specks_commandfmt( struct stuff **stp, const char *fmt, ... ) {
  char cmd[1024], *cp;
  int ok;
  va_list args;
  va_start(args, fmt);
  vsprintf(cmd, fmt, args);
  va_end(args);
  for(cp = cmd; isspace(*cp); cp++)
      ;
  if(*cp == '\0') return 1;	// accept null command implicitly
  ok = specks_commandstr( stp, cmd );
  if(ok) parti_redraw();
  else msg("Unrecognized control command: %s", cmd);
  return ok;
}

/*
 * These two are a quick fix to using argc and argv[] in the
 * onStart() function. I don't believe you can pass parameters
 * into, but I could be wrong.
 *
 */
int globalArgc;
char** globalArgv;

/* =================================================================== */

int pp_read( struct stuff **, int argc, char *argv[], char *fromfname, void * ) {

    /* this module defines only one command at present ... */
    if(!strcmp( argv[0], "subcam")) {
	if(argc >= 9) {
	    parti_make_subcam( argv[1], argc-2, argv+2 );
	} else {
	    msg("subcam: expected name az el rol L R B T, not %s",
		rejoinargs( 1, argc, argv ));
	}
	return 1;
    }
    return 0;
}


void parti_lighting() {
    static GLuint lightlist[MAXDSPCTX];

    int ctx = get_dsp_context();	/* this would come from Syzygy wall number, say */
    int listmaking = 0;

    if((unsigned int)ctx < MAXDSPCTX) {
	if(lightlist[ctx] > 0) {
	    glCallList( lightlist[ctx] );
	    return;
	} else {
	    lightlist[ctx] = glGenLists( 1 );
	    glNewList( lightlist[ctx], GL_COMPILE_AND_EXECUTE );
	    listmaking = 1;
	}
    } else {
	listmaking = 0;
    }

    static GLfloat lmambient[] = { 0.1, 0.1, 0.1, 1.0 };
    static GLfloat amblight[] = { 0.1,	0.1, 0.1, 1.0 };
    static GLfloat whitelight[] = { 1, 1, 1, 1 };
    static GLfloat Ce[] = { 0, 0, 0, 1 };
    static GLfloat Ca[] = { 1, 1, 1, 1 };
    static GLfloat Cd[] = { 1, 1, 1, 1 };
    static GLfloat Cs[] = { 1, 1, 1, 1 };
    static GLfloat lightpos[3][4] = {
		0, 0, -1, 0,
		1, 0, -.5, 0,
		0, 1, 0, 0,
    };

    glShadeModel( GL_SMOOTH );

    glLightModeli( GL_LIGHT_MODEL_LOCAL_VIEWER, GL_FALSE );
    glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE );
    glLightModelfv( GL_LIGHT_MODEL_AMBIENT, lmambient );
    glDisable( GL_LIGHTING );

    int i;

    for(i = 0; i < 8; i++)
	glDisable( GL_LIGHT0+i );

    for(i = 0; i < 3; i++) {
	glLightfv( GL_LIGHT0+i, GL_AMBIENT, amblight );
	glLightfv( GL_LIGHT0+i, GL_SPECULAR, whitelight );
	glLightfv( GL_LIGHT0+i, GL_DIFFUSE, whitelight );
	glLightfv( GL_LIGHT0+i, GL_POSITION, lightpos[i] );
	glEnable( GL_LIGHT0+i );
    }


    glMaterialfv( GL_FRONT_AND_BACK, GL_EMISSION, Ce );
    glMaterialfv( GL_FRONT_AND_BACK, GL_AMBIENT, Ca );
    glMaterialfv( GL_FRONT_AND_BACK, GL_DIFFUSE, Cd );
    glMaterialfv( GL_FRONT_AND_BACK, GL_SPECULAR, Cs );
    glMaterialf( GL_FRONT_AND_BACK, GL_SHININESS, 32.0 );

    glColorMaterial( GL_FRONT_AND_BACK, GL_DIFFUSE );
    glDisable( GL_COLOR_MATERIAL );

    glEnable( GL_BLEND );
    glBlendFunc( GL_SRC_ALPHA,  GL_ONE_MINUS_SRC_ALPHA );
    glDisable( GL_BLEND );

    if(listmaking)
	glEndList();
}

PvObject::PvObject() : st_(0), name_(0), id_(-1), parent_(0) {
}

PvObject::PvObject( const char *name, int id ) {
    this->init( name, id );
}

void PvObject::init( const char *name, int id ) {
    struct stuff *st = specks_init( 0, NULL );
    st->clk = ppszg.clk;
    this->st_ = st;
    this->To2w( &Tidentity );
    this->name_ = name ? strdup(name) : strdup("");
    this->id_ = id;
    this->parent_ = 0;
}

void PvObject::draw( bool inpick ) {

    if(st_ == NULL)
	return;

    specks_set_timestep( st_ );
    specks_current_frame( st_, st_->sl );

    glPushMatrix();
    glMultMatrixf( & To2w_.m[0] );

    st_->inpick = (int)inpick;
    drawspecks( st_ );
    st_->inpick = 0;

    glPopMatrix();
}



void drawetc( const Point *cen, float censize ) {
  static Point xyz[3] = {{{1,0,0}}, {{0,1,0}}, {{0,0,1}}};
  static Point nxyz[3] = {{{-1,0,0}}, {{0,-1,0}}, {{0,0,-1}}};
  static Point zero = {{0,0,0}};
  static float white[3] = {1,1,1};
  int i;

  // if(ppszg.drawtrace)
  //   (*ppszg.drawtrace)();

  parti_lighting();
  glDisable(GL_LIGHTING);
  if(censize > 0) {
    glPushMatrix();
    glScalef(censize,censize,censize);
    glBegin(GL_LINES);
    for(i = 0; i < 3; i++) {
	glColor3fv(xyz[i].x);
	glVertex3fv(xyz[i].x);
	glVertex3f(0,0,0);
    }
    glEnd();
    glPopMatrix();
  }

  if(censize != 0) {
    censize = fabs(censize);
    glPushMatrix();
    glTranslatef(cen->x[0],cen->x[1],cen->x[2]);
    glScalef(censize, censize, censize);
    glBegin(GL_LINES);
    for(i = 0; i < 3; i++) {
	glColor3fv(white);
	glVertex3fv(nxyz[i].x);
	glVertex3fv(zero.x);

	glColor3fv(xyz[i].x);
	glVertex3fv(zero.x);
	glVertex3fv(xyz[i].x);
    }
    glEnd();
    glPopMatrix();
  }
}

// Draw entire partiview scene.
// XXX How can we handle background color, ppszg.bgcolor?   Do we glClear() here ??
//
// YYY setting "inpick" is part of what's needed to go into OpenGL "selection" mode,
// YYY but we don't need that immediately if at all.
//  Just call draw() and let inpick default to false.
void PvScene::draw( bool inpick ) {

    drawetc( center(), censize() );

    for(int i = 0; i < nobjs(); i++) {
	obj(i)->draw( inpick );
    }
}

PvObject *PvScene::addobj( const char *objname, int id ) {
    if(id < 0)
	id = objs_.size();
    if(objstuff(id) == 0) {
	if(id >= objs_.capacity())
	    objs_.reserve( id + 15 );
	if(id >= objs_.size())
	    objs_.resize( id+1 );
	objs_[id].init( objname, id );
    } else {
	msg("Warning: Reusing object g%d (named %s)", id, objname);
    }
    return &objs_[id];
}


// watching file descriptors for external input, e.g. from network.
// XXX good to implement these someday.
void parti_asyncfd( int fd ) {
    msg("IGNORING parti_asyncfd(%d)", fd);
}
void parti_unasyncfd( int fd ) {
    // XXX
}

typedef enum { REMARK, WARN } MsgLevel;

MsgLevel msglevel = REMARK;

int vmsg( const char *fmt, va_list args ) {

  char str[10240];
#ifdef HAVE_SNPRINTF
  vsnprintf(str, sizeof(str), fmt, args);
#else
  vsprintf(str, fmt, args);
#endif
  // XXX Now do something with str!
  fputs(str, stderr);
  fputc('\n', stderr);
  if(msglevel == REMARK)
    ar_log_remark() << str;
  else
    ar_log_warning() << str;
  return 0;
}

int msg( const char *fmt, ... ) {
  int val;

  va_list args;

  msglevel = REMARK;

  va_start(args, fmt);
  val = vmsg( fmt, args );
  va_end(args);
  return val;
}

void warn( const char *fmt, ... ) {

  va_list args;

  msglevel = WARN;

  va_start(args, fmt);
  vmsg(fmt, args);
  va_end(args);
}


// partiview <-> syzygy navigation.

void copyfrom_arMatrix( Matrix *dstT, const arMatrix4 *srcarT )
{
    int i;
    for(i = 0; i < 16; i++)
	dstT->m[i] = srcarT->v[i];
}

void copyto_arMatrix( arMatrix4 *dstarT, const Matrix *srcT )
{
    int i;
    for(i = 0; i < 16; i++)
	dstarT->v[i] = srcT->m[i];
}


void parti_getc2w( Matrix *c2w ) {
  /*
   * Modified July 23, 2008 William Davis
   * We needed the non-inverted matrix,
   * changed from ar_getNavInvMatrix to
   * ar_getNavMatrix();
   */
  arMatrix4 navmat = ar_getNavMatrix();
  copyfrom_arMatrix( c2w, &navmat );
}

void parti_setc2w( const Matrix *c2w ) {
  //msg("IGNORING parti_setc2w() (\"jump\")");
  //XXX handle the "jump" command.  Something like
  /*
   * Modified July 23, 2008 William Davis
   * Sets the navigation matrix to the coordinates
   * specified by the "jump" command
   * Removed eucinv( &w2c, c2w )
   * Modified copyto_arMatrix() to match
   */
  arMatrix4 jumpto;
  copyto_arMatrix( &jumpto, c2w );
  ar_setNavMatrix( ar_matrixFromNavCoords(jumpto) );
}

double syzygy_time() {	// called from sclock.c
    return ppszg.getTime() * 0.001;
}

// Camera Animation (from .wf path)
static void playidle( void * ) {
  struct wfpath *path = &ppszg.path;

  if(!ppszg.playing) {
    // XXX disable calling playidle() in this case. // Fl::remove_idle( playidle, NULL );
    ppszg.playidling = 0;
    return;
  }

  double now;
#if TESTCAVE
  now = ppszg.getTime();
#else 
  now = wallclock_time();
#endif
  
  if(ppszg.playspeed == 0)
    ppszg.playspeed = 1;

  int frame = ppszg.framebase;

  if(ppszg.playevery) {
    frame += (int) ((ppszg.playspeed < 0)	/* round away from zero */
		    ? ppszg.playspeed - .999f
		    : ppszg.playspeed + .999f);
    ppszg.framebase = frame;
    ppszg.playtimebase = now;
  } else {
    frame += (int) ((now - ppszg.playtimebase) * ppszg.playspeed * path->fps);
  }

  if(frame < path->frame0 || frame >= path->frame0 + path->nframes) {

    frame = (frame < path->frame0)
	  ? path->frame0
	  : path->frame0 + path->nframes-1;

    switch(ppszg.playloop) {
    case 0:	/* Not looping; just stop at end. */
	// XXX disable calling playidle() here   // Fl::remove_idle( playidle, NULL );
	ppszg.playidling = 0;
	//parti_play( "stop" );
	break;

    case -1:	/* Bounce: stick at same end of range, change direction */
	ppszg.playspeed = -ppszg.playspeed;
	break;

    case 1:	/* Loop: jump to other end of range */
	frame = (frame == path->frame0)
		? path->frame0 + path->nframes-1
		: path->frame0;
    }
    ppszg.framebase = frame;
    ppszg.playtimebase = now;
    
    parti_setframe( frame );
    return;
  }

  /*
   * Cases:
   *  - we're displaying frames slower than real time.
   *	=> use an idle function.
   *  - we're displaying frames faster than real time.
   *    => use a timeout.
   */

  int needidle = ppszg.playidling;
  float dt = 0;

  if(frame != path->curframe) {
    parti_setframe( frame );
    needidle = 1;
  } else {
    frame += (ppszg.playspeed > 0) ? 1 : -1;
    dt = (frame - ppszg.framebase) / (ppszg.playspeed * path->fps)
	+ ppszg.playtimebase - now;
    needidle = (dt < .05);
  }

  if(needidle && !ppszg.playidling) {
    // XXX enable calling playidle() here  // Fl::add_idle( playidle, NULL );
    ppszg.playidling = 1;
  }
  if(!needidle && ppszg.playidling) {
    // XXX disable calling playidle() here // Fl::remove_idle( playidle, NULL );
    ppszg.playidling = 0;
  }
  ppszg.playidling = needidle;

  if(!needidle) {
    // XXX schedule calling playidle once, "dt" seconds from now // Fl::add_timeout( dt, playidle, NULL );
  }
}



void parti_play( const char *rate ) {
  struct wfpath *path = &ppszg.path;
  int playnow = 1;
  int awaitdone = 0;
  msg("Inside the parti_play!");
  msg(rate);
  if(rate != NULL) {
    char *ep;
    float sp = strtod(rate, &ep);
    printf("Rate: %f\n", sp);
    if(strchr(ep, 's')) playnow = 0;
    if(strchr(ep, 'l')) ppszg.playloop = 1;
    else if(strchr(ep, 'k')) ppszg.playloop = -1; /* rock */
    else if(strchr(ep, 'f') || strchr(ep, 'e')) ppszg.playevery = 1;
    else if(strchr(ep, 'r') || strchr(ep, 't')) ppszg.playevery = 0;
    if(strstr(rate, "wait"))
	awaitdone = 1;

    if(ep == rate) {
	/* No leading number -- did we get just "-" or "-?" or "-help"? */
	if(0==strcmp(rate, "-") || strchr(rate, '?') || strchr(rate, 'h')) {
	    msg("Usage: play [rate][s][l|k][f|r][wait]  e.g.  \"play\" or \"play 30\" or \"play 10kf wait\"");
	    msg("   rate  frames/sec; [s]et speed, don't play now; [l]oop/roc[k]");
	    msg("         play every [f]rame/skip to maintain [r]ate;  [wait] until done");
	    return;
	}
	if(sp == 0) playnow = 0;
	else ppszg.playspeed = sp;
    }
  }

  // Disable playidle() idle-function
  // Fl::remove_idle( playidle, NULL );
  // Fl::remove_timeout( playidle, NULL );
  ppszg.playing = 0;
  ppszg.playidling = 0;

  if(playnow) {
#if CAVE
    ppszg.playtimebase = .001 * ppszg.getTime();
#else
    ppszg.playtimebase = wallclock_time();
#endif
    printf("playtimebase: %f", ppszg.playtimebase);
    ppszg.framebase = path->curframe;
    if(ppszg.playspeed > 0 && path->curframe >= path->frame0+path->nframes-1)
	ppszg.framebase = path->frame0;
    else if(ppszg.playspeed < 0 && path->curframe <= path->frame0)
	ppszg.framebase = path->frame0 + path->nframes - 1;

    parti_setframe( ppszg.framebase );

    /* Switch into "play" mode */
    // XXX start calling playidle() idle-function 
    ppszg.playidling = 1;
    ppszg.playing = 1;
  }

}


// Idle function -- enabled when data-animation clock is running
void pp_stepper() {
  if(ppszg.st) {
    clock_tick( ppszg.st->clk );
    specks_set_timestep( ppszg.st );
  }
}

// Data animation
void parti_set_running( struct stuff *st, int on ) {
    if(clock_running(st->clk) != on) {
	if(on) {
	    // XXX enable pp_stepper idle function
	} else {
	    // XXX disable pp_stepper idle function, no longer needed (?)
	}
	if(st->clk) st->clk->walltimed = 1;
	clock_set_running(st->clk, on);
    }
}

void readrc( struct stuff **stp ) {
  char *rc = findfile( NULL, "~/.partiviewrc" );
  if(rc) specks_read( stp, rc );
  rc = findfile( NULL, "./.partiviewrc" );
  if(rc) specks_read( stp, rc );
}

static int cmdargs(int argc, char **argv, int & optind) {
  char *arg = argv[optind];
  if(!strcmp("-c", arg)) {
    arg = argv[optind+1];
    char *t = NewN(char, strlen(arg)+1);
    char *av[128];
    int ac = tokenize(arg, t, 128, av, NULL);
    specks_parse_args( &ppszg.st, ac, av );
    Free(t);
    optind += 2;
    return 1;
  }

  return 0;
}

int pp_parse_args( struct stuff **, int argc, char *argv[], char *fromfname, void * ) {
  if(argc < 1) return 0;

  int i;
  if(!strcmp( argv[0], "exit" )) {
      	exit(0);

  } else if(!strcmp( argv[0], "stereo" )) {
	for(i = 1; i < argc; i++)
	    parti_stereo( argv[i] );

	msg("stereo %s", parti_stereo(NULL));

  } else if(!strcmp( argv[0], "winsize" )) {
	msg("winsize %s", parti_winsize( rejoinargs( 1, argc, argv ) ) );

  } else if(!strncmp( argv[0], "snapset", 7 ) || !strcmp( argv[0], "snapshot" )) {
	char *frameno = NULL, *basename = NULL;
	char *size = NULL;
	int now = (0 == strcmp(argv[0], "snapshot"));
	while(argc > 2) {
	    if(!strcmp(argv[1], "-w")) {
		size = argv[2];
	    } else if(!strcmp(argv[1], "-n")) {
		frameno = argv[2];
	    } else
		break;
	    argc -= 2, argv += 2;
	}
	if(argc > 1)
	    basename = rejoinargs( 1, argc, argv );
	parti_snapset( basename, frameno, size );
	if(now) {
	    char snapinfo[1024]; 
	    if(parti_snapshot(snapinfo) >= 0)
		msg("Snapped %s", snapinfo);
	}

  } else if(!strcmp( argv[0], "clip" )) {
	msg("%s", parti_clip( argv[1], argc>2?argv[2]:NULL ) );

  } else if(!strcmp( argv[0], "pickrange" )) {
	float pickrange = parti_pickrange( argv[1] );
	msg("pickrange %g", parti_pickrange( NULL ));

  } else if(!strcmp( argv[0], "fov" ) || !strcmp( argv[0], "fovy" )) {
	float fovy = parti_fovy( (argc>1) ? argv[1] : NULL );
	msg("fovy %g", fovy);

  } else if(!strcmp( argv[0], "subcam")) {
	int index;
	if(argc > 1) {
	    if(!strcmp(argv[1], "-") || !strcmp(argv[1], "off")) {
		parti_select_subcam(0);
	    } else {
		index = parti_make_subcam( argv[1], argc-2, argv+2 );
		if(index > 0) {
		    parti_select_subcam(index);
		} else {
		    char *what = parti_subcam_list();
		    msg(what[0]=='\0' ?  "No subcams defined yet" :
					"Known subcams: %s", what);
		}
	    }
	}
	index = parti_current_subcam();
	if(index > 0) {
	    char params[100];
	    char *name = parti_get_subcam( index, params );
	    msg("subcam %s  %s # az el rol  L R B T", name, params);
	} else {
	    int wx, wy;
	    float hx, hy = .5 * parti_fovy(NULL);
	    sscanf( parti_winsize(NULL), "%d%*c%d", &wx, &wy );
	    if(wy == 0) wy = 1;
	    hx = atan( tan(hy*(M_PI/180))*wx / wy ) * (180/M_PI);
	    msg("subcam off  0 0 0  %g %g  %g %g # a e r   L R B T (default)",
			hx, hx, hy, hy);
	}

  } else if(!strncmp( argv[0], "focallen", 8 )) {
	float focallen = parti_focal( (argc>1) ? argv[1] : NULL );
	msg("focallength %g", focallen);
	
  } else if(!strcmp( argv[0], "focalpoint" )) {
        /* args:
	 * on/off
	 * x y z [minfocallen]
	 */
        Point fpt;
	float minlen;
	int ison = parti_focalpoint( argc-1,  (argv+1), &fpt, &minlen );

	msg(ison ? "focalpoint %g %g %g  %g # pointxyz minfocallen"
		 : "focalpoint off # %g %g %g  %g # pointxyz minfocallen",
		 fpt.x[0],fpt.x[1],fpt.x[2], minlen);


  } else if(!strncmp( argv[0], "jump", 4 )) {
    Point xyz;
    float aer[3];
    static int stupid[3] = {1,0,2};	/* aer -> rx ry rz */
    Matrix c2w;
    parti_getc2w( &c2w );
    tfm2xyzaer( &xyz, aer, &c2w );
    if(argc>1) {
      for(i=1; i<argc && i<4+3; i++) {
	if(i-1<3) xyz.x[i-1] = getfloat(argv[i], xyz.x[i-1]);
	else aer[stupid[i-4]] = getfloat(argv[i], aer[stupid[i-4]]);
      }
      xyzaer2tfm( &c2w, &xyz, aer );
      parti_setc2w( &c2w );
    }
    msg("jump %g %g %g  %g %g %g  (XYZ RxRyRz)",
	xyz.x[0],xyz.x[1],xyz.x[2],
	aer[1],aer[0],aer[2]);
   
  } else if(!strncmp( argv[0], "home", 4 )) {//marx version 0.7.03
    Point xyz;
    float aer[3];
    static int stupid[3] = {1,0,2};	/* aer -> rx ry rz */
    Matrix c2w;
    parti_getc2w( &c2w );
    tfm2xyzaer( &xyz, aer, &c2w );
    if(argc>1) {
      for(i=1; i<argc && i<4+3; i++) {
	if(i-1<3) xyz.x[i-1] = getfloat(argv[i], xyz.x[i-1]);
	else aer[stupid[i-4]] = getfloat(argv[i], aer[stupid[i-4]]);
      }
      xyzaer2tfm( &c2w, &xyz, aer );
      parti_setc2w( &c2w );
      ppszg.home[0] = xyz.x[0]; ppszg.home[1] = xyz.x[1];  ppszg.home[2]  = xyz.x[2];

    //ppszg.home[3] = aer[0];   ppszg.home[4] = aer[1];    ppszg.home[5]  = aer[2]; bug in releases >= 0.7.03 
      ppszg.home[3] = aer[1];   ppszg.home[4] = aer[0];    ppszg.home[5]  = aer[2]; //version 0.7.05 fix for the line above

    }
    msg("home %g %g %g  %g %g %g  (XYZ RxRyRz)", ppszg.home[0], ppszg.home[1], ppszg.home[2], ppszg.home[3], ppszg.home[4], ppszg.home[5]);  

  } else if((!strcmp( argv[0], "rdata" ) || !strcmp(argv[0], "readpath"))
		&& argc>1) {
	bool freet = false;
	char *tfname = argv[1];
	char *realfile = findfile( fromfname, tfname );
	if(realfile == NULL) {
	    tfname = (char *)malloc(strlen(argv[1]) + 32);
	    freet = true;
	    sprintf(tfname, "data/record/%s%s", argv[1],
		strstr(argv[1], ".wf") ? "" : ".wf");
	    realfile = findfile( NULL, tfname+12 );
	}
	if(realfile == NULL)
	    realfile = findfile( fromfname, tfname );
	if(realfile)
	    parti_readpath( realfile );
	else
	    msg("%s: can't find \"%s\" nor data/record/... nor ...wf",
		argv[0],argv[1]);
	if(freet)
	    free(tfname);

  } else if(!strcmp( argv[0], "play" )) {
	parti_play( argc>1 ? rejoinargs(1, argc, argv) : NULL );

  } else if(!strcmp( argv[0], "frame" )) {
	CONST struct wfpath *pp;
	i = parti_frame( argv[1], &pp );
	msg("frame %d (of %d..%d)", pp->curframe,
		pp->frame0, pp->nframes + pp->frame0 - 1);

  } else if(!strcmp( argv[0], "interest") || !strcmp( argv[0], "int" ) ||
		!strcmp( argv[0], "center" ) ||
		!strcmp( argv[0], "cen" )) {
	Point cen;
	float censize = parti_getcensize();
	parti_getcenter( &cen );
	if(argc > 1) {
	    sscanf(argv[1], "%f%*c%f%*c%f%*c%f",
		&cen.x[0],&cen.x[1],&cen.x[2], &censize);
	    if(argc > 3)
		for(i=0;i<3;i++)
		    cen.x[i] = getfloat(argv[i+1], cen.x[i]);
	    if(argc == 3 || argc == 5)
		sscanf(argv[argc-1], "%f", &censize);
	    parti_center( &cen );
	    if(censize != parti_getcensize())
		parti_censize( censize );
	}
	msg("center %g %g %g  %g(radius)", cen.x[0],cen.x[1],cen.x[2], censize);

  } else if(!strcmp( argv[0], "censize" )) {
	if(argc>1)
	    parti_censize( getfloat(argv[1], parti_getcensize()));
	msg("censize %g  (interest-marker size)", parti_getcensize());

  } else {
	return 0;
  }

  return 1;
}

void pp_ui_postinit() {
  parti_set_timebase( ppszg.st, 0.0 );
  parti_set_timestep( ppszg.st, 0.0 );
  parti_set_running( ppszg.st, 0 );
  parti_set_fwd( ppszg.st, 1 );
}

void clearBuffer(char* buf, int bufsize)
{
  for(int i=0; i < bufsize; i++)
  {
    buf[i] = '\0';
  }
}

PpCmd::PpCmd( CmdType type, int argc, char **argv )
{
    this->type = type;
    this->argc = argc;
    this->argv = argv;
}

PpCmd::~PpCmd() {
}

int PpCmd::frozenLen() const {
    int len = 2*argc+2;
    for(int i = 0; i < argc; i++)
	len += strlen(argv[i]);
    return len;
}

/* Serialize the command, and append result to 'buf' */
int PpCmd::freezeInto( string &buf ) const { 
    int ineed = buf.size() + frozenLen() + 1;

    if(buf.capacity() < ineed)
	buf.reserve( 2*ineed+1 );

    for(int i = 0; i < argc; i++) {
	buf.append( 1, (char)type );
	buf.append( argv[i], strlen(argv[i])+1 );
    }
    buf.append( 1, '\000' );	// mark end of last arg
    return buf.size();
}

int PpCmd::thawFrom( char *buf, int &offset, bool copystrings ) {

    if(buf == 0)
	return 0;

    int p = offset;

    argc = 0;
    argv = 0;
    type = (CmdType) buf[p];
    switch(type) {
    case CMD_DATA:
    case CMD_CONTROL:
	break;		// OK.

    case CMD_NONE:
	offset = p;
	return -1;	// EOF

    default:
	msg("PpCmd::thawFrom(): Trouble decoding %p at offset %d (got 0x%x not 0/1/2)", buf, p, buf[p]);
	type = CMD_NONE;
	return -1;
    }

    // count args
    do {
	p++;
	p += strlen( &buf[p] ) + 1;
	argc++;
    } while(buf[p] != 0);
    argv = new char *[argc+1];

    // copy args
    p = offset;
    for(int a = 0; a < argc; a++) {
	p++;
	argv[a] = copystrings ? strdup(&buf[p]) : &buf[p];
	p += strlen(&buf[p]) + 1;
    }
    argv[argc] = 0;
    offset = p+1;
    return argc;
}

Ppszg::~Ppszg() {
    delete scene;
    // delete clk;  Don't delete the clock -- might be shared with other objects
}
    

Ppszg::Ppszg() {
    scene = new PvScene();
    st = NULL;
    clipnear_ = 0.1;
    clipfar_ = 2500;
    focallen_ = 3;

    snapfmt = NULL;
    snapfno = 0;
    pickrange = 3.5;
    
    memset(home, 0, sizeof(home));

    clk = new SClock();
    clock_init( clk );
    clock_set_speed( clk, 1.0 );
    clk->continuous = 1;
    clk->walltimed = 1;

    memset( &path, 0, sizeof(path) );
    playing = 0;
    playevery = 0;
    playidling = 0;
    playspeed = 20;
    framebase = 1;
    playtimebase = 0;
    playloop = 0;

    timebasetime = 0;

    subcam = 0;

    /* arSockets - originally from salimiman by Jim Crowell*/
    /* I'm not sure if some of this should be done elsewhere */
    listenSocket = new arSocket(AR_LISTENING_SOCKET);
    listenSocket->ar_create();
    listenSocket->setSendBufferSize(2048);
    listenSocket->setReceiveBufferSize(2048);
    listenSocket->reuseAddress(true);
    list<string> acceptMask;
    listenSocket->setAcceptMask(acceptMask);
    /* Bind to port 8675 */
    listenSocket->ar_bind(NULL, 8675);
    listenSocket->ar_listen(256);
    /* List of connected arSockets */
    socketList = NULL;
}


bool Ppszg::onStart( arSZGClient& SZGClient ) {
  /*
   * Add Transfer Fields to Framework so data is
   * accessible and syncronized to all clients
   * Added July 23, 2008 William Davis
   */
  addTransferField("curtime", &(ppszg.clk->curtime), AR_DOUBLE, 1);
  addTransferField("running", &(ppszg.clk->running), AR_INT, 1);
  addTransferField("continuous", &(ppszg.clk->continuous), AR_INT, 1);
  addTransferField("center_", &(ppszg.scene->center_), AR_FLOAT, 3);
  addTransferField("censize_", &(ppszg.scene->censize_), AR_FLOAT, 1);
  addInternalTransferField( "commands", AR_CHAR, 1 );

  parti_bgcolor( "0 0 0" );

  parti_add_commands( pp_parse_args, "partiview", NULL );
  parti_add_reader( pp_read, "partiview", NULL );
  plugin_init();


  // pp_ui_init();
  // ppui.view->pickbuffer( COUNT(pickbuffer), pickbuffer );

  parti_clip( "0.1", "2500" );
  parti_censize( 1.0 );
  // parti_pickrange( 3.5 );

  parti_object( "g1", NULL, 1 );
  readrc( &ppszg.st );

  for(int i = 1; i < globalArgc; i++) {
    specks_read( &ppszg.st, globalArgv[i] );
  }

  // pp_ui_postinit();

  return true;
}

#if 0
bool arMasterSlaveFramework::addInternalTransferField( std::string fieldName,
                                                         arDataType dataType,
                                                         int numElements );

The pointer argument is omitted, and the numElements argument now denotes the initial size. The size can be changed by calling:

  bool arMasterSlaveFramework::setInternalTransferFieldSize( std::string fieldName,
                                                             arDataType dataType,
                                                             int newSize );
#endif


void Ppszg::onWindowStartGL( arGUIWindowInfo * ) {
  set_dsp_context( 0 );	/* assign a display context, so texture code can track window changes */
}

void Ppszg::onPreExchange( void ) {	// called on master only
  // do navigation...
  // process commands read from network...
  //   and package any with
  /* Here we want to do the listening on the sockets to interpret
   * any commands we might receive over them
   */
  static int socketid = 0;
  if(ppszg.listenSocket->readable())
  {
    bufferedSocket* sockp = new bufferedSocket();
    arSocketAddress addr;
    ppszg.listenSocket->ar_accept(sockp, &addr);

    sockp->setID(socketid++);
    sockp->next = ppszg.socketList;
    ppszg.socketList = sockp;
  }

  cmdbuf_.resize( 1 );		// wipe master->slaves buffer for this cycle

  /* Now read data into the buffer */
  bufferedSocket* sock = ppszg.socketList;
  bufferedSocket** previousSocketp = &ppszg.socketList;
  while( sock != NULL ) {
	int len = sock->poll();
	while(len >= 0) {		// repeat for all the lines in buffer

            int maxargs = len/2 + 2;    // max possible number of args
            char **av = new char *[maxargs];
            char tbuf[ 2*len + 1024 ];
		// temp buffer.  Extra room allows for $expansion etc.
		// XXX should pass this length to tokenize!

            int ac = tokenize( sock->str(), tbuf, maxargs, av, NULL );

            if(ac > 0) {
		// Not a comment nor a blank line.  Try to parse it locally.
		if( ! specks_parse_args( &ppszg.st, ac, av ) ) {
		    msg("Unrecognized control command: %s", sock->str());
		}
		// In any case, pass it on to slaves
		PpCmd cmd( CMD_CONTROL, ac, av );
		cmd.freezeInto( cmdbuf_ );
            }
	    delete av;

            len = sock->consume();      // Done processing that line.  Got another on hand?
	}
	if(sock->expired()) {
	   *previousSocketp = sock->next;
	   delete sock;
	} else {
	    previousSocketp = &sock->next;
	}
	sock = *previousSocketp;
  }

  // Now ship bundled commands to slaves
  setInternalTransferFieldSize( "commands", AR_CHAR, cmdbuf_.size() );

  if(cmdbuf_.size() > 1 && getenv("CMDTEST")) {
	PpCmd tcmd;
	int offset = 0;
	int room = cmdbuf_.size();
	    msg("Got %d-bytes of command packet", room-1);	// XXX DEBUG
	while(offset+1 < room) {
	    int oof = offset;
	    tcmd.thawFrom( &cmdbuf_[0], offset, false );
	    // false -> build argc/argv which point into cmdp string directly
	    msg("Offset %d..%d: command [%d] %s", oof,offset, tcmd.argc, rejoinargs( 0, tcmd.argc, tcmd.argv ));
	}
  }

  int room = 0;
  char *cmdp = (char *)getTransferField( "commands", AR_CHAR, room );
  if(cmdp == 0 || room != cmdbuf_.size()) {
    msg("panic: transfer field size %d != the size %d we told it to be!\n",
		room, cmdbuf_.size());
  } else {
       memcpy( cmdp, &cmdbuf_[0], room );
  }

  /*
   * Added July 23, 2008 William Davis
   * Forces the framework to listen to updates to navigation matrix
   * changed from a "jump" command
   */ 
  navUpdate();

  clock_tick( ppszg.clk );		// let time advance
}

void Ppszg::onPostExchange( void )	// called on master + slaves
{
    if(! this->getMaster() ) {
	// We're a slave.  Decode any commands passed by master this cycle.
	int room = 0;
	char *cmdp = (char *)getTransferField( "commands", AR_CHAR, room );
	if(cmdp == 0) {
	    static int once = 1;
	    if(once) msg("Ppszg::onPostExchange: can't get transfer field \"commands\"");
	    once = 0;
	    return;
	}
	PpCmd pp;
	int offset = 0;
	if(room > 1)
	    msg("Got %d-bytes of command packet", room-1);	// XXX DEBUG
	while(offset+1 < room) {
	    pp.thawFrom( cmdp, offset, false );
	    // false -> build argc/argv which point into cmdp string directly
	    msg("Processing command %s", rejoinargs( 0, pp.argc, pp.argv ));	// XXX DEBUG
	    if(pp.type == CMD_CONTROL) {
		if(! specks_parse_args( &ppszg.st, pp.argc, pp.argv ) ) {
		    msg("Unrecognized control command on slave: %s",
			rejoinargs( 0, pp.argc, pp.argv ));
		}
	    } else {
		msg("can only handle control cmds, not: %s",
		    rejoinargs(0, pp.argc, pp.argv));
	    }
	}
    }
}

void Ppszg::onWindowInit( void ) {	// clear to background...
    glClearColor( bgcolor.x[0], bgcolor.x[1], bgcolor.x[2], 1 );
    ar_defaultWindowInitCallback();
}

void Ppszg::onDraw( arGraphicsWindow& win, arViewport& vp ) {
    /*
     * Addded July 23, 2008 William Davis
     * Loads the navigation matrix into the framework, making
     * sure we listen to the changes enacted by the "jump" command
     * in parti_setc2w()
     */
    loadNavMatrix();

    scene->draw();
}

void Ppszg::onDisconnectDraw( void ) {
    glClearColor( 1, .1, .1, 1 );
    glClear( GL_COLOR_BUFFER_BIT );
}

void Ppszg::onCleanup( void ) {
}

void Ppszg::onUserMessage( const string& messageBody ) {
    // called in master on "dex <N> user <some message for this program>"
    // parse messageBody, add to command queue for next preExchange time
}

#ifdef __linux__
#include <GL/glut.h>	// hack: Syzygy uses glutWireSphere() in simulator mode, but freeglut insists on glutInit() first
#endif

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

#ifdef __linux__
    int targc = 1;		// rest of hack.
    glutInit( &targc, argv );
#endif

    globalArgc = argc; //Make these two variables accessible to the
    globalArgv = argv; // onStart callback

    if( ! ppszg.init( argc, argv ) ) {
	fprintf(stderr, "arMasterSlaveFramework::init() failed, giving up...\n");
	exit(1);
    }

    if( ! ppszg.start() ) {
	fprintf(stderr, "arMasterSlaveFramework::start() failed, giving up...\n");
	exit(1);
    }
    // Shouldn't reach here.
    fprintf(stderr, "Shouldn't reach here.\n");
    exit(2);
}