/*
 * 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 */

/*
 * 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);
}
#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 */
};

/*
 * A Viewer
 */
struct view {
  int w, h;		/* 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 |= 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]);
}
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);
  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 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,		/* int w, h; */
  }, *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]);

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