/* ui_camera.c: camera menu */

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

#include "ui.h"
#include "uit.h"
#include "camera.h"
#include "transform.h"
#include "render.h"
#include "pools.h"
#include "error.h"

static Widget viewsBox, viewButtonsForm; 	/* dialog with predefined views */
static int nrviews = 0, curview = -1;
static Widget *viewButtons;
static CAMERA *cams;
static float anim_time = 5., pause_time = 0.;
static Widget fpsLabel;		/* label widget displaying the frames/sec rate */
static int movebusy = FALSE;

static void UpdateFramesPerSecond(double fps)
{
  char s[100];
  sprintf(s, "%.3g frames/second", fps);
  SetLabelString(fpsLabel, s);
}

static void GoToView(CAMERA dst)
{
  int nframes = 0;
  CAMERA src = Camera;
  double tstart = get_time();

  movebusy = TRUE;

  /* rerender current frame */
  CameraSet(&Camera, 
	    &src.eyep, &src.lookp, &src.updir, src.fov,
	    Camera.hres, Camera.vres, &src.background);
  ProcessWaitingEvents();
  RenderScene();
  nframes++;

  while (1) {
    CAMERA cam = Camera;
    double s = (get_time() - tstart) / anim_time;
    if (s >= 1.-EPSILON) break;

    VECTORINTERPOLATE(src.eyep, dst.eyep, s, cam.eyep);
    VECTORINTERPOLATE(src.lookp, dst.lookp, s, cam.lookp);
    VECTORINTERPOLATE(src.updir, dst.updir, s, cam.updir);
    cam.fov = (1. - s) * src.fov + s * dst.fov;

    CameraSet(&Camera, 
	      &cam.eyep, &cam.lookp, &cam.updir, cam.fov,
	      Camera.hres, Camera.vres, &cam.background);
    ProcessWaitingEvents();
    RenderScene();
    nframes++;
  }

  UpdateFramesPerSecond(renderopts.frames_per_sec);

  CameraSet(&Camera,
	    &dst.eyep, &dst.lookp, &dst.updir, dst.fov,
	    Camera.hres, Camera.vres, &dst.background);
  ProcessWaitingEvents();
  RenderScene();

  movebusy = FALSE;
}

void GoToNextView(void)
{
  if (movebusy)
    return;

  if (curview >= nrviews || curview < 0)
    curview = 0;
  else
    curview = (curview + 1) % nrviews;
  GoToView(cams[curview]);
}

void GoToPreviousView(void)
{
  if (movebusy)
    return;

  if (curview >= nrviews || curview < 0)
    curview = 0;
  else
    curview = (curview - 1 + nrviews) % nrviews;
  GoToView(cams[curview]);
}

void GoToViewNr(int viewnr)
{
  if (movebusy)
    return;

  if (viewnr < 0 || viewnr >= nrviews) {
    Error(NULL, "No view nr %d\n", viewnr);
    return;
  }

  curview = viewnr;
  GoToView(cams[curview]);
}

static int making_the_buttons = FALSE;
static void GoToViewCallback(Widget button, XtPointer client_data, XtPointer call_data)
{
  int set = (((XmToggleButtonCallbackStruct *)call_data)->set == XmSET);
  if (set && !making_the_buttons) {
    curview = (int)client_data;
    GoToView(cams[curview]);
  }
}

void CreatePredefViewButtons(int nr, CAMERA *cam, char **descr)
{
  int i;

  /* first dispose of the previous buttons */
  if (nrviews > 0) {
    for (i=0; i<nrviews; i++) {
      XtDestroyWidget(viewButtons[i]);
    }
    Free((char *)viewButtons, nrviews * sizeof(Widget));
    Free((char *)cams, nrviews * sizeof(CAMERA));
  }

  nrviews = nr;
  curview = 0;
  making_the_buttons = TRUE;
  viewButtons = (Widget *)Alloc(nrviews * sizeof(Widget));
  cams = (CAMERA *)Alloc(nrviews * sizeof(CAMERA));
  for (i=0; i<nr; i++) {
    cams[i] = cam[i];
    viewButtons[i] = CreateToggleButton(viewButtonsForm, descr[i], i==curview ? True : False,
					GoToViewCallback, (XtPointer)i);
  }
  making_the_buttons = FALSE;
}

static void CycleForwardCallback(Widget w, XtPointer client_data, XtPointer call_data)
{
  int i;

  XtUnmanageChild(viewsBox);

  if (curview >= nrviews || curview < 0)
    curview = 0;

  if (nrviews > 1) {
    for (i=0; i<nrviews; i++) {
      curview = (curview + 1) % nrviews;
      GoToView(cams[curview]);
      if (pause_time > 0) sleep(pause_time);
    }
  }
}

static void CycleBackwardCallback(Widget w, XtPointer client_data, XtPointer call_data)
{
  int i;

  XtUnmanageChild(viewsBox);

  if (curview >= nrviews || curview < 0)
    curview = 0;

  if (nrviews > 1) {
    for (i=0; i<nrviews; i++) {
      curview = (curview - 1 + nrviews) % nrviews;
      GoToView(cams[curview]);
      if (pause_time > 0) sleep(pause_time);
    }
  }
}

static Widget CreatePredefViewsDialog(Widget parent, char *name)
{
  Widget form, frame, subform;
  Boolean valid;

  viewsBox = CreateDialog(parent, name);

  form = CreateRowColumn(viewsBox, "predefViewsForm");

  frame = CreateFrame(form, "predefViewsExtraFrame", NULL);
  subform = CreateRowColumn(frame, "predefViewsExtraForm");
  CreateFormEntry(subform, "animTimeLabel", "animTimeTextf",
		  FET_FLOAT, (XtPointer)&anim_time, &valid, 0);
  CreateFormEntry(subform, "pauseTimeLabel", "pauseTimeTextf", 
		  FET_FLOAT, (XtPointer)&pause_time, &valid, 0);
  CreatePushButton(subform, "cycleForwardButton", CycleForwardCallback, NULL);
  CreatePushButton(subform, "cycleBackwardButton", CycleBackwardCallback, NULL);
  fpsLabel = CreateLabel(subform, "fpsLabel");
  UpdateFramesPerSecond(0.);
  XtManageChild(subform);

  frame = CreateFrame(form, "predefViewsButtonFrame", "predefViewsButtonTitle");
  viewButtonsForm = CreateRadioBox(frame, "predefViewsButtonForm");
  nrviews = 0;
  XtManageChild(viewButtonsForm);

  XtManageChild(form);

  return viewsBox;
}

/*************************** Camera Menu ******************************/
CAMERA alternate_camera;

static void EditCameraOKCallback(Widget w, XtPointer client_data, XtPointer calldata)
{
  CAMERA *cam = (CAMERA *)client_data;

  CameraSet(&Camera, &cam->eyep, &cam->lookp, &cam->updir, 
	    cam->fov, Camera.hres, Camera.vres, &Camera.background);
  RenderScene();
}

static void EditCameraCancelCallback(Widget w, XtPointer client_data, XtPointer calldata)
{
  Widget editCameraDialog = XtParent(w);
  CAMERA *cam = (CAMERA *)client_data;

  XtDestroyWidget(editCameraDialog);
  CameraDestroy(cam);
}

static void ShowEditCameraBox(Widget w, XtPointer client_data, XtPointer call_data)
{
  Widget editCameraBox, editCameraForm, frame, subform;
  CAMERA *cam;
  static Boolean valid;

  editCameraBox = CreateDialog(w, "editCameraBox");
  editCameraForm = CreateRowColumn(editCameraBox, "editCameraForm");

  cam = CameraCreate();	
  *cam = Camera;

  frame = CreateFrame(editCameraForm, "eyepFrame", "eyepTitle");
  subform = CreateRowColumn(frame, "eyepForm");
  CreateFormEntry(subform, "xLabel", "xTextf", 
		  FET_FLOAT, (XtPointer)&cam->eyep.x, &valid, 0);
  CreateFormEntry(subform, "yLabel", "yTextf", 
		  FET_FLOAT, (XtPointer)&cam->eyep.y, &valid, 0);
  CreateFormEntry(subform, "zLabel", "zTextf", 
		  FET_FLOAT, (XtPointer)&cam->eyep.z, &valid, 0);
  XtManageChild(subform);

  frame = CreateFrame(editCameraForm, "lookpFrame", "lookpTitle");
  subform = CreateRowColumn(frame, "lookpForm");
  CreateFormEntry(subform, "xLabel", "xTextf", 
		  FET_FLOAT, (XtPointer)&cam->lookp.x, &valid, 0);
  CreateFormEntry(subform, "yLabel", "yTextf", 
		  FET_FLOAT, (XtPointer)&cam->lookp.y, &valid, 0);
  CreateFormEntry(subform, "zLabel", "zTextf", 
		  FET_FLOAT, (XtPointer)&cam->lookp.z, &valid, 0);
  XtManageChild(subform);

  frame = CreateFrame(editCameraForm, "updirFrame", "updirTitle");
  subform = CreateRowColumn(frame, "updirForm");
  CreateFormEntry(subform, "xLabel", "xTextf", 
		  FET_FLOAT, (XtPointer)&cam->updir.x, &valid, 0);
  CreateFormEntry(subform, "yLabel", "yTextf", 
		  FET_FLOAT, (XtPointer)&cam->updir.y, &valid, 0);
  CreateFormEntry(subform, "zLabel", "zTextf", 
		  FET_FLOAT, (XtPointer)&cam->updir.z, &valid, 0);
  XtManageChild(subform);

  frame = CreateFrame(editCameraForm, "fovFrame", "fovTitle");
  subform = CreateRowColumn(frame, "fovForm");
  CreateFormEntry(subform, NULL, "fovTextf", 
		  FET_FLOAT, (XtPointer)&cam->fov, &valid, 0);
  XtManageChild(subform);

  XtManageChild(editCameraForm);

  /* OK and Cancel buttons are automatically created when a callback or labelstring
   * is defined for them. */
  XtAddCallback(editCameraBox, XmNokCallback, EditCameraOKCallback, (XtPointer)cam);
  XtAddCallback(editCameraBox, XmNcancelCallback, EditCameraCancelCallback, (XtPointer)cam);

  /* realize the dialog box on the screen */
  XtManageChild(editCameraBox);
}

static void SaveCameraCallback(Widget w, XtPointer client_data, XtPointer call_data)
{
  CameraPush(&Camera);
  RenderScene();
}

static void RestoreCameraCallback(Widget w, XtPointer client_data, XtPointer call_data)
{
  CameraPop(&Camera);
  RenderScene();
}

static void SetAlternateCameraCallback(Widget w, XtPointer client_data, XtPointer call_data)
{
  alternate_camera = Camera;
}

static void ToggleAlternateCameraCallback(Widget w, XtPointer client_data, XtPointer call_data)
{
  CAMERA tmp = Camera;
  /* keep hres and vres */
  alternate_camera.hres = Camera.hres;
  alternate_camera.vres = Camera.vres;
  Camera = alternate_camera;
  Camera.changed = 0;
  alternate_camera = tmp;
  RenderScene();
}

static void ResetCameraCallback(Widget w, XtPointer client_data, XtPointer call_data)
{
  SetDefaultView();
  RenderScene();
}

/* Write VRML ViewPoint node for the given camera position */
void WriteVRMLViewPoint(FILE *fp, TRANSFORM model_xf, CAMERA *cam, char *vpname)
{
  VECTOR X, Y, Z, view_rotaxis, eyep;
  TRANSFORM view_xf;
  float view_rotangle;

  VECTORSCALE( 1., cam->X, X); /* cam->X points right in window */
  VECTORSCALE(-1., cam->Y, Y); /* cam->Y points down in window, VRML wants y up */
  VECTORSCALE(-1., cam->Z, Z); /* cam->Z points away, VRML wants Z to point towards viewer */

  /* apply model transform */
  TRANSFORM_VECTOR_3D(model_xf, X, X);
  TRANSFORM_VECTOR_3D(model_xf, Y, Y);
  TRANSFORM_VECTOR_3D(model_xf, Z, Z);

  /* construct view orientation transform and recover axis and angle */
  view_xf = IdentityTransform;
  SET_3X3MATRIX(view_xf.m,
		X.x, Y.x, Z.x,
		X.y, Y.y, Z.y,
		X.z, Y.z, Z.z);
  RecoverRotation(view_xf, &view_rotangle, &view_rotaxis);

  /* apply model transform to eye point */
  TRANSFORM_POINT_3D(model_xf, cam->eyep, eyep);

  fprintf(fp, "Viewpoint {\n  position %g %g %g\n  orientation %g %g %g %g\n  fieldOfView %g\n  description \"%s\"\n}\n\n", 
	  eyep.x, eyep.y, eyep.z,
	  view_rotaxis.x, view_rotaxis.y, view_rotaxis.z, view_rotangle,
	  (float)(2. * cam->fov * M_PI / 180.),
	  vpname);
}

void WriteVRMLViewPoints(FILE *fp, TRANSFORM model_xf)
{
  CAMERA *cam = (CAMERA *)NULL;
  int count = 1;
  WriteVRMLViewPoint(fp, model_xf, &Camera, "ViewPoint 1");
  while ((cam = NextSavedCamera(cam)) != (CAMERA *)NULL) {
    char vpname[20];
    count++; sprintf(vpname, "ViewPoint %d", count);
    WriteVRMLViewPoint(fp, model_xf, cam, vpname);
  }
}

static void PrintCameraCallback(Widget w, XtPointer client_data, XtPointer call_data)
{
  WriteVRMLViewPoints(stdout, IdentityTransform);
}

void CreateCameraMenu(Widget menuBar)
{
  Widget cameraMenu;

  alternate_camera = Camera;

  cameraMenu = CreateSubMenu(menuBar, "cameraButton", "cameraMenu");

  CreateCascadeDialog(cameraMenu, "predefViewsButton", CreatePredefViewsDialog, "predefViewsBox", DEFAULT_CALLBACK, NULL);
  CreatePushButton(cameraMenu, "editCameraButton", ShowEditCameraBox, (XtPointer)NULL);
  CreatePushButton(cameraMenu, "resetCameraButton", ResetCameraCallback, (XtPointer)NULL);
  CreateSeparator(cameraMenu, "cameraSeparator");

  CreatePushButton(cameraMenu, "saveCameraButton", SaveCameraCallback, (XtPointer)NULL);
  CreatePushButton(cameraMenu, "restoreCameraButton", RestoreCameraCallback, (XtPointer)NULL);
  CreateSeparator(cameraMenu, "cameraSeparator");

  CreatePushButton(cameraMenu, "setAlternateCameraButton", SetAlternateCameraCallback, (XtPointer)NULL);
  CreatePushButton(cameraMenu, "toggleAlternateCameraButton", ToggleAlternateCameraCallback, (XtPointer)NULL);
  CreateSeparator(cameraMenu, "cameraSeparator");

  CreatePushButton(cameraMenu, "printCameraButton", PrintCameraCallback, (XtPointer)NULL);
}

