#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/keysym.h>
#include "font.h"

#define WINDOWWIDTH 1024
#define WINDOWHEIGHT 768

int zoom = 0;
double scale = 1;
double invscale = 1;
double xmiddle = 0;
double ymiddle = 0;
long long edgethickness = 0;
long long linearedges = 0;

typedef unsigned int uint32;

uint32 bitmap[WINDOWHEIGHT][WINDOWWIDTH];
char *title = "draw";
int bitmapchanged;

uint32 drawstring_color[256];

void drawstring(int x,int y,uint32 color,const char *string)
{
  char c;
  int f;
  int i;
  int j;
  int pos;
  int colorblue = color & 255;
  int colorgreen = (color >> 8) & 255;
  int colorred = (color >> 16) & 255;

  for (i = 0;i < 256;++i) {
    drawstring_color[i] =
        ((i + (colorblue  * (255 - i)) / 255) << 0)
      + ((i + (colorgreen * (255 - i)) / 255) << 8)
      + ((i + (colorred   * (255 - i)) / 255) << 16)
      ;
  }

  for (;;) {
    c = *string;
    if (!c) return;
    for (f = 0;font_char[f];++f)
      if (font_char[f] == c) {
        pos = font_start[f] * FONT_HEIGHT;
        for (i = 0;i < font_width[f];++i,++x) {
          if (x >= WINDOWWIDTH) return;
          for (j = 0;j < FONT_HEIGHT;++j,++pos)
            if (x >= 0 && y + j >= 0 && y + j < WINDOWHEIGHT)
              bitmap[y + j][x] = drawstring_color[(uint32) font_pixels[pos]];
        }
        break;
      }
    ++string;
  }
}

struct vertex {
  unsigned long long value;
  int numinputs;
  unsigned long long input[3];
  char *label;
  double x;
  double y;
} *vertices = 0;
long long verticesnum = 0;
long long verticesalloc = 0;

struct edge {
  long long from;
  long long to;
  long long flipped;
} *edges = 0;
long long edgesnum = 0;
long long edgesalloc = 0;

void ensurevertex(long long v)
{
  long long i;

  if (v < 0) exit(111);
  if (v >= verticesalloc) {
    struct vertex *newvertices;
    verticesalloc = v + (v >> 3) + 1;
    newvertices = malloc(verticesalloc * sizeof(*newvertices));
    if (!newvertices) exit(111);
    for (i = 0;i < verticesnum;++i) newvertices[i] = vertices[i];
    if (vertices) free(vertices);
    vertices = newvertices;
  }
  while (v >= verticesnum) {
    vertices[verticesnum].label = 0;
    vertices[verticesnum].x = 0;
    vertices[verticesnum].y = 0;
    vertices[verticesnum].value = 0;
    ++verticesnum;
  }
}

char line[65536];
char label[65536];

void readdata(void)
{
  long long i;
  long long v1;
  long long v2;
  double d;
  long long flipped;

  while (fgets(line,sizeof line,stdin)) {
    if (sscanf(line,"edge:%lld:%lld:%lld:",&v1,&v2,&flipped) == 3) {
      if (edgesnum >= edgesalloc) {
        struct edge *newedges;
        edgesalloc = edgesnum + (edgesnum >> 3) + 1;
        newedges = malloc(edgesalloc * sizeof(*newedges));
        if (!newedges) exit(111);
        for (i = 0;i < edgesnum;++i) newedges[i] = edges[i];
        if (edges) free(edges);
        edges = newedges;
      }
      edges[edgesnum].from = v1;
      edges[edgesnum].to = v2;
      edges[edgesnum].flipped = flipped;
      ++edgesnum;
      continue;
    }
    if (sscanf(line,"vertex:%lld:label:%1000[^:\n]",&v1,label) == 2) {
      ensurevertex(v1);
      vertices[v1].label = strdup(label);
      continue;
    }
    if (sscanf(line,"vertex:%lld:x:%lf",&v1,&d) == 2) {
      ensurevertex(v1);
      vertices[v1].x = d;
      continue;
    }
    if (sscanf(line,"vertex:%lld:y:%lf",&v1,&d) == 2) {
      ensurevertex(v1);
      vertices[v1].y = -d;
      continue;
    }
  }
  if (ferror(stdin)) exit(111);
}

void reinitialize(void)
{
  long long i;
  for (i = 0;i < verticesnum;++i) {
    vertices[i].numinputs = 0;
    if (vertices[i].label)
      if (vertices[i].label[0] == 'k' || vertices[i].label[0] == 'i')
        vertices[i].value ^= (-(long long) (random() & 1));
  }
}

void recalculate(void)
{
  long long i;
  long long to;
  for (i = 0;i < edgesnum;++i) {
    to = edges[i].to;
    if (!vertices[to].label) continue;
    if (edges[i].flipped)
      vertices[to].input[vertices[to].numinputs++] = ~vertices[edges[i].from].value;
    else
      vertices[to].input[vertices[to].numinputs++] = vertices[edges[i].from].value;
    if (!strcmp(vertices[to].label,"&")) {
      if (vertices[to].numinputs == 2) {
        vertices[to].value = vertices[to].input[0] & vertices[to].input[1];
        vertices[to].numinputs = 0;
      }
    } else if (!strcmp(vertices[to].label,"^")) {
      if (vertices[to].numinputs == 2) {
        vertices[to].value = vertices[to].input[0] ^ vertices[to].input[1];
        vertices[to].numinputs = 0;
      }
    } else if (!strcmp(vertices[to].label,"maj")) {
      if (vertices[to].numinputs == 3) {
        vertices[to].value =
          (vertices[to].input[0] & vertices[to].input[1])
          | (vertices[to].input[0] & vertices[to].input[2])
          | (vertices[to].input[1] & vertices[to].input[2]);
        vertices[to].numinputs = 0;
      }
    } else if (!strcmp(vertices[to].label,"^^")) {
      if (vertices[to].numinputs == 3) {
        vertices[to].value = vertices[to].input[0] ^ vertices[to].input[1] ^ vertices[to].input[2];
        vertices[to].numinputs = 0;
      }
    } else if (!strcmp(vertices[to].label,"=")) {
      if (vertices[to].numinputs == 1) {
        vertices[to].value = vertices[to].input[0];
        vertices[to].numinputs = 0;
      }
    } else if (vertices[to].label[0] == 'o') {
      if (vertices[to].numinputs == 1) {
        vertices[to].value = vertices[to].input[0];
        vertices[to].numinputs = 0;
      }
    }
  }
}

long long xchunk = 64;
long long ychunk = 64;

int perscale(void)
{
  static double leftover = 0;
  leftover += 1;
  if (leftover >= scale) { leftover -= scale; return 1; }
  return 0;
}

long long pointedvertex = -1;

void redraw(void)
{
  long long i;
  long long j;
  long long k;
  int x;
  int y;
  double x1;
  double y1;
  double x2;
  double y2;
  int s;
  int color;
  int loop;
  unsigned char *ram;
  double slope;
  int flaglinear;

  bitmapchanged = 1;

  for (y = 0;y < WINDOWHEIGHT;++y)
    for (x = 0;x < WINDOWWIDTH;++x)
      bitmap[y][x] = 0xffffff;

#define ON 0xff5050
#define OFF 0x00ff00
#define MIXED 0x800000

  for (i = 0;i < verticesnum;++i) {
    if (vertices[i].label && vertices[i].label[0] == '=') continue;
    if (((vertices[i].x - xmiddle) * xchunk) * invscale + WINDOWWIDTH / 2 >= WINDOWWIDTH) continue;
    if (((vertices[i].x - xmiddle + 1) * xchunk) * invscale + WINDOWWIDTH / 2 < 0) continue;
    if (((vertices[i].y - ymiddle) * ychunk) * invscale + WINDOWHEIGHT / 2 >= WINDOWHEIGHT) continue;
    if (((vertices[i].y - ymiddle + 1) * ychunk) * invscale + WINDOWHEIGHT / 2 < 0) continue;
    for (k = 0;k < ychunk;k += scale) {
      y = ((vertices[i].y - ymiddle) * ychunk + k) * invscale + WINDOWHEIGHT / 2;
      if (y < 0) continue;
      if (y >= WINDOWHEIGHT) break;
      for (j = 0;j < xchunk;j += scale) {
        x = ((vertices[i].x - xmiddle) * xchunk + j) * invscale + WINDOWWIDTH / 2;
        if (x < 0) continue;
        if (x >= WINDOWWIDTH) break;
        if (zoom == 0) {
          if ((vertices[i].value >> j) & 1)
            bitmap[y][x] = ON;
          else
            bitmap[y][x] = OFF;
        } else {
          if (vertices[i].value == -1)
            bitmap[y][x] = ON;
          else if (vertices[i].value == 0)
            bitmap[y][x] = OFF;
          else
            bitmap[y][x] = MIXED;
        }
        if (vertices[i].label && vertices[i].label[0] == 'o')
          bitmap[y][x] ^= 0xffffff;
      }
    }
  }

  if (edgethickness > 0) {
    int linearnow;
    for (linearnow = linearedges;linearnow >= 0;--linearnow)
      for (i = 0;i < edgesnum;++i) {
        flaglinear = 0;
        if (vertices[edges[i].to].label && vertices[edges[i].to].label[0] == '^') flaglinear = 1;
        if (vertices[edges[i].to].label && vertices[edges[i].to].label[0] == 'o') flaglinear = 1;
        if (vertices[edges[i].to].label && vertices[edges[i].to].label[0] == '=') flaglinear = 1;
        if (flaglinear && !linearnow) continue;
        if (((vertices[edges[i].to].y - ymiddle + 0.5) * ychunk) * invscale + WINDOWHEIGHT / 2 < 0) continue;
        if (((vertices[edges[i].from].y - ymiddle + 0.5) * ychunk) * invscale + WINDOWHEIGHT / 2 >= WINDOWHEIGHT) continue;
        for (j = (xchunk - edgethickness) / 2;j < (xchunk + edgethickness) / 2;++j) {
          x1 = ((vertices[edges[i].from].x - xmiddle) * xchunk + j) * invscale + WINDOWWIDTH / 2;
          y1 = ((vertices[edges[i].from].y - ymiddle + 0.5) * ychunk) * invscale + WINDOWHEIGHT / 2;
          x2 = ((vertices[edges[i].to].x - xmiddle) * xchunk + j) * invscale + WINDOWWIDTH / 2;
          y2 = ((vertices[edges[i].to].y - ymiddle + 0.5) * ychunk) * invscale + WINDOWHEIGHT / 2;
          if (y2 <= y1) continue;
          if (y1 >= WINDOWHEIGHT) continue;
          if (y2 < 0) continue;
          slope = (x2 - x1) / (y2 - y1);
          for (y = ceil(y1);y <= floor(y2);y += 1) if (perscale()) {
            if (y < 0) continue;
            if (y >= WINDOWHEIGHT) break;
            x = x1 + (y - y1) * slope;
            if (x < 0) continue;
            if (x >= WINDOWWIDTH) continue;
            if (flaglinear)
              bitmap[y][x] = 0x0000ff;
            else
              bitmap[y][x] = 0;
          }
        }
      }
  }

  if (pointedvertex >= 0 && pointedvertex < verticesnum) {
    i = pointedvertex;
    for (j = 0;j < xchunk;j += 1)
      for (k = 0;k < ychunk;k += 1) {
        if (j > 0 && j < xchunk - 1 && k > 0 && k < ychunk - 1) continue;
        y = ((vertices[i].y - ymiddle) * ychunk + k) * invscale + WINDOWHEIGHT / 2;
        x = ((vertices[i].x - xmiddle) * xchunk + j) * invscale + WINDOWWIDTH / 2;
        if (y < 0) continue;
        if (y >= WINDOWHEIGHT) break;
        if (x < 0) continue;
        if (x >= WINDOWWIDTH) break;
        bitmap[y][x] = 0;
      }
    sprintf(line,"%lld%lld%lld%lld%lld%lld%lld%lld%lld%lld%lld%lld%lld%lld%lld%lld%lld%lld%lld%lld%lld%lld%lld%lld%lld%lld%lld%lld%lld%lld%lld%lld (%lld %.0lf %.0lf) %.1000s"
      ,vertices[pointedvertex].value & 3
      ,(vertices[pointedvertex].value >> 2) & 3
      ,(vertices[pointedvertex].value >> 4) & 3
      ,(vertices[pointedvertex].value >> 6) & 3
      ,(vertices[pointedvertex].value >> 8) & 3
      ,(vertices[pointedvertex].value >> 10) & 3
      ,(vertices[pointedvertex].value >> 12) & 3
      ,(vertices[pointedvertex].value >> 14) & 3
      ,(vertices[pointedvertex].value >> 16) & 3
      ,(vertices[pointedvertex].value >> 18) & 3
      ,(vertices[pointedvertex].value >> 20) & 3
      ,(vertices[pointedvertex].value >> 22) & 3
      ,(vertices[pointedvertex].value >> 24) & 3
      ,(vertices[pointedvertex].value >> 26) & 3
      ,(vertices[pointedvertex].value >> 28) & 3
      ,(vertices[pointedvertex].value >> 30) & 3
      ,(vertices[pointedvertex].value >> 32) & 3
      ,(vertices[pointedvertex].value >> 34) & 3
      ,(vertices[pointedvertex].value >> 36) & 3
      ,(vertices[pointedvertex].value >> 38) & 3
      ,(vertices[pointedvertex].value >> 40) & 3
      ,(vertices[pointedvertex].value >> 42) & 3
      ,(vertices[pointedvertex].value >> 44) & 3
      ,(vertices[pointedvertex].value >> 46) & 3
      ,(vertices[pointedvertex].value >> 48) & 3
      ,(vertices[pointedvertex].value >> 50) & 3
      ,(vertices[pointedvertex].value >> 52) & 3
      ,(vertices[pointedvertex].value >> 54) & 3
      ,(vertices[pointedvertex].value >> 56) & 3
      ,(vertices[pointedvertex].value >> 58) & 3
      ,(vertices[pointedvertex].value >> 60) & 3
      ,(vertices[pointedvertex].value >> 62) & 3
      ,pointedvertex
      ,vertices[pointedvertex].y
      ,vertices[pointedvertex].x
      ,vertices[pointedvertex].label ? vertices[pointedvertex].label : "unnamed");
    drawstring(40,10,0,line);
  }
}

void pointnode(int xmouse,int ymouse)
{
  double x;
  double y;
  double dist;
  long long i;
  long long besti;
  double bestdist = 0;

  for (i = 0;i < verticesnum;++i) {
    x = ((vertices[i].x - xmiddle + 0.5) * xchunk) * invscale + WINDOWWIDTH / 2;
    y = ((vertices[i].y - ymiddle + 0.5) * ychunk) * invscale + WINDOWHEIGHT / 2;
    dist = (xmouse - x) * (xmouse - x) + (ymouse - y) * (ymouse - y);
    if (i == 0 || dist < bestdist) {
      bestdist = dist;
      besti = i;
    }
  }

  if (verticesnum > 0) pointedvertex = besti;

  redraw();
}

#define ZOOMS 25
double zoomscale[ZOOMS] = {
  1.000000000000000000000000000
, 1.122462048309372981433533049
, 1.259921049894873164767210607
, 1.414213562373095048801688724
, 1.587401051968199474751705639
, 1.781797436280678609480452411
, 2.000000000000000000000000000
, 2.244924096618745962867066099
, 2.519842099789746329534421214
, 2.828427124746190097603377448
, 3.174802103936398949503411278
, 3.563594872561357218960904822
, 4.000000000000000000000000000
, 4.489848193237491925734132198
, 5.039684199579492659068842429
, 5.656854249492380195206754896
, 6.349604207872797899006822557
, 7.127189745122714437921809644
, 8.000000000000000000000000000
, 8.979696386474983851468264397
, 10.07936839915898531813768485
, 11.31370849898476039041350979
, 12.69920841574559579801364511
, 14.25437949024542887584361928
, 16
} ;

void selectychunk(int c)
{
  if (c < 4) return;
  if (c > 64) return;
  if (c == ychunk) return;
  ychunk = c;
  redraw();
}

void selectzoom(int n)
{
  if (n < 0) return;
  if (n >= ZOOMS) return;
  if (n == zoom) return;
  zoom = n;
  scale = zoomscale[zoom];
  invscale = 1 / scale;
  redraw();
}

void selectxmiddle(long long x)
{
  xmiddle = x;
  redraw();
}

void selectymiddle(long long y)
{
  ymiddle = y;
  redraw();
}

void selectedges(long long e)
{
  if (e < 0) return;
  if (e > 20) return;
  if (e == edgethickness) return;
  edgethickness = e;
  redraw();
}

void selectbit(long long b)
{
  unsigned long long x;
  if (pointedvertex < 0) return;
  if (pointedvertex >= verticesnum) return;
  x = vertices[pointedvertex].value;
  x = -(x & 1);
  switch(b) {
    case 0: x ^= 0xaaaaaaaaaaaaaaaaULL; break;
    case 1: x ^= 0xccccccccccccccccULL; break;
    case 2: x ^= 0xf0f0f0f0f0f0f0f0ULL; break;
    case 3: x ^= 0xff00ff00ff00ff00ULL; break;
    case 4: x ^= 0xffff0000ffff0000ULL; break;
    case 5: x ^= 0xffffffff00000000ULL; break;
    case 6: x ^= 0xffffffffffffffffULL; break;
  }
  vertices[pointedvertex].value = x;
  recalculate();
  redraw();
}

int button1pressed = 0;
int button3pressed = 0;

int main(int argc,char **argv)
{
  Display *display;
  int screen;
  int depth;
  XImage *image;
  Window window;
  GC gc;
  XSetWindowAttributes attrib;
  XTextProperty titleprop;
  XEvent event;
  static char keybuf[64];
  KeySym keysym;
  Font font;

  display = XOpenDisplay(0);
  if (!display) return 111;
  screen = DefaultScreen(display);
  depth = DefaultDepth(display,screen);

  attrib.border_pixel = WhitePixel(display,screen);
  attrib.background_pixel = WhitePixel(display,screen);
  attrib.override_redirect = 0;

  window = XCreateWindow(display,DefaultRootWindow(display),0,0,WINDOWWIDTH,WINDOWHEIGHT,0,depth,InputOutput,CopyFromParent,CWBackPixel | CWBorderPixel,&attrib);
  if (!window) return 111;

  if (XStringListToTextProperty(&title,1,&titleprop) == 0) return 111;
  XSetTextProperty(display,window,&titleprop,XA_WM_NAME);

  gc = XCreateGC(display,window,0,0);
  if (!gc) return 111;

  font = XLoadFont(display,"-adobe-helvetica-bold-r-normal--25-180-100-100-p-138-iso8859-1");
  XSetFont(display,gc,font);

  XMapRaised(display,window);
  XSelectInput(display,window,ExposureMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask);

  image = XCreateImage(display,CopyFromParent,depth,ZPixmap,0,(char *) bitmap,WINDOWWIDTH,WINDOWHEIGHT,32,0);
  if (!image) return 111;
  XInitImage(image);
  image->byte_order = LSBFirst;
  image->bitmap_bit_order = MSBFirst;

  selectzoom(0);

  readdata();
  reinitialize();
  recalculate();
  redraw();

  for (;;) {
    if (bitmapchanged) {
      XPutImage(display,window,gc,image,0,0,0,0,WINDOWWIDTH,WINDOWHEIGHT);
      bitmapchanged = 0;
    }
    XNextEvent(display,&event);
    switch(event.type) {
      case ButtonPress:
        pointnode(event.xbutton.x,event.xbutton.y);
        /*
         * if (event.xbutton.button == Button3) selectnode(pointednode);
         */
        if (event.xbutton.button == Button1) button1pressed = 1;
        if (event.xbutton.button == Button3) button3pressed = 1;
        break;
      case ButtonRelease:
        if (event.xbutton.button == Button1) button1pressed = 0;
        if (event.xbutton.button == Button3) button3pressed = 0;
        break;
      case Expose:
        bitmapchanged = 1;
        break;
      case KeyPress:
        XLookupString(&event.xkey,keybuf,(sizeof keybuf) - 1,&keysym,0);
        if (keysym == XK_q) return 0;
        else if (keysym == XK_0) selectbit(0);
        else if (keysym == XK_1) selectbit(1);
        else if (keysym == XK_2) selectbit(2);
        else if (keysym == XK_3) selectbit(3);
        else if (keysym == XK_4) selectbit(4);
        else if (keysym == XK_5) selectbit(5);
        else if (keysym == XK_6) selectbit(6);
        else if (keysym == XK_7) selectbit(7);
        else if (keysym == XK_e) selectedges(edgethickness + 1);
        else if (keysym == XK_E) selectedges(edgethickness - 1);
        else if (keysym == XK_w) { linearedges = !linearedges; redraw(); }
        else if (keysym == XK_r) { reinitialize(); recalculate(); redraw(); }
        else if (keysym == XK_h) selectxmiddle(xmiddle - scale);
        else if (keysym == XK_l) selectxmiddle(xmiddle + scale);
        else if (keysym == XK_j) selectymiddle(ymiddle + (scale * xchunk) / ychunk);
        else if (keysym == XK_k) selectymiddle(ymiddle - (scale * xchunk) / ychunk);
        else if (keysym == XK_a) selectzoom(zoom + 1);
        else if (keysym == XK_z) selectzoom(zoom - 1);
        else if (keysym == XK_Y) selectychunk(ychunk - 1);
        else if (keysym == XK_y) selectychunk(ychunk + 1);
    }
  }
}
