/*
 * v_hilbert.c - visualize Hilbert-Peano curve
 *
 * Copyright (C) 2008-2009 Taiji Yamada <taiji@aihara.co.jp>
 */
#include <stdio.h>	/* printf, etc */
#include <stdlib.h>	/* exit, etc */
#include <string.h>	/* strcmp, etc */
#include <assert.h>	/* assert */
#include <sys/select.h>	/* select, etc */
#include <X11/Xlib.h>	/* X Window System, Version 11 */
#include <X11/keysym.h>	/* XK_* */
#include <GL/glx.h>	/* OpenGL Extension to the X Window System */
#include <GL/gl.h>	/* OpenGL */
#include <GL/glu.h>	/* OpenGL Utility */

char *ultobstr(unsigned long u, int bitlen, char *str, size_t z)
{ /* convert value to binary string */
  int l = 0;
  char buf[z];

  strlcpy(buf, "", z);
  do {
    strlcpy(str, (u & 1UL) ? "1" : "0", z);
    strlcat(str, buf, z);
    strlcpy(buf, str, z);
    u >>= 1;
  } while ((bitlen < 0) ? u : (++l < bitlen));
  return str;
}

/*
 * Hilbert Curve Utilities
 *
 * References
 * [1] Henry S. Warren, Jr., ``Hacker's Delight,'' Addison Wesley, 2003.
 */
long ulnlz(unsigned long x)
{ /* number of leading zeros */
  unsigned long y;
  long n = 32, c = 16;

  do {
    y = x >> c;
    if (y != 0) { n -= c; x = y; }
    c >>= 1;
  } while (c != 0);
  return n - x;
}
long ulntz(unsigned long x)
{ /* number of trailing zeros */
  long n = 0;

  x = ~x & (x - 1);
  while (x != 0) {
    n++;
    x >>= 1;
  }
  return n;
}
void hilbert_curve(int dir, int rot, int order,
		   void (*step)(void *v, int dir), void *v)
{
  /* dir & 3: 0(right), 1(up), 2(left), 3(down)
     rot: +1(clockwise), -1(anticlockwise) */
  if (order <= 0) return;

  dir += rot;
  hilbert_curve(dir, -rot, order-1, step, v);
  step(v, dir);

  dir -= rot;
  hilbert_curve(dir, +rot, order-1, step, v);
  step(v, dir);

  hilbert_curve(dir, +rot, order-1, step, v);
  dir -= rot;
  step(v, dir);

  hilbert_curve(dir, -rot, order-1, step, v);
}
unsigned long hilbert_pos2step(int n, unsigned long pos[])
{
  long long i;
  unsigned long state = 0, s = 0, p[2] = { pos[0], pos[1] }, r;

  for (i=n-1; i>=0; i--) {
    r = 4*state | 2*( (p[0] >> i) & 1 ) | ( (p[1] >> i) & 1 );
    /* 0x361E9CB4: 0b 0011 0110 0001 1110 1001 1100 1011 0100 */
    s = (s << 2) | ( (0x361E9CB4 >> 2*r) & 3 );
    /* 0x8FE65831: 0b 1000 1111 1110 0110 0101 1000 0011 0001 */
    state = (0x8FE65831 >> 2*r) & 3;
  }
  return s;
}
void hilbert_step2pos(int n, unsigned long s, unsigned long pos[])
{
  long long i;
  unsigned long state = 0, p[2] = { 0, 0 }, r;

  for (i=(n-1)<<1; i>=0; i-=2) {
    r = 4*state | ( (s >> i) & 3 );
    /* 0x936C: 0b 1001 0011 0110 1100 */
    p[0] = (p[0] << 1) | ( (0x936C >> r) & 1 );
    /* 0x39C6: 0b 0011 1001 1100 0110 */
    p[1] = (p[1] << 1) | ( (0x39C6 >> r) & 1 );
    /* 0x3E6B94C1: 0b 0011 1110 0110 1011 1001 0100 1100 0001  */
    state = (0x3E6B94C1 >> 2*r) & 3;
  }
  pos[0] = p[0];
  pos[1] = p[1];
}
int hilbert_pos2dir(int n, unsigned long pos[])
{
  long long i;
  unsigned long state = 0, *p = pos, r;
  long dp[2] = { -((1 << n) - 1), 0 }; /* { -(2^n - 1), 0 } */

  for (i=n-1; i>=0; i--) {
    r = 4*state | 2*( (p[0] >> i) & 1 ) | ( (p[1] >> i) & 1 );
    /* 0xbddb: 0b 1011 1101 1101 1011 */
    if ((0xbddb >> r) & 1) { /* -1: 00, 0: 01, +1: 10 */
      /* 0x16451659: 0b 00010110 01000101 00010110 01011001 */
      dp[0] = ((0x16451659 >> 2*r) & 3) - 1;
      /* 0x51166516: 0b 01010001 00010110 01100101 00010110 */
      dp[1] = ((0x51166516 >> 2*r) & 3) - 1;
    }
    /* 0x8FE65831: 0b 1000 1111 1110 0110 0101 1000 0011 0001 */
    state = (0x8FE65831 >> 2*r) & 3;
  }
  /* return value: -1(unknown), 0(right), 1(up), 2(left), 3(down) */
  return
    (!dp[0] && dp[1] == -1) ? 3 : (dp[0] == -1 && !dp[1]) ? 2 :
    (!dp[0] && dp[1] == +1) ? 1 : (dp[0] == +1 && !dp[1]) ? 0 : -1;
}
#define hilbert_size(order)	(1ULL<<(order))			/* 2^n */
#define hilbert_length(order)	hilbert_size((order)<<1)	/* 2^(2n) */
//#define hilbert_max(order)	(hilbert_length(order)-1)	/* 0b011..111 */
#define hilbert_bitlen(order)	((order)<<1)			/* 2n bitsize */
#define hilbert_max(order)	(~(~0UL<<hilbert_bitlen(order)))	/* 0b011..111 */
#define hilbert_lmask(order,l)	\
  (hilbert_max(order) & (~0UL << (hilbert_bitlen(order)-(l))))
			/* number of leading non-zeros to mask: 3 -> 0b1110.. */
#define hilbert_maskl(order,m)	(hilbert_bitlen(order)-ulntz(m))
			/* mask to number of leading non-zeros: 0b1110.. -> 3 */
#define hilbert_lb(order,s,m)	((s) & (m))
						/* lower bound of step/mask */
#define hilbert_ub(order,s,m)	((s) | (~(m) & hilbert_max(order)))
						/* upper bound of step/mask */

struct hilbert {
  int dir, rot, order;	/* global dir, rot and order */
  unsigned long cp[2];	/* current position */
  unsigned long cs;	/* current number of steps */
  int flag;
  unsigned long s, m;
};

/*
 * A Viewer
 */
struct view {
  int w, h, debug;	/* width, height */
  double s[2], op[2];	/* scaling factors, origin points in R^2 */
  int pointer[2];
  Display *display;
  XVisualInfo *vis;
  GLXContext ctx;
  int screen_number;
  Window parent_window, window, sub_windows[1];
  GC gc;
  Colormap cmap;
  long event_mask;
  XEvent event;
};
void view_init(struct view *v)
{
  int attribute_list[] = {
    GLX_RGBA,
    GLX_RED_SIZE, 1,
    GLX_GREEN_SIZE, 1,
    GLX_BLUE_SIZE, 1,
    GLX_DOUBLEBUFFER,
    None,
  };
  XSetWindowAttributes attributes;

  v->display = XOpenDisplay(NULL);
  assert(v->display);
  if (!glXQueryExtension(v->display, NULL, NULL))
    exit(1);
  v->screen_number = DefaultScreen(v->display);
  v->vis = glXChooseVisual(v->display, v->screen_number, attribute_list);
  assert(v->vis);
  v->ctx = glXCreateContext(v->display, v->vis, 0, !0);
  v->parent_window = RootWindow(v->display, v->screen_number);
  v->cmap =
    XCreateColormap(v->display, v->parent_window, v->vis->visual, AllocNone);
  attributes.colormap = v->cmap;
  v->window = XCreateWindow(v->display, v->parent_window,
			    0, 0, v->w, v->h, 0,
			    v->vis->depth, InputOutput, v->vis->visual,
			    CWColormap, &attributes);
  v->sub_windows[0] =
    XCreateSimpleWindow(v->display, v->window,
                        v->w-128, 0, 128, v->h, 0,
                        BlackPixel(v->display, v->screen_number),
                        WhitePixel(v->display, v->screen_number));
  v->gc = DefaultGC(v->display, v->screen_number);
  XSetForeground(v->display, v->gc, BlackPixel(v->display, v->screen_number));
  v->event_mask = ExposureMask;
  v->event_mask |= KeyPressMask;
  v->event_mask |= ButtonPressMask|ButtonMotionMask;
  v->event_mask |= PointerMotionMask;
  v->event_mask |= StructureNotifyMask;
  XSelectInput(v->display, v->window, v->event_mask);
  XMapWindow(v->display, v->window);
  XMapSubwindows(v->display, v->window);
  XFlush(v->display);
}
struct view_hilbert {
  struct view *v;
  struct hilbert *h;
};
void view_hilbert_step(struct view_hilbert *vh, int dir)
{
  struct view *v = vh->v;
  struct hilbert *h = vh->h;

  switch (dir & 3) {
  case 0: h->cp[0]++; break;
  case 1: h->cp[1]++; break;
  case 2: h->cp[0]--; break;
  case 3: h->cp[1]--; break;
  }
  glVertex2d(h->cp[0], h->cp[1]);
  if (v->debug) {
    char str[3][1024];

    printf("%d\t%s\t%s\t%s\n", dir,
	   ultobstr(h->cs, h->order<<1, str[0], sizeof(str[0])),
	   ultobstr(h->cp[0], h->order, str[1], sizeof(str[1])),
	   ultobstr(h->cp[1], h->order, str[2], sizeof(str[2])));
    h->cs++;
  }
}
void view_draw_tile(struct view *v, void *o)
{
  struct hilbert *h = (struct hilbert *)o;
  unsigned long cs = hilbert_pos2step(h->order, h->cp);

  if (/*hilbert_lb(h->order,h->s,h->m) <= cs &&
	cs <= hilbert_ub(h->order,h->s,h->m)*/
      (cs & h->m) == (h->s & h->m)) {
    glColor3d(.5, .5, (cs == h->s) ? 1 : .8);
    glBegin(GL_POLYGON);
    glVertex2d(-.5 + h->cp[0], -.5 + h->cp[1]);
    glVertex2d(+.5 + h->cp[0], -.5 + h->cp[1]);
    glVertex2d(+.5 + h->cp[0], +.5 + h->cp[1]);
    glVertex2d(-.5 + h->cp[0], +.5 + h->cp[1]);
    glEnd();
  }
}
void view_hilbert_step_tile(struct view_hilbert *vh, int dir)
{
  struct hilbert *h = vh->h;

  switch (dir & 3) {
  case 0: h->cp[0]++; break;
  case 1: h->cp[1]++; break;
  case 2: h->cp[0]--; break;
  case 3: h->cp[1]--; break;
  }
  view_draw_tile(vh->v, vh->h);
}
#define vx(x) ((x)*hilbert_size(h->order)/((v->w-128)*v->s[0]))
#define vy(y) ((y)*hilbert_size(h->order)/(v->h*v->s[1]))
void view_draw_note(struct view *v, void *o)
{
  struct hilbert *h = (struct hilbert *)o;
  unsigned long pos[] = {
    vx(-v->op[0]+v->pointer[0]),
    vy(-v->op[1]+v->h-1-v->pointer[1])
  }, s;
  char buf[3][256];
  int i, x = 8, y = v->h/2 + 16;

  snprintf(buf[0], sizeof(buf[0]), "pos: %lu, %lu", pos[0], pos[1]);
  s = hilbert_pos2step(h->order, pos);
  snprintf(buf[1], sizeof(buf[1]), "step: %lu", s);
  hilbert_step2pos(h->order, s, pos);
  snprintf(buf[2], sizeof(buf[2]), "pos: %lu, %lu", pos[0], pos[1]);
  XSetForeground(v->display, v->gc, WhitePixel(v->display, v->screen_number));
  XFillRectangle(v->display, v->sub_windows[0], v->gc, 0, v->h/2, v->w, v->h/2);
  XSetForeground(v->display, v->gc, BlackPixel(v->display, v->screen_number));
  for (i=0; i<sizeof(buf)/sizeof(buf[0]); i++) {
    XDrawString(v->display, v->sub_windows[0], v->gc, x, y, buf[i], strlen(buf[i]));
    y += 16;
  }
}
void view_draw(struct view *v, void *o)
{
  struct hilbert *h = (struct hilbert *)o;
  struct view_hilbert view_hilbert = { v, h }, *vh = &view_hilbert;
  unsigned long lb[2] = {
    (-1+vx(-v->op[0]) < 0) ? 0 : (-1+vx(-v->op[0])),
    (-1+vy(-v->op[1]) < 0) ? 0 : (-1+vy(-v->op[1]))
  }, ub[2] = {
    (vx(-v->op[0]+v->w-128) + 2 > hilbert_size(h->order)) ? hilbert_size(h->order) :
    (vx(-v->op[0]+v->w-128) + 2),
    (vy(-v->op[1]+v->h) + 2 > hilbert_size(h->order)) ? hilbert_size(h->order) :
    (vy(-v->op[1]+v->h) + 2)
  };
  int partial = !(lb[0] == 0 &&
		  lb[1] == 0 &&
		  ub[0] == hilbert_size(h->order) &&
		  ub[1] == hilbert_size(h->order));
  {
    char buf[3][256];
    int i, x = 8, y = 16, w = 128-1-2*x;
    double p[2] = {
      w*vx(-v->op[0])/hilbert_size(h->order),
      w*(1.-vy(-v->op[1]+v->h-1)/hilbert_size(h->order))
    }, s[2] = {
      w*vx(v->w-128+1)/hilbert_size(h->order),
      w*vy(v->h+1)/hilbert_size(h->order)
    };
    int r[2][4] = {	/* regions */
      {
	p[0], p[1],
	p[0] + ((s[0] < 1) ? 1 : s[0]), p[1] + ((s[1] < 1) ? 1 : s[1])
      }, { 0, 0, w, w }
    };

    for (i=0; i<4; i++)	/* clip region into region */
      r[0][i] = ((i < 2 && r[0][i] > r[1][i]) ||
		 (!(i < 2) && r[0][i] < r[1][i])) ? r[0][i] : r[1][i];
    snprintf(buf[0], sizeof(buf[0]), "order:  %d", h->order);
    snprintf(buf[1], sizeof(buf[1]), "size:   %llu", hilbert_size(h->order));
    snprintf(buf[2], sizeof(buf[2]), "length: %llu", hilbert_length(h->order));
    XSetForeground(v->display, v->gc, WhitePixel(v->display, v->screen_number));
    XFillRectangle(v->display, v->sub_windows[0], v->gc, 0, 0, v->w, v->h/2);
    XSetForeground(v->display, v->gc, BlackPixel(v->display, v->screen_number));
    {
      int x = 8, y = v->h/2-128+x;

      XDrawRectangle(v->display, v->sub_windows[0], v->gc,
		     x + r[0][0], y + r[0][1],
		     r[0][2] - r[0][0], r[0][3] - r[0][1]);
      XDrawRectangle(v->display, v->sub_windows[0], v->gc,
		     x + r[1][0], y + r[1][1],
		     r[1][2] - r[1][0], r[1][3] - r[1][1]);
    }
    for (i=0; i<sizeof(buf)/sizeof(buf[0]); i++) {
      XDrawString(v->display, v->sub_windows[0], v->gc, x, y, buf[i], strlen(buf[i]));
      y += 16;
    }
  }
  glXMakeCurrent(v->display, v->window, v->ctx);
#if 0 /* simpler than the alternative, but fails over 12-bit size of viewport */
  glViewport(v->op[0], v->op[1], (v->w-128)*v->s[0], v->h*v->s[1]);
  glLoadIdentity();
  glOrtho(-.5, hilbert_size(h->order) - .5, -.5, hilbert_size(h->order) - .5,
	  -1, 1); /* left, right, bottom, top, near, far */
#else
  glViewport(0, 0, v->w-128, v->h);
  glLoadIdentity();
  glOrtho(-.5 + vx(-v->op[0]), vx(-v->op[0]+v->w-128) - .5,
	  -.5 + vy(-v->op[1]), vy(-v->op[1]+v->h) - .5,
	  -1, 1); /* left, right, bottom, top, near, far */
#endif
  glClearColor(1, 1, 1, 1);
  glClear(GL_COLOR_BUFFER_BIT);
  if (v->debug) {
    char str[3][1024];

    h->cs = 0;
    printf("%s\t%s\t%s\t%s\n", "dir", "step", "x", "y");
    printf("%d\t%s\t%s\t%s\n", h->dir,
	   ultobstr(h->cs, h->order<<1, str[0], sizeof(str[0])),
	   ultobstr(h->cp[0], h->order, str[1], sizeof(str[1])),
	   ultobstr(h->cp[1], h->order, str[2], sizeof(str[2])));
    h->cs++;
  }
  if (h->flag) {
    h->cp[0] = h->cp[1] = 0;
    view_draw_tile(v, h);
    hilbert_curve(h->dir, h->rot, h->order,
		  (void(*)(void *, int))&view_hilbert_step_tile, vh);
  }
  glColor3d(0, 0, 0);
  if (!partial) {
    glBegin(GL_LINE_STRIP);
    h->cp[0] = h->cp[1] = 0;
    glVertex2d(h->cp[0], h->cp[1]);
    hilbert_curve(h->dir, h->rot, h->order,
		  (void(*)(void *, int))&view_hilbert_step, vh);
    glEnd();
  }
  else {
    unsigned long cp[2];

    for (cp[1]=lb[1]; cp[1]<ub[1]; cp[1]++)
      for (cp[0]=lb[0]; cp[0]<ub[0]; cp[0]++) {
	int dir;

	h->cp[0] = cp[0];
	h->cp[1] = cp[1];
	if ((dir = hilbert_pos2dir(h->order, h->cp)) < 0) continue;
	glBegin(GL_LINE_STRIP);
	glVertex2d(h->cp[0], h->cp[1]);
	view_hilbert_step(vh, dir);
	glEnd();
      }
  }
  glFlush();
  glXSwapBuffers(v->display, v->window);
  XFlush(v->display);
}
void view_loop(struct view *v, void *o)
{
  struct hilbert *h = (struct hilbert *)o;
  struct timeval timeout = { 0, 10 }, tv;
  fd_set rfds;

  while (!0) {
    while (XPending(v->display)) {
      XNextEvent(v->display, &v->event);
      switch (v->event.type) {
      case Expose: view_draw(v, o); break;
      case ButtonPress:
	v->pointer[0] = v->event.xbutton.x;
	v->pointer[1] = v->event.xbutton.y;
	break;
      case MotionNotify:
	if (v->event.xmotion.state & Button2Mask) {
	  v->op[0] += v->event.xmotion.x - v->pointer[0];
	  v->op[1] -= v->event.xmotion.y - v->pointer[1];
	  v->pointer[0] = v->event.xmotion.x;
	  v->pointer[1] = v->event.xmotion.y;
	  view_draw(v, o);
	}
	else if (v->event.xmotion.state & Button3Mask) {
	  double s[2] = {
	    (double)(v->event.xmotion.x)/v->pointer[0],
	    (double)(v->h-1-v->event.xmotion.y)/(v->h-1-v->pointer[1])
	  };

	  if (s[0] > 0 && s[1] > 0) {
	    if (!(v->event.xmotion.state & ShiftMask))
	      s[0] = s[1] = ((s[0] < s[1]) ? s[0] : s[1]);
	    v->s[0] *= s[0];
	    v->s[1] *= s[1];
	    v->op[0] *= s[0];
	    v->op[1] *= s[1];
	    v->pointer[0] = v->event.xmotion.x;
	    v->pointer[1] = v->event.xmotion.y;
	    view_draw(v, o);
	  }
	}
	else if (!v->event.xmotion.state) {
	  v->pointer[0] = v->event.xmotion.x;
	  v->pointer[1] = v->event.xmotion.y;
	  view_draw_note(v, o);
	}
	break;
      case KeyPress:
	switch (XKeycodeToKeysym(v->display, v->event.xkey.keycode,
				 (v->event.xkey.state&ShiftMask)?1:0)) {
	case XK_Escape: return; break;
	case XK_n: h->order++;
	  if (!(hilbert_size(h->order) < v->h/2) &&
	      v->s[0] < hilbert_size(h->order)/(v->h/2))
	    v->s[0] = v->s[1] = hilbert_size(h->order)/(v->h/2);
	  view_draw(v, o);
	  break;
	case XK_p: h->order--; view_draw(v, o); break;
	case XK_f: v->s[0] *= 2; v->s[1] = v->s[0]; view_draw(v, o); break;
	case XK_b: v->s[0] /= 2; v->s[1] = v->s[0]; view_draw(v, o); break;
	case XK_g:
	  if (!(hilbert_size(h->order) < v->h/2))
	    v->s[0] = v->s[1] = hilbert_size(h->order)/(v->h/2);
	  else
	    v->s[0] = v->s[1] = 1;
	  view_draw(v, o);
	  break;
	case XK_c: v->op[0] = v->op[1] = 0; view_draw(v, o); break;
	}
	break;
      case ConfigureNotify:
	v->w = v->event.xconfigure.width;
	v->h = v->event.xconfigure.height;
	XMoveWindow(v->display, v->sub_windows[0], v->w-128, 0);
	break;
      }
    }
    FD_ZERO(&rfds);
    FD_SET(0/*STDIN_FILENO*/, &rfds);
    tv = timeout;
    if (select(0/*STDIN_FILENO*/+1, &rfds, NULL, NULL, &tv) == -1) {
      perror("select");
    }
    else if (FD_ISSET(0/*STDIN_FILENO*/, &rfds)) {
      char buf[256];
      int n;
      unsigned long s, l, m;

      if (!fgets(buf, sizeof(buf), stdin)) {
	tv = timeout;
	select(0/*STDIN_FILENO*/, NULL, NULL, NULL, &tv);
	continue;
      }
      switch ((n=sscanf(buf, "%lu/%lu", &s, &l))) {
      case 1: h->flag = !0; m = hilbert_max(h->order); break;
      case 2: h->flag = !0; m = hilbert_lmask(h->order, l); break;
      default: h->flag = 0; continue; break;
      }
      printf("step/bitlen(mask):\t%lu/%lu(%ld)\n", s,
	     hilbert_maskl(h->order, m), m);
      printf("mask binary:\t%s\n", ultobstr(m, h->order<<1, buf, sizeof(buf)));
      printf("step range:\t%lu-%lu\n",
	     hilbert_lb(h->order,s,m), hilbert_ub(h->order,s,m));
      h->s = s;
      h->m = m;
      view_draw(v, o);
    }
  }
}
void view_term(struct view *v)
{
  XUnmapSubwindows(v->display, v->window);
  XUnmapWindow(v->display, v->window);
  XCloseDisplay(v->display);
}

int main(int argc, char *argv[])
{
  int a;
  struct view view = {
    512+128, 512, 0, { 1, 1 },	/* int w, h, debug; double s[2], */
  }, *v = &view;
  struct hilbert hilbert = {
    0, 1, 8			/* int dir, rot, order; */
  }, *h = &hilbert;

  for (a=1; a<argc; a++)
    if (strcmp(argv[a], "-n") == 0)
      h->order = atoi(argv[++a]);
    else if (strcmp(argv[a], "-D") == 0)
      v->debug = !0;

  view_init(v);
  view_loop(v, h);
  view_term(v);
  return 0;
}
