/* ui_main.c: user interface main window */

#include <time.h>
#include <stdlib.h>
#include <unistd.h>

#include <Xm/Xm.h>
#include <Xm/RepType.h>

#include "config.h"

#include "ui.h"
#include "uit.h"
#include "defaults.h"
#include "render.h"
#include "canvas.h"
#include "batch.h"
#include "error.h"
#include "options.h"
#include "camera.h"
#include "ipc.h"

/* #define EXTERNAL_CANVAS_TESTING */

/******************** local interface related variables **********************/
static CANVAS_TYPE canvastype = INTEGRATED_CANVAS;
int batch_processing = FALSE;
int batch_quit_at_end = FALSE;
static Window extern_canvas_window = 0;
static Dimension hres = 0, vres = 0;
static char *canvas_dpyname = NULL;
static Display *canvas_display;

/****************** global declarations concerning the GUI ********************/
/* the program detects the best X visual to use and creates a suitables
 * standard colormap to use with it automatically. The way to do this depends
 * on the graphics library being used, and is coded in render.c */
XVisualInfo best_visual;
Colormap cmap;

/* the X visual info is not correctly passed from parent to sub menus
 * with Motif 1.2, so we set it explicitely ourselves when creating
 * the submenus */
Arg visargs[3];
int nrvisargs;

/* X application context and some important widgets */
XtAppContext app_context;
Widget topLevel=0, canvas;
Display *display;

/* current directory needed when loading MGF files. */
char *current_directory;

/* fallback resources ... in ui_defaults.c (automatically generated) */
extern char *fallback_resources[];

static void ProcessWaitingExternalCanvasEvents(void)
{
  if (canvastype == EXTERNAL_CANVAS) {
    while (XPending(canvas_display)) {
      XEvent event;
      XNextEvent(canvas_display, &event);
      ExternalCanvasEvent(&event);
    }
  }
}

/* Processes pending X events */
void ProcessWaitingEvents(void)
{
  if (Ipc.have_ipc)
    IpcCheckForMessages();

  if (!batch_processing || canvastype == INTEGRATED_CANVAS) {
    XtInputMask mask;
    while ((mask = XtAppPending(app_context))) 
      XtAppProcessEvent(app_context, mask);
  }

  ProcessWaitingExternalCanvasEvents();
}

/* Called during radiance computations - checks for events (like the user clicking
 * on some button, or moving the mouse while holding one of the mouse buttons ... ) 
 * makes the program behave much more flexible */
void CheckForEvents(void)
{
  clock_t lastt = 0;

  if (batch_processing && canvastype == OFFSCREEN_CANVAS)
    return;	/* no GUI, so don't even bother calling clock() ... */

  /* checking for events too often slows down the computations too much ... 
   * we check only once each second. */
  if (clock() - lastt < CLOCKS_PER_SEC)
    return;

  ProcessWaitingEvents();

  lastt = clock();
}

/* returns TRUE if the program thinks it is working and FALSE if it
 * thinks it is not working. The program thinks it is working when the 
 * current canvas mode is CANVASMODE_WORKING, see canvas.h. This
 * routine is used for e.g. warning the user about options that can't be
 * changed until the computations are interrupted. */
int Working(void)
{
  return CanvasGetMode() == CANVASMODE_WORKING;
}

void SetWindowTitle(char *title)
{
  if (topLevel)
    SetDialogTitle(topLevel, title);
}

/************************** interface options ********************************/
static void BatchOption(void *value)
{
  batch_processing = TRUE;
}

static void BatchQuitOption(void *value)
{
  batch_quit_at_end = TRUE;
}

static void OffscreenOption(void *value)
{
  canvastype = OFFSCREEN_CANVAS;
}

static void CanvasIdOption(void *value)
{
  extern_canvas_window = *(int *)value;
  canvastype = EXTERNAL_CANVAS;
}

static void CloseCanvasDisplay(void)
{
  XCloseDisplay(canvas_display);
}

static void CanvasDisplayOption(void *value)
{
  canvas_dpyname = *(char **)value;
}

static void HResOption(void *value)
{
  hres = *(int *)value;
}

static void VResOption(void *value)
{
  vres = *(int *)value;
}

#ifdef EXTERNAL_CANVAS_TESTING
/* serves for external canvas testing */
static void CreateTestCanvas(void *value)
{
  Display *dpy;
  Window win;

  dpy = XOpenDisplay(NULL);  
  if (RenderGetVisualInfoAndColormap(DefaultScreenOfDisplay(dpy),
				     &best_visual,
				     &cmap)) {
    XSetWindowAttributes swattr;
    swattr.background_pixmap = None;
    swattr.background_pixel = 0;
    swattr.border_pixmap = CopyFromParent;
    swattr.border_pixel = 0;
    swattr.backing_store = NotUseful;
    swattr.colormap = cmap;
    win = XCreateWindow(dpy, DefaultRootWindow(dpy), 0, 0, 640, 480, 0, 
		        best_visual.depth, InputOutput, best_visual.visual, 
		        CWBackPixel | CWBorderPixel | CWBackingStore | CWColormap, 
		        &swattr);
  } else {
    win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 0, 0, 640 ,480, 0, 0, 0);
  }

  XSelectInput(dpy, win, KeyReleaseMask);
  XMapWindow(dpy, win);

  fprintf(stderr, "win = %ld\n", win);

  while (1) {
    XEvent event;
    XNextEvent(dpy, &event);
    switch (event.type) {
    case KeyRelease:
      exit(0);
    default:
      break;
    }
  }
}
#endif

static void ThreeButtonMotionOption(void *value)
{
  twobutton_motion = FALSE;
}

static void TwoButtonMotionOption(void *value)
{
  twobutton_motion = TRUE;
}

static CMDLINEOPTDESC interfaceOptions[] = {
  {"-batch",	3,	TYPELESS,	NULL,	BatchOption,
   "-batch         \t\t: non-interactive processing"},
  {"-batch-quit-at-end", 8,	TYPELESS,	NULL,	BatchQuitOption,
   "-batch-quit-at-end\t: (batch mode) quit program at end"},
  {"-offscreen", 3,	TYPELESS,	NULL,	OffscreenOption,
   "-offscreen     \t\t: render into offscreen window"},
  {"-canvas-window",	3,	Tint,		NULL, 	CanvasIdOption,
   "-canvas-window <window-id>\t: render into external window with given id"},
  {"-canvas-display", 9, Tstring,	NULL, 	CanvasDisplayOption,
   "-canvas-display <display-name>\t: display name of external canvas window"},
  {"-hres",	3,	Tint,		NULL,	HResOption,
   "-hres <integer>\t\t: horizontal resolution (private canvas only)"},
  {"-vres",	3,	Tint,		NULL,	VResOption,
   "-vres <integer>\t\t: vertical resolution (private canvas only)"},
#ifdef EXTERNAL_CANVAS_TESTING
  {"-create-test-canvas", 3, TYPELESS, 	NULL,	CreateTestCanvas,
   "-create-test-canvas\t: for external canvas testing"},
#endif
  {"-2button-motion", 3, TYPELESS, 	NULL,	TwoButtonMotionOption,
   "-2button-motion\t: camera manipulation using 2 mouse buttons"},
  {"-3button-motion", 3, TYPELESS, 	NULL,	ThreeButtonMotionOption,
   "-3button-motion\t: camera manipulation using 3 mouse buttons"},
  {NULL	, 	0,	TYPELESS, 	NULL, 	DEFAULT_ACTION,
   NULL}
};

void InterfaceDefaults(void)
{
  twobutton_motion = FALSE;
}

void ParseInterfaceOptions(int *argc, char **argv)
{
  ParseOptions(interfaceOptions, argc, argv);
  ParseBatchOptions(argc, argv);
}

void PrintInterfaceOptions(FILE *fp)
{
  fprintf(fp, "\nInterface options:\n");
  PrintOptions(fp, interfaceOptions);
  PrintBatchOptions(fp);
}

/*************************** menubar ******************************************/
/* creates the submenus in the main menu bar */
static void CreateMainMenu(Widget menuBar)
{
  CreateFileMenu(menuBar);
  CreateRadianceMenu(menuBar);
  CreateRenderMenu(menuBar);
  CreateRayTracingMenu(menuBar);
  CreateToneMappingMenu(menuBar);
  CreateCameraMenu(menuBar);
#ifdef GIDEBUG
  CreateDebugMenu(menuBar);
#endif
  CreateHelpMenu(menuBar);
}

/* work procedure for batch processing */
static Boolean StartBatchProcessing(XtPointer client_data)
{
  while (!RenderInitialized())
    ProcessWaitingEvents();
  RenderScene();

  Batch();
  if (batch_quit_at_end)
    exit(0);

  switch (canvastype) {
  case OFFSCREEN_CANVAS:
    exit(0);

  case EXTERNAL_CANVAS:
    while (1) {
      XEvent event;
      if (XCheckMaskEvent(canvas_display, ~NoEventMask, &event))
	ExternalCanvasEvent(&event);

      if (Ipc.have_ipc)
	IpcCheckForMessages();

      usleep(100000);
    }

  default:
    /* do nothing */
    break;
  }

  return True;
}

void StartUserInterface(int *argc, char **argv)
{
  Widget mainWindow, menuBar;
  XSetWindowAttributes wattrs;

  if (!batch_processing || canvastype == INTEGRATED_CANVAS) {
  /* Create a toplevel widget, handle options ... */
  topLevel = XtVaAppInitialize(&app_context,	/* application context */
				 APP_CLASS_NAME,	/* application class name */
			       NULL, 0,		/* command line option list */
			       argc, argv,	/* command line args */
			       fallback_resources, /* used when the application
						    * defaults file is missing. */
			       NULL);		/* terminate varargs list */

  XmRepTypeInstallTearOffModelConverter();

  display = XtDisplay(topLevel);
  } else
    display = (Display *)NULL;

  /* All options should have disappeared from argv now. */
  if (*argc > 1) {
    if (*argv[1] == '-')
      Error(NULL, "Unrecognized option '%s'", argv[1]);
    else
      ReadFile(argv[1]);
  }

  /* obtain the XVisualInfo and Colormap to use for rendering with the
   * graphics library. If a non-default visual and colormap is needed,
   * fill in the visualid, depth and colomrap in the visarg argument list,
   * so the visual can be set on each widget whenever needed. Failing to do so
   * will lead to BadMatch errors from the X server. */
  if (canvastype == INTEGRATED_CANVAS &&
      RenderGetVisualInfoAndColormap(XtScreen(topLevel), 
				     &best_visual,
				     &cmap)) {
    /* Set the visual, depth, and colormap for the shell - as a matter of fact, these things
     * have to be passed to all routines creating popup shells and dialogs */
    XtSetArg(visargs[0], XtNvisual, best_visual.visual);
    XtSetArg(visargs[1], XtNdepth, best_visual.depth);
    XtSetArg(visargs[2], XtNcolormap, cmap);
    nrvisargs = 3;
    XtSetValues(topLevel, visargs, nrvisargs);
  } else
    nrvisargs = 0;

  if (!batch_processing) {
    /* get user config resources */
    LoadUserOptions();

    /* create main window */
    mainWindow = CreateForm(topLevel, "mainWindow");

    /* menubar on top of the main window */
    menuBar = CreateMenuBar(mainWindow, "menuBar");
  
    /* create the menus in the menubar */
    CreateMainMenu(menuBar);
    XtManageChild(menuBar);
  } else
    mainWindow = topLevel;

  /* create the window in which to render (canvas window) */
  switch (canvastype) {
  case INTEGRATED_CANVAS:
    canvas = CreateIntegratedCanvasWindow(mainWindow, hres, vres);
    if (twobutton_motion && !batch_processing) {
      /* create canvas popup menu, activated with third mouse button. */
      CreateCanvasPopupMenu();
    }
    break;

  case EXTERNAL_CANVAS:
    canvas_display = XOpenDisplay(canvas_dpyname);
    if (!canvas_display)
      Fatal(1, NULL, "Couldn't open connection to display '%s'", 
	    canvas_dpyname ? canvas_dpyname : "(null)");
    else
      atexit(CloseCanvasDisplay);		/* make sure the canvas displayc onnection is closed at exit */

    ConnectExternalCanvasWindow(canvas_display, extern_canvas_window);
    break;

  case OFFSCREEN_CANVAS:
    if (hres <= 0) hres = 640;	/* these default values should preferably be */
    if (vres <= 0) vres = 480;	/* extracted from the Xt resource database. */
    CreateOffscreenCanvasWindow(hres, vres);
    break;

  default:
    Fatal(-1, "StartUserInterface", "Invalid canvas type %d", canvastype);
  }

  if (!batch_processing) {
    XtManageChild(mainWindow);
  }

  if (!batch_processing || canvastype == INTEGRATED_CANVAS) {
    /* realize all widgets - bring them on the screen */
    XtRealizeWidget(topLevel);
  }

  if (canvastype == INTEGRATED_CANVAS) {
    /* set the backing store attribuut of the canvas window to NotUseful */
    wattrs.backing_store = NotUseful;
    XChangeWindowAttributes(XtDisplay(canvas), XtWindow(canvas), CWBackingStore, &wattrs);
#ifdef EXTERNAL_CANVAS_TESTING
    fprintf(stderr, "Canvas Window ID: %ld\n", XtWindow(canvas));
#endif
  }

  /* initialize the canvas widget */
  CanvasInit();

  if (batch_processing) {
    if (canvastype == INTEGRATED_CANVAS)
      XtAppAddWorkProc(app_context, StartBatchProcessing, NULL);
    else {
      StartBatchProcessing((XtPointer)NULL);
      exit(0);
    }
  }

  /* infinte loop catching and handling events */
  if (canvastype == EXTERNAL_CANVAS) {
    /* we use polling since we need to handle events from two displays
     * simultaneously. */
    while (1) {
      ProcessWaitingEvents();
      usleep(50000);
    }
  } else
    XtAppMainLoop(app_context);
}

