/*
 *  xcl.c
 *  Control-Line in a box
 *  Copyright (c) 2000 by Martin Berentsen <berentsen@sent5.uni-duisburg.de>
 *
 * speed is not in input mode
 * DEFAULT for count must be added in app-defaults
 *
 * change-log 0.4pl2 -> 0.5pl0 25/07/2000
 * -singl command line option becomes to -count num 
 *
 * TODO as next:
 * - a user defined speed for every used plane 
 *
 */

#if !defined( lint ) && !defined( SABER )
static const char sccsid[] = "@(#)xcl.c 0.5pl0  2000/07/25";
#endif

#define RAND(x)	(((my_random() % x )- x/2))  /* random number around 0 */
#define ROTATEDELAY 20000      /* delay for view-model rotating */
#define STARTUPDELAY 5000      /* delay for the first calibration loop */
#define REGULATE 25                    /* regulate delay every xx frames */
#define FRAMETIME 45000                /* time for one frame */
#define MINPLANES 1           /* define the min number of planes */
#define MAXCOUNT 30           /* define the max number of planes */

#ifdef STANDALONE
#define PROGCLASS "Life"
#define HACK_INIT init_life
#define HACK_DRAW draw_life
#define life_opts xlockmore_opts
#define DEFAULTS "*delay: 20000 \n" \
 "*count: -3 \n"
#define UNIFORM_COLORS
#include "xlockmore.h"		/* in xscreensaver distribution */
#else /* STANDALONE */
#include "xlock.h"		/* in xlockmore distribution */
#endif

#include "xcl.h"             /* model line data file */

/* all parameters are global */
static float speed[MAXCOUNT];     /* Speed in km/h */
static float speed_in;       /* speed set by user */
static int frametime;       /* time for one frame in usecs */
static int line_length;     /* lines in mm*/
static float spectator;     /* spectator distance from zero*/
static Bool viewmodel;      /* shows one rotating model*/
static Bool oldcolor;       /* use the old yellow/red color combination */
static Bool debug;          /* debug modus */
static Bool automatic;      /* automatic scale for fit into window */
static Bool randomstart;    /* don't use the same start position */
static int random_pid;


#define DEF_SPEED_IN  "105.0"
#define DEF_FRAMETIME  "45000"
#define DEF_LINE_LENGTH  "15910"
#define DEF_SPECTATOR  "22000"
#define DEF_VIEWMODEL  "False"
#define DEF_OLDCOLOR  "False"
#define DEF_XCLDEBUG  "False"
#define DEF_AUTOMATIC  "True"
#define DEF_RANDOMSTART  "False"


static XrmOptionDescRec opts[] =
{
  {(char *) "-speed", (char *) ".xcl.speed", XrmoptionSepArg, (caddr_t) NULL},
  {(char *) "-frametime", (char *) ".xcl.frametime", XrmoptionSepArg, (caddr_t) NULL},
  {(char *) "-line_length", (char *) ".xcl.line_length", XrmoptionSepArg, (caddr_t) NULL},
  {(char *) "-spectator", (char *) ".xcl.spectator", XrmoptionSepArg, (caddr_t) NULL},
  {(char *) "-viewmodel", (char *) ".xcl.viewmodel", XrmoptionNoArg, (caddr_t) "on"},
  {(char *) "+viewmodel", (char *) ".xcl.viewmodel", XrmoptionNoArg, (caddr_t) "off"},
  {(char *) "-oldcolor", (char *) ".xcl.oldcolor", XrmoptionNoArg, (caddr_t) "on"},
  {(char *) "+oldcolor", (char *) ".xcl.oldcolor", XrmoptionNoArg, (caddr_t) "off"},
  {(char *) "-xcldebug", (char *) ".xcl.xcldebug", XrmoptionNoArg, (caddr_t) "on"},
  {(char *) "+xcldebug", (char *) ".xcl.xcldebug", XrmoptionNoArg, (caddr_t) "off"},
  {(char *) "-automatic", (char *) ".xcl.automatic", XrmoptionNoArg, (caddr_t) "on"},
  {(char *) "+automatic", (char *) ".xcl.automatic", XrmoptionNoArg, (caddr_t) "off"},
  {(char *) "-randomstart", (char *) ".xcl.randomstart", XrmoptionNoArg, (caddr_t) "on"},
  {(char *) "+randomstart", (char *) ".xcl.randomstart", XrmoptionNoArg, (caddr_t) "off"}
};
static argtype vars[] =
{
  {(caddr_t *) & speed_in, (char *) "speed", (char *) "Speed", (char *) DEF_SPEED_IN, t_Float},
  {(caddr_t *) & frametime, (char *) "frametime", (char *) "frametime", (char *) DEF_FRAMETIME, t_Int},
  {(caddr_t *) & line_length, (char *) "line_length", (char *) "Line_length", (char *) DEF_LINE_LENGTH, t_Int},
  {(caddr_t *) & spectator, (char *) "spectator", (char *) "Spectator", (char *) DEF_SPECTATOR, t_Float},
  {(caddr_t *) & viewmodel, (char *) "viewmodel", (char *) "Viewmodel", (char *) DEF_VIEWMODEL, t_Bool},
  {(caddr_t *) & oldcolor, (char *) "oldcolor", (char *) "Oldcolor", (char *) DEF_OLDCOLOR, t_Bool},
  {(caddr_t *) & debug, (char *) "xcldebug", (char *) "Xcldebug", (char *) DEF_XCLDEBUG, t_Bool},
  {(caddr_t *) & automatic, (char *) "automatic", (char *) "Automatic", (char *) DEF_AUTOMATIC, t_Bool},
  {(caddr_t *) & randomstart, (char *) "randomstart", (char *) "Randomstart", (char *) DEF_RANDOMSTART, t_Bool}
};
static OptionStruct desc[] =
{
  {(char *) "-speed num", (char *) "speed for the planes in km/h "},
  {(char *) "-frametime num", (char *) "time for one frame on the screen in usecs "},
  {(char *) "-line_length num", (char *) "distance between the pilot and the plane in mm "},
  {(char *) "-spectator num", (char *) "distance  between  spectator  and  pilot in mm"},
  {(char *) "-/+viewmodel", (char *) "turn on/off an anim view of one model"},
  {(char *) "-/+oldcolor", (char *) "turn on/off the old yellow/red combination"},
  {(char *) "-/+xcldebug", (char *) "turn on/off some timing information"},
  {(char *) "-/+automatic", (char *) "turn on/off auto scale for fit into the window"},
  {(char *) "-/+randomstart", (char *) "turn on/off a random start point for models at startup"}
};

ModeSpecOpt xcl_opts =
{sizeof opts / sizeof opts[0], opts, sizeof vars / sizeof vars[0], vars, desc};

#ifdef USE_MODULES    /* for xlockmore */
ModStruct   xcl_description =
{"xcl", "init_xcl", "draw_xcl", "release_xcl",
 "refresh_xcl", "init_xcl", NULL, &xcl_opts ,
 20000, -3, 1, 1, 64, 1.0, "",
 "Shows a control line combat model race", 0, NULL};
#endif

typedef struct
{
  int planes;    /* number of planes viewed*/
  int lines;     /* number of lines counted in model */
  double az[MAXCOUNT];  /* azimuth of plane */
  double el[MAXCOUNT];  /* elevation of plane */
  int width;
  int height;
  int mid_x;
  int mid_y;
  float Alpha;   /* rotate.1 */
  float Beta;    /* rotate.2 */
  float Gamma;   /* rotate.3 */
  float Vx;      /* width from zero in X */
  float Vy;      /* width from zero in Y */
  float Vz;      /* width from zero in Z */
  float G;       /* ZOOM  */
  float Ca,Cb,Cc,Sa,Sb,Sc; /* only for faster calculation */
  float Bx,By ;
  struct timeval tv1;
  double time1, time2, time3; /* calibrate time */
  int drawtime;
  double alpha[MAXCOUNT];            /* direction */
  double omega_const[MAXCOUNT];      /*  constant omega speed */
  double delta_az[MAXCOUNT];
  double delta_el[MAXCOUNT];
  int turn[MAXCOUNT];
  int turn_direction[MAXCOUNT];
  int random_pid;
  long planecolor[MAXCOUNT],bg;      /* used colours */
  GC gc[MAXCOUNT];
  GC erase_gc;
  XSegment *xseg[MAXCOUNT];
  XSegment *xseg_old[MAXCOUNT];
  int xcldelay;
  int no_preset;
} xclstruct;

static xclstruct *xcls = NULL;

int my_random(void)  /* not really good, but it works */
{
  static int number;
  number -= random_pid;
  return ( 0x0fff & (number));
}

int countlines(void)
{
  int array;
  int count = 0;
  for(array = 0;array<LINEARRAYS;array++)
    {
      count += model_data2[array][0] - 1;
    }
  return(count);
}

void um2(int X, int Y, int Z, xclstruct *dp)
{
  float X2,X3,X4,Y1,Y3,Y4,Z1,Z2,Z4;

  Y1 = dp->Ca * Y - dp->Sa * Z;
  Z1 = dp->Sa * Y + dp->Ca * Z;
  X2 = dp->Cb * X - dp->Sb * Z1;
  Z2 = dp->Sb * X + dp->Cb * Z1;
  X3 = dp->Cc * X2 - dp->Sc * Y1;
  Y3 = dp->Sc * X2 + dp->Cc * Y1;
  X4 = X3 + dp->Vx;
  Y4 = Y3 + dp->Vy;
  Z4 = Z2 + dp->Vz;

  dp->Bx = dp->mid_x + (-X4 / Y4 * dp->G);
  dp->By = dp->mid_y - (-Z4 / Y4 * dp->G);
}

void view_3d(XSegment *xseg, xclstruct *dp)
{
  int count = 0;
  int I,J;
  float BX [ENDPOINTS];
  float BY [ENDPOINTS];

  dp->Ca = cos(dp->Alpha);
  dp->Cb = cos(dp->Beta);
  dp->Cc = cos(dp->Gamma);
  dp->Sa = sin(dp->Alpha);
  dp->Sb = sin(dp->Beta);
  dp->Sc = sin(dp->Gamma);

  for(I = 0; I < ENDPOINTS; I++)
    {
      um2( model_data1 [ I * 3 + 0], model_data1 [ I * 3 + 1], model_data1 [ I * 3 + 2],dp);
      BX [I] = dp->Bx;
      BY [I] = dp->By;
    }
  for (I = 0; I < LINEARRAYS; I++)
    {
      for (J = 1; J < model_data2[I][0]; J++)
	{
	  xseg[count].x1 = (short) BX[(model_data2[I][J])-1];
	  xseg[count].y1 = (short) BY[(model_data2[I][J])-1]+(dp->mid_y/2);
	  xseg[count].x2 = (short) BX[(model_data2[I][J+1])-1];
	  xseg[count].y2 = (short) BY[(model_data2[I][J+1])-1]+(dp->mid_y/2);
	  count++;
	}
    }
}

long get_color(Display *dpy,char *color,XColor *final_color)
{
  XColor cdef;
  Colormap cmap;
  cmap = DefaultColormap(dpy,DefaultScreen(dpy));

  if (!XParseColor(dpy, cmap, color, &cdef) ||
      !XAllocColor(dpy, cmap, &cdef))
    {
      fprintf(stderr, "Color \"%s\" wasn't found\n", color);
    }
  
  if (final_color != NULL) *final_color = cdef;/* Copy the final color. */
  
  return(cdef.pixel);
}

void get_GC(Display *dpy,Window win,GC *gc,long color)
{
  unsigned long valuemask = 0; /*ignore XGCvalues and use defaults */
  XGCValues values;
  unsigned int line_width = 1;
  int line_style = LineSolid;  /* LineOnOffDash;*/
  int cap_style = CapRound;
  int join_style = JoinRound;

  *gc = XCreateGC(dpy, win, valuemask , &values);

  XSetForeground(dpy, *gc, color);

  XSetLineAttributes(dpy, *gc, line_width, line_style,
		     cap_style, join_style);
}

void refresh_xcl(ModeInfo * mi)
{
  draw_xcl(mi);
}

void release_xcl(ModeInfo * mi)
{
  if (xcls != NULL) {
    int screen;
    int i;
    for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) {
      xclstruct  *dp = &xcls[screen];
      
      for(i=0;i< dp->planes; i++) {
	if(dp->xseg[i] != NULL)
	  free(dp->xseg[i]);
	if(dp->xseg_old[i] != NULL)
	  free(dp->xseg_old[i]);
	if(dp->gc[i] != NULL)
	  XFreeGC(MI_DISPLAY(mi), dp->gc[i]);
      }
      if(dp->erase_gc != NULL)
	XFreeGC(MI_DISPLAY(mi), dp->erase_gc);
    }
    (void) free((void *) xcls);
    xcls = NULL;
  }
}

void init_xcl(ModeInfo * mi)
{
  xclstruct *dp;
  int i;            /* scratch */
  
  if (xcls == NULL) {
    if ((xcls = (xclstruct *) calloc(MI_NUM_SCREENS(mi),
				     sizeof (xclstruct))) == NULL)
      return;
  }
  dp = &xcls[MI_SCREEN(mi)];
  
  /* Update every time */
  dp->width = MI_WIDTH(mi);
  dp->height = MI_HEIGHT(mi);
  dp->mid_x = (dp->width / 2);
  dp->mid_y = (dp->height / 2);
  
  if(dp->no_preset != 1) {
    dp->no_preset = 1;
    /* some presettings */
    dp->planes = MI_COUNT(mi);
    if (dp->planes < -MINPLANES) {
      dp->planes = NRAND(-MI_COUNT(mi) -MINPLANES + 1) + MINPLANES;
    } else if (dp->planes < MINPLANES) {
      dp->planes = MINPLANES;
    }
    if(dp->planes > MAXCOUNT)
      dp->planes = MAXCOUNT;
    dp->Alpha = 0.0;          /* rotate.1 */
    dp->Beta  = 0.0;          /* rotate.2 */
    dp->Gamma = 0.0;          /* rotate.3 */
    dp->Vx = 1;               /* width from zero in X */
    dp->Vy = 800;             /* width from zero in Y */
    dp->Vz = -300;            /* width from zero in Z */
    dp->G =  500.0;           /* ZOOM  */
    dp->time3 = 1.0;
    dp->drawtime = 25000;
    dp->xcldelay = STARTUPDELAY;
    for(i=0;i< dp->planes; i++) {
      dp->az[i] = 2 * M_PI * i / (float)((dp->planes));
      dp->el[i] = 0.0;
      dp->alpha[i] = 0.75;      /* direction */
      dp->turn[i] = 0;
      dp->turn_direction[i] = 1;
      
      speed[i] = speed_in;  /* see TODO */
    }
    
    random_pid = getpid(); /* goes here first for randomstart */
    
    if(randomstart)
      {
	for(i=0;i< dp->planes; i++) { 
	  switch(i)
	    {
	    case 0:
	      dp->az[0] += (random_pid % 31) / 5.0;
	    default:
	      dp->az[i] = dp->az[0] + 2 * M_PI * i / (float)((dp->planes));
	    }
	}
      }
    
    dp->bg = MI_BLACK_PIXEL(mi);
    
    if(MI_IS_MONO(mi))
      for(i=0;i< dp->planes; i++) { 
	dp->planecolor[i] = MI_WHITE_PIXEL(mi);
      }
    else {
      if(!oldcolor) {
	for(i=0;i< dp->planes; i++) { 
	  dp->planecolor[i] = MI_PIXEL(mi, NRAND(MI_NPIXELS(mi)));
	}
      }
      else { /* with count >2 no so good */
	for(i=0;i< dp->planes; i++) { 
	  switch(i)
	    {
	    case 0:
	      dp->planecolor[0] = get_color(MI_DISPLAY(mi),"yellow",NULL);
	    case 1:
	      dp->planecolor[1] = get_color(MI_DISPLAY(mi),"red",NULL);
	    default:
	      dp->planecolor[i] = MI_PIXEL(mi, NRAND(MI_NPIXELS(mi)));
	    }
	}
      }
    }

    if(dp->erase_gc == NULL)
      get_GC(MI_DISPLAY(mi), MI_WINDOW(mi), &(dp->erase_gc),dp->bg);

    dp->lines = countlines();

    for(i=0;i< dp->planes; i++) { 
      if(dp->gc[i] == NULL)
	get_GC(MI_DISPLAY(mi), MI_WINDOW(mi), &(dp->gc[i]),
	       dp->planecolor[i]);
      dp->omega_const[i] = speed[i]/3.6 /line_length*1000.0;
      
      if(dp->xseg[i] == NULL)
	dp-> xseg[i] = (XSegment *) malloc(sizeof(XSegment) * dp->lines);
      if(dp->xseg_old[i] == NULL)
	dp->xseg_old[i] = (XSegment *) malloc(sizeof(XSegment) * dp->lines);
    }
    
    if(MI_IS_VERBOSE(mi)) {
      printf("X control line combat in a box\n");
      printf("Version: %s\n",sccsid);
      printf("Line length: %gm\n",line_length/1000.0);
      printf("Speed %g km/h  \n",speed[0]);
      printf("Lines per plane: %d\n",dp->lines);
      printf("Spectator at %gm\n",spectator/1000.0);
      printf("Try %g frames per Second (frametime: %dus)\n",
	     1000000.0/frametime,frametime);
      printf("Calibration at %d frames\n",REGULATE);
    }
  }

  /* clear the screen */
  
  MI_CLEARWINDOW(mi);
  /* XFillRectangle(MI_DISPLAY(mi), MI_WINDOW(mi),dp->erase_gc,0,0,MI_WIDTH(mi),MI_HEIGHT(mi));    */
  
  
  gettimeofday(&(dp->tv1),0);
  dp->time1 = (double)dp->tv1.tv_sec + (double)dp->tv1.tv_usec/(double)1000000;
  
  dp->xcldelay = frametime;
}

void draw_xcl(ModeInfo * mi)
{
  static int count = 0;
  int i;
  xclstruct *dp = &xcls[MI_SCREEN(mi)];
  
  if(viewmodel == True)
    {
      dp->Vx = 1;                 /* movement in X (width,negativ)*/
      if(dp->width < 900)
	dp->Vy = 800*800/dp->width;   /*             Y (deep)*/
      else
	dp->Vy = 800*1200/dp->width;  /*             Y (deep)*/
      dp->Vz = -300;              /*             Z (height) */
      dp->G = 350.0;          /* make it smaller when display is smaller */
      
      dp->Alpha += 0.03;
      dp->Beta += 0.006;
      dp->Gamma += 0.009;
      
      if (count != 0)
	{
	  XDrawSegments(MI_DISPLAY(mi),MI_WINDOW(mi),dp->erase_gc,
			dp->xseg_old[0],dp->lines);
	  XDrawSegments(MI_DISPLAY(mi),MI_WINDOW(mi),dp->gc[0],
			dp->xseg[0],dp->lines);
	  usleep(ROTATEDELAY * 2);
	}
      memcpy(dp->xseg_old[0],dp->xseg[0],sizeof(XSegment)*dp->lines);
      view_3d(dp->xseg[0],dp);
      count ++;
    }
  else
    {
      if(automatic)
	dp->G = dp->width / 2.1 ;
      
      for(i=0;i<dp->planes;i++) {
	
	memcpy(dp->xseg_old[i],dp->xseg[i],sizeof(XSegment)*dp->lines);
	
	dp->Alpha = - dp->alpha[i];
	dp->Beta = - dp->el[i];
	dp->Gamma = dp->az[i];
	
	dp->Vx = -(int)(cos(dp->az[i]) * cos(dp->el[i]) * line_length);
	dp->Vy = spectator -(int)(sin(dp->az[i]) * cos(dp->el[i]) * line_length);
	dp->Vz = (int)(sin(dp->el[i]) * line_length);

	view_3d(dp->xseg[i],dp);
	XDrawSegments(MI_DISPLAY(mi),MI_WINDOW(mi),dp->erase_gc,dp->xseg_old[i],dp->lines);
	XDrawSegments(MI_DISPLAY(mi),MI_WINDOW(mi),dp->gc[i],dp->xseg[i],dp->lines);
      }
      
      XFlush(MI_DISPLAY(mi));

     /* now move all planes */
      for(i=0;i<dp->planes;i++)
	{
	  dp->delta_az[i] = cos(dp->alpha[i]) * dp->omega_const[i] *
	    frametime/1000000;
	  dp->delta_el[i] = sin(dp->alpha[i]) * dp->omega_const[i] *
	    frametime/1000000;

	  dp->az[i] -= dp->delta_az[i];
	  dp->el[i] -= dp->delta_el[i];

	  if (dp->el[i] >= 0.0)
	    switch (dp->turn[i])
	      {
	      case 0:
		dp->turn_direction[i] *= -1;
		dp->alpha[i] += 0.62831853 * dp->turn_direction[i];
		dp->turn[i] ++;
		break;
	      case 1:
	      case 2:
	      case 3:
	      case 4:
	      case 5:
		dp->alpha[i] += 0.62831853 * dp->turn_direction[i];
		dp->turn[i] ++;
		break;
	      default:
		dp->turn[i] ++;
		break;
	      }
	  else
	    dp->turn[i] = 0;
	  if (dp->el[i] <= -(M_PI / 2.0))
	    {
	      dp->alpha[i] += M_PI;
	      dp->az[i] += M_PI;
	      /* el[i] = el[i] + (M_PI / 2.0) ; */
	    }
	  else
	    if(dp->turn[i] == 0)
	      dp->alpha[i] += (double)(RAND(600))  / 6283.0 * 2.0 * M_PI;
	} /* for (i) */

      count++;

      usleep(dp->xcldelay);
      if((count % REGULATE) == 0)
	{
	  gettimeofday(&(dp->tv1),0);
	  dp->time2 = (double)dp->tv1.tv_sec + (double)dp->tv1.tv_usec/
	    (double)1000000;
	  dp->time3 = dp->time2 - dp->time1;
	  dp->time1 = dp->time2;
	  dp->drawtime = (int) (dp->time3 * (1000000/REGULATE))  
	    - dp->xcldelay;
	  if((dp->xcldelay = frametime - dp->drawtime) <= 0) {
	    dp->xcldelay = 10;  /* 1 is possible xor xscreensaver mode */
	  }
	  if(debug == True)
	    printf("t_draw: %d, t_delay: %d\n",dp->drawtime,dp->xcldelay);
	}
    }
}
