#include <stdio.h>
#include <stdlib.h>
#include <GL/glut.h>

static GLfloat spin = 0;
static int level = 4;
static int model = 0;
static GLfloat rotx, roty;
static int ox = -1, oy = -1;
static int mot;
#define PAN	1
#define ROT	2

void
movelight(int x, int y) {
    spin += (x-ox);
    ox = x; oy = y;
    if (spin > 360.) spin -= 360.;
    if (spin < -360.) spin -= -360.;
    glutPostRedisplay();
}

void
rotate(int x, int y) {
    rotx += x-ox;
    if (rotx > 360.) rotx -= 360.;
    else if (rotx < -360.) rotx += 360.;
    roty += y-oy;
    if (roty > 360.) roty -= 360.;
    else if (roty < -360.) roty += 360.;
    ox = x; oy = y;
    glutPostRedisplay();
}

void
motion(int x, int y) {
    if (mot == PAN) movelight(x, y);
    else if (mot == ROT) rotate(x,y);
}

void
mouse(int button, int state, int x, int y) {
    if(state == GLUT_DOWN) {
	switch(button) {
	case GLUT_LEFT_BUTTON:
	    mot = PAN;
	    motion(ox = x, oy = y);
	    break;
	case GLUT_RIGHT_BUTTON:
	    mot = ROT;
	    motion(ox = x, oy = y);
	    break;
	case GLUT_MIDDLE_BUTTON:
	    break;
	}
    } else if (state == GLUT_UP) {
	mot = 0;
    }
}

void togglewire(void) {
    static int toggle = 0;
    toggle ^= 1;
    glPolygonMode(GL_FRONT_AND_BACK, toggle ? GL_LINE : GL_FILL);
}

extern void sphere(int level);
void genmodel(void) {

    glNewList(1, GL_COMPILE);
    if (model) {
	GLUquadricObj *q = gluNewQuadric();
	gluSphere(q, 1.0, 10*level, 10*level);
	gluDeleteQuadric(q);
    } else {
	sphere(level-1);
    }
    glEndList();
}

void togglemodel(void) {
    model ^= 1;
    genmodel();
}

void levelup(void) {
    level += 1;
    if (level > 7) level = 7;
    genmodel();
}

void leveldown(void) {
    level -= 1;
    if (level <= 0) level = 1;
    genmodel();
}

void help(void) {
    printf("'h'      - help\n");
    printf("'t'      - tessellation style\n");
    printf("'UP'     - increase tessellation\n");
    printf("'DOWN'   - decrease tessellation\n");
    printf("left mouse   - rotate sphere\n");
    printf("right mouse  - move light\n");
}

void init(void) {
    GLfloat specular[4] = { 1., 1., 1., 1. };

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    genmodel();
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular);
    glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 30);
}

void display(void) {
    GLfloat position[] = { 0.0, 0.0, 3.5, 1.0 };

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glPushMatrix();
    glTranslatef(0.0, 0.0, -5.0); 

    glPushMatrix();
    glRotatef(spin, 1.0, 0.0, 0.0);
    glRotatef(0.0, 1.0, 0.0, 0.0);
    glLightfv(GL_LIGHT0, GL_POSITION, position);

    glPopMatrix();

    glRotatef(rotx, 0., 1., 0.);
    glRotatef(roty, 1., 0., 0.);
    glCallList(1);
    glPopMatrix();
    glutSwapBuffers();
}

void reshape(int w, int h) {
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(40.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
    glMatrixMode(GL_MODELVIEW);
}

/*ARGSUSED1*/
void
key(unsigned char key, int x, int y) {
    switch(key) {
    case 't': togglemodel(); break;
    case 'w': togglewire(); break;
    case 'h': help(); break;
    case '\033': exit(EXIT_SUCCESS); break;
    default: break;
    }
    glutPostRedisplay();
}

/*ARGSUSED1*/
void
special(int key, int x, int y) {
    switch(key) {
    case GLUT_KEY_UP:	levelup(); break;
    case GLUT_KEY_DOWN:	leveldown(); break;
    }
    glutPostRedisplay();
}

int main(int argc, char** argv) {
    glutInit(&argc, argv);
    glutInitWindowSize(512, 512);
    glutInitDisplayMode(GLUT_RGBA|GLUT_DOUBLE|GLUT_DEPTH);
    (void)glutCreateWindow(argv[0]);
    init();
    glutDisplayFunc(display);
    glutKeyboardFunc(key);
    glutSpecialFunc(special);
    glutReshapeFunc(reshape);
    glutMouseFunc(mouse);
    glutMotionFunc(motion);
    glutMainLoop();
    return 0;
}
