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

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

#include <GL/gl.h>
#include "Gview.H"
#include "partiview.H"
#include "geometry.h"
#include "shmem.h"
#include <string.h>
#include <stdarg.h>
#include <math.h>
#include <errno.h>

#include "partiviewc.h"

#include <FL/Fl.H>
#include <FL/fl_file_chooser.H>

struct _ppui  ppui;


struct stuff *stuffs[MAXSTUFF];

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

#define MAXARGS 128
  char *av[MAXARGS];
  int ac;
  char *txt = (char *)alloca( 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;
  return specks_parse_args( stp, ac,av);
}

int specks_commandfmt( struct stuff **stp, const char *fmt, ... ) {
  char cmd[1024];
  int ok;
  va_list args;
  va_start(args, fmt);
  vsprintf(cmd, fmt, args);
  va_end(args);
  ok = specks_commandstr( stp, cmd );
  if(ok && ppui.view) ppui.view->redraw();
  return ok;
}


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

void pp_nav_init(Fl_Menu_Button *);
int  pp_viewevent_cb( Fl_Gview *, int ev );

void pp_ui_init() {

  pp_nav_init(ppui.nav);

  /* ppui.cmd->handle_cb = pp_cmd_handle_cb;  No, this doesn't work yet */

  ppui.view->eventhook = pp_viewevent_cb;
}


void pp_cmd_cb( HistInput* inp, void * ) {
  if(inp->hist())
    inp->hist()->addline( inp->value(), 0 );
  specks_commandstr( &ppui.st, inp->value() );
  ppui_refresh( ppui.st );
  ppui.view->redraw();
}


static enum Gv_Nav codes[] = { GV_FLY, GV_ORBIT, GV_ROTATE, GV_TRANSLATE };
static char *navtitles[] =   {"[f]ly","[o]rbit","[r]ot","[t]ran"};

void pp_nav_init(Fl_Menu_Button *m) {
  m->add("[f]ly|[o]rbit|[r]ot|[t]ran");

  for(int i = m->size(); --i >= 0; ) 
    (((Fl_Menu_Item *)(m->menu()))[i]).labelcolor( m->labelcolor() );
}

void pp_nav_cb(Fl_Menu_Button* m, struct stuff **vst) {
  int v = m->value();
  if(v >= 0 && v < COUNT(codes))
    ppui.view->nav( codes[v] );
}   

void pp_obj_cb(Fl_Menu_Button* m, void *) {
  parti_object( m->text(), &ppui.st );
}

void pp_objtog_cb(Fl_Button* b, void *) {
  int objno = 1;
  sscanf(b->label(), "g%d", &objno);
  if(objno<0||objno>=MAXSTUFF||stuffs[objno]==NULL)
    return;
  stuffs[objno]->useme = !stuffs[objno]->useme;
  ppui_refresh( ppui.st );
  ppui.view->redraw();
}
  

void pp_slum_cb(Fl_Value_Slider* sl, struct stuff ** ) {
  struct stuff *st = ppui.st;
  int cd = st->curdata, by = st->sizedby;
  if((unsigned int)cd >= MAXFILES) cd = 0;
  if((unsigned int)by >= MAXVAL+1) by = 0;
  struct valdesc *vd = &st->vdesc[cd][by];
  vd->lum = pow(10., sl->value());
  ppui.view->redraw();
}

void pp_rdata_cb( Fl_Button *, struct stuff ** ) {
  struct stuff *st = ppui.st;
  const char *result;
  result = fl_file_chooser("Choose virdir .wf path", "*.wf", NULL);
  if (result)
    parti_readpath( st, result );
}

void pp_playframe_cb( Fl_Counter *counter, struct stuff ** ) {
  struct stuff *st = ppui.st;
  if(ppui.playing)
    parti_play( st, "0" );
  parti_setframe( st, (int)counter->value() );
}

void pp_playtime_cb( Fl_Value_Slider *slider, struct stuff ** ) {
  struct stuff *st = ppui.st;
  struct wfpath *path = &ppui.path;
  if(ppui.playing)
    parti_play( st, "0" );
  parti_setframe( st, (int) (slider->value() * path->fps + path->frame0) );
}

void pp_play_cb( Fl_Button *play, struct stuff ** ) {
  if(Fl::event_state(FL_BUTTON3)) {
    ppui.playmenu->popup();
  } else {
    parti_play( ppui.st,
	play->value() ? NULL /*play at default speed*/ 
		      : "0"  /*stop*/
	);
  }
}

int pp_viewevent_cb( Fl_Gview *view, int ev ) {

  char snapinfo[1024];
  int fno;
  double bump;
  struct stuff *st = ppui.st;

  if(ev == FL_KEYBOARD || ev == FL_SHORTCUT) {

    if(ppui.cmdhist && ppui.cmdhist->handle_nav(ev))
	return 1;

    switch(Fl::event_key()) {
    case FL_Print:
	if(view->num.has) {
	    ppui.snapfno = view->num.value();
	    view->num.has = 0;
	}
	fno = parti_snapshot(snapinfo);
	if(fno >= 0)
	    msg("Snapped %s", snapinfo);
	return 1;
    }
    switch(Fl::event_text()[0]) {

    case '\t':
	if(!ppui.cmdhist || !ppui.cmdhist->input())
	    return 0;
	ppui.cmdhist->input()->take_focus();
	return 1;

    case 'S':
	if(view->num.has) {
	    float v = view->num.fvalue();
	    if(v == 0) {
		parti_stereo("off");
	    } else {
		if(fabs(v) < 1)		/* plausible? */
		    view->stereosep( v );
		parti_stereo("on");
	    }
	    view->num.has = 0;
	} else {
	    parti_stereo( view->stereo()==GV_MONO ? "on" : "off" );
	}
	msg("stereo %s (focallen %g)",
		parti_stereo(NULL), view->focallen());
	return 1;

    case '>': bump = 1; goto step;
    case '<': bump = -1;
     step:
	static double bumpunit;
	if(view->num.has) bumpunit = view->num.fvalue();
	view->num.has = 0;
	st->time0 += bump * bumpunit;
	st->timeplay = 0;
	specks_set_time( st, 0.0 );
	// ppui_refresh();  ???  Yes, after we add an animation panel
	view->redraw();
	return 1;
    }
  }

  return 0;
}

void ppui_refresh( struct stuff *st ) {

  if(ppui.view == NULL) return;

  /* Possibly switch selected object */
  int targ = ppui.view->target();

  if(targ >= 0 && targ < MAXSTUFF && stuffs[targ] != NULL && stuffs[targ] != ppui.st) {
    st = ppui.st = stuffs[targ];
  }

  enum Gv_Nav nav = ppui.view->nav();
  for(int navcode = 0; navcode < COUNT(codes); navcode++) {
    if(codes[navcode] == nav) {
	ppui.nav->value( navcode );
	if(strcmp(ppui.nav->label(), navtitles[navcode])) {
	    ppui.nav->label( navtitles[navcode] );
	    ppui.nav->parent()->redraw();
	}
    }
  }

  int tno, maxobject = 0;
  for(tno = 0; tno < MAXSTUFF; tno++) {
    if(stuffs[tno]) maxobject = tno;
    if(ppui.st == stuffs[tno]) {
	char oname[12];
	Fl_Font lfont = ppui.view->movingtarget()
					? FL_HELVETICA_BOLD_ITALIC
					: FL_HELVETICA;
	sprintf(oname, "[g%d]", tno);

	if(strcmp(ppui.obj->label(), oname) || lfont != ppui.obj->labelfont()) {
	    ppui.obj->label( strdup( oname ) );
		// Iff we might actually move the target object,
		// display its [gN] title in BOLD ITALICs.
	    ppui.obj->labelfont( lfont );
	    ppui.obj->parent()->redraw();
	}
    }
  }

  if(st == NULL)
    return;

  if(ppui.objgroup->active() != st->useme) {
    if(st->useme) ppui.objgroup->activate();
    else ppui.objgroup->deactivate();
  }

  // objtogs is wrapped in a Group which is invisible by default.
  if(!ppui.objtogs->parent()->visible() && maxobject > 1) {
    ppui.objtogs->parent()->show();
  }

  if(ppui.objtogs->visible()) {
    Fl_Button *b, *b0 = (Fl_Button *)ppui.objtogs->child(0);
    for(tno = 0; tno <= maxobject; tno++) {
	if(tno >= ppui.objtogs->children()) {
	    char name[8];
	    sprintf(name, "g%d", tno);
	    b = new Fl_Button( b0->x(),b0->y(),b0->w(),b0->h(), strdup(name) );
	    b->labelcolor( b0->labelcolor() );
	    b->color( b0->color() );
	    b->selection_color( b0->selection_color() );
	    b->down_box( b0->down_box() );
	    b->type( b0->type() );
	    b->callback( (Fl_Callback*)pp_objtog_cb );

	    ppui.objtogs->add( b );
	}
	b = (Fl_Button *)ppui.objtogs->child(tno);
	
	if(stuffs[tno] == NULL) {
	    if(b->visible()) b->hide();
	} else {
	    if((b->value() != 0) != stuffs[tno]->useme)
		b->value( stuffs[tno]->useme );
	    if(!b->visible()) b->show();
	}
    }
  }
    

  ppui.point->value( st->usepoint );
  ppui.poly->value( st->usepoly );
  ppui.label->value( st->usetext );
  ppui.texture->value( st->usetextures );
  ppui.box->value( st->useboxes!=0 );
  int boxcolor = ( st->useboxes==2 ? 1/*red*/ : 2/*green*/ );
  if(boxcolor != ppui.box->color2()) {
    ppui.box->color2( boxcolor );
    ppui.box->redraw();
  }


  int cd = st->curdata, by = st->sizedby;
  if((unsigned int)cd >= MAXFILES) cd = 0;
  if((unsigned int)by >= MAXVAL+1) by = 0;
  struct valdesc *vd = &st->vdesc[cd][by];
  float lum = vd->lum;
  if(lum <= 0) lum = 1;
  ppui.slum->value( log10( lum ) );
  char slumlabel[20];
  if(st->vdesc[cd][by].name[0])
    sprintf( slumlabel, "logslum %.10s", vd->name );
  else
    sprintf( slumlabel, "logslum(var%d)", by );
  if(strcmp(ppui.slum->label(), slumlabel)) {
    static char slumlab[20];
    strcpy(slumlab, slumlabel);
    ppui.slum->label(slumlab);
    ppui.slum->parent()->redraw();
  }
}


void pp_viewchanged( Fl_Gview *gview, void *st ) {
  ppui_refresh( ppui.st );
}


int msg( const char *fmt, ... ) {
  char str[10240];

  va_list args;
  va_start(args, fmt);
  vsprintf(str, fmt, args);
  va_end(args);

  if(ppui.cmdhist)
    ppui.cmdhist->addline( str, 1 );

  return printf("%s\n", str);
}

void quietwarning( const char *fmt, ... ) {
  char msg[10240];
  static char avoid[] = "X_ChangeProperty: ";
  va_list args;
  va_start(args, fmt);
  vsprintf(msg, fmt, args);
  va_end(args);
  if(0!=strncmp(msg, avoid, sizeof(avoid)-1))
    fputs(msg, stderr);
}


void drawjunk(Fl_Gview *view, void *obj, void *arg) {
  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};
  float censize = parti_getcensize();
  int i;
  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();
    const Point *cen = view->center();
    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();
  }
}

int main(int argc, char *argv[])
{
  GLuint pickbuffer[20480];

  Fl::warning = quietwarning;

  ppui.mainwin = make_window();

  static Point black = {0,0,0};
  ppui.view->bgcolor( &black );

  /* make_window() sets ppui.view, etc. */

  pp_ui_init();
  ppui.view->add_drawer( drawjunk, NULL, NULL, NULL, 0 );
  ppui.view->pickbuffer( COUNT(pickbuffer), pickbuffer );

  ppui.view->zspeed = 5;
  ppui.view->farclip( 2500 );
  ppui.censize = 1.0;

  ppui.view->movingtarget( 0 );
  ppui.view->msg = msg;

  ppui.playspeed = 1;
  ppui.playframe->lstep(10);

  parti_object( "g1", NULL );
  for(int i = 1; i < argc; i++) {
    specks_read( &ppui.st, argv[i] );
  }

  ppui.view->notifier( pp_viewchanged, ppui.st );
  ppui_refresh( ppui.st );

  ppui.mainwin->show(1, argv);
  ppui.view->show();
  return Fl::run();
}