/* This file is part of `mogul'
 * Copyright (c) 1995 David Deaven
 *
 * In the following, "you" and "You" refer to any entity other than 
 * the Microsoft corporation and its subsidiaries.  All rights are
 * reserved by the author in connection with Microsoft and its
 * subsidiaries.  The Microsoft Network is explicity denied the right
 * to copy, reproduce, or transmit this software.
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Author: deaven $
 * $Id: controls.c,v 2.1 1996/07/11 20:56:22 deaven Exp deaven $
 */

#include <stdio.h>
#include <math.h>
#include <string.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Scrollbar.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/Label.h>
#include "dmd/memory.h"
#include "dmd/geometry.h"
#include "dmd/neighbor.h"
#include "xmogul.h"

struct box_info {
	MogulPanel mp;
	Widget w;
};

struct ControlsRec {
	struct Gfile *gf;	/* the input geometry stream */
	Geometry *g;
	NeighborList *nl;
	MogulParser parser;
	char *program;
	int play;		/* play mode */
	Widget framescroll;	/* geometry stream scrollbar */
	Widget framelabel;
	Widget title;
	char label_buf[32];	/* text for the frame label */

	struct {
	  Camera c;
	  int button;
	  Dimension w, h;
	  double s, x, y, r;
	} orbit;
};

static void orbit_button(Widget w, XtPointer client_data, XEvent *e)
{
	MogulPanel mp = (MogulPanel) client_data;
	struct ControlsRec *z = mp->controls;
	switch (e->type) {
	  case ButtonPress:
	  { XButtonEvent *event = (XButtonEvent *) e;
	    XtVaGetValues(w,
	      XtNwidth, &z->orbit.w,
	      XtNheight, &z->orbit.h, NULL);
	    z->orbit.s = 1.0 / (z->orbit.w > z->orbit.h ? z->orbit.w : z->orbit.h);
	    z->orbit.x = z->orbit.s * (event->x - z->orbit.w / 2);
	    z->orbit.y = z->orbit.s * (z->orbit.h / 2 - event->y);
	    z->orbit.c = CameraCopy(MogulGetCamera(mp->plot), z->orbit.c);
	    { int m;
	      double gc[3], c[3], dx;
	      GgetBoxCenter(z->g, gc);
	      CameraGetPosition(z->orbit.c, c);
	      z->orbit.r = 0.0;
	      for(m=0; m<3; m++) {
		dx = c[m] - gc[m];
		z->orbit.r += dx * dx;
	      }
	      z->orbit.r = sqrt(z->orbit.r);
	    }
	    z->orbit.button = event->button;
	  }
	  break;

	  case MotionNotify:
	  { XMotionEvent *event = (XMotionEvent *) e;
	    double
	      x = z->orbit.s * (event->x - z->orbit.w / 2),
	      y = z->orbit.s * (z->orbit.h / 2 - event->y);
	    switch(z->orbit.button) {

	      case 1:
	      case 3:
		CameraMoveOnGlassSphere(MogulGetCamera(mp->plot),
		  x, y, z->orbit.x, z->orbit.y, z->orbit.r, z->orbit.c);
		break;

	      case 2:
		{
		  struct plotAttributes *attr = MogulGetPlotAttributes(mp->plot);
		  CameraPan(MogulGetCamera(mp->plot),
		    (z->orbit.x - x) * attr->width,
		    (z->orbit.y - y) * attr->height, 0.0);
		}
		break;

	    }
	    z->orbit.x = x;
	    z->orbit.y = y;
	    z->orbit.c = CameraCopy(MogulGetCamera(mp->plot), z->orbit.c);
	    if(MogulRender(mp->plot, (MogulDevice) MWGetDevice(w)))
	      MogulPerror("rendering in orbit");
	  }
	  break;

	  default:
	    break;
	}
}

/* Set the scroll bar and frame indicator to appropriate values,
 * indicating the current state of the geometry file.
 */
static void scrollset(MogulPanel mp)
{
	float top, shown;
	int frame, maxframe;
	Arg args[10];
	Cardinal n;
	XtArgVal *av;

	if(mp->controls->gf == NULL) return;

	GFileInfo(mp->controls->gf, NULL, NULL, &frame, &maxframe);

	shown = 1.0 / (float) (maxframe + 1); /* size of thumb */
	top = (float) (frame - 1) / (float) (maxframe); /* position of thumb */
	sprintf(mp->controls->label_buf, "%05d", frame);

	n = 0;
	av = (XtArgVal *) &top; XtSetArg(args[n], XtNtopOfThumb, *av); n++;
	av = (XtArgVal *) &shown; XtSetArg(args[n], XtNshown, *av); n++;
	XtSetValues(mp->controls->framescroll, args, n);
	XtVaSetValues(mp->controls->framelabel, XtNlabel, mp->controls->label_buf, NULL);
}

/* Read the next geometry from the geometry file.
 *
 * This is the only place where a new geometry is set in the Mogul
 * widget.
 */
static int next_geometry(MogulPanel mp)
{
	Geometry *g;

	if(mp->controls->gf && (g = GFileRead(mp->controls->gf)))
	{
	  MogulClearObjects(mp->plot);

	  Gfree(mp->controls->g);
	  mp->controls->g = g;

	  newnl(mp->controls->g, mp->controls->nl);

	  if(mp->controls->program)
	  {
	    if(MogulParseString(mp->controls->parser, mp->controls->program,
		mp->plot, (MogulDevice) MWGetDevice(mp->mogul),
		mp->controls->g, mp->controls->nl))
	    {
	      MogulPerror("parsing initialization script");
	    }
	  }

	  scrollset(mp);
	  MPZoomZero(mp);

	  XtVaSetValues(mp->controls->title, XtNlabel, mp->controls->g->name, NULL);

	  if(MogulRender(mp->plot, (MogulDevice) MWGetDevice(mp->mogul)))
	    MogulPerror("during initial render");
	}

	return 0;
}

/* Attempt to set the frame to target, return the
 * actual fram number achieved, or -1 if no
 * frame could be loaded.
 */
int MPSetFrame(MogulPanel mp, int target)
{
	int frame, actual;
	if(!mp->controls->gf) return -1;
	GFileInfo(mp->controls->gf, NULL, NULL, &frame, NULL);
	actual = GFileAdvance(target - frame,  mp->controls->gf);
	actual += frame;
	if(next_geometry(mp)) return -1;
	return actual;
}

static void frame_jump_callback(Widget widget,
  XtPointer client_data, XtPointer call_data)
{
	struct box_info *bi = (struct box_info *) client_data;
	float percent = *((float *) call_data);
	int target, maxframe;

	if(bi->mp->controls->gf == NULL) return;

	GFileInfo(bi->mp->controls->gf, NULL, NULL, NULL, &maxframe);

	target = percent * maxframe;

	MPSetFrame(bi->mp, target);
}

static void press_frame_scroll(MogulPanel mp)
{
	XButtonEvent xbe;
	Dimension width, height;

	XtVaGetValues(mp->controls->framescroll,
	  XtNwidth, &width,
	  XtNheight, &height,
	  NULL);

	bzero(&xbe, sizeof(xbe));
	xbe.display = XtDisplay(mp->controls->framescroll);
	xbe.window = XtWindow(mp->controls->framescroll);
	xbe.root = DefaultRootWindow(xbe.display);
	xbe.state = Button3Mask;
	xbe.button = Button3;
	xbe.same_screen = True;
	xbe.x = width - 1;
	xbe.y = height / 2;

	xbe.type = ButtonPress;
	XSendEvent(xbe.display, xbe.window, False, 0, (XEvent *) &xbe);

	xbe.type = ButtonRelease;
	XSendEvent(xbe.display, xbe.window, False, 0, (XEvent *) &xbe);
}

static void frame_scroll_callback(Widget widget,
  XtPointer client_data, XtPointer call_data)
{
	struct box_info *bi = (struct box_info *) client_data;
	int position = (int) call_data;

	if(bi->mp->controls->gf == NULL) return;

	if(position > 0) GFileRewind(2, bi->mp->controls->gf);

	if(next_geometry(bi->mp) < 0)
	  bi->mp->controls->play = 0; /* make sure we stop at the end */

	if(bi->mp->controls->play) press_frame_scroll(bi->mp);
}

static void play_callback(Widget widget,
  XtPointer client_data, XtPointer call_data)
{
	struct box_info *bi = (struct box_info *) client_data;

	bi->mp->controls->play = 1;
	press_frame_scroll(bi->mp);
}

static void stop_callback(Widget widget,
  XtPointer client_data, XtPointer call_data)
{
	struct box_info *bi = (struct box_info *) client_data;
	bi->mp->controls->play = 0;
}

static void rewind_callback(Widget widget,
  XtPointer client_data, XtPointer call_data)
{
	struct box_info *bi = (struct box_info *) client_data;
	int frame, maxframe;

	if(bi->mp->controls->gf == NULL) return;

	GFileInfo(bi->mp->controls->gf, NULL, NULL, &frame, &maxframe);
	if(frame > 0) {
	  GFileRewind(frame + 2, bi->mp->controls->gf);
	  press_frame_scroll(bi->mp);
	}
}

static void ff_callback(Widget widget,
  XtPointer client_data, XtPointer call_data)
{
	struct box_info *bi = (struct box_info *) client_data;

	if(bi->mp->controls->gf == NULL) return;

	while(GFileRead(bi->mp->controls->gf) != NULL) scrollset(bi->mp);
	GFileRewind(1, bi->mp->controls->gf);
	press_frame_scroll(bi->mp);
}

int MPReopenFile(MogulPanel mp)
{
	if(GFileReopen(mp->controls->gf) < 0) return -1;
	return next_geometry(mp);
}

int MPSetProgram(MogulPanel mp, char *program)
{
	if(program != mp->controls->program) {
	  myfree(mp->controls->program);
	  mp->controls->program = mystrdup(program);
	}

	MogulClearObjects(mp->plot);

	if(MogulParseString(mp->controls->parser, mp->controls->program, mp->plot,
	  (MogulDevice) MWGetDevice(mp->mogul),
	  mp->controls->g, mp->controls->nl))
	{
	  MogulPerror("setting new initialization script");
	}

	if(MogulRender(mp->plot, (MogulDevice) MWGetDevice(mp->mogul)))
	{
	  MogulPerror("rendering");
	}

	MPZoomZero(mp);

	return 0;
}

char *MPGetProgram(MogulPanel mp)
{
	return mp->controls->program;
}

int MPLoadFile(MogulPanel mp, char *path)
{
	struct Gfile *gf;

	if(path == NULL || (gf = GFileOpen(path)) == NULL) return -1;

	GFileClose(mp->controls->gf);
	mp->controls->gf = gf;

	return next_geometry(mp);
}

int MPWriteTransformed(MogulPanel mp, FILE *f)
{
	if(mp->controls->g) {
	  int i;
	  Geometry *g = Gcpy(NULL, mp->controls->g);
	  double t[4*4];
	  CameraGetTransform(MogulGetCamera(mp->plot), t);
	  for(i=0; i<g->n; i++)
	    Transf(mp->controls->g->x + 3*i, t, g->x + 3*i);
	  Gprint(f, g);
	  Gfree(g);
	}
	return 0;
}

char *MPCurrentFile(MogulPanel mp)
{
	char *path = NULL;
	if(mp->controls->gf)
	  GFileInfo(mp->controls->gf, NULL , &path, NULL, NULL);
	return path;
}

void MPCreateControls(MogulPanel mp)
{
	Widget form;
	Arg args[10];
	Cardinal n;
	Pixmap pm;
	Display *display = XtDisplay(mp->frame);
	struct box_info *bi;

#include "solidrightarrow.xbm"
#include "leftleftarrow.xbm"
#include "rightrightarrow.xbm"
#include "square.xbm"

	mp->controls = NewPointer(mp->controls);
	mp->controls->gf = NULL;
	mp->controls->g = NULL;
	mp->controls->nl = nlalloc(mp->bondRange);
	mp->controls->parser = NewMogulParser();
	mp->controls->program = NULL;
	mp->controls->play = 0;

	form = XtVaCreateManagedWidget("Controls", formWidgetClass, mp->frame,
	  NULL);

	/* Frame scrollbar */

	bi = NewPointer(bi);
	bi->w =
	  XtVaCreateManagedWidget("framescroll", scrollbarWidgetClass, form,
	  XtNorientation, XtorientHorizontal, NULL);
	bi->mp = mp;
	XtAddCallback(bi->w, XtNjumpProc, frame_jump_callback, bi);
	XtAddCallback(bi->w, XtNscrollProc, frame_scroll_callback, bi);
	mp->controls->framescroll = bi->w;

	/* Frame label */

	mp->controls->framelabel =
	  XtVaCreateManagedWidget("framelabel", labelWidgetClass, form,
	  XtNlabel, "", NULL);

	/* Buttons */

#define BUTTON(NAME,BITMAP,CALLBACK) { \
  bi = NewPointer(bi); \
  n = 0; \
  pm = XCreateBitmapFromData(display, DefaultRootWindow(display), \
    BITMAP ## _bits, BITMAP ## _width, BITMAP ## _height); \
  XtSetArg(args[n], XtNbitmap, pm); ++n; \
  bi->w = XtCreateManagedWidget(NAME, commandWidgetClass, form, args, n); \
  bi->mp = mp; \
  XtAddCallback(bi->w, XtNcallback, CALLBACK, bi); \
}

	BUTTON("Rewind", leftleftarrow, rewind_callback);
	BUTTON("Play", solidrightarrow, play_callback);
	BUTTON("Stop", square, stop_callback);
	BUTTON("FastForward", rightrightarrow, ff_callback);

#undef BUTTON

	/* Geometry title */

	n = 0;
	XtSetArg(args[n], XtNlabel, ""); ++n;
	mp->controls->title = XtCreateManagedWidget("title",
	  labelWidgetClass, form, args, n);

	/* event handler for orbits */

	mp->controls->orbit.c = NULL;
	XtAddEventHandler(mp->mogul, ButtonPressMask | ButtonMotionMask,
	  FALSE, orbit_button, mp);
}

