/*
 * 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 <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.
 */
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];
}
#define hilbert_size(order)	(1ULL<<(order))			/* 2^n */
#define hilbert_length(order)	hilbert_size((order)<<1)	/* 2^(2n) */

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

/*
 * A Viewer
 */
struct view {
  int w, h, debug;	/* width, height */
  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 |= 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++;
  }
}
#define vx(x) ((x)*hilbert_size(h->order)/(v->w-128))
#define vy(y) ((y)*hilbert_size(h->order)/v->h)
void view_draw_note(struct view *v, void *o)
{
  struct hilbert *h = (struct hilbert *)o;
  unsigned long pos[] = {
    vx(v->pointer[0]),
    vy(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;
  {
    char buf[3][256];
    int i, x = 8, y = 16;

    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));
    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);
  glViewport(0, 0, v->w-128, v->h);
  glLoadIdentity();
  glOrtho(-.5, hilbert_size(h->order) - .5, -.5, hilbert_size(h->order) - .5,
	  -1, 1); /* left, right, bottom, top, near, far */
  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++;
  }
  glColor3d(0, 0, 0);
  {
    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();
  }
  glFlush();
  glXSwapBuffers(v->display, v->window);
  XFlush(v->display);
}
void view_loop(struct view *v, void *o)
{
  struct hilbert *h = (struct hilbert *)o;

  while (!0) {
    {
      XNextEvent(v->display, &v->event);
      switch (v->event.type) {
      case Expose: view_draw(v, o); break;
      case MotionNotify:
	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++; view_draw(v, o); break;
	case XK_p: h->order--; 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;
      }
    }
  }
}
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,		/* int w, h, debug; */
  }, *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;
}
