/* This file contains routines that manage the user interface.
 *
 *       This code (for what it's worth) is under the GNU copyleft.
 *
 *   Dominic Giampaolo
 *   dbg@sgi.com
 */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <math.h>
#include "libsx.h"    
#include "gfx.h"
#include "main.h"
#include "callbacks.h"
#include "bezier.h"

#ifndef caddr_t
#define caddr_t char*
#endif

/*
 * Would you believe that posix doesn't actually require that M_PI_2 and
 * M_PI_4 be defined?  What a load of crap....
 */
#ifndef M_PI_2
#define M_PI_2          1.57079632679489661923
#endif
#ifndef M_PI_4
#define M_PI_4          0.78539816339744830962
#endif

/* internal prototypes */
void timeout(caddr_t data, XtIntervalId *id);
int  check_point(MyProgram *me, ushort x, ushort y);



/* Each of the following functions is tied to a given interface button.
 * They are called whenever the user clicks on something on the display, 
 * and happen independant of anything else that may be taking place on
 * the display.
 */


/* Just quit out of everything.
 */
void quit(Widget w, void *data)
{
  exit(0);
}


/* Cause the curve to be redrawn.  Not much to do here, this is basically
 * just a wrapper function for do_redisplay().
 */
void redraw_curve(Widget w, void *data)
{
  MyProgram *me = (MyProgram *)data;
  
  redisplay(NULL, me->width, me->height, me);
}


/* This resets the curve back to the original.
 */
void reset_curve(Widget w, void *data)
{
  MyProgram *me = (MyProgram *)data;
  
  me->pts[0].x = 0;      me->pts[0].y = 5000;
  me->pts[1].x = 2500;   me->pts[1].y = 7500;
  me->pts[2].x = 5000;   me->pts[2].y = 2500;
  me->pts[3].x = 7500;   me->pts[3].y = 5000;

  redisplay(NULL, me->width, me->height, me);
}


/* This starts the animation going.  It sets a flag and then calls the
 * timeout() routine.  timeout() does its business and then adds itself
 * as a time-out that will happen every so many milliseconds.
 */
void bez_anim(Widget w, void *data)
{
  MyProgram *me = (MyProgram *)data;
  
  if (me->remove_timeout == FALSE)  /* already doing it */
    return;
  me->remove_timeout = FALSE;

  ClearDrawArea();
  timeout((caddr_t)me, NULL);   /* start the ball rolling */
}


/* This unsets a flag that was set in bez_anim().  This makes the 
 * timeout() routine remove itself, and so the animation stops.
 */
void stop_anim(Widget w, void *data)
{
  MyProgram *me = (MyProgram *)data;
  
  if (me->remove_timeout == TRUE)  /* already stopped */
    return;  

  me->remove_timeout = TRUE;   /* makes the timeout function remove itself */
}


void time_out_adjust(Widget w, float new_val, void *data)
{
  char buff[80];
  MyProgram *me=data;

  me->timeout_val = new_val;

  sprintf(buff, "Timeout Val: %-6f    ", me->timeout_val);
  SetLabel(me->label_widget, buff);
}





/* These callbacks down here are the standard ones for the drawing area, 
 * and are where you'd implement your interface "policy".  By that, I mean
 * these functions and what they do determines how your program behaves 
 * and controls what the user will think of your program (i.e. this is
 * great or this is garbage).  You really should think out what you do
 * in these functions.  How do you want mouse clicks to work, what should
 * dragging operations behave like, etc.  These are all important issues
 * that get implemented in the next few functions and determine the look
 * and feel (oh oh, watch out for Apple's lawyers :^) of your application.
 */



/*
 * Here is where we redraw things when necessary.  It also re-maps the
 * view ports to reflect the new changes in the window size.
 */
void redisplay(Widget w, int new_width, int new_height, void *data)
{
  MyProgram *me = data;
  
  ClearDrawArea();                        /* start with a clean slate */

  me->width  = new_width;                 /* save new window size */
  me->height = new_height;


  /*
   * Setup the proper mapping from world coordinates (10000 x 10000) to
   * window coordinates (width x height)
   */
  SetView(0,0, 10000, 10000,  &me->world_view); 
  SetView(0,0, me->width, me->height, &me->view_port);
  MapView(&me->world_view, &me->view_port);

  SetColor(WHITE);

  /* draw the control points */
  FilledBox(me->pts[0].x, me->pts[0].y, 100, 100); 
  FilledBox(me->pts[1].x, me->pts[1].y, 100, 100); 
  FilledBox(me->pts[2].x, me->pts[2].y, 100, 100); 
  FilledBox(me->pts[3].x, me->pts[3].y, 100, 100); 

  bezier4(me->pts, 20);   /* draw the actual bezier curve */
}



#define NO_MB 0
#define MB_1  1
#define MB_2  2
#define MB_3  4
#define MB_4  8
#define MB_5  16
int mouse_button = 0;


void button_down(Widget w, int which_button, int x, int y, void *data)
{
  MyProgram *me=data;
  Vp2World(&x, &y);   /* convert from device to world coords */

  switch (which_button)
   {   
     case 1: mouse_button |= MB_1;    
             if (check_point(me, x, y))
	       me->mode = MODE_MOVING;
             break;

     case 2: mouse_button |= MB_2;
             break;
     
     case 3: mouse_button |= MB_3;
             break;
   }
}


void button_up(Widget w, int which_button, int x, int y, void *data)
{
  MyProgram *me=data;

  Vp2World(&x, &y);   /* convert from device to world coords */

  switch (which_button) 
   { 
     case 1: mouse_button &= ~MB_1;
             me->mode = MODE_NOTHING;

             redraw_curve(NULL, me);  /* so any screen turds are cleaned up */

	     break;
			    
     case 2: mouse_button &= ~MB_2;
             break;
			    
     case 3: mouse_button &= ~MB_3;
             break;
   }
}






/* Called when DrawingArea canvas have pointer motion events */
void process_motion(Widget w, int x, int y, void *data)
{
  static int oldx = 0, oldy = 0;
  Point old, cur;
  MyProgram *me=data;

  old.x = oldx;
  old.y = oldy;

  cur.x = x;
  cur.y = y;


  if ((me->mode & MODE_MOVING) == 0)
    return;

  if (me->cur_point == -1)
    return;

  Vp2World(&x, &y);   /* convert from device to world coords */


  SetColor(BLACK);              /* erase the old stuff */
  FilledBox(me->pts[me->cur_point].x,  me->pts[me->cur_point].y, 100, 100); 
  bezier4(me->pts, 20);


  if (x < 0)         /* keep things in bounds */
    x = 0;
  if (x > 10000)
    x = 10000;
  if (y < 0)
    y = 0;
  if (y > 10000)
    y = 10000;

  me->pts[me->cur_point].x = x;     me->pts[me->cur_point].y = y;

  SetColor(WHITE);                  /* redraw the new stuff */
  bezier4(me->pts, 20);

  FilledBox(me->pts[me->cur_point].x,  me->pts[me->cur_point].y, 100,100); 
}


/* Checks if a mouse click is near any control points.  Notice the slop
 * factor.  If we expected the user to click _exactly_ on the control
 * point, they'd get frustrated pretty quickly.  You generally need
 * some kind of slop factor like this.  It should also be parameterized
 * instead of hardcoded because on a different resolution screen, you'd 
 * want smaller or larger "slop" values.
 */
int check_point(MyProgram *me, ushort x, ushort y)
{
  int i;

  for(i=0; i < 4; i++)
   {
     /*
      * Here's a strange bug for ya: If you don't cast x & y to
      * be int, the test below will fail for the first control
      * point on some machines (because pts.x-150 becomes negative)
      * which is cast to be unsigned to match the type on the right.
      * Thanks go to Pierre Asselin (pa@appmag.com) for finding 
      * this one.
      */
     if ((int)x > me->pts[i].x-150 && (int)x < me->pts[i].x+150)
	if ((int)y > me->pts[i].y-150 && (int)y < me->pts[i].y+150)
	 {
	   me->cur_point = i;
	   return TRUE;
	 }
   }

  return FALSE;
}


/* Here is where the animation takes place.  What happens is that we 
 * first check to see if we should stop animating and remove ourselves.
 * If so, we just call redraw_curve() and return.  Otherwise we check if 
 * we should erase the last frame, and if so, just erase the stuff by 
 * redrawing it in white and then adding ourselves to the time-out list 
 * once again.  Else, we are supposed to move things and redraw in black.
 * The updating of the control points should probably be in a separate 
 * function, but I'm lazy for now.  The ugliness that updates the control
 * points, moves them along a parametric curve that I stole from the Hill
 * graphics book.  It's a pretty neat curve (i.e. really twisty and curvy).
 * Anyway, after all that rigamarole, we just add ourselves to the timeout
 * chain again, and then return.
 * 
 * The thing that may not be immediately obvious is how this function 
 * gets called.  Well, lemme tellya.  The function AddTimeOut() sets
 * up a function (this one) to be called when a given amount of time
 * expires.  The code that actually calls this routine is part of the
 * X toolkit library, and isn't really visible.  The only other thing to
 * notice is that each time when we are done, we have to add ourselves 
 * again to the time-out list so that we will get called again in the
 * next few milliseconds (otherwise nothing would get called and the
 * animation would stop, which incidentally is how stop the animation).
 */

void timeout(caddr_t data, XtIntervalId *id)
{
  static double rad  = 0.0;
  MyProgram *me = (MyProgram *)data;

  if (me->remove_timeout == TRUE)
   {
     redraw_curve(NULL, me);
     return;
   }

  SetColor(BLACK);                   /* erase */
  bezier4(me->pts, 20);
  
 {                                   /* redraw */
   int size = 5000;
   double x,y, n = 6.0;
   
   rad += 0.01;
   
   me->pts[0].x = (size * (cos(n * rad)) * cos(rad)) + 5000;
   me->pts[0].y = (size * (cos((n-1) * rad)) * sin(rad)) + 5000;
   
   
   me->pts[1].x = (size * (cos(n * (rad + M_PI_2))) * cos(rad)) + 5000;
   me->pts[1].y = (size * (cos((n-1) * (rad + M_PI_2))) * sin(rad)) + 5000;
   
   me->pts[2].x = (size * (cos(n * (rad + M_PI_4))) * cos(rad)) + 5000;
   me->pts[2].y = (size * (cos((n-1) * (rad + M_PI_4))) * sin(rad)) + 5000;
   
   size /= 2;
   me->pts[3].x = (size * (cos(rad) + sin(8*rad))) + 5000;
   me->pts[3].y = ((size/2) * (2*sin(rad) + sin(7*rad))) + 5000;
   
   SetColor(WHITE);
   bezier4(me->pts, 20);
   
   if (me->remove_timeout == FALSE)
     AddTimeOut((me->timeout_val*1000), (GeneralCB)timeout, data);
 }

}


