/* vrmlview.C: simple VRML'97 model viewer */

#include "xrml.H"
#include "oglrenderer.H"

#include "ui.h"
#include "uit.h"
#include "render.h"
#include "camera.h"
#include "canvas.h"
#include "matrix.H"

#include "world.H"
#include "vrml2_utf8/parser.H"
#include "vrml2_utf8/exporter.H"
#ifdef PLY
#include "ply/importer.H"
#endif
#ifdef CSO
#include "cso/importer.H"
#endif
#include "error.H"

#include <stdio.h>
#include <sys/time.h>
#include <unistd.h>

#include "options.h"

static char* progname = 0;
static double start_time = 0.;
static class world *wrl = 0;
static class opengl_renderer* render = 0;
static bool first_frame = false;

typedef enum IN_FILTER { IN_VRML, IN_PLY, IN_CSO } IN_FILTER;
static ENUMDESC inFiltVals[] = {
  { IN_VRML, "vrml", 4 },
  { IN_PLY,  "ply" , 3 },
  { IN_CSO, "so", 3 },
  { 0, NULL, 0 }     // sentinel
};
MakeEnumOptTypeStruct(inFiltTypeStruct, inFiltVals);
#define TInFilt (&inFiltTypeStruct)

static xrml::importer* the_importer = 0;

static void no_support(char *what)
{
  xrml::Fatal(-1, NULL, "%s has been compiled without %s support", progname, what);
}

static void SetImporter(void *value)
{
  IN_FILTER which = *(IN_FILTER*)value;
  switch (which) {
  case IN_VRML: 
    the_importer = new vrml2_utf8::parser;  
    break;
  case IN_PLY: 
#ifdef PLY
    the_importer = new ply::importer; 
#else
    no_support("ply input");
#endif
    break;
  case IN_CSO: 
#ifdef CSO
    the_importer = new cso::importer; 
#else
    no_support("C++/so input");
#endif
    break;
  default:
    cerr << "Unrecognized import filter identification number " << which << "\n";
    exit(-1);
  }
}

// forward declaration
static void ShowUsage(void*);

static void SetIfsHackOpt(void* value)
{
  renderopts.ifs_hack = TRUE;
}

static CMDLINEOPTDESC options[] = {
  {"-input-file-format", 2, TInFilt, NULL, SetImporter,
   "-input-file-format  vrml|ply     : input file format"},
  {"-hack", 3, TYPELESS, NULL, SetIfsHackOpt,
   "-hack                        : hack for faster IndexedFaceSet rendering"},
  {"-help", 3, TYPELESS, NULL, ShowUsage,
   "-help                            : explain usage of this program"},
  {NULL	, 	0,	TYPELESS, 	NULL, 	DEFAULT_ACTION,
   NULL} // sentinel
};

static void ShowUsage(void*)
{
  fprintf(stdout, "%s: quick and dirty 3D model viewer.\n", progname);
  fprintf(stdout, "\n");
  fprintf(stdout, "Usage:\n");
  fprintf(stdout, "\n");
  fprintf(stdout, "\t%s [options] [input-file-names]\n", progname);
  fprintf(stdout, "\n");
  fprintf(stdout, "Options:\n");
  fprintf(stdout, "\n");
  PrintOptions(stdout, options);
  fprintf(stdout, "\n");
  fprintf(stdout, "%s reads the input files in order, converting them to\n", progname);
  fprintf(stdout, "an XRML scene graph and displays them. If no input files are\n");
  fprintf(stdout, "given on the command line, input files still can be opened\n");
  fprintf(stdout, "using the menus in the program.\n");
  fprintf(stdout, "\n");
  fprintf(stdout, "Note: not all input/output file formats listed above may be supported.\n");
  fprintf(stdout, "\n");
  exit(0);
}

// returns number of seconds since Januari, 1st, 1970.
double get_time(void)
{
  struct timeval t;
  struct timezone tz = {0, 0};
  gettimeofday(&t, &tz);
  return (double)t.tv_sec + (double)t.tv_usec * 1e-6;
}

static double last_frame_time = 0.;
double get_frame_time(void)
{
  if (renderopts.animate)
    return last_frame_time = get_time();
  else
    return last_frame_time;
}

void SetDefaultRenderOptions(void)
{
#ifndef DEFAULT_GAMMA
#define DEFAULT_GAMMA 1.0
#endif
  RENDEROPTIONS default_renderopts = {
    FALSE, TRUE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, BC_ON,
    RED, YELLOW, BLACK, DEFAULT_GAMMA,
    0., false
  };

  renderopts = default_renderopts;
}

void SetDefaultCamera(void)
{
  VECTOR eyep = {0., 0., 10.}, centre = {0., 0., 0.}, up = {0., 1., 0.};
  RGB Black = BLACK;
  CameraSet(&Camera, &eyep, &centre, &up, 45.0/2., -1, -1, &Black);
}

void SetBackfaceCulling(int mode)
{
  renderopts.backface_culling = (BACKFACECULLINGMODE)mode;
  render->dynamicBackfaceCulling = (mode == BC_DYNAMIC);
}

void ReadScene(int nrfiles, char **fname)
{
  if (wrl) delete wrl;

  wrl = new class world;

  // import the scene graph from the input files
  if (!the_importer) the_importer = new vrml2_utf8::parser;
  for (int i=0; i<nrfiles; i++) {
    xrml::importer *imp = the_importer->instantiate();
    cerr << "Parsing " << fname[i] << "\n";
    if (!wrl->parse(fname[i], start_time, imp))
      break;
    delete imp;
  }
  delete the_importer;

  if (!wrl->sceneGraph)	// read the file.
    return;

  wrl->set_renderer(render);

  // get default viewpoint
  render->default_view = wrl->stacks->viewpoint.top();

  NavigationInfo* ninfo = wrl->stacks->navigationInfo.top();
  if (ninfo->headlight)
    renderopts.enable_headlight = TRUE;

  // initialize world bounding box
  render->min = Vec3(HUGE, HUGE, HUGE);
  render->max = Vec3(-HUGE, -HUGE, -HUGE);

  first_frame = true;
  RenderScene();
}

CAMERA ViewCamera(Viewpoint *view)
{
  CAMERA cam;

  Vec3 eye, centre, up;
  Mat4 view_orientation;
  view_orientation.rotate(Vec4(view->orientation));

  eye = Vec3(view->position);
  centre = Vec3(0., 0., -10) * view_orientation + eye;
  up = Vec3(0., 1., 0.) * view_orientation;

  VECTOR ceyep  = {eye.x, eye.y, eye.z},
	 clookp = {centre.x, centre.y, centre.z},
	 cupdir = {0., 1., 0.};

  CameraSet(&cam, &ceyep, &clookp, &cupdir,
	    view->fieldOfView * 180./M_PI / 2.,
	    Camera.hres, Camera.vres, &Camera.background);

  return cam;
}

void SetDefaultView(void)
{
  if (wrl && wrl->stacks)
    Camera = ViewCamera(wrl->stacks->viewpoint.top());
}

void SetPredefinedViews(void)
{
  int i, nr = render->views.size;
  CAMERA *cams; cams = new CAMERA[nr];
  char **descr; descr = new char *[nr];

  for (i=0; i<nr; i++) {
    cams[i] = ViewCamera(render->views[i]);
    descr[i] = (char *)render->views[i]->description;
  }

  CreatePredefViewButtons(nr, cams, descr);

  delete[] cams;
  delete[] descr;
}

void SetInfo(void)
{
  if (render->winfo)
    SetModelInfo((char *)render->winfo->title);
  else
    SetModelInfo("No model info");
}

void RenderScene(void)
{
  double start_render_time = get_time();

  if (!RenderStartFrame())
    return;

  if (first_frame) {
    start_time = wrl->time;
  }

  render->enable_headlight = renderopts.enable_headlight;
  render->disable_textures = renderopts.disable_textures;
  render->use_dlists = renderopts.use_display_lists;
  render->draw_outlines = renderopts.draw_outlines;

  CanvasPushMode(CANVASMODE_RENDER);

  if (wrl && wrl->sceneGraph) {
    Viewpoint *vp = render->default_view;
    Vec3 oldmin = render->min, oldmax = render->max;

    if (first_frame) {
      render->first_frame = true;
      wrl->set_time(last_frame_time = get_time());
      start_time = wrl->time;
      wrl->newframe();	// render with time of parsing
      render->ninfo = 0;
      wrl->render();
      SetPredefinedViews();
      SetInfo();
      SetDefaultView();
      render->first_frame = first_frame = false;
    }

    wrl->set_time(get_frame_time());
    wrl->newframe();
    wrl->render();
    
    if (!vp || render->min != oldmin || render->max != oldmax) {
      BOUNDINGBOX bounds = {render->min.x, render->min.y, render->min.z, 
			      render->max.x, render->max.y, render->max.z};
      RenderSetBoundingBox(bounds);

      // clipping planes need to be changed ...
      RenderStartFrame();

      wrl->render();
    }
    RenderEndFrame();
  }

  double render_time = get_time() - start_render_time;
  if (render_time > 0.) {
    char buf[100];
    sprintf(buf, "vv: %.3g fps", 1./render_time);
    SetDialogTitle(topLevel, buf);
  }
  CanvasPullMode();
}

int main(int argc, char **argv)
{
  SetInfoCallback(printmsg);

  render = new opengl_renderer;

  char *slash = strrchr(argv[0], '/');
  progname = slash ? slash+1 : argv[0];

  // ParseOptions removes the arguments from argc/argv that
  // it recognizes.
  ParseOptions(options, &argc, argv);

  if (argc > 1)
    ReadScene(argc-1, argv+1);

  SetDefaultRenderOptions();
  SetDefaultCamera();
  StartUserInterface(argc, argv);
  return 0;	/* StartUserInterface() actually never returns. */
}
