#define ProgName "xtrapbot"
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <X11/extensions/xtraplib.h>
#include <X11/extensions/xtraplibp.h>
#include <X11/keysym.h>
#include <time.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>


#ifndef vaxc
#define globalref extern
#endif
#ifdef Lynx
extern char *optarg;
extern int optind;
extern int opterr;
#endif

char *word;
char *gauss_par;
char *inter_par;
char *simple;
long num_gen;
XrmOptionDescRec optionTable [] =
{
   {"-w", "*word",XrmoptionSepArg, (caddr_t) NULL},
   {"-s", "*simple",XrmoptionSepArg, (caddr_t) NULL},
   {"-n", "*num_gen",XrmoptionSepArg, (caddr_t) NULL},
   {"-g", "*gauss_par",XrmoptionSepArg, (caddr_t) NULL},
   {"-i", "*inter_par",XrmoptionSepArg, (caddr_t) NULL},
};
void print_help() {
   printf("%s -- X bot generating user specified PDF based words\n",ProgName);
   printf("------------------------------------------------\n");
   printf("-w word\tThe word that you want to generate.\n");
   printf("-n num\tThe number of instances you want to generate.\n");
   printf("-s[mean_0,noi_0]...[mean_n,noi_n]\n\tSimple noisy emission.\n\t"
         "The parameters are the mean/maximum .oise of each letter in word.\n");
   printf("-g[mean_0,std_0]...[mean_n,std_n]\n\tGaussian emission.\n\t"
         "The parameters are the mean/standard diviation of each letter in word.\n");
   printf("-i[mean0,ns_0]...[mean_n,ns_n]\n\tGaussian or Noisy inter-key timings.\n\t"
         "The type on distribution depends on weather -s or -g was used.\n");
   printf("-v\tEnable verbose messages.\n");
   printf("-h\tThis message.\n");
   printf("------------------------------------------------\n");
   exit(0);
}


/* Special private indicators */
static BOOL     verbose_flag = FALSE;
static Window root;
static BOOL passive_shift; /* Cap's assumed?                       */
static BOOL shift;         /* Cap's on?                            */
KeyCode shift_code;

#define GAUSS 1
#define NOISE 2
struct Distribution {
   char ch;
   float mean;
   union {
      float std;
      float noise;
   } u;
   int type;
};

struct Digraph {
   struct Distribution g1,g2;
   float mean,std;
};

// -- random number stuff
gsl_rng *grngmt,*grnggaus,*grnginter;

double MT_rand(gsl_rng *grng) { 
   double r=gsl_rng_uniform(grng);
   return (gsl_rng_uniform(grng)>=0.5)?r:-1*r;
}

static void inter_sleep(struct Distribution *d) {
   double slp=0;
   if(d->type==GAUSS) {
      slp=gsl_ran_gaussian(grnginter,d->u.std)+d->mean;
   } else  {
      slp=d->mean+MT_rand(grnginter)*d->u.noise;
   }

   if(slp<0) { slp=0; }

   if(verbose_flag) { printf("* sleeping for %f\n",slp); }

   msleep(slp);
}

static void KeyClick_sdur(XETC *tc, KeyCode keycode,struct Distribution *g) {
   double slp=g->mean+MT_rand(grngmt)*g->u.noise;
   if (passive_shift && !shift) {
      XESimulateXEventRequest(tc, KeyPress, shift_code, 0, 0, 0);
   }
   XESimulateXEventRequest(tc, KeyPress, keycode, 0, 0, 0);
   if(verbose_flag) { printf("=%c sleeping for %f\n",g->ch,slp); }
   if(slp<0) {
      slp=0;
//      fprintf(stderr,"You messed up! the sleep time is negative! noise should not be higher than mean...\n");
 //     exit(-1);
   }
   msleep(slp);
   XESimulateXEventRequest(tc, KeyRelease, keycode, 0, 0, 0);
   if (passive_shift && !shift) {
      XESimulateXEventRequest(tc, KeyRelease, shift_code, 0, 0, 0);
   }
   passive_shift = FALSE;  
}

static void KeyClick_gdur(XETC *tc, KeyCode keycode,struct Distribution *g) {
   double slp=gsl_ran_gaussian(grnggaus,g->u.std)+g->mean;
   if (passive_shift && !shift) {
      XESimulateXEventRequest(tc, KeyPress, shift_code, 0, 0, 0);
   }
   XESimulateXEventRequest(tc, KeyPress, keycode, 0, 0, 0);
   if(verbose_flag) { printf("=%c sleeping for %f\n",g->ch,slp); }
   if(slp<0) {
      slp=0;
//      fprintf(stderr,"You messed up! the sleep time is negative! Check your mean/std...\n");
 //     exit(-1);
   }
   msleep(slp);
   XESimulateXEventRequest(tc, KeyRelease, keycode, 0, 0, 0);
   if (passive_shift && !shift) {
      XESimulateXEventRequest(tc, KeyRelease, shift_code, 0, 0, 0);
   }
   passive_shift = FALSE;  
}

/*
static void KeyClick_cdur(XETC *tc, KeyCode keycode1,KeyCode keycode2,struct Digraph *c) {
   double intr=gsl_ran_gaussian(grngcomp,c->std)+c->mean;

   if(intr<0) {
      double dur1=gsl_ran_gaussian(grnggaus,c->g1.std)+c->g1.mean;
      double dur2=gsl_ran_gaussian(grnggaus,c->g2.std)+c->g2.mean;
      double slp1=dur1+intr; //intr is negative
      double slp2=dur2+intr; //intr is negative
      if (passive_shift && !shift) {
         XESimulateXEventRequest(tc, KeyPress, shift_code, 0, 0, 0);
      }
      XESimulateXEventRequest(tc, KeyPress, keycode1, 0, 0, 0);
      if(verbose_flag) { printf("=%c pressed, sleeping for %f\n",c->g1.ch,slp1); }
      if(slp1<0) { 
         fprintf(stderr,"You messed up! the sleep time is negative! Check your mean/std...\n");
         exit(-1);
      }
      msleep(slp1);
      XESimulateXEventRequest(tc, KeyPress, keycode2, 0, 0, 0);
      if(verbose_flag) { printf("=%c pressed sleeping for %f\n",c->g2.ch,intr); }
      if(intr<0) { 
         fprintf(stderr,"You messed up! the sleep time is negative! Check your mean/std...\n");
         exit(-1);
      }
      msleep(intr);
      XESimulateXEventRequest(tc, KeyRelease, keycode1, 0, 0, 0);
      if(verbose_flag) { printf("=%c released sleeping for %f\n",c->g1.ch,intr); }
      if(slp2<0) { 
         fprintf(stderr,"You messed up! the sleep time is negative! Check your mean/std...\n");
         exit(-1);
      }
      msleep(slp2);
      XESimulateXEventRequest(tc, KeyRelease, keycode2, 0, 0, 0);
      if(verbose_flag) { printf("=%c released\n",c->g2.ch); }

   } else {
      KeyClick_gdur(tc,keycode1,&(c->g1));
      msleep(intr);
      KeyClick_gdur(tc,keycode2,&(c->g2));
   }
}
*/

static void KeyClick(XETC *tc, KeyCode keycode) {
   if (passive_shift && !shift) {
      XESimulateXEventRequest(tc, KeyPress, shift_code, 0, 0, 0);
   }
   XESimulateXEventRequest(tc, KeyPress, keycode, 0, 0, 0);
   XESimulateXEventRequest(tc, KeyRelease, keycode, 0, 0, 0);
   if (passive_shift && !shift) {
      XESimulateXEventRequest(tc, KeyRelease, shift_code, 0, 0, 0);
   }
   passive_shift = FALSE;  
}

static KeyCode get_char ( XETC *tc , CARD32 keysym);
static KeyCode get_keycode ( XETC *tc , KeySym keysym);
struct Distribution *parse_dist(char *word, char *meanstd,int type);

struct Distribution *parse_gauss(char *word, char *meanstd) {
   return parse_dist(word,meanstd,GAUSS);
}

struct Distribution *parse_simple(char *word, char *meanstd) {
   return parse_dist(word,meanstd,NOISE);
}

struct Distribution *parse_dist(char *word, char *meanstd,int type) {
   int i;
   struct Distribution tmp;
   char *str=meanstd;
   struct Distribution *vect=malloc(sizeof(struct Distribution)*strlen(word));
   if(!vect) {
      perror("malloc failed to allocate Gaussian vector");
      exit(-1);
   }

   for(i=0;i<strlen(word);i++) { 
      if(sscanf(str,"[%f,%f]",&tmp.mean,&tmp.u.std)==2) {
         vect[i].mean=tmp.mean;
         vect[i].u.std=tmp.u.std;
         vect[i].ch=word[i];
         vect[i].type=type;
         str=index(str,']');
         str++;
      } else {
         fprintf(stderr,"Failed to parse [mean,std] of the gaussian parameters\n");
         exit(-1);
      }
   }

   for(i=0;verbose_flag && i<strlen(word);i++) {
      printf("%c\tm=%f\tv=%f\n",vect[i].ch,vect[i].mean,vect[i].u.std);
   }

   return vect;
   
}

int main(int argc, char *argv[]) {
   Widget appW;
   Display *dpy;
   XETrapGetCurRep   ret_cur;
   XETC    *tc;
   XtAppContext app;
   char *tmp = NULL;
   INT16 ch;
   /* ESC & CSI Parsing variables */
   int *popterr;
   char **poptarg;
   struct Distribution *inter_vect=NULL;

   grngmt=gsl_rng_alloc(gsl_rng_mt19937);
   grnggaus=gsl_rng_alloc(gsl_rng_default);
   gsl_rng_set(grngmt,time(0));
   gsl_rng_set(grnggaus,time(0));


#ifndef vms
   popterr = &opterr;
   poptarg = &optarg;
#else
   popterr = XEgetopterr();
   poptarg = XEgetoptarg();
#endif
   *popterr = 0; /* don't complain about -d for display */

   while ((ch = getopt(argc, argv, "d:vs:w:n:g:i:")) != EOF)
   {
      switch(ch)
      {
         case 'v':
            verbose_flag = TRUE;
            break;
         case 'd':   /* -display, let's let the toolkit parse it */
            break;
         case 'w':      /* word to generate */
            word=*poptarg;
            break;
         case 's':
            simple=*poptarg;
            break;
         case 'n':      /* number of instances to generate */
            num_gen=strtol(*poptarg,0,0);
            break;
         case 'g':      /* gaussian mean/std */
            gauss_par=*poptarg;
            break;
         case 'i':
            inter_par=*poptarg;
            break;
         case 'h': case '?':
            print_help();
            exit(0);
         default:
            break;
      }
   }
   appW = XtAppInitialize(&app,"XTrap",optionTable,(Cardinal)1L, (int *)&argc, (String *)argv, NULL,(ArgList)&tmp, (Cardinal)0);
   /*appW = XtAppInitialize(&app,"XTrap",(XrmOptionDescList)NULL,(Cardinal)0L,
         (int *)&argc, (String *)argv, NULL,(ArgList)&tmp,
         (Cardinal)0);*/

   dpy = XtDisplay(appW);
   if (verbose_flag) {
      printf("Display:  %s \n", DisplayString(dpy));
   }
   if ((tc = XECreateTC(dpy,0L, NULL)) == False) {
      fprintf(stderr,"%s: could not initialize XTrap extension\n", ProgName);
      exit (1L);
   }
   root = RootWindow(dpy,DefaultScreen(dpy));
   (void)XEStartTrapRequest(tc);
   shift_code = XKeysymToKeycode(tc->dpy,XK_Shift_L);

   if (verbose_flag) {
      (void)XEGetCurrentRequest(tc,&ret_cur);
      XEPrintCurrent(stderr,&ret_cur);
   }

   if(!word) {
      fprintf(stderr,"No word specified\n");
      exit(-1);
   } else if(!num_gen) {
      fprintf(stderr,"No number of samples specified\n");
      exit(-1);
   } else if(!gauss_par&&!simple) {
      fprintf(stderr,"No gaussian/simple generation parameters specified\n");
      exit(-1);
   }

   if(inter_par) {
      char *inword=strdup(word);
      inword[strlen(inword)-1]=0;
      if(gauss_par) {
         grnginter=gsl_rng_alloc(gsl_rng_default);
         gsl_rng_set(grnginter,time(0));
         inter_vect=parse_gauss(inword,inter_par);
      } else {
         grnginter=gsl_rng_alloc(gsl_rng_mt19937);
         gsl_rng_set(grnginter,time(0));
         inter_vect=parse_simple(inword,inter_par);
      }
   }

   fprintf(stderr,"Sleeping for 3 seconds\n"); sleep(3);
   if(gauss_par) {
      struct Distribution *vect=parse_gauss(word,gauss_par);
      int i,j;
      KeyCode keycode;
      for(i=0;i<num_gen;i++) {
         for(j=0;j<strlen(word);j++) {
            keycode = get_char(tc, (CARD32)vect[j].ch);
            if (keycode)
               KeyClick_gdur(tc, keycode, &vect[j]);
            if(inter_vect) 
               inter_sleep(&inter_vect[j]);
         }
      }
   } else if(simple) {
      struct Distribution *vect=parse_simple(word,simple);
      int i,j;
      KeyCode keycode;
      for(i=0;i<num_gen;i++) {
         for(j=0;j<strlen(word);j++) {
            keycode = get_char(tc, (CARD32)vect[j].ch);
            if (keycode)
               KeyClick_sdur(tc, keycode, &vect[j]);
            if(inter_vect) 
               inter_sleep(&inter_vect[j]);
         }
      }
   } 

   /* Clean things up */
   XEFreeTC(tc);      
   (void)XCloseDisplay(dpy);

   gsl_rng_free(grngmt);
   gsl_rng_free(grnggaus);
   if(grnginter) {gsl_rng_free(grnginter); }

   exit(0L);
}

static KeyCode get_char(XETC *tc,CARD32 keysym) {
   if (iscntrl(keysym)) {   
      switch(keysym) {
         case 0x09:  keysym = XK_Tab; break;
         case 0x0d:  keysym = XK_Return; break;
         case 0x7f:  keysym = XK_Delete; break;
         case 0x1B:   keysym = XK_Escape; break;
      }
   }
   passive_shift = (keysym >= XK_A && keysym <= XK_Z) ? TRUE : FALSE;
   switch(keysym) {   /* Special case shift's */
      case '!': case '"': case '@': case '#': case '$':
      case '%': case '^': case '&': case '*': case '(':
      case ')': case '_': case '+': case '{': case '}':
      case '|': case ':': case '>': case '?': case '~':
         passive_shift = TRUE;
   }
   return(get_keycode(tc, keysym));
}

static KeyCode get_keycode(XETC *tc, KeySym keysym) {
   char *keystr = (char *)XKeysymToString(keysym);
   KeyCode keycode;

   keystr = (keystr == NULL) ? "unknown" : keystr;
   if (!(keycode = XKeysymToKeycode(tc->dpy,keysym))) {
      fprintf(stderr,"\n[%s ('%%0x%04x') returns bad Keycode, ignored]\n",
            keystr, (unsigned int)keysym);
   }

   return(keycode);
}

