/*
estreamdraw.c version 20070120
D. J. Bernstein
Public domain.

Warning: This code is awful from many perspectives;
I'm embarrassed to not be rewriting it before publishing it.
*/

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <math.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Command.h>
#include <X11/keysym.h>
#include <Xm/Xm.h>
#include <Xm/DrawingA.h>

#define TIMEOUT 20 /* milliseconds until next action */

int viewtype = 1; /* otherwise view machine */

double overhead = 0;
#define YCFIXED(z) (750 - 200 * log10(z))
#define YCBAD (-300)
#define YC(z) ((z > 0) ? YCFIXED((z) + overhead) : YCBAD)

int selecttype = 3;
double plustype = 0;
#define TYPES 6
#define XT(j) ((590.0 / (TYPES - 1)) * (j) + 105)
char *type[TYPES] = {
  "cipheragility"
, "cipherlong"
, "cipher1500"
, "cipher576"
, "cipher40"
, "cipher40k"
} ;
char *typename[TYPES] = {
  "Encrypt many parallel streams in 256-byte blocks"
, "Encrypt one long stream"
, "Set up nonce and encrypt 1500-byte packet"
, "Set up nonce and encrypt 576-byte packet"
, "Set up nonce and encrypt 40-byte packet"
, "Set up key, set up nonce, and encrypt 40-byte packet"
} ;

#define MAXMACHINES 64
char *machine[MAXMACHINES];
char *machinename[MAXMACHINES];
double machinelogcycles[MAXMACHINES];
int machines = 0;
int selectmachine = 0;
double plusmachine = 0;
#define XM(j) ((590.0 / (machines - 1)) * (j) + 105)

#define MAXCIPHERS 127
char *cipher[MAXCIPHERS];
char *ciphername[MAXCIPHERS];
char *ciphershortname[MAXCIPHERS];
int ciphers = 0;
int selectcipher = 0;
int selectsecondcipher = -1;

char line[1000];
char word0[1000];
char word1[1000];
char word2[1000];
char word3[1000];
double cycles[TYPES][MAXMACHINES][MAXCIPHERS + 1];

void readdata(void)
{
  int t;
  int m;
  int c;
  double z;

  while (fgets(line,sizeof line,stdin)) {
    if (sscanf(line,"%s %s %s %[^\n]",word0,word1,word2,word3) == 4 && !strcmp(word0,"machine")) {
      if (machines == MAXMACHINES) {
	fprintf(stderr,"fatal: too many machines\n");
	exit(1);
      }
      machine[machines] = strdup(word1);
      if (!machine[machines]) {
	fprintf(stderr,"fatal: out of memory\n");
	exit(1);
      }
      machinename[machines] = strdup(word3);
      if (!machinename[machines]) {
	fprintf(stderr,"fatal: out of memory\n");
	exit(1);
      }
      ++machines;
      continue;
    }
    if (sscanf(line,"%s %s %[^\n]",word0,word1,word2) == 3 && !strcmp(word0,"machine")) {
      if (machines == MAXMACHINES) {
	fprintf(stderr,"fatal: too many machines\n");
	exit(1);
      }
      machine[machines] = strdup(word1);
      if (!machine[machines]) {
	fprintf(stderr,"fatal: out of memory\n");
	exit(1);
      }
      machinename[machines] = strdup(word1);
      if (!machinename[machines]) {
	fprintf(stderr,"fatal: out of memory\n");
	exit(1);
      }
      ++machines;
      continue;
    }
    if (sscanf(line,"%s %s %s %[^\n]",word0,word1,word2,word3) == 4 && !strcmp(word0,"cipher")) {
      if (ciphers == MAXCIPHERS) {
	fprintf(stderr,"fatal: too many ciphers\n");
	exit(1);
      }
      cipher[ciphers] = strdup(word1);
      if (!cipher[ciphers]) {
	fprintf(stderr,"fatal: out of memory\n");
	exit(1);
      }
      ciphershortname[ciphers] = strdup(word2);
      if (!ciphershortname[ciphers]) {
	fprintf(stderr,"fatal: out of memory\n");
	exit(1);
      }
      ciphername[ciphers] = strdup(word3);
      if (!ciphername[ciphers]) {
	fprintf(stderr,"fatal: out of memory\n");
	exit(1);
      }
      if (!strcmp(cipher[ciphers],"256.AESCTR")) selectcipher = ciphers;
      ++ciphers;
      continue;
    }
    if (sscanf(line,"%s %s %s %lf",word0,word1,word2,&z) == 4) {
      for (t = 0;t < TYPES;++t) if (!strcmp(type[t],word0)) break;
      if (t < TYPES) {
        for (m = 0;m < machines;++m) if (!strcmp(machine[m],word1)) break;
	if (m < machines) {
          for (c = 0;c < ciphers;++c) if (!strcmp(cipher[c],word2)) break;
	  if (c < ciphers) {
            if (!cycles[t][m][c])
              cycles[t][m][c] = z;
	    continue;
	  }
	}
      }
    }
  }
}

void sortdata(void)
{
  int i;
  int t;
  int j;
  int k;
  double z;
  double znum;
  char *s;

  for (j = 0;j < machines;++j) {
    z = 0;
    znum = 0;
    for (i = 0;i < TYPES;++i)
      for (k = 0;k < ciphers;++k)
	if (cycles[i][j][k]) {
	  z += log(cycles[i][j][k]);
	  znum += 1;
	}
    if (znum) z /= znum;
    machinelogcycles[j] = z;
  }
  for (j = 0;j < machines;++j) {
    t = j;
    for (k = j;k < machines;++k)
      if (machinelogcycles[k] > machinelogcycles[t]) t = k;
    if (t != j) {
      s = machine[j]; machine[j] = machine[t]; machine[t] = s;
      s = machinename[j]; machinename[j] = machinename[t]; machinename[t] = s;
      z = machinelogcycles[j]; machinelogcycles[j] = machinelogcycles[t]; machinelogcycles[t] = z;
      for (i = 0;i < TYPES;++i)
	for (k = 0;k < ciphers;++k) {
	  z = cycles[i][j][k];
	  cycles[i][j][k] = cycles[i][t][k];
	  cycles[i][t][k] = z;
	}
    }
  }
}

XtAppContext a;
Widget top;
Widget draw;
Display *display;
Screen *screen;
Window root;
XGCValues gcv;
GC gc;
Pixmap pix;
Colormap colormap;
XColor color[MAXCIPHERS + 1];
int pos[MAXCIPHERS + 1];
double lastz[MAXCIPHERS + 1];
int lastzpos[MAXCIPHERS + 1];
double oldlastz[MAXCIPHERS + 1];
int oldlastzpos[MAXCIPHERS + 1];

void redraw(void)
{
  int i;
  int j;
  int k;
  int z1;
  int z2;
  int t0;
  int t1;
  double fraction;
  int minz;
  int maxz;

  XSetForeground(display,gc,WhitePixelOfScreen(screen));
  XFillRectangle(display,pix,gc,0,0,1024,768);

  minz = 768;
  maxz = 0;

  for (j = 0;j < machines;++j)
    for (k = 0;k < TYPES;++k)
      cycles[k][j][ciphers] = cycles[k][j][selectcipher];
  ciphershortname[ciphers] = ciphershortname[selectcipher];

  for (i = 0;i <= ciphers;++i) {
    oldlastz[i] = 0;
    lastz[i] = 0;
    if (i == selectcipher) continue;
    if (i == selectsecondcipher)
      XSetForeground(display,gc,color[ciphers].pixel);
    else
      XSetForeground(display,gc,color[i].pixel);
    if (viewtype) {
      if (plustype < 0) {
        t0 = selecttype + floor(plustype);
        t1 = selecttype + ceil(plustype);
        fraction = plustype - floor(plustype);
      } else {
        t0 = selecttype + ceil(plustype);
        t1 = selecttype + floor(plustype);
        fraction = ceil(plustype) - plustype;
      }
      for (j = 0;j < machines;++j) {
	  if (j == selectmachine) {
	    oldlastz[i] = YC(cycles[t0][j][i]);
	    lastz[i] = YC(cycles[t1][j][i]);
	  }
        if (cycles[t0][j][i] && cycles[t1][j][i]) {
	  z1 = YC(cycles[t1][j][i]) * fraction + YC(cycles[t0][j][i]) * (1 - fraction);
	  if (j == selectmachine) {
	    if (z1 < minz) minz = z1;
	    if (z1 > maxz) maxz = z1;
	  }
	  if (i % 10 < 5) {
	    XFillRectangle(display,pix,gc,XM(j) - 3,z1 - 2,7,5);
	    XFillRectangle(display,pix,gc,XM(j) - 2,z1 - 3,5,7);
	  } else {
	    XDrawRectangle(display,pix,gc,XM(j) - 3,z1 - 3,7,7);
	  }
        }
      }
      for (j = 1;j < machines;++j) {
	z1 = YC(cycles[t1][j - 1][i]) * fraction + YC(cycles[t0][j - 1][i]) * (1 - fraction);
	z2 = YC(cycles[t1][j][i]) * fraction + YC(cycles[t0][j][i]) * (1 - fraction);
        if (cycles[t0][j - 1][i] && cycles[t0][j][i])
        if (cycles[t1][j - 1][i] && cycles[t1][j][i]) {
          XDrawLine(display,pix,gc,XM(j - 1),z1,XM(j),z2);
          XDrawLine(display,pix,gc,XM(j - 1),z1 + 1,XM(j),z2 + 1);
	  if (i == ciphers || i == selectsecondcipher) {
            XDrawLine(display,pix,gc,XM(j - 1),z1 - 1,XM(j),z2 - 1);
            XDrawLine(display,pix,gc,XM(j - 1),z1 + 2,XM(j),z2 + 2);
	  }
	}
      }
    } else {
      if (plusmachine < 0) {
        t0 = selectmachine + floor(plusmachine);
        t1 = selectmachine + ceil(plusmachine);
        fraction = plusmachine - floor(plusmachine);
      } else {
        t0 = selectmachine + ceil(plusmachine);
        t1 = selectmachine + floor(plusmachine);
        fraction = ceil(plusmachine) - plusmachine;
      }
      for (k = 0;k < TYPES;++k) {
	  if (k == selecttype) {
	    oldlastz[i] = YC(cycles[k][t0][i]);
	    lastz[i] = YC(cycles[k][t1][i]);
	  }
        if (cycles[k][t0][i] && cycles[k][t1][i]) {
	  z1 = YC(cycles[k][t1][i]) * fraction + YC(cycles[k][t0][i]) * (1 - fraction);
	  if (k == selecttype) {
	    if (z1 < minz) minz = z1;
	    if (z1 > maxz) maxz = z1;
	  }
	  if (i % 10 < 5) {
	    XFillRectangle(display,pix,gc,XT(k) - 3,z1 - 2,7,5);
	    XFillRectangle(display,pix,gc,XT(k) - 2,z1 - 3,5,7);
	  } else {
	    XDrawRectangle(display,pix,gc,XT(k) - 3,z1 - 3,7,7);
	  }
        }
      }
      for (k = 1;k < TYPES;++k) {
	z1 = YC(cycles[k - 1][t1][i]) * fraction + YC(cycles[k - 1][t0][i]) * (1 - fraction);
	z2 = YC(cycles[k][t1][i]) * fraction + YC(cycles[k][t0][i]) * (1 - fraction);
        if (cycles[k - 1][t0][i] && cycles[k][t0][i])
        if (cycles[k - 1][t1][i] && cycles[k][t1][i]) {
          XDrawLine(display,pix,gc,XT(k - 1),z1,XT(k),z2);
          XDrawLine(display,pix,gc,XT(k - 1),z1 + 1,XT(k),z2 + 1);
	  if (i == ciphers || i == selectsecondcipher) {
            XDrawLine(display,pix,gc,XT(k - 1),z1 - 1,XT(k),z2 - 1);
            XDrawLine(display,pix,gc,XT(k - 1),z1 + 2,XT(k),z2 + 2);
	  }
	}
      }
    }
  }

  lastz[selectcipher] = lastz[ciphers];
  for (i = 0;i < ciphers;++i) pos[i] = i;
  for (i = 0;i < ciphers;++i) {
    k = i;
    for (j = i;j < ciphers;++j) if (lastz[pos[j]] >= lastz[pos[k]]) k = j;
    if (k != i) { j = pos[k]; pos[k] = pos[i]; pos[i] = j; }
  }
  for (i = 0;i < ciphers;++i) lastzpos[pos[i]] = i % 4;
  lastzpos[ciphers] = lastzpos[selectcipher];

  oldlastz[selectcipher] = oldlastz[ciphers];
  for (i = 0;i < ciphers;++i) pos[i] = i;
  for (i = 0;i < ciphers;++i) {
    k = i;
    for (j = i;j < ciphers;++j) if (oldlastz[pos[j]] >= oldlastz[pos[k]]) k = j;
    if (k != i) { j = pos[k]; pos[k] = pos[i]; pos[i] = j; }
  }
  for (i = 0;i < ciphers;++i) oldlastzpos[pos[i]] = i % 4;
  oldlastzpos[ciphers] = oldlastzpos[selectcipher];

  for (i = 0;i <= ciphers;++i) if (lastz[i] == YCBAD) lastzpos[i] = 500;
  for (i = 0;i <= ciphers;++i) if (oldlastz[i] == YCBAD) oldlastzpos[i] = 500;

  for (i = 0;i <= ciphers;++i) {
    if (i == selectsecondcipher)
      XSetForeground(display,gc,color[ciphers].pixel);
    else
      XSetForeground(display,gc,color[i].pixel);
    /*
    if (lastz[i] && oldlastz[i])
    */
      XDrawString(display,pix,gc
	,XM(machines - 1) + 8 + (fraction * lastzpos[i] + (1 - fraction) * oldlastzpos[i]) * 70
	,fraction * lastz[i] + (1 - fraction) * oldlastz[i] + 12
	,ciphershortname[i],strlen(ciphershortname[i]));
  }

  XSetForeground(display,gc,BlackPixelOfScreen(screen));

  maxz = YCFIXED(1);
  if (viewtype)
    XDrawLine(display,pix,gc,XM(selectmachine),minz,XM(selectmachine),maxz);
  else
    XDrawLine(display,pix,gc,XT(selecttype),minz,XT(selecttype),maxz);
  XDrawLine(display,pix,gc,XM(0),YCFIXED(1),XM(machines - 1),YCFIXED(1));
  XDrawLine(display,pix,gc,XM(0),YCFIXED(10),XM(machines - 1),YCFIXED(10));
  XDrawLine(display,pix,gc,XM(0),YCFIXED(100),XM(machines - 1),YCFIXED(100));
  XDrawLine(display,pix,gc,XM(0),YCFIXED(1000),XM(machines - 1),YCFIXED(1000));
  XDrawString(display,pix,gc,80,12 + YCFIXED(1),"1",1);
  XDrawString(display,pix,gc,80,12 + YCFIXED(3),"3",1);
  XDrawString(display,pix,gc,60,12 + YCFIXED(10),"10",2);
  XDrawString(display,pix,gc,60,12 + YCFIXED(30),"30",2);
  XDrawString(display,pix,gc,40,12 + YCFIXED(100),"100",3);
  XDrawString(display,pix,gc,40,12 + YCFIXED(300),"300",3);
  XDrawString(display,pix,gc,20,12 + YCFIXED(1000),"1000",4);
  XDrawString(display,pix,gc,120,45,typename[selecttype],strlen(typename[selecttype]));
  XDrawString(display,pix,gc,120,85,machinename[selectmachine],strlen(machinename[selectmachine]));
  if (cycles[selecttype][selectmachine][selectcipher])
    if (overhead)
      sprintf(line,"%s + %.1f cycles/byte: %.1f cycles/byte",ciphername[selectcipher],overhead,cycles[selecttype][selectmachine][selectcipher] + overhead);
    else
      sprintf(line,"%s: %.1f cycles/byte",ciphername[selectcipher],cycles[selecttype][selectmachine][selectcipher]);
  else
    sprintf(line,"%s: not timed",ciphername[selectcipher],cycles[selecttype][selectmachine][selectcipher]);
  XDrawString(display,pix,gc,120,125,line,strlen(line));

  XCopyArea(display,pix,XtWindow(draw),gc,0,0,1024,768,0,0);
}

void timeout(XtPointer client_data,XtIntervalId *id)
{
  XtAppAddTimeOut(a,TIMEOUT,timeout,0);
  if (plustype > 0) {
    if (plustype > 1) plustype -= 3 * 0.03125;
    plustype -= 0.03125;
    redraw();
  }
  if (plustype < 0) {
    if (plustype < -1) plustype += 3 * 0.03125;
    plustype += 0.03125;
    redraw();
  }
  if (plusmachine > 0) {
    if (plusmachine > 1) plusmachine -= 3 * 0.03125;
    plusmachine -= 0.03125;
    redraw();
  }
  if (plusmachine < 0) {
    if (plusmachine < -1) plusmachine += 3 * 0.03125;
    plusmachine += 0.03125;
    redraw();
  }
}

void selectcipherdown(void)
{
  int i;
  int k;
  double *z = cycles[selecttype][selectmachine];

  k = selectcipher;
  for (i = 0;i < ciphers;++i)
    if (z[i] < z[selectcipher] || (z[i] == z[selectcipher] && i < selectcipher)) {
      if (k != selectcipher)
        if (z[i] < z[k] || (z[i] == z[k] && i < k))
	  continue;
      k = i;
    }
  selectcipher = k;
}

void selectcipherup(void)
{
  int i;
  int k;
  double *z = cycles[selecttype][selectmachine];

  k = selectcipher;
  for (i = 0;i < ciphers;++i)
    if (z[i] > z[selectcipher] || (z[i] == z[selectcipher] && i > selectcipher)) {
      if (k != selectcipher)
        if (z[i] > z[k] || (z[i] == z[k] && i > k))
	  continue;
      k = i;
    }
  selectcipher = k;
}

void input(Widget draw,XtPointer client_data,XmDrawingAreaCallbackStruct *cbk)
{
  char buf[1];
  KeySym k;

  if (cbk->event->type == KeyPress) {
    if (XLookupString((XKeyEvent *) cbk->event,buf,sizeof buf,&k,0) == 1)
      switch(k) {
        case 'q':
          XFlush(display);
          exit(0);
        case ']':
	  if (viewtype) {
	    if (selecttype + 1 < TYPES) { plustype -= 1; ++selecttype; }
	  } else {
	    if (selectmachine + 1 < machines) { plusmachine -= 1; ++selectmachine; }
	  }
          redraw();
          XSync(display,False);
          break;
        case '[':
	  if (viewtype) {
	    if (selecttype - 1 >= 0) { plustype += 1; --selecttype; }
	  } else {
	    if (selectmachine - 1 >= 0) { plusmachine += 1; --selectmachine; }
	  }
          redraw();
          XSync(display,False);
          break;
	case '=':
	  selectsecondcipher = selectcipher;
	  redraw();
	  XSync(display,False);
	  break;
	case '-':
	  selectsecondcipher = -1;
	  redraw();
	  XSync(display,False);
	  break;
	case 'o':
	  if (overhead < 1000) ++overhead;
          redraw();
          XSync(display,False);
          break;
	case 'O':
	  if (overhead > 0) --overhead;
          redraw();
          XSync(display,False);
          break;
	case 'j': case XK_KP_Down:
	  selectcipherdown();
          redraw();
          XSync(display,False);
          break;
	case 'k': case XK_KP_Up:
	  selectcipherup();
          redraw();
          XSync(display,False);
          break;
	case 'h': case XK_KP_Left:
	  if (viewtype) {
	    if (selectmachine > 0) --selectmachine;
	  } else {
	    if (selecttype > 0) --selecttype;
	  }
          redraw();
          XSync(display,False);
          break;
	case 'l': case XK_KP_Right:
	  if (viewtype) {
	    if (selectmachine < machines - 1) ++selectmachine;
	  } else {
	    if (selecttype < TYPES - 1) ++selecttype;
	  }
          redraw();
          XSync(display,False);
          break;
	case ' ':
	  viewtype = !viewtype;
          redraw();
          XSync(display,False);
          break;
      }
  }
}

void expose(Widget draw,XtPointer client_data,XmDrawingAreaCallbackStruct *cbk)
{
  XCopyArea(cbk->event->xexpose.display,pix,cbk->window,gc,0,0,1024,768,0,0);
}

int main(int argc,char **argv)
{
  unsigned short x;
  unsigned short y;
  int i;

  readdata();
  sortdata();
  if (!machines) { fprintf(stderr,"fatal: no machines\n"); exit(100); }
  if (!ciphers) { fprintf(stderr,"fatal: no ciphers\n"); exit(100); }
  selectmachine = machines - 1;

  /* XXX: catch various X errors */
  
  top = XtOpenApplication(&a,"draw",0,0,&argc,argv,0,applicationShellWidgetClass,0,0);

  XtMakeResizeRequest(top,1024,768,&x,&y);

  draw = XtVaCreateWidget("draw",xmDrawingAreaWidgetClass,top,NULL);

  display = XtDisplay(draw);
  screen = XtScreen(draw);
  root = RootWindowOfScreen(screen);

  gcv.foreground = BlackPixelOfScreen(XtScreen(draw));
  gc = XCreateGC(display,root,GCForeground,&gcv);

  pix = XCreatePixmap(display,root,1024,768,DefaultDepthOfScreen(screen));
  colormap = DefaultColormap(display,DefaultScreen(display));

  for (i = 0;i < ciphers;++i) {
    color[i].flags = DoRed | DoGreen | DoBlue;
    if (i % 5 == 0) {
      color[i].red = 65535;
      color[i].green = 0;
      color[i].blue = 0;
    } else if (i % 5 == 1) {
      color[i].red = 0;
      color[i].green = 40000;
      color[i].blue = 0;
    } else if (i % 5 == 2) {
      color[i].red = 0;
      color[i].green = 0;
      color[i].blue = 65535;
    } else if (i % 5 == 3) {
      color[i].red = 40000;
      color[i].green = 0;
      color[i].blue = 40000;
    } else {
      color[i].red = 0;
      color[i].green = 50000;
      color[i].blue = 50000;
    }
    XAllocColor(display,colormap,&color[i]);
  }
  color[i].flags = DoRed | DoGreen | DoBlue;
  color[i].red = 0;
  color[i].green = 0;
  color[i].blue = 0;
  XAllocColor(display,colormap,&color[i]);

  XSetFont(display,gc,XLoadFont(display,"-adobe-helvetica-bold-r-normal--34-240-100-100-p-182-iso8859-1"));

  XtAddCallback(draw,XmNexposeCallback,(XtCallbackProc) expose,0);
  XtAddCallback(draw,XmNinputCallback,(XtCallbackProc) input,0);
  XtAppAddTimeOut(a,TIMEOUT,(XtTimerCallbackProc) timeout,0);
  /* XtAppAddInput(a,0,(XtPointer) XtInputReadMask,get,0); */

  XtManageChild(draw);
  XtRealizeWidget(top);

  redraw();

  XtAppMainLoop(a);
  exit(111);
}