#ifdef WIN32
#include <windows.h>
#endif
#if defined(__APPLE__) || defined(MACOSX)
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#include <GLUT/glut.h>
#else
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

/*
 * Compute the value of all the nth degree Bernstein polynomials.
 * input:
 *  n : degree of curve;
 *  u : curve parameter on interval [0,1];
 * output:
 *  B: n+1 Bernstein values.
 */
void allBernstein(int n, double u, double B[]) {
  int j,k;
  double u1 = 1.0 - u;
  double saved;
  B[0] = 1.0;
  for (j = 1; j <= n; j++) {
    saved = 0.0;
    for (k = 0; k < j; k++) {
      double temp = B[k];
      B[k] = saved + u1*temp;
      saved = u*temp;
    }
    B[j] = saved;
  }
}

/*
 * Compute point of nth degree Bezier curve.
 * input:
 *   P : n+1 control points;
 *   n : degree of curve;
 *   u : curve parameter on interval [0,1];
 * output:
 *   C : resulting point.
 */
void pointOnBezierCurve(double P[][3], int n, double u, double C[3]) {
  double B[n+1];
  int k;
  allBernstein(n, u, B);
  C[0] = C[1] = C[2] = 0.0;
  for (k = 0; k <= n; k++) {
    C[0] += B[k]*P[k][0];
    C[1] += B[k]*P[k][1];
    C[2] += B[k]*P[k][2];
  }
}

#define MAX_DEGREE 20

int numControls;                   /* number of control points */
double controls[MAX_DEGREE+1][3];  /* control points */

void reshape(int w, int h) {
  static GLboolean first = GL_TRUE;

  if (first) {
    int i;
    numControls = 4;
    controls[0][0] = controls[1][0] = (w/3.0);
    controls[2][0] = controls[3][0] = (2.0*w/3.0);
    controls[0][1] = controls[3][1] = (2.0*h/3.0);
    controls[1][1] = controls[2][1] = (h/3.0);
    controls[0][2] = controls[1][2] = controls[2][2] = controls[3][2] = 0.0;
    first = GL_FALSE;
  }

  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(0,w-1, h-1,0, -1,1);  /* flip y-axis around */
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

#define SAMPLES 300
#define DU  (1.0/(SAMPLES-1))

void display(void) {
  int i;
  double u;

  glClear(GL_COLOR_BUFFER_BIT);

  glColor3f(0.0, 0.0, 1.0);
  glBegin(GL_LINE_STRIP);
  for (i = 0; i < numControls; i++)
    glVertex3dv(controls[i]);
  glEnd();
  
  glColor3f(1.0, 1.0, 0.0);
  glBegin(GL_LINE_STRIP);
  for (i = 0, u = 0.0; i < SAMPLES; i++, u += DU) {
    double C[3];
    pointOnBezierCurve(controls, numControls-1, u, C);
    glVertex3dv(C);
  }
  glEnd();

  glFlush();
}

#define DRAG_RAD 8

int dragControl = -1;
GLboolean addControl = GL_FALSE;

void mouse(int button, int state, int x, int y) {
  if (button == GLUT_LEFT_BUTTON) {
    if (state == GLUT_DOWN) {
      if (addControl) {
	if (numControls <= MAX_DEGREE) {
	  controls[numControls][0] = (double) x;
	  controls[numControls][1] = (double) y;
	  controls[numControls][2] = 0.0;
	  numControls++;
	  glutPostRedisplay();
	}
	addControl = GL_FALSE;
      } else {
	int i;
	for (i = 0; i < numControls; i++)
	  if (fabs(controls[i][0] - x) + fabs(controls[i][1] - y) < DRAG_RAD) {
	    dragControl = i;
	    break;
	  }
      }
    } else if (state == GLUT_UP) {
      dragControl = -1;
    }
  }
}

void mouseMotion(int x, int y) {
  if (dragControl != -1) {
    controls[dragControl][0] = (double) x;
    controls[dragControl][1] = (double) y;
    glutPostRedisplay();
  }
}

enum {
  MENU_ADD_CONTROL = 1,
  MENU_QUIT = 666
};

void menu(int option) {
  switch(option) {
  case MENU_ADD_CONTROL:
    addControl = GL_TRUE;
    break;
  case MENU_QUIT: exit(0);
  }
}

#define ESC 27

void keyboard(unsigned char key, int x, int y) {
  if (key == ESC) exit(0);
}

int main(int argc, char *argv[]) {
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
  glutInitWindowSize(500,500);
  glutInitWindowPosition(10,10);
  glutCreateWindow("Bezier Curve");
  glutDisplayFunc(display);
  glutReshapeFunc(reshape);
  glutMouseFunc(mouse);
  glutMotionFunc(mouseMotion);
  glutCreateMenu(menu);
  glutAddMenuEntry("Add Control", MENU_ADD_CONTROL);
  glutAddMenuEntry("Quit", MENU_QUIT);
  glutAttachMenu(GLUT_RIGHT_BUTTON);
  glutKeyboardFunc(keyboard);
  glutMainLoop();
  return 0;
}
