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

/*
 * 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
}
/*
 * A Viewer
 */
struct view {
  int w, h;		/* width, height */
  double deg[3], s;	/* 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;

  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 s[12*2];
    double lb[3] = {-1,-1,-1}, ub[3] = {1,1,1}, p[8][3];

    cube3(lb, ub, p, s);
    glBegin(GL_LINES);
    for (i=0; i<12; i++) {
      int i0 = s[i*2], i1 = s[i*2+1];
      glColor3d(0, 0, 0);
      glVertex3d(p[i0][0], p[i0][1], p[i0][2]);
      glVertex3d(p[i1][0], p[i1][1], p[i1][2]);
    }
    glEnd();
  }
  glFlush();
  glXSwapBuffers(v->display, v->window);
  XFlush(v->display);
}
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;
      }
      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, { -120, 0, -60 }, 0.9,	/* int w, h; double deg[3], s */
  }, *v = &view;

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