/*
 * cave/draw.c version 20060614
 * D. J. Bernstein
 * Public domain.
 */

#define GRAPHWIDTH 664
#define GRAPHHEIGHT 350
#define BIGGRAPHHEIGHT 600
#define GRAPHLEFT 50
#define GRAPHBOTTOM 700
#define GRAPHRIGHT (GRAPHLEFT + GRAPHWIDTH)
#define GRAPHTOP (GRAPHBOTTOM - GRAPHHEIGHT)

int graphwidth = GRAPHWIDTH;
int graphheight = GRAPHHEIGHT;
int graphleft = GRAPHLEFT;
int graphbottom = GRAPHBOTTOM;
int graphright = GRAPHLEFT + GRAPHWIDTH;
int graphtop = GRAPHBOTTOM - GRAPHHEIGHT;
int desiredgraphheight = GRAPHHEIGHT;

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
#include "window.h"

char line[65536];

long long numbatmanversionnames; char **batmanversionnames;
long long numcomputernames; char **computernames;
long long numbatnames; char **batnames;
long long numbatversionnames; char **batversionnames;
long long numparamnames; char **paramnames;
long long nummessagelengths; char **messagelengthnames; long long *messagelengths;
long long numobjects; char **objects;

long long numpoints;
struct point {
  double x;
  double y;
  int source;
} *points;
struct point selectedpoint;

struct {
  long long batmanversion;
  long long computer;
  long long bat;
  long long batversion;
  long long param;
  long long intmeasures[5];
  long long logmeasures[5];
  long long *msgmeasures[6];
  double *chances[3];
} *z;

#define patentclaims intmeasures[0]
#define copyrightclaims intmeasures[1]
#define timingattacks intmeasures[2]
#define fakekeyattacks intmeasures[3]
#define ccattacks intmeasures[4]
#define keypaircycles logmeasures[0]
#define secretkeybytes logmeasures[1]
#define publickeybytes logmeasures[2]
#define sharedsecretcycles logmeasures[3]
#define sharedsecretbytes logmeasures[4]
#define ciphertextcycles msgmeasures[0]
#define ciphertextbytes msgmeasures[1]
#define plaintextcycles msgmeasures[2]
#define signedmessagecycles msgmeasures[3]
#define signedmessagebytes msgmeasures[4]
#define messagesignedcycles msgmeasures[5]
#define distinguishingchance chances[0]
#define forgerychance chances[1]
#define cdhchance chances[2]

void nomem(void)
{
  exit(111);
}

void readerror(void)
{
  exit(110);
}

void readstrings(long long *num,char ***x)
{
  int i;

  if (scanf("%lld",num) != 1) readerror();
  if (*num > 1000000) nomem();
  x[0] = malloc(*num * sizeof(char *));
  if (!x[0]) nomem();
  for (i = 0;i < *num;++i) {
    if (scanf("%200s",line) != 1) readerror();
    x[0][i] = strdup(line);
    if (!x[0][i]) nomem();
  }
}

double *allocatedouble(long long n)
{
  double *x;
  long long i;
  if (n > 1000000) nomem();
  x = malloc(n * sizeof(double));
  if (!x) nomem();
  for (i = 0;i < n;++i) x[i] = -1;
  return x;
}

long long *allocatelonglong(long long n)
{
  long long *x;
  long long i;
  if (n > 1000000) nomem();
  x = malloc(n * sizeof(long long));
  if (!x) nomem();
  for (i = 0;i < n;++i) x[i] = -1;
  return x;
}

void readz(void)
{
  long long i;
  long long j;
  double d;

  z = malloc(numobjects * sizeof(*z));
  if (!z) nomem();
  for (i = 0;i < numobjects;++i) {
    if (sscanf(objects[i],"%lld,%lld,%lld,%lld,%lld"
                 ,&z[i].batmanversion
                 ,&z[i].computer
                 ,&z[i].bat
                 ,&z[i].batversion
                 ,&z[i].param
              ) < 5) readerror();
    if (z[i].batmanversion < 0) readerror(); if (z[i].batmanversion >= numbatmanversionnames) readerror();
    if (z[i].computer < 0) readerror(); if (z[i].computer >= numcomputernames) readerror();
    if (z[i].bat < 0) readerror(); if (z[i].bat >= numbatnames) readerror();
    if (z[i].batversion < 0) readerror(); if (z[i].batversion >= numbatversionnames) readerror();
    if (z[i].param < 0) readerror(); if (z[i].param >= numparamnames) readerror();
    z[i].patentclaims = -1;
    z[i].copyrightclaims = -1;
    z[i].timingattacks = -1;
    z[i].fakekeyattacks = -1;
    z[i].ccattacks = -1;
    z[i].keypaircycles = -1;
    z[i].secretkeybytes = -1;
    z[i].publickeybytes = -1;
    z[i].sharedsecretcycles = -1;
    z[i].sharedsecretbytes = -1;
    z[i].ciphertextcycles = allocatelonglong(nummessagelengths);
    z[i].ciphertextbytes = allocatelonglong(nummessagelengths);
    z[i].plaintextcycles = allocatelonglong(nummessagelengths);
    z[i].signedmessagecycles = allocatelonglong(nummessagelengths);
    z[i].signedmessagebytes = allocatelonglong(nummessagelengths);
    z[i].messagesignedcycles = allocatelonglong(nummessagelengths);
    z[i].distinguishingchance = allocatedouble(41 * 21 * 11);
    z[i].forgerychance = allocatedouble(41 * 21 * 11);
    z[i].cdhchance = allocatedouble(41 * 21 * 11);
  }

  while (scanf("%lld",&i) == 1) {
    if (i >= numobjects) readerror();
    if (i < 0) readerror();
    if (scanf("%lld",&z[i].patentclaims) < 1) readerror();
    if (scanf("%lld",&z[i].copyrightclaims) < 1) readerror();
    if (scanf("%lld",&z[i].timingattacks) < 1) readerror();
    if (scanf("%lld",&z[i].fakekeyattacks) < 1) readerror();
    if (scanf("%lld",&z[i].ccattacks) < 1) readerror();
    if (scanf("%lld",&z[i].keypaircycles) < 1) readerror();
    if (scanf("%lld",&z[i].secretkeybytes) < 1) readerror();
    if (scanf("%lld",&z[i].publickeybytes) < 1) readerror();
    if (scanf("%lld",&z[i].sharedsecretcycles) < 1) readerror();
    if (scanf("%lld",&z[i].sharedsecretbytes) < 1) readerror();
    for (j = 0;j < nummessagelengths;++j)
      if (scanf("%lld",&z[i].ciphertextcycles[j]) < 1) readerror();
    for (j = 0;j < nummessagelengths;++j)
      if (scanf("%lld",&z[i].ciphertextbytes[j]) < 1) readerror();
    for (j = 0;j < nummessagelengths;++j)
      if (scanf("%lld",&z[i].plaintextcycles[j]) < 1) readerror();
    for (j = 0;j < nummessagelengths;++j)
      if (scanf("%lld",&z[i].signedmessagecycles[j]) < 1) readerror();
    for (j = 0;j < nummessagelengths;++j)
      if (scanf("%lld",&z[i].signedmessagebytes[j]) < 1) readerror();
    for (j = 0;j < nummessagelengths;++j)
      if (scanf("%lld",&z[i].messagesignedcycles[j]) < 1) readerror();
    for (j = 0;j < 41 * 21 * 11;++j) {
      if (scanf("%lf",&d) < 1) readerror();
      if (d >= 0) d = exp(log(0.5) * d / 256.0);
      z[i].distinguishingchance[j] = d;
    }
    for (j = 0;j < 41 * 21 * 11;++j) {
      if (scanf("%lf",&d) < 1) readerror();
      if (d >= 0) d = exp(log(0.5) * d / 256.0);
      z[i].forgerychance[j] = d;
    }
    for (j = 0;j < 41 * 21 * 11;++j) {
      if (scanf("%lf",&d) < 1) readerror();
      if (d >= 0) d = exp(log(0.5) * d / 256.0);
      z[i].cdhchance[j] = d;
    }
  }

  if (ferror(stdin)) readerror();

  points = malloc(numobjects * nummessagelengths * sizeof(*points));
  if (!points) nomem();
}

void parsemessagelengths(void)
{
  long long i;
  messagelengths = allocatelonglong(nummessagelengths);
  for (i = 0;i < nummessagelengths;++i)
    sscanf(messagelengthnames[i],"%lld",&messagelengths[i]);
}

void readdata(void)
{
  readstrings(&numbatmanversionnames,&batmanversionnames);
  readstrings(&numcomputernames,&computernames);
  readstrings(&numbatnames,&batnames);
  readstrings(&numbatversionnames,&batversionnames);
  readstrings(&numparamnames,&paramnames);
  readstrings(&nummessagelengths,&messagelengthnames);
  parsemessagelengths();
  readstrings(&numobjects,&objects);
  readz();
}

#define NUMFEATURES 28
struct {
  long long macro;
  long long label;
  long long fixed;
  long long hidden;
  long long logmeasure;
  long long intmeasure;
  long long msgmeasure;
  long long chance;
  long long class;
  char *name;
  double min;
  double max;
} features[NUMFEATURES] = {
#define FBMV 0
  { FBMV, 0, 0, 0, -1, -1, -1, -1, 1, "BATMAN version" }
#define FCNA 1
, { FCNA, 0, 0, 0, -1, -1, -1, -1, 1, "Computer name" }
#define FBNA 2
, { FBNA, 0, 0, 0, -1, -1, -1, -1, 1, "BAT name" }
#define FBVE 3
, { FBVE, 0, 0, 0, -1, -1, -1, -1, 1, "BAT version" }
#define FBPA 4
, { FBPA, 0, 0, 0, -1, -1, -1, -1, 1, "BAT parameters" }
#define FOMB 5
, { FOMB, 0, 0, 0, -1, -1, -1, -1, 2, "Original-message bytes" }
#define FCTB 6
, { FCTB, 0, 0, 0, -1, -1,  1, -1, 2, "Ciphertext bytes" }
#define FCTC 7
, { FCTC, 0, 0, 0, -1, -1,  0, -1, 3, "Encryption cycles" }
#define FPTC 8
, { FPTC, 0, 0, 0, -1, -1,  2, -1, 3, "Decryption cycles" }
#define FSMB 9
, { FSMB, 0, 0, 0, -1, -1,  4, -1, 2, "Signed-message bytes" }
#define FSMC 10
, { FSMC, 0, 0, 0, -1, -1,  3, -1, 3, "Signing cycles" }
#define FMSC 11
, { FMSC, 0, 0, 0, -1, -1,  5, -1, 3, "Verification cycles" }
#define FPAT 12
, { FPAT, 0, 0, 0, -1,  0, -1, -1, 4, "Patent claims" }
#define FCOP 13
, { FCOP, 0, 0, 0, -1,  1, -1, -1, 4, "Copyright claims" }
#define FKPC 14
, { FKPC, 0, 0, 0,  0, -1, -1, -1, 3, "Key-generation cycles" }
#define FPKB 15
, { FPKB, 0, 0, 0,  2, -1, -1, -1, 2, "Public-key bytes" }
#define FSKB 16
, { FSKB, 0, 0, 0,  1, -1, -1, -1, 2, "Secret-key bytes" }
#define FSSC 17
, { FSSC, 0, 0, 0,  3, -1, -1, -1, 3, "Shared-secret cycles" }
#define FSSB 18
, { FSSB, 0, 0, 0,  4, -1, -1, -1, 2, "Shared-secret bytes" }
#define FASE 19
, { FASE, 0, 0, 0, -1, -1, -1, -1, 5, "Attack seconds = 2 ^" }
#define FAEU 20
, { FAEU, 0, 0, 0, -1, -1, -1, -1, 5, "Attack euros = 2 ^" }
#define FKAT 21
, { FKAT, 0, 0, 0, -1, -1, -1, -1, 5, "Keys attacked = 2 ^" }
#define FDIC 22
, { FDIC, 0, 0, 0, -1, -1, -1,  0, 6, "Distinguishing chance" }
#define FCCA 23
, { FCCA, 0, 0, 0, -1,  4, -1, -1, 7, "Chosen-ciphertext attacks" }
#define FFOC 24
, { FFOC, 0, 0, 0, -1, -1, -1,  1, 6, "Forgery chance" }
#define FDHC 25
, { FDHC, 0, 0, 0, -1, -1, -1,  2, 6, "CDH chance" }
#define FFKA 26
, { FFKA, 0, 0, 0, -1,  3, -1, -1, 7, "Fake-key attacks" }
#define FTIA 27
, { FTIA, 0, 0, 0, -1,  2, -1, -1, 7, "Timing attacks" }
} ;

long long keygenheight = GRAPHTOP + GRAPHHEIGHT / 2 + 22;
long long desiredkeygenheight = GRAPHTOP + GRAPHHEIGHT / 2 + 22;

long long selectedobject;
long long selectedmessagelength = 1;
long long selectedelog = 30;
long long selectedklog = 40;
long long selectedslog = 30;
long long selectedfeature = FSKB;
long long xfeature = FPKB;
long long yfeature = FKPC;

double zoom = 1;
double desiredzoom = 1;
double xshift = 0.5;
double desiredxshift = 0.5;
double yshift = 0.5;
double desiredyshift = 0.5;

int background(void)
{
  int result = 0;

  if (zoom < desiredzoom) {
    zoom *= 1.15;
    if (zoom >= desiredzoom) zoom = desiredzoom;
    result = 1;
  }
  if (zoom > desiredzoom) {
    zoom *= 0.86956;
    if (zoom <= desiredzoom) zoom = desiredzoom;
    result = 1;
  }
  if (yshift < desiredyshift) {
    yshift += 0.05 * zoom;
    if (yshift >= desiredyshift) yshift = desiredyshift;
    result = 1;
  }
  if (yshift >= desiredyshift) {
    yshift -= 0.05 * zoom;
    if (yshift <= desiredyshift) yshift = desiredyshift;
    result = 1;
  }
  if (xshift < desiredxshift) {
    xshift += 0.05 * zoom;
    if (xshift >= desiredxshift) xshift = desiredxshift;
    result = 1;
  }
  if (xshift >= desiredxshift) {
    xshift -= 0.05 * zoom;
    if (xshift <= desiredxshift) xshift = desiredxshift;
    result = 1;
  }
  if (graphheight != desiredgraphheight) {
    if (graphheight < desiredgraphheight) {
      graphheight += 10;
      if (graphheight > desiredgraphheight) graphheight = desiredgraphheight;
    }
    if (graphheight > desiredgraphheight) {
      graphheight -= 10;
      if (graphheight < desiredgraphheight) graphheight = desiredgraphheight;
    }
    graphtop = graphbottom - graphheight;
    if (desiredkeygenheight < graphtop + graphheight / 8 || desiredkeygenheight > graphbottom - graphheight / 8) desiredkeygenheight = graphtop + graphheight / 2;
    result = 1;
  }
  if (keygenheight != desiredkeygenheight) {
    if (keygenheight < desiredkeygenheight) ++keygenheight;
    if (keygenheight > desiredkeygenheight) --keygenheight;
    result = 1;
  }
  return result;
}

int keypress(long long k)
{
  if (k == 'z') {
    if (desiredzoom > 0.015625) desiredzoom *= 0.5;
    if (desiredxshift < 0.5 * desiredzoom) desiredxshift = 0.5 * desiredzoom;
    if (desiredyshift < 0.5 * desiredzoom) desiredyshift = 0.5 * desiredzoom;
    if (desiredxshift >= 1 - 0.5 * desiredzoom) desiredxshift = 1 - 0.5 * desiredzoom;
    if (desiredyshift >= 1 - 0.5 * desiredzoom) desiredyshift = 1 - 0.5 * desiredzoom;
    return 0;
  }
  if (k == 'a') {
    if (desiredzoom < 1) desiredzoom *= 2;
    if (desiredxshift < 0.5 * desiredzoom) desiredxshift = 0.5 * desiredzoom;
    if (desiredyshift < 0.5 * desiredzoom) desiredyshift = 0.5 * desiredzoom;
    if (desiredxshift >= 1 - 0.5 * desiredzoom) desiredxshift = 1 - 0.5 * desiredzoom;
    if (desiredyshift >= 1 - 0.5 * desiredzoom) desiredyshift = 1 - 0.5 * desiredzoom;
    return 0;
  }
  if (k == 0xff52) { /* up */
    desiredyshift += 0.5 * desiredzoom;
    if (desiredyshift >= 1 - 0.5 * desiredzoom) desiredyshift = 1 - 0.5 * desiredzoom;
    return 0;
  }
  if (k == 0xff54) { /* down */
    desiredyshift -= 0.5 * desiredzoom;
    if (desiredyshift < 0.5 * desiredzoom) desiredyshift = 0.5 * desiredzoom;
    return 0;
  }
  if (k == 0xff53 || k == 0xffe4) { /* right or rightctrl */
    desiredxshift += 0.5 * desiredzoom;
    if (desiredxshift >= 1 - 0.5 * desiredzoom) desiredxshift = 1 - 0.5 * desiredzoom;
    return 0;
  }
  if (k == 0xff51 || k == 0xffe3) { /* left or leftctrl */
    desiredxshift -= 0.5 * desiredzoom;
    if (desiredxshift < 0.5 * desiredzoom) desiredxshift = 0.5 * desiredzoom;
    return 0;
  }
  if (0) if (k == 'f') {
    if (desiredgraphheight == GRAPHHEIGHT)
      desiredgraphheight = BIGGRAPHHEIGHT;
    else
      desiredgraphheight = GRAPHHEIGHT;
    return 0;
  }
  if (k == 'x') {
    xfeature = selectedfeature;
    return 1;
  }
  if (k == 'y') {
    yfeature = selectedfeature;
    return 1;
  }
  if (k == 't') {
    features[selectedfeature].label = !features[selectedfeature].label;
    if (features[selectedfeature].label) features[selectedfeature].fixed = 0;
    return 1;
  }
  if (k == 'l') {
    features[selectedfeature].fixed = !features[selectedfeature].fixed;
    if (features[selectedfeature].fixed) features[selectedfeature].label = 0;
    return 1;
  }
  if (k == 'i') {
    features[selectedfeature].hidden = 1;
    return 1;
  }
  if (k == 'v') {
    features[selectedfeature].hidden = 0;
    return 1;
  }
  if (k == 'k') {
    if (selectedfeature > 0) --selectedfeature;
    return 1;
  }
  if (k == 'j') {
    if (selectedfeature < NUMFEATURES - 1) ++selectedfeature;
    return 1;
  }
  if (k == ']') {
    switch(selectedfeature) {
      case FOMB: if (selectedmessagelength < nummessagelengths - 1) selectedmessagelength += 1; break;
      case FASE: if (selectedslog < 40) selectedslog += 1; break;
      case FAEU: if (selectedelog < 40) selectedelog += 2; break;
      case FKAT: if (selectedklog < 40) selectedklog += 4; break;
    }
    return 1;
  }
  if (k == '[') {
    switch(selectedfeature) {
      case FOMB: if (selectedmessagelength > 1) selectedmessagelength -= 1; break;
      case FASE: if (selectedslog > 0) selectedslog -= 1; break;
      case FAEU: if (selectedelog > 0) selectedelog -= 2; break;
      case FKAT: if (selectedklog > 0) selectedklog -= 4; break;
    }
    return 1;
  }
  if (k == 1000000 + '[') {
    if (selectedobject > 0) --selectedobject;
    return 1;
  }
  if (k == 1000000 + ']') {
    if (selectedobject < numobjects - 1) ++selectedobject;
    return 1;
  }
  if (k == 113) return -1;
  return 0;
}

void printfeature(long long n,const char *featurevalue)
{
  int x;
  int y;
  if (n < 14) {
    x = graphleft;
    y = 24 + 22 * n;
  } else {
    x = graphleft + 512;
    y = 24 + 22 * (n - 14);
  }
  if (n == xfeature) window_drawstring(x - 40,y,"x",2);
  else if (features[n].fixed) window_drawstring(x - 40,y,"l",2);
  else if (features[n].label) window_drawstring(x - 40,y,"t",2);
  if (n == yfeature) window_drawstring(x - 30,y,"y",2);
  if (n == selectedfeature) window_drawstring(x - 20,y,">",2);
  window_drawstring(x,y,features[n].name,features[n].hidden ? 6 : 4);
  window_drawstring(x + 260,y,featurevalue,features[n].hidden ? 6 : 0);
}

void printfeaturenum(long long n,long long featurevalue)
{
  char buf[100];
  if (n < 0) return;
  if (n >= NUMFEATURES) return;
  if (featurevalue >= 0) {
    sprintf(buf,"%lld",featurevalue);
    printfeature(n,buf);
  } else {
    printfeature(n,"-");
  }
}

void printfeaturelog(long long n,double featurevalue)
{
  char buf[100];
  if (n < 0) return;
  if (n >= NUMFEATURES) return;
  if (featurevalue >= 0) {
    if (featurevalue > 1)
      sprintf(buf,"%.0f = 2^%.2f",featurevalue,log(featurevalue) / log(2));
    else if (featurevalue == 1)
      sprintf(buf,"1");
    else
      sprintf(buf,"1/2^%.2f",-log(featurevalue) / log(2));
    printfeature(n,buf);
  } else {
    printfeature(n,"-");
  }
}

char str[200];

void printlog(int x,int y,double n,int c)
{
  sprintf(str,"%.0f = 2^%.2f",n,log(n) / log(2));
  if (str[strlen(str) - 1] == '0') str[strlen(str) - 1] = 0;
  if (str[strlen(str) - 1] == '0') str[strlen(str) - 1] = 0;
  if (str[strlen(str) - 1] == '.') str[strlen(str) - 1] = 0;
  window_drawstring(x,y,str,c);
}

void printnum(int x,int y,double n,int c)
{
  sprintf(str,"%.0f",n);
  window_drawstring(x,y,str,c);
}

int objectmatchesfixed(int i)
{
  long long eks;
  if (features[FBMV].fixed) if (z[i].batmanversion != z[selectedobject].batmanversion) return 0;
  if (features[FCNA].fixed) if (z[i].computer != z[selectedobject].computer) return 0;
  if (features[FBNA].fixed) if (z[i].bat != z[selectedobject].bat) return 0;
  if (features[FBVE].fixed) if (z[i].batversion != z[selectedobject].batversion) return 0;
  if (features[FBPA].fixed) if (z[i].param != z[selectedobject].param) return 0;
  if (features[FPAT].fixed) if (z[i].patentclaims != z[selectedobject].patentclaims) return 0;
  if (features[FCOP].fixed) if (z[i].copyrightclaims != z[selectedobject].copyrightclaims) return 0;
  if (features[FKPC].fixed) if (z[i].keypaircycles != z[selectedobject].keypaircycles) return 0;
  if (features[FPKB].fixed) if (z[i].publickeybytes != z[selectedobject].publickeybytes) return 0;
  if (features[FSKB].fixed) if (z[i].secretkeybytes != z[selectedobject].secretkeybytes) return 0;
  if (features[FSSC].fixed) if (z[i].sharedsecretcycles != z[selectedobject].sharedsecretcycles) return 0;
  if (features[FSSB].fixed) if (z[i].sharedsecretbytes != z[selectedobject].sharedsecretbytes) return 0;
  if (features[FCCA].fixed) if (z[i].ccattacks != z[selectedobject].ccattacks) return 0;
  if (features[FFKA].fixed) if (z[i].fakekeyattacks != z[selectedobject].fakekeyattacks) return 0;
  if (features[FTIA].fixed) if (z[i].timingattacks != z[selectedobject].timingattacks) return 0;
  if (features[FOMB].fixed) ; /* XXX */
  if (features[FCTB].fixed) ; /* XXX */
  if (features[FCTC].fixed) ; /* XXX */
  if (features[FPTC].fixed) ; /* XXX */
  if (features[FSMB].fixed) ; /* XXX */
  if (features[FSMC].fixed) ; /* XXX */
  if (features[FPAT].fixed) ; /* XXX */
  if (features[FASE].fixed) ; /* XXX */
  if (features[FAEU].fixed) ; /* XXX */
  if (features[FKAT].fixed) ; /* XXX */
  eks = selectedslog + 41 * ((selectedklog / 4) + 11 * (selectedelog / 2));
  /* XXX: allow user to select cutoff for 0.000001 here */
  if (features[FDIC].fixed)
    if (z[i].distinguishingchance[eks] < 0 || z[i].distinguishingchance[eks] > 0.000001) return 0;
  if (features[FFOC].fixed)
    if (z[i].forgerychance[eks] < 0 || z[i].forgerychance[eks] > 0.000001) return 0;
  if (features[FDHC].fixed)
    if (z[i].cdhchance[eks] < 0 || z[i].cdhchance[eks] > 0.000001) return 0;
  return 1;
}

double besttwopower(double x) /* down to power of 2 */
{
  if (x >= 1 && x < 2) return 1;
  return exp(log(2) * floor(log(x) / log(2) - 0.5));
}

void findminmax(void)
{
  int n;
  long long i;
  long long j;
  double x;
  double min;
  double max;
  int haveminmax;

  haveminmax = 0; min = 0; max = 0;
  for (j = 0;j < nummessagelengths;++j) {
    x = messagelengths[j]; if (x < 1) continue;
    if (!haveminmax) { min = x; max = x; haveminmax = 1; }
    if (x < min) min = x; if (x > max) max = x;
  }
  for (i = 0;i < numobjects;++i) {
    x = z[i].publickeybytes;
    if (x >= 1) {
      if (!haveminmax) { min = x; max = x; haveminmax = 1; }
      if (x < min) min = x; if (x > max) max = x;
    }
    x = z[i].secretkeybytes;
    if (x >= 1) {
      if (!haveminmax) { min = x; max = x; haveminmax = 1; }
      if (x < min) min = x; if (x > max) max = x;
    }
    x = z[i].sharedsecretbytes;
    if (x >= 1) {
      if (!haveminmax) { min = x; max = x; haveminmax = 1; }
      if (x < min) min = x; if (x > max) max = x;
    }
    for (j = 0;j < nummessagelengths;++j) {
      x = z[i].ciphertextbytes[j];
      if (x >= 1) {
        if (!haveminmax) { min = x; max = x; haveminmax = 1; }
        if (x < min) min = x; if (x > max) max = x;
      }
      x = z[i].signedmessagebytes[j];
      if (x >= 1) {
        if (!haveminmax) { min = x; max = x; haveminmax = 1; }
        if (x < min) min = x; if (x > max) max = x;
      }
    }
  }
  min = besttwopower(min); max = 1 / besttwopower(1 / max);
  features[FOMB].min = min; features[FOMB].max = max;
  features[FPKB].min = min; features[FPKB].max = max;
  features[FSKB].min = min; features[FSKB].max = max;
  features[FCTB].min = min; features[FCTB].max = max;
  features[FSMB].min = min; features[FSMB].max = max;

  haveminmax = 0; min = 0; max = 0;
  for (i = 0;i < numobjects;++i) {
    x = z[i].keypaircycles;
    if (x >= 1) {
      if (!haveminmax) { min = x; max = x; haveminmax = 1; }
      if (x < min) min = x; if (x > max) max = x;
    }
    x = z[i].sharedsecretcycles;
    if (x >= 1) {
      if (!haveminmax) { min = x; max = x; haveminmax = 1; }
      if (x < min) min = x; if (x > max) max = x;
    }
    for (j = 0;j < nummessagelengths;++j) {
      x = z[i].ciphertextcycles[j];
      if (x >= 1) {
        if (!haveminmax) { min = x; max = x; haveminmax = 1; }
        if (x < min) min = x; if (x > max) max = x;
      }
      x = z[i].plaintextcycles[j];
      if (x >= 1) {
        if (!haveminmax) { min = x; max = x; haveminmax = 1; }
        if (x < min) min = x; if (x > max) max = x;
      }
      x = z[i].signedmessagecycles[j];
      if (x >= 1) {
        if (!haveminmax) { min = x; max = x; haveminmax = 1; }
        if (x < min) min = x; if (x > max) max = x;
      }
      x = z[i].messagesignedcycles[j];
      if (x >= 1) {
        if (!haveminmax) { min = x; max = x; haveminmax = 1; }
        if (x < min) min = x; if (x > max) max = x;
      }
    }
  }
  min = besttwopower(min); max = 1 / besttwopower(1 / max);
  features[FKPC].min = min; features[FKPC].max = max;
  features[FSSC].min = min; features[FSSC].max = max;
  features[FCTC].min = min; features[FCTC].max = max;
  features[FPTC].min = min; features[FPTC].max = max;
  features[FSMC].min = min; features[FSMC].max = max;
  features[FMSC].min = min; features[FMSC].max = max;

  haveminmax = 1; min = 0; max = 100;
  for (i = 0;i < numobjects;++i) {
    x = z[i].patentclaims;
    if (x >= 1) {
      if (!haveminmax) { min = x; max = x; haveminmax = 1; }
      if (x < min) min = x; if (x > max) max = x;
    }
    x = z[i].copyrightclaims;
    if (x >= 1) {
      if (!haveminmax) { min = x; max = x; haveminmax = 1; }
      if (x < min) min = x; if (x > max) max = x;
    }
  }
  features[FPAT].min = min; features[FPAT].max = max;
  features[FCOP].min = min; features[FCOP].max = max;

  haveminmax = 1; min = 0; max = 40;
  for (i = 0;i < numobjects;++i) {
    x = z[i].ccattacks;
    if (x >= 1) {
      if (!haveminmax) { min = x; max = x; haveminmax = 1; }
      if (x < min) min = x; if (x > max) max = x;
    }
    x = z[i].fakekeyattacks;
    if (x >= 1) {
      if (!haveminmax) { min = x; max = x; haveminmax = 1; }
      if (x < min) min = x; if (x > max) max = x;
    }
    x = z[i].timingattacks;
    if (x >= 1) {
      if (!haveminmax) { min = x; max = x; haveminmax = 1; }
      if (x < min) min = x; if (x > max) max = x;
    }
  }
  features[FCCA].min = min; features[FCCA].max = max;
  features[FFKA].min = min; features[FFKA].max = max;
  features[FTIA].min = min; features[FTIA].max = max;
}

void drawgraph(void)
{
  int flagselected;
  int logarithmicx;
  int logarithmicy;
  long long i;
  long long j;
  double x;
  double y;
  double screenx;
  double screeny;
  double xmin;
  double ymin;
  double xmax;
  double ymax;
  double xscale;
  double yscale;
  double xoffset;
  double yoffset;

  if (xfeature < 0) return;
  if (xfeature >= NUMFEATURES) return;
  if (yfeature < 0) return;
  if (yfeature >= NUMFEATURES) return;

  window_fillrectangle(graphleft,graphtop,1,graphheight,6);
  window_fillrectangle(graphright,graphtop,1,graphheight,6);
  window_fillrectangle(graphleft,graphbottom,graphwidth,1,6);
  window_fillrectangle(graphleft,graphtop,graphwidth,1,6);
  window_drawstring(graphleft + graphwidth / 2 + 10,graphbottom + 44,features[xfeature].name,4);

  numpoints = 0;
  flagselected = 0;

  logarithmicx = 0;
  if (features[xfeature].logmeasure >= 0) logarithmicx = 1;
  if (features[xfeature].msgmeasure >= 0) logarithmicx = 1;
  if (features[xfeature].chance >= 0) logarithmicx = 1;
  if (xfeature == FOMB) logarithmicx = 1;

  logarithmicy = 0;
  if (features[yfeature].logmeasure >= 0) logarithmicy = 1;
  if (features[yfeature].msgmeasure >= 0) logarithmicy = 1;
  if (features[yfeature].chance >= 0) logarithmicy = 1;
  if (yfeature == FOMB) logarithmicy = 1;

  if (xfeature == FOMB && features[yfeature].msgmeasure >= 0) {
    for (i = 0;i < numobjects;++i) {
      if (!objectmatchesfixed(i)) continue;
      for (j = 0;j < nummessagelengths;++j) {
	x = messagelengths[j]; if (x < 1) continue;
        y = z[i].msgmeasures[features[yfeature].msgmeasure][j]; if (y < 1) continue;
        points[numpoints].x = x;
        points[numpoints].y = y;
	points[numpoints].source = i;
        if (i == selectedobject && j == selectedmessagelength) {
          selectedpoint = points[numpoints];
	  flagselected = 1;
        }
        ++numpoints;
      }
    }
  } else {
    for (i = 0;i < numobjects;++i) {
      if (!objectmatchesfixed(i)) continue;
      if (features[xfeature].logmeasure >= 0) {
        x = z[i].logmeasures[features[xfeature].logmeasure]; if (x < 1) continue;
      } else if (features[xfeature].intmeasure >= 0) {
        x = z[i].intmeasures[features[xfeature].intmeasure]; if (x < 0) continue;
      } else if (features[xfeature].msgmeasure >= 0) {
        x = z[i].msgmeasures[features[xfeature].msgmeasure][selectedmessagelength]; if (x < 1) continue;
      } else {
        goto unsupportedgraph;
      }
      if (features[yfeature].logmeasure >= 0) {
        y = z[i].logmeasures[features[yfeature].logmeasure]; if (y < 1) continue;
      } else if (features[yfeature].intmeasure >= 0) {
        y = z[i].intmeasures[features[yfeature].intmeasure]; if (y < 0) continue;
      } else if (features[yfeature].msgmeasure >= 0) {
        y = z[i].msgmeasures[features[yfeature].msgmeasure][selectedmessagelength]; if (y < 1) continue;
      } else {
        goto unsupportedgraph;
      }
      points[numpoints].x = x;
      points[numpoints].y = y;
      points[numpoints].source = i;
      if (i == selectedobject) {
        selectedpoint = points[numpoints];
	flagselected = 1;
      }
      ++numpoints;
    }
  }

  if (!numpoints) {
    window_drawstring(graphright + 10,graphbottom - graphheight / 2 + 24,features[yfeature].name,4);
    window_drawstring(graphleft + 10,graphtop + 25,"No points to graph",2);
    return;
    unsupportedgraph:
    window_drawstring(graphright + 10,graphbottom - graphheight / 2 + 24,features[yfeature].name,4);
    window_drawstring(graphleft + 10,graphtop + 25,"Graph not supported yet; sorry!",2);
    return;
  }

  xmin = features[xfeature].min;
  xmax = features[xfeature].max;
  if (logarithmicx) {
    xmin = log(xmin);
    xmax = log(xmax);
  }
  xmax -= xmin;
  xmin += (xshift - 0.5 * zoom) * xmax;
  xmax = xmin + xmax * zoom;
  if (logarithmicx) {
    printlog(graphleft,graphbottom + 44,exp(log(2) * xmin),6);
    printlog(graphright,graphbottom + 44,exp(log(2) * xmax),6);
  } else {
    printnum(graphleft,graphbottom + 44,xmin,6);
    printnum(graphright,graphbottom + 44,xmax,6);
  }
  if (xmin == xmax) {
    xscale = 1;
    xoffset = graphleft + graphwidth / 2 - xmin;
  } else {
    xscale = graphwidth / (xmax - xmin);
    xoffset = graphleft - xmin * xscale;
  }

  ymin = features[yfeature].min;
  ymax = features[yfeature].max;
  if (logarithmicy) {
    ymin = log(ymin);
    ymax = log(ymax);
  }
  ymax -= ymin;
  ymin += (yshift - 0.5 * zoom) * ymax;
  ymax = ymin + ymax * zoom;
  if (logarithmicy) {
    printlog(graphright + 10,graphbottom + 22,exp(log(2) * ymin),6);
    printlog(graphright + 10,graphtop - 22,exp(log(2) * ymax),6);
  } else {
    printnum(graphright + 10,graphbottom + 22,ymin,6);
    printnum(graphright + 10,graphtop - 22,ymax,6);
  }
  if (ymin == ymax) {
    yscale = 1;
    yoffset = graphbottom - graphheight / 2 - ymin;
  } else {
    yscale = graphheight / (ymin - ymax);
    yoffset = graphbottom - ymin * yscale;
  }

  if (flagselected) {
    screenx = x = selectedpoint.x;
    screeny = y = selectedpoint.y;
    if (logarithmicx) screenx = log(screenx);
    if (logarithmicy) screeny = log(screeny);
    screenx = xoffset + xscale * screenx;
    screeny = yoffset + yscale * screeny;
    window_fillrectangle(screenx,graphtop,1,graphheight,0);
    window_fillrectangle(graphleft,screeny,graphwidth,1,0);
    if (screeny > graphbottom - 88)
      (logarithmicx ? printlog : printnum)(screenx,graphbottom + 22,x,0);
    else
      (logarithmicx ? printlog : printnum)(screenx + 5,graphbottom - 5,x,0);
    (logarithmicy ? printlog : printnum)(graphright + 10,screeny,y,0);
    if (screeny > desiredkeygenheight - 44 && screeny < desiredkeygenheight + 44) {
      if (screeny < desiredkeygenheight) desiredkeygenheight = screeny + 44;
      if (screeny > desiredkeygenheight) desiredkeygenheight = screeny - 44;
      if (desiredkeygenheight < graphtop + graphheight / 8 || desiredkeygenheight > graphbottom - graphheight / 8) desiredkeygenheight = graphtop + graphheight / 2;
    }
    window_drawstring(graphright + 10,keygenheight,features[yfeature].name,4);
  } else {
    window_drawstring(graphright + 10,keygenheight,features[yfeature].name,4);
  }

  for (i = 0;i < numpoints;++i) {
    screenx = points[i].x;
    screeny = points[i].y;
    if (logarithmicx) screenx = log(screenx);
    if (logarithmicy) screeny = log(screeny);
    screenx = xoffset + xscale * screenx;
    screeny = yoffset + yscale * screeny;
    window_fillrectangle(screenx - 2,screeny - 2,5,5,3);
    if (features[FCNA].label || features[FBNA].label || features[FBVE].label || features[FBPA].label) {
      sprintf(line,"%s%s%s%s%s%s%s%s"
        ,features[FCNA].label
           ? computernames[z[points[i].source].computer]
           : ""
        ,features[FCNA].label
           ? " "
           : ""
        ,features[FBNA].label
           ? batnames[z[points[i].source].bat]
           : ""
        ,features[FBNA].label
           ? " "
           : ""
        ,features[FBVE].label
           ? batversionnames[z[points[i].source].batversion]
           : ""
        ,features[FBVE].label
           ? " "
           : ""
        ,features[FBPA].label && strcmp(paramnames[z[points[i].source].param],"-")
           ? paramnames[z[points[i].source].param]
           : ""
        ,features[FBPA].label && strcmp(paramnames[z[points[i].source].param],"-")
           ? " "
           : ""
        );
      if (screenx <= graphleft + graphwidth / 2 && screeny > graphtop + 66)
        window_drawstring(screenx + 5,screeny - 5,line,3);
      else
        window_drawstring(screenx + 5,screeny + 22,line,3);
    }
  }

  if (flagselected) {
    screenx = selectedpoint.x;
    screeny = selectedpoint.y;
    if (logarithmicx) screenx = log(screenx);
    if (logarithmicy) screeny = log(screeny);
    screenx = xoffset + xscale * screenx;
    screeny = yoffset + yscale * screeny;
    window_fillrectangle(screenx - 2,screeny - 2,5,5,0);
    sprintf(line,"%s %s-%s/%s %s"
                    ,computernames[z[selectedobject].computer]
                    ,batnames[z[selectedobject].bat]
                    ,batversionnames[z[selectedobject].batversion]
                    ,batmanversionnames[z[selectedobject].batmanversion]
                    ,strcmp(paramnames[z[selectedobject].param],"-") ? paramnames[z[selectedobject].param] : ""
           );
    if (screenx <= graphleft + graphwidth / 2 && screeny > graphtop + 66)
      window_drawstring(screenx + 5,screeny - 5,line,0);
    else
      window_drawstring(screenx + 5,screeny + 22,line,0);
  }
}

void redraw(void)
{
  long long eks;

  if (graphheight == GRAPHHEIGHT) {
    printfeature(FBMV,batmanversionnames[z[selectedobject].batmanversion]);
    printfeature(FCNA,computernames[z[selectedobject].computer]);
    printfeature(FBNA,batnames[z[selectedobject].bat]);
    printfeature(FBVE,batversionnames[z[selectedobject].batversion]);
    printfeature(FBPA,paramnames[z[selectedobject].param]);
    printfeature(FOMB,messagelengthnames[selectedmessagelength]);
    printfeaturenum(FCTB,z[selectedobject].ciphertextbytes[selectedmessagelength]);
    printfeaturenum(FCTC,z[selectedobject].ciphertextcycles[selectedmessagelength]);
    printfeaturenum(FPTC,z[selectedobject].plaintextcycles[selectedmessagelength]);
    printfeaturenum(FSMB,z[selectedobject].signedmessagebytes[selectedmessagelength]);
    printfeaturenum(FSMC,z[selectedobject].signedmessagecycles[selectedmessagelength]);
    printfeaturenum(FMSC,z[selectedobject].messagesignedcycles[selectedmessagelength]);
    printfeaturelog(FKPC,z[selectedobject].keypaircycles);
    printfeaturelog(FPKB,z[selectedobject].publickeybytes);
    printfeaturelog(FSKB,z[selectedobject].secretkeybytes);
    printfeaturelog(FSSC,z[selectedobject].sharedsecretcycles);
    printfeaturelog(FSSB,z[selectedobject].sharedsecretbytes);
    printfeaturenum(FASE,selectedslog);
    printfeaturenum(FAEU,selectedelog);
    printfeaturenum(FKAT,selectedklog);
    eks = selectedslog + 41 * ((selectedklog / 4) + 11 * (selectedelog / 2));
    printfeaturelog(FDIC,z[selectedobject].distinguishingchance[eks]);
    printfeaturelog(FFOC,z[selectedobject].forgerychance[eks]);
    printfeaturelog(FDHC,z[selectedobject].cdhchance[eks]);
    printfeaturenum(FTIA,z[selectedobject].timingattacks);
    printfeaturenum(FFKA,z[selectedobject].fakekeyattacks);
    printfeaturenum(FCCA,z[selectedobject].ccattacks);
    printfeaturenum(FPAT,z[selectedobject].patentclaims);
    printfeaturenum(FCOP,z[selectedobject].copyrightclaims);
  }
  window_drawstring(graphleft,24 + 11 * 29,"Features: jk. Axes: xy. Zoom: za, arrows. Text: t. Limit: l. More: {}[]q.",5);
  drawgraph();
}

int main(int argc,char **argv)
{
  readdata();
  findminmax();
  window_exec(argc,argv,redraw,keypress,background);
}
