/*

   tkodometer 1.0,  Stephen O. Lidie, Lehigh University Computing Center, 94/07/10.

   lusol@Lehigh.EDU

   This is the Tcl/Tk version of my first XLIB program, xodometer.  Portions of
   tkodometer are written in C for improved efficiency (thanks to Michael D. Moore
   for these performance hints).

*/

#include <math.h>
#include <stdio.h>
#include <signal.h>
#include <sys/errno.h>
#include <tcl.h>
#include <tk.h>
#include "evap/evap.h"
#include "evap/tkodo_pdt.out"


int ComputeDistancesCMD(ClientData client_data,
		    Tcl_Interp *interp,
		    int argc,
		    char *argv[]);
int ImportXodoCMD(ClientData client_data,
		    Tcl_Interp *interp,
		    int argc,
		    char *argv[]);
int ResetTripCMD(ClientData client_data,
		    Tcl_Interp *interp,
		    int argc,
		    char *argv[]);
int SaveTkodoCMD(ClientData client_data,
		    Tcl_Interp *interp,
		    int argc,
		    char *argv[]);
void evap_type_conversion(evap_Parameter_Value *pvte);
void finish_tkodo();

Tcl_Interp *interp;

Display *theDisplay;
Window theWindow;
int absx, absy, relx, rely;
unsigned int ModKeyMask;
Window QueryRoot, QueryChild;

char *cunits;
char *hunits;
extern errno;
FILE *OF, *XF;
int prev_x, prev_y = 0;
int threshold;
double acceleration;
double X_mm_per_pixel, Y_mm_per_pixel;
double trip_cursor_distance = 0.0;
double total_cursor_distance = 0.0;
double trip_pointer_distance = 0.0;
double total_pointer_distance = 0.0;


int main(argc, argv)
     int argc;
     char *argv[];
{

  struct sigaction action;
  Tk_Window mainWindow;
  char eval_cmd[515];
  int accel_numerator, accel_denominator, i, pixels;
  char tkodo_units_human[80], tkodo_units[80];
  char *X_default;
  
  evap(&argc, &argv, pdt, NULL, pvt); /* evaluate_parameters */

  /* Initialize the Tcl/Tk environment. */

  interp = Tcl_CreateInterp();

  mainWindow = Tk_CreateMainWindow(interp, pvt[P_display].value.string_value, pvt[P_HELP].unconverted_value, "Tkodo");
  if (mainWindow == NULL) {
    fprintf(stderr, "%s\n", interp->result);
    exit(1);
  }
  if (Tcl_Init(interp) == TCL_ERROR) {
    fprintf(stderr, "Tcl_Init failed:  %s\n", interp->result);
    exit(1);
  }
  if (Tk_Init(interp) == TCL_ERROR) {
    fprintf(stderr, "Tk_Init failed:  %s\n", interp->result);
    exit(1);
  }

  /* Register new Tcl/Tk commands. */

  Tcl_CreateCommand( interp, "compute_distances", ComputeDistancesCMD, (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
  Tcl_CreateCommand( interp, "import_xodo", ImportXodoCMD, (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
  Tcl_CreateCommand( interp, "reset_trip", ResetTripCMD, (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
  Tcl_CreateCommand( interp, "save_tkodo", SaveTkodoCMD, (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);

  /* Do special XLIB initialization. */

  theDisplay = Tk_Display(mainWindow);
  Tk_MakeWindowExist(mainWindow);
  theWindow = Tk_WindowId(mainWindow);

  /*
     For all unspecified parameters see if there is an X default value.
     Variable "i" is type integer and "X_default" is type char *.
     
     Since there is no concept of a 'list of' X resource we ignore list
     parameters.
  */
  
  for ( i = 0 ; i < P_NUMBER_OF_PARAMETERS; i++ ) {
    if ( (! pvt[i].specified) && (pvt[i].list == NULL) ) {
      X_default = XGetDefault( theDisplay, pvt[P_HELP].unconverted_value, pvt[i].parameter );
      if ( X_default != NULL ) {
	pvt[i].unconverted_value = X_default;
	pvt[i].specified = 1;
	evap_type_conversion( &pvt[i] ); /* convert string to proper type */
      } /* ifend non-null X default for this parameter */
    } /* ifend unspecified scalar parameter */
  } /* forend all evaluate_parameters parameters */

  /* Global initialization. */

  X_mm_per_pixel =
    (double)pvt[P_display_width_millimeters].value.integer_value / (double)pvt[P_display_width_pixels].value.integer_value;
  Y_mm_per_pixel =
    (double)pvt[P_display_height_millimeters].value.integer_value / (double)pvt[P_display_height_pixels].value.integer_value;

  XQueryPointer(theDisplay, theWindow, &QueryRoot, &QueryChild, &absx, &absy, &relx, &rely, &ModKeyMask);
  prev_x = absx;
  prev_y = absy;

  XGetPointerControl( theDisplay, &accel_numerator, &accel_denominator, &threshold);
  acceleration = (double)accel_numerator / (double)accel_denominator;

  Tcl_SetVar2(interp, "Options", "mit", pvt[P_microsecond_interval_time].unconverted_value, TCL_GLOBAL_ONLY);
  sprintf(eval_cmd, "%f %d %f", pvt[P_pointer_scale_factor].value.real_value, threshold, acceleration);
  Tcl_SetVar2(interp, "Options", "numbers", eval_cmd, TCL_GLOBAL_ONLY);
  Tcl_SetVar2(interp, "Options", "fn", pvt[P_fontname].unconverted_value, TCL_GLOBAL_ONLY);
  Tcl_SetVar2(interp, "Options", "foreground", pvt[P_foreground].unconverted_value, TCL_GLOBAL_ONLY);
  Tcl_SetVar2(interp, "Options", "background", pvt[P_background].unconverted_value, TCL_GLOBAL_ONLY);
  Tcl_SetVar2(interp, "Options", "o", pvt[P_odometer].unconverted_value, TCL_GLOBAL_ONLY);
  Tcl_SetVar2(interp, "Options", "oat", pvt[P_odometer_autosave_time].unconverted_value, TCL_GLOBAL_ONLY);
  Tcl_SetVar2(interp, "Options", "program_name", pvt[P_HELP].unconverted_value, TCL_GLOBAL_ONLY);
  Tcl_SetVar2(interp, "Options", "i",  pvt[P_iconic].specified ? "1" : "0", TCL_GLOBAL_ONLY );
  Tcl_SetVar(interp, "LIBDIR", LIBDIR, TCL_GLOBAL_ONLY);

  OF = fopen(pvt[P_odometer_file].value.file_value, "r");
  if (OF == NULL && errno != ENOENT) {
    perror("Cannot open tkodometer_file for read:  ");
    exit(1);
  } else if( OF != NULL) {
    fscanf(OF, "%lf %lf %s %s", &total_cursor_distance, &total_pointer_distance, tkodo_units_human, tkodo_units);
    fclose(OF);
  } else {
    strcpy(tkodo_units_human, "km");
    strcpy(tkodo_units, "0.000001");
  }
  Tcl_SetVar(interp, "tkodo_units", tkodo_units, TCL_GLOBAL_ONLY);
  Tcl_SetVar(interp, "tkodo_units_human", tkodo_units_human, TCL_GLOBAL_ONLY);

  /* Given the screen scaling information provided by the user, compute the number of pixels in 1 inch for both the X and Y
     dimensions.  Use this data to draw the Help/Numbers display so that tkodometer calibration data can be visually verified.
  */
  pixels = (int)( (25.4 / X_mm_per_pixel) + 0.5 );
  sprintf(eval_cmd, "%d", pixels);
  Tcl_SetVar(interp, "pixels_per_inch_x", eval_cmd, TCL_GLOBAL_ONLY);
  pixels = (int)( (25.4 / Y_mm_per_pixel) + 0.5 );
  sprintf(eval_cmd, "%d", pixels);
  Tcl_SetVar(interp, "pixels_per_inch_y", eval_cmd, TCL_GLOBAL_ONLY);
  pixels = (int)( (10.0 / X_mm_per_pixel) + 0.5 );
  sprintf(eval_cmd, "%d", pixels);
  Tcl_SetVar(interp, "pixels_per_cm_x", eval_cmd, TCL_GLOBAL_ONLY);
  pixels = (int)( (10.0 / Y_mm_per_pixel) + 0.5 );
  sprintf(eval_cmd, "%d", pixels);
  Tcl_SetVar(interp, "pixels_per_cm_y", eval_cmd, TCL_GLOBAL_ONLY);

  /* Trap Control/C to save the tkodometer file. */

  sigemptyset(&action.sa_mask);	/* disable all signals */
  action.sa_flags = 0;
  action.sa_handler = finish_tkodo;
  if (sigaction(SIGINT, &action, NULL) != 0) {
    perror("Cannot set signal SIGINT:  ");
  }

  /* Establish the initial geometry, and "source" the Tcl/Tk code. */

  if (pvt[P_geometry].specified) {
    sprintf(eval_cmd, "wm geometry . %s", pvt[P_geometry].unconverted_value);
    Tcl_Eval(interp, eval_cmd);
    if (*interp->result != 0) {
      printf("Geometry request specification error:  %s\n", interp->result);
    }
  }
  sprintf(eval_cmd, "wm title . {%s}; source ${LIBDIR}/tkodo.tcl",
	  pvt[P_title].value.string_value);
  Tcl_Eval(interp, eval_cmd);
  if (*interp->result != 0) {
    printf("Tcl_Eval error:  %s\n", interp->result);
    exit(1);
  }

  /* Do tkodometer events and stuff. */

  Tk_MainLoop();

  Tcl_Eval(interp, "exit");
  exit(0);

} /* end tkodo */


void finish_tkodo()
{

  Tcl_Eval(interp, "save_tkodo; exit_tkodo");
  exit(0);

} /* end finish_tkodo */


int ComputeDistancesCMD(ClientData client_data,
			Tcl_Interp *interp,
			int argc,
			char *argv[])
{

  double dXmm, dYmm;
  int dX, dY;
  double units;
  int x, y;
  double distance;
  char line[1024];
  char d1[20], d2[20];

  if (argc != 2) {
    Tcl_AppendResult(interp, "Invalid argument count: ", argv[0], " window_name", (char *) NULL);
    return( TCL_ERROR );
  }

  XQueryPointer(theDisplay, theWindow, &QueryRoot, &QueryChild, &x, &y, &relx, &rely, &ModKeyMask);

  dX = x - prev_x;
  dY = y - prev_y;

  prev_x = x;
  prev_y = y;

  if ( dX < 0.0 )
    dX = -dX;
  if ( dY < 0.0 )
    dY = -dY;

  cunits = Tcl_GetVar(interp, "tkodo_units", TCL_GLOBAL_ONLY);
  sscanf(cunits, "%lf", &units);
  hunits = Tcl_GetVar(interp, "tkodo_units_human", TCL_GLOBAL_ONLY);

  dXmm = (double)dX * X_mm_per_pixel;
  dYmm = (double)dY * Y_mm_per_pixel;

  distance = sqrt( (dXmm * dXmm) + (dYmm * dYmm) );

  if ( distance != 0 ) {

    trip_cursor_distance += distance;
    total_cursor_distance += distance;
    if ( dX > threshold || dY > threshold ) {
      distance /= acceleration; /* if cursor was accelerated, we suppose! */
    }
    distance /= pvt[P_pointer_scale_factor].value.real_value;
    trip_pointer_distance += distance;
    total_pointer_distance += distance;

    sprintf(d1, "%011.5f", fmod(total_cursor_distance*units, 100000.0));
    sprintf(d2, "%011.5f", fmod(trip_cursor_distance*units, 100000.0));
    sprintf(line, "%s.codo.ctotal1 configure -text %c%c%c%c%c; %s.codo.ctotal2 configure -text %c%c%c%c%c",
	    argv[1], d1[0], d1[1], d1[2], d1[3], d1[4],
	    argv[1], d1[6], d1[7], d1[8], d1[9], d1[10]);
    Tcl_Eval(interp, line);
    sprintf(line, "%s.codo.ctrip1 configure -text %c%c%c%c%c; %s.codo.ctrip2 configure -text %c%c%c%c%c",
	    argv[1], d2[0], d2[1], d2[2], d2[3], d2[4],
	    argv[1], d2[6], d2[7], d2[8], d2[9], d2[10]);
    Tcl_Eval(interp, line);

    sprintf(d1, "%011.5f", fmod(total_pointer_distance*units, 100000.0));
    sprintf(d2, "%011.5f", fmod(trip_pointer_distance*units, 100000.0));
    sprintf(line, "%s.podo.ptotal1 configure -text %c%c%c%c%c; %s.podo.ptotal2 configure -text %c%c%c%c%c",
	    argv[1], d1[0], d1[1], d1[2], d1[3], d1[4],
	    argv[1], d1[6], d1[7], d1[8], d1[9], d1[10]);
    Tcl_Eval(interp, line);
    sprintf(line, "%s.podo.ptrip1 configure -text %c%c%c%c%c; %s.podo.ptrip2 configure -text %c%c%c%c%c",
	    argv[1], d2[0], d2[1], d2[2], d2[3], d2[4],
	    argv[1], d2[6], d2[7], d2[8], d2[9], d2[10]);
    Tcl_Eval(interp, line);

    sprintf(line, "%s.misc configure -text {U=%-3s  (%4d,%4d)}", argv[1], hunits, x, y);
    Tcl_Eval(interp, line);
  }

  return(TCL_OK);

} /* end ComputeDistancesCMD */


int ImportXodoCMD(ClientData client_data,
		    Tcl_Interp *interp,
		    int argc,
		    char *argv[])
{


  if (argc != 2) {
    Tcl_AppendResult(interp, "Invalid argument count: ", argv[0], " ~/.xodo", (char *) NULL);
    return( TCL_ERROR );
  }
  
  XF = fopen(argv[1], "r");
  if (XF == NULL) {
    Tcl_AppendResult(interp, "Cannot open xodometer file ", argv[1], " for read.  Perhaps you don't use xodometer?",
		     (char *) NULL);
    return( TCL_ERROR );
  } else {
    fscanf(XF, "%lf %lf", &total_cursor_distance, &total_pointer_distance);
    fclose(XF);
    return(TCL_OK);
  }

} /* end ImportXodoCMD */


int ResetTripCMD(ClientData client_data,
		    Tcl_Interp *interp,
		    int argc,
		    char *argv[])
{


  if (argc != 2) {
    Tcl_AppendResult(interp, "Invalid argument count: ", argv[0], " cursor|pointer", (char *) NULL);
    return( TCL_ERROR );
  }
  
  if (strcmp(argv[1], "cursor") == 0) {
    trip_cursor_distance = 0.0;
  } else if (strcmp(argv[1], "pointer") == 0) {
    trip_pointer_distance = 0.0;
  } else {
    Tcl_AppendResult(interp, "Invalid value: ", argv[1], ".  Must be cursor|pointer.", (char *) NULL);
    return(TCL_ERROR);
  }
  return( TCL_OK );

} /* end ResetTripCMD */


int SaveTkodoCMD(ClientData client_data,
		    Tcl_Interp *interp,
		    int argc,
		    char *argv[])
{


  if (argc != 1) {
    Tcl_AppendResult(interp, "Invalid argument count: ", argv[0], (char *) NULL);
    return( TCL_ERROR );
  }
  
  OF = fopen(pvt[P_odometer_file].value.file_value, "w");
  if (OF == NULL) {
    Tcl_AppendResult(interp, "Cannot open tkodometer file for write.", (char *) NULL);
    return( TCL_ERROR );
  } else {
    cunits = Tcl_GetVar(interp, "tkodo_units", TCL_GLOBAL_ONLY);
    hunits = Tcl_GetVar(interp, "tkodo_units_human", TCL_GLOBAL_ONLY);
    fprintf(OF, "%f %f %s %s\n", total_cursor_distance, total_pointer_distance, hunits, cunits);
    fclose(OF);
  }

  return(TCL_OK);

} /* end SaveTkodoCMD */
