#include <stdio.h>	/* printf, etc */
#include <stdlib.h>	/* exit, etc */
#include <string.h>	/* memcpy, etc */
#include <math.h>	/* M_PI, 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 */

double deg2rad(double deg)
{
  return deg*M_PI/180;
}
/*
 * 3D utilities
 */
void cube3(double lb[3], double ub[3], double vertices[8][3],
	   int segments[12*2])
{
  int i, j;

#if 1
  static int segs[12*2] = {
    0,4, 2,6, 1,5, 3,7,
    0,2, 4,6, 1,3, 5,7,
    0,1, 4,5, 2,3, 6,7,
  };

  for (i=0; i<3; i++)
    for (j=0; j<12/3; j++) {
      segments[(12/3*i+j)*2+0] = segs[(12/3*i+j)*2+0];
      segments[(12/3*i+j)*2+1] = segs[(12/3*i+j)*2+1];
    }
  for (i=0; i<8; i++) {
    vertices[i][0] = ((i>>0)%2) ? ub[0] : lb[0];
    vertices[i][1] = ((i>>1)%2) ? ub[1] : lb[1];
    vertices[i][2] = ((i>>2)%2) ? ub[2] : lb[2];
  }
#else
  for (i=0; i<3; i++)
    for (j=0; j<12/3; j++) {
      int v0 = 0, v1 = 0, k;
      for (k=0; k<3; k++) {
        v0 = ((i>k)?((j>>k)%2):(i==k)?0:((j>>(k-1))%2)) + 2*v0;
        v1 = ((i>k)?((j>>k)%2):(i==k)?1:((j>>(k-1))%2)) + 2*v1;
      }
      segments[(12/3*i+j)*2+0] = v0;
      segments[(12/3*i+j)*2+1] = v1;
    }
  for (i=0; i<1<<3; i++)
    for (j=0; j<3; j++)
      vertices[i][j] = ((i>>j)%2) ? ub[j] : lb[j];
#endif
}
/*
 * nD Utilities
 */
double **mat(int M, int N)
{
  int i;
  double **m;

#if defined(UNEFFICIENT)
  m = (double **)malloc(sizeof(double *)*M);
#if defined(UNSAFE)
  for (i=0; i<M; i++)
    m[i] = (double *)malloc(sizeof(double)*N);
#else
  if (!m)
    return NULL;
  for (i=0; i<M; i++) {
    m[i] = (double *)malloc(sizeof(double)*N);
    if (!m[i]) {
      do free(m[--i]); while (i != 0);
      free(m);
      return NULL;
    }
  }
#endif
#else
  m = (double **)malloc(sizeof(double *)*M);
#if defined(UNSAFE)
  m[0] = (double *)malloc(sizeof(double)*M*N);
#else
  if (!m)
    return NULL;
  m[0] = (double *)malloc(sizeof(double)*M*N);
  if (!m[0]) {
    free(m);
    return NULL;
  }
#endif
  for (i=1; i<M; i++) {
    m[i] = m[i-1] + N;
  }
#endif
  return m;
}
void free_mat(int M, double **m)
{
#if defined(UNEFFICIENT)
  int i;

  for (i=0; i<M; i++)
    free(m[i]);
#else
  free(m[0]);
#endif
  free(m);
}
void unit_mat(int M, int N, double **m)
{
  int i, j;

  for (i=0; i<M; i++)
    for (j=0; j<N; j++)
      m[i][j] = (i==j) ? 1 : 0;
}
void cp_mat(int M, int N, double **a, double **b)
{
#if defined(UNEFFICIENT)
  int i, j;

  for (i=0; i<M; i++)
    for (j=0; j<N; j++)
      b[i][j] = a[i][j];
#else
  memcpy(b[0], a[0], sizeof(double)*M*N);
#endif
}
/*
  a[M][N], b[N][L], c[M][L]: matrix
 */
void mat_mult_mat(int M, int N, int L, double **a, double **b, double **c)
{
  int i, j, k;

  for (i=0; i<M; i++)
    for (j=0; j<L; j++)
      for (c[i][j] = 0, k=0; k<N; k++)
        c[i][j] += a[i][k]*b[k][j];
}
/*
  a[N][N]: matrix
  b[N], c[N]: vector
 */
void mat_mult_vec(int N, double **a, double b[], double c[])
{
  int i, j;
  
  for (i=0; i<N; i++)
    for (c[i] = 0, j=0; j<N; j++)
      c[i] += a[i][j]*b[j];
}
void rot_mat(int M, int f, int t, double a, double **m)
{
  double c = cos(a), s = sin(a);

  unit_mat(M, M, m);
  m[f][f] = c; m[f][t] = -s;
  m[t][f] = s; m[t][t] =  c;
}
void cube(int n, double lb[], double ub[], double **vertices, int *segments)
{
  long i, j, nv = 1<<n, ns = (1<<(n-1))*n;

  for (i=0; i<n; i++)
    for (j=0; j<ns/n; j++) {
      int v0 = 0, v1 = 0, k;
      for (k=0; k<n; k++) {
        v0 = ((i>k)?((j>>k)%2):(i==k)?0:((j>>(k-1))%2)) + 2*v0;
        v1 = ((i>k)?((j>>k)%2):(i==k)?1:((j>>(k-1))%2)) + 2*v1;
      }
      segments[(ns/n*i+j)*2+0] = v0;
      segments[(ns/n*i+j)*2+1] = v1;
    }
  for (i=0; i<nv; i++)
    for (j=0; j<n; j++)
      vertices[i][j] = ((i>>j)%2) ? ub[j] : lb[j];
}
/*
 * A Viewer
 */
struct view {
  int w, h;		/* width, height */
  double hdeg, deg[3], s;	/* hyper angle in R^n, angle in R^3, scaling factor */
  int pointer[2];
  Display *display;
  XVisualInfo *vis;
  GLXContext ctx;
  int screen_number;
  Window parent_window, window;
  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;

  if (!(v->display = XOpenDisplay(NULL)))
    exit(1);
  if (!glXQueryExtension(v->display, NULL, NULL))
    exit(1);
  v->screen_number = DefaultScreen(v->display);
  v->vis = glXChooseVisual(v->display, v->screen_number, attribute_list);
  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->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 |= StructureNotifyMask;
  XSelectInput(v->display, v->window, v->event_mask);
  XMapWindow(v->display, v->window);
  XFlush(v->display);
}
void view_draw(struct view *v, void *o)
{
  int i, j, n = *(int *)o;
  double **matn = mat(n, n);

  {
    double **matm = mat(n, n), **m_ij = mat(n, n);

    unit_mat(n, n, matn);
    for (i=0; i<n; i++)
      for (j=0; j<n; j++)
	if (i != j) {
	  rot_mat(n, i, j, deg2rad(v->hdeg), m_ij);
	  cp_mat(n, n, matn, matm);
	  mat_mult_mat(n, n, n, matm, m_ij, matn);
	}
    free_mat(n, matm);
    free_mat(n, m_ij);
  }
  glXMakeCurrent(v->display, v->window, v->ctx);
  glViewport(0, 0, v->w, v->h);
  glLoadIdentity();
  glFrustum(-1/v->s, 1/v->s, -1/v->s, 1/v->s, 4, 100);
  gluLookAt(0, 0, -8, 0, 0, 0, 0, 1, 0);
  glClearColor(1, 1, 1, 1);
  glClear(GL_COLOR_BUFFER_BIT);
  glRotated(v->deg[0], 1, 0, 0);
  glRotated(v->deg[1], 0, 1, 0);
  glRotated(v->deg[2], 0, 0, 1);
  {
    int nv = 1<<n, ns = (1<<(n-1))*n, s[ns*2];
    double lb[n], ub[n], **p = mat(nv,n), **q = mat(nv,n);

    for (i=0; i<n; i++) {
      lb[i] = -1; ub[i] = 1;
    }
    cube(n, lb, ub, p, s);
    for (i=0; i<nv; i++)
      mat_mult_vec(n, matn, p[i], q[i]);
    glBegin(GL_LINES);
    for (i=0; i<ns; i++) {
      int i0 = s[i*2], i1 = s[i*2+1];
      glColor3d(0, 0, 0);
      glVertex3d(q[i0][0], q[i0][1], q[i0][2]);
      glVertex3d(q[i1][0], q[i1][1], q[i1][2]);
    }
    glEnd();
    free_mat(nv, p);
    free_mat(nv, q);
  }
  glFlush();
  glXSwapBuffers(v->display, v->window);
  XFlush(v->display);
  free_mat(n, matn);
}
void view_loop(struct view *v, void *o)
{
  while (!0) {
    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:
      v->deg[0] -= (v->event.xmotion.y - v->pointer[1])*180/v->h;
      v->deg[2] += (v->event.xmotion.x - v->pointer[0])*180/v->w;
      v->pointer[0] = v->event.xmotion.x;
      v->pointer[1] = v->event.xmotion.y;
      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_slash:    v->hdeg -= 1; view_draw(v, o); break;
      case XK_asterisk: v->hdeg += 1; view_draw(v, o); break;
      }
      break;
    case ConfigureNotify:
      v->w = v->event.xconfigure.width;
      v->h = v->event.xconfigure.height;
      view_draw(v, o);
      break;
    }
  }
}
void view_term(struct view *v)
{
  XUnmapWindow(v->display, v->window);
  XCloseDisplay(v->display);
}

int main(int argc, char *argv[])
{
  struct view view = {
    600, 600, 0, { -120, 0, -60 }, 0.9,	/* int w, h; double hdeg, deg[3], s */
  }, *v = &view;
  int n = 5;

  view_init(v);
  view_loop(v, &n);
  view_term(v);
  return 0;
}
