/* a 3D chess display for KnightCap

   Andrew.Tridgell@anu.edu.au January 1997 */

#include "includes.h"
#include "knightcap.h"

extern struct state *state;
int need_redraw=1;

#if RENDERED_DISPLAY
#include <GL/glut.h>
#include "trackball.h"

static int window_size = 500;

#define SLEEP_TIME 100000 /* microseconds */


static int do_scaling;
static int do_motion;
static int mouse_moved;
extern int demo_mode;
static int downx, downy;

/* the pieces don't sit quite on the board - there is a
   small gap */
#define BOARDGAP 0.05

#define BOARD1(p) (state->position.board[p])
#define BOARD(x,y) BOARD1(POSN(x,y))

static float a_color[4] =      {0.75, 0.75, 0.55};
static float b_color[4] =      {0.4, 0.4, 0.6};

/* traditional white and black squares */
static float square_color_1[4] =      {0.7, 0.7, 0.7};
/* static float square_color_2[4] =      {0, 0, 0}; */

enum {MENU_RESET, MENU_QUIT, MENU_FLIP, MENU_RESET_VIEW, MENU_DEMO,
      MENU_COMPUTER_WHITE, MENU_COMPUTER_BLACK, MENU_SAVE, MENU_RESTORE,
      MENU_EVAL};


enum {EDIT_NONE, EDIT_RECREATE, EDIT_CLEAR, EDIT_WHITES_MOVE,
      EDIT_BLACKS_MOVE,
      EDIT_BPAWN, EDIT_BKNIGHT, EDIT_BBISHOP,
      EDIT_BROOK, EDIT_BQUEEN, EDIT_BKING, 
      EDIT_WPAWN, EDIT_WKNIGHT, EDIT_WBISHOP,
      EDIT_WROOK, EDIT_WQUEEN, EDIT_WKING};



#define NUM_SLICES 10
#define NUM_STACKS 8

static int menu_ics_robot, menu_always_think, menu_demo_mode;

void redraw(void)
{
	need_redraw = 1;
}

static void set_cursor(int c)
{
	static int last_c;
	if (last_c == c) return;
	glutSetCursor(c);
	last_c = c;
}

/* this is taken from the glut library */
static void drawBox(GLfloat x0, GLfloat x1, GLfloat y0, GLfloat y1,
		    GLfloat z0, GLfloat z1)
{
	static GLfloat n[6][3] =
	{
		{-1.0, 0.0, 0.0},
		{0.0, 1.0, 0.0},
		{1.0, 0.0, 0.0},
		{0.0, -1.0, 0.0},
		{0.0, 0.0, 1.0},
		{0.0, 0.0, -1.0}
	};
	static GLint faces[6][4] =
	{
		{0, 1, 2, 3},
		{3, 2, 6, 7},
		{7, 6, 5, 4},
		{4, 5, 1, 0},
		{5, 6, 2, 1},
		{7, 4, 0, 3}
	};
	GLfloat v[8][3];
	GLint i;
	
	v[0][0] = v[1][0] = v[2][0] = v[3][0] = x0;
	v[4][0] = v[5][0] = v[6][0] = v[7][0] = x1;
	v[0][1] = v[1][1] = v[4][1] = v[5][1] = y0;
	v[2][1] = v[3][1] = v[6][1] = v[7][1] = y1;
	v[0][2] = v[3][2] = v[4][2] = v[7][2] = z0;
	v[1][2] = v[2][2] = v[5][2] = v[6][2] = z1;
	
	for (i = 0; i < 6; i++) {
		glBegin(GL_QUADS);
		glNormal3fv(&n[i][0]);
		glVertex3fv(&v[faces[i][0]][0]);
		glVertex3fv(&v[faces[i][1]][0]);
		glVertex3fv(&v[faces[i][2]][0]);
		glVertex3fv(&v[faces[i][3]][0]);
		glEnd();
	}
}

static void draw_pawn(void)
{
	GLUquadricObj *q;
	q = gluNewQuadric();
	gluCylinder(q, 0.22, 0.1, 0.65, NUM_SLICES, NUM_STACKS);
	glTranslatef(0,0,0.6);
	glutSolidSphere(0.14,NUM_SLICES,NUM_STACKS);
}

static void draw_rook(void)
{
	GLUquadricObj *q;

	/* the base */
	drawBox(-0.25, 0.25, -0.25, 0.25, 0, 0.1);
	glTranslatef(0,0,0.1);

	/* the torso */
	q = gluNewQuadric();
	gluCylinder(q, 0.22, 0.16, 0.5, NUM_SLICES, NUM_STACKS);
	glTranslatef(0, 0, 0.5);

	/* the top */
	drawBox(-0.2, 0.2, -0.2, 0.2, 0, 0.1);
	glTranslatef(0,0,0.1);

	glTranslatef(-0.15, -0.15, 0.05);
	glutSolidCube(0.1);
	glTranslatef(0.3, 0, 0);
	glutSolidCube(0.1);
	glTranslatef(0, 0.3, 0);
	glutSolidCube(0.1);
	glTranslatef(-0.3, 0, 0);
	glutSolidCube(0.1);
}

static void draw_bishop(void)
{
	GLUquadricObj *q;

	glScalef(1.2, 1.2, 1);

	/* the torso */
	q = gluNewQuadric();
	gluCylinder(q, 0.22, 0.12, 0.9, NUM_SLICES, NUM_STACKS);
	glTranslatef(0, 0, 0.85);

	/* the sash */
	glPushMatrix();
	glRotatef(-45, 1, 0, 0);
	glScalef(1, 1.5, 1);
	glutSolidTorus(0.05, 0.12, NUM_SLICES, NUM_STACKS);
	glPopMatrix();

	/* the shoulders? */
	glTranslatef(0, 0, 0.2);
	glScalef(1,1,1.5);
	glutSolidSphere(0.1, NUM_SLICES, NUM_STACKS);	
}

static void draw_queen(void)
{
	GLUquadricObj *q;
	glScalef(1,1,1.15);

	/* the base */
	glTranslatef(0, 0, 0.07);
	glutSolidTorus(0.06, 0.23, NUM_SLICES, NUM_STACKS);

	/* the torso */
	q = gluNewQuadric();
	gluCylinder(q, 0.25, 0.15, 1.0, NUM_SLICES, NUM_STACKS);
	glTranslatef(0,0, 0.9);

	/* the ruff */
	glutSolidTorus(0.08, 0.16, NUM_SLICES, NUM_STACKS);
	glTranslatef(0,0, 0.15);

	/* the head */
	glutSolidSphere(0.15, NUM_SLICES, NUM_STACKS);

	/* the crown */
	glTranslatef(0,0,0.08);
	glutSolidTorus(0.03, 0.13, NUM_SLICES, NUM_SLICES);

	/* and a knob on top */
	glTranslatef(0,0, 0.07);
	glutSolidSphere(0.07, NUM_SLICES/2, NUM_STACKS/2);
}

static void draw_king(void)
{
	GLUquadricObj *q;
	q = gluNewQuadric();

	/* the base */
	drawBox(-0.27, 0.27, -0.27, 0.27, 0, 0.1);
	glTranslatef(0,0,0.1);

	/* torso and shoulders */
	gluCylinder(q, 0.25, 0.15, 1.0, NUM_SLICES, NUM_STACKS);
	glTranslatef(0,0,1);
	glutSolidTorus(0.07, 0.14, NUM_SLICES, NUM_STACKS);

	/* the cross */
	drawBox(-0.075, 0.075, -0.075, 0.075, 0, 0.4);
	glTranslatef(0,0,0.2);

	drawBox(-0.2, 0.2, -0.075, 0.075, -0.07, 0.07);
}


static void draw_knight(void)
{
	GLUquadricObj *q;
	q = gluNewQuadric();


	glRotatef(-90,0,0,1);

	/* base */
	glTranslatef(0,0,0.05);
	glutSolidTorus(0.1,0.2,NUM_SLICES,NUM_STACKS);
	glTranslatef(0,0,0.1);
	glutSolidTorus(0.1,0.2,NUM_SLICES,NUM_STACKS);
	glTranslatef(0,0,0.05);

	/* torso */
	glRotatef(10, 0, 1, 0);
	gluCylinder(q, 0.2, 0.15, 0.7, NUM_SLICES, NUM_STACKS);
	glTranslatef(0,0, 0.65);

	/* head */
	glutSolidSphere(0.16, NUM_SLICES, NUM_STACKS);

	/* ears */
	glPushMatrix();
	glRotatef(10, 0, 1, 0);
	glRotatef(25, 1, 0, 0);
	glTranslatef(0.05,0,0.13);
	gluCylinder(q, 0.08, 0.0, 0.15, NUM_SLICES/2, NUM_STACKS/2);
	glPopMatrix();

	glPushMatrix();
	glRotatef(10, 0, 1, 0);
	glRotatef(-25, 1, 0, 0);
	glTranslatef(0.05,0,0.13);
	gluCylinder(q, 0.08, 0, 0.15, NUM_SLICES/2, NUM_STACKS/2);
	glPopMatrix();	

	/* nose */
	glRotatef(-110, 0, 1, 0);
	gluCylinder(q, 0.17, 0.1, 0.3, NUM_SLICES, NUM_STACKS);
	glTranslatef(0,0, 0.3);
	glutSolidSphere(0.1, NUM_SLICES, NUM_STACKS);
}


/* its cute to make the bishops and knights face the opponents king */
static void face_king(int p, int x, int y)
{
	Square kpos = PIECES(&state->position, -p)[IKING].pos;
	double dx, dy, theta;

	dx = XPOS(kpos) - x;
	dy = YPOS(kpos) - y;

	theta = 360*atan(dx/(dy+0.01))/(2*M_PI);

	if (dy < 0) theta += 180;

	glRotatef(-theta,0,0,1);
}

static void draw_piece(int p, int x, int y)
{
	glPushMatrix();
	glTranslatef(0.5, 0.5, BOARDGAP);
	if (p > 0) {
		glColor3fv(a_color);
	} else {
		glColor3fv(b_color);
	}

	switch (abs(p)) {
	case PAWN:
		draw_pawn();
		break;
	case KNIGHT:
		face_king(p, x, y);
		draw_knight();
		break;
	case BISHOP:
		face_king(p, x, y);
		draw_bishop();
		break;
	case ROOK:
		draw_rook();
		break;
	case QUEEN:
		draw_queen();
		break;
	case KING:
		draw_king();
		break;
	}

	glPopMatrix();
}


static void draw_pieces(void)
{
	int i,j;
	int p;

	for (i=0;i<8;i++)
		for (j=0;j<8;j++) {
			if (!(p=BOARD(i,j))) continue;
			glPushMatrix();
			glTranslatef(i,j,0);
			draw_piece(p, i, j);
			glPopMatrix();
		}
}

/* this draws the board itself - must be called with lighting 
   off (otherwise we'd need to give the squares some depth) */
static void draw_board(void)
{
	int i, j;
	
	glPushAttrib(GL_ENABLE_BIT);
	glDisable(GL_LIGHTING);
	glDisable(GL_DITHER);
	for (i=0;i<8;i++)
		for (j=0;j<8;j++) {
			if (!((i+j)&1)) continue;

			glPushMatrix();
			glTranslatef(i, j, 0);
			glColor3fv(square_color_1);
			glRectf(0,0,1,1);
			glPopMatrix();
		}
	glEnable(GL_DITHER);
	glPopAttrib();
}

static void setup_lighting(void)
{
	GLfloat light_position[] = {2, -1, 20.0, 0.0};

	glLightfv(GL_LIGHT0, GL_POSITION, light_position);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);

	glEnable(GL_COLOR_MATERIAL);
	glShadeModel(GL_SMOOTH);
}

static void position_board(void)
{
	GLfloat m[4][4];

	glScalef(state->scale, state->scale, state->scale);

	setup_lighting();

	gluPerspective(26, 1, 10, 100);

	glMatrixMode(GL_MODELVIEW);

	glTranslatef(0, 0, -20);

	build_rotmatrix(m, state->curquat);
	glMultMatrixf(&m[0][0]);

	if (state->flip_view) {
		/* its easier to flip the view than to use the trackball */
		glRotatef(180, 0, 0, 1);
	}

	glTranslatef(-4, -4, 0);
}

/* this starts us at a reasonable viewing angle */
static void reset_view(void)
{
	state->scale = 1;
	state->curquat[0] = 0.28;
	state->curquat[1] = 0;
	state->curquat[2] = 0;
	state->curquat[3] = 0.90;
}


void draw_all(void)
{
	need_redraw = 0;
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glPushMatrix();
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	position_board();
	draw_pieces();
	draw_board();
        glPopMatrix();

	glutSwapBuffers();
}


/* check if mouse click at (px,py) is in a square, returning the
   square in (*rx,*ry) if it is */
static int in_square(int px,int py, Square *p)
{
	int x, y;
	int ret;
	GLint vp[4];
	int squares_only=1;

	glGetIntegerv(GL_VIEWPORT, vp);

        glPushMatrix();

again:

	for (x=0;x<8;x++)
		for (y=0;y<8;y++) {
			glFeedbackBuffer(0, GL_2D, NULL);
			glRenderMode(GL_FEEDBACK);
			glInitNames();
			glPushName(~0);
			glPushMatrix();
			glMatrixMode(GL_PROJECTION);
			glLoadIdentity();
			gluPickMatrix(px, vp[3] - py, 1, 1, vp);
			position_board();
			glTranslatef(x, y, 0);
			if (!squares_only && BOARD(x,y)) {
				draw_piece(BOARD(x,y), x, y);
			} else {
				glRectf(0, 0, 1, 1);
			}
			glPopMatrix();
				
			ret = glRenderMode(GL_RENDER);
				
			if (ret != 0) {
				(*p) = POSN(x,y);
				glPopMatrix();
				return 1;
			}
		}

	if (!squares_only) {
		squares_only = 1;
		goto again;
	}

	glPopMatrix();

	return 0;
}


static void mouse_func(int button, int bstate, int x, int y)
{
	static Square from, to;
	static int do_movement;

	if (bstate == GLUT_DOWN && button == GLUT_MIDDLE_BUTTON) {
		downx = x;
		downy = y;		
	}

	if (bstate == GLUT_DOWN && button == GLUT_LEFT_BUTTON) {
		mouse_moved = 0;
		downx = x;
		downy = y;
		if(glutGetModifiers() & GLUT_ACTIVE_SHIFT) {
			do_scaling = 1;
		} else if (in_square(x,y,&from) && BOARD1(from)) {
			do_movement = 1;
		} else {
			do_motion = 1;
		}
	}

	if (bstate == GLUT_UP && button == GLUT_LEFT_BUTTON) {
		do_motion = 0;
		do_scaling = 0;
		if (do_movement && in_square(x,y,&to) && to != from &&
		    next_to_play(&state->position) != state->computer) {
			Move move;
			move.from = from; 
			move.to = to;
			prog_tell_move(&state->position,&move);
			player_moved(from, to);
		}
		do_movement = 0;
	}
}

static void motion_func(int x, int y)
{
	float lastquat[4];
	GLint vp[4];
	
	if (x == downx && y == downy) return;

	mouse_moved = 1;

	if (!do_motion && !do_scaling) return;

	glGetIntegerv(GL_VIEWPORT, vp);

	if (do_motion) {
		trackball(lastquat,
			  (2.0*downx - vp[2]) / vp[2],
			  (vp[3] - 2.0*downy) / vp[3],
			  (2.0*x - vp[2]) / vp[2],
			  (vp[3] - 2.0*y) / vp[3]
			  );

		add_quats(lastquat, state->curquat, state->curquat);
	}

	if (do_scaling) {
		state->scale *= (1.0 + (((float) (downy - y)) / vp[3]));
	}

	downx = x;
	downy = y;
	redraw();
}


static void edit_menu(int v)
{
	Square sq;

	if (!in_square(downx, downy, &sq))
		return;

	switch (v) {
	case EDIT_NONE:
		lprintf(0,"remove at %d %d\n", downx, downy);
		state->position.board[sq] = 0;
		break;
	case EDIT_RECREATE:
		lprintf(0,"recreate\n");
		create_pboard(&state->position);
		break;

	case EDIT_CLEAR:
		memset(state->position.board, 0, 
		       sizeof(state->position.board));
		break;

	case EDIT_WHITES_MOVE:
		state->position.move_num &= ~1;
		break;

	case EDIT_BLACKS_MOVE:
		state->position.move_num |= 1;
		break;

	case EDIT_BPAWN:
	case EDIT_BKNIGHT:
	case EDIT_BBISHOP:
	case EDIT_BROOK:
	case EDIT_BQUEEN:
	case EDIT_BKING:
		lprintf(0,"set black %d at %d %d\n",
			v - EDIT_BPAWN, downx, downy);
		
		state->position.board[sq] = -(PAWN + (v - EDIT_BPAWN));
		break;

	case EDIT_WPAWN:
	case EDIT_WKNIGHT:
	case EDIT_WBISHOP:
	case EDIT_WROOK:
	case EDIT_WQUEEN:
	case EDIT_WKING:
		lprintf(0,"set black %d at %d %d\n",
			v - EDIT_WPAWN, downx, downy);
		
		state->position.board[sq] = PAWN + (v - EDIT_WPAWN);
		break;
	}

	redraw();
}


static void main_menu(int v)
{
	switch (v) {
	case MENU_EVAL:
		eval_debug(&state->position);
		break;

	case MENU_SAVE:
		save_game("knightcap.save");
		break;

	case MENU_RESTORE:
		restore_game("knightcap.save");
		break;

	case MENU_DEMO:
		demo_mode = !demo_mode;
		if (!demo_mode) 
			state->stop_search = 1;
		if (demo_mode) {
			glutChangeToMenuEntry(menu_demo_mode, 
					    "disable demo mode", MENU_DEMO);
		} else {
			glutChangeToMenuEntry(menu_demo_mode, 
					    "demo mode", MENU_DEMO);
		}
		break;

	case MENU_QUIT:
		state->stop_search = 1;
		state->quit = 1;
		prog_exit();
		exit(0);
		break;

	case MENU_RESET:
		reset_board();
		break;

	case MENU_RESET_VIEW:
		reset_view();
		break;

	case MENU_FLIP:
		state->flip_view = !state->flip_view;
		break;
	}

	redraw();
}


static void computer_menu(int v)
{
	if (v == 2) {
		state->always_think = !state->always_think;
		if (state->always_think) {
			glutChangeToMenuEntry(menu_always_think, 
					    "disable always think",v);
		} else {
			glutChangeToMenuEntry(menu_always_think, 
					    "always think", v);
		}
		return;
	}
	if (v == 3) {
		state->ics_robot = !state->ics_robot;
		if (state->ics_robot) {
			glutChangeToMenuEntry(menu_ics_robot, 
					    "disable ICS robot",v);
		} else {
			glutChangeToMenuEntry(menu_ics_robot, 
					    "enable ICS robot", v);
		}
		return;
	}
	state->stop_search = 1;
	state->computer = v;
}

static void move_time_menu(int v)
{
	state->move_time = v;
}

static void create_menus(void)
{
	int comp, undo, redo, move;

	comp = glutCreateMenu(computer_menu);
	glutAddMenuEntry("computer plays white", PIECE_WHITE);
	glutAddMenuEntry("computer plays black", PIECE_BLACK);
	glutAddMenuEntry("disable computer", 0);
	menu_always_think = 4;
	glutAddMenuEntry("always think", 2);
	menu_ics_robot = 5;
	glutAddMenuEntry("enable ICS robot", 3);

	undo = glutCreateMenu(undo_menu);
	glutAddMenuEntry("1 move", 1);
	glutAddMenuEntry("2 moves", 2);
	glutAddMenuEntry("4 moves", 4);
	glutAddMenuEntry("8 moves", 8);

	redo = glutCreateMenu(undo_menu);
	glutAddMenuEntry("1 move", -1);
	glutAddMenuEntry("2 moves",-2);
	glutAddMenuEntry("4 moves",-4);
	glutAddMenuEntry("8 moves",-8);

	move = glutCreateMenu(move_time_menu);
	glutAddMenuEntry("2  seconds", 2);
	glutAddMenuEntry("5  seconds", 5);
	glutAddMenuEntry("9  seconds", 9);
	glutAddMenuEntry("10 seconds", 10);
	glutAddMenuEntry("30 seconds", 30);
	glutAddMenuEntry("60 seconds", 60);
	glutAddMenuEntry("5  minutes", 300);

	glutCreateMenu(main_menu);
	glutAddMenuEntry("reset game", MENU_RESET);
	glutAddMenuEntry("reset view", MENU_RESET_VIEW);
	glutAddMenuEntry("flip view", MENU_FLIP);
	menu_demo_mode = 4;
	glutAddMenuEntry("demo mode", MENU_DEMO);
	glutAddMenuEntry("eval debug", MENU_EVAL);
	glutAddSubMenu("computer",comp);
	glutAddSubMenu("undo",undo);
	glutAddSubMenu("redo",redo);
	glutAddSubMenu("move time",move);
	glutAddMenuEntry("save game", MENU_SAVE);
	glutAddMenuEntry("restore game", MENU_RESTORE);
	glutAddMenuEntry("quit", MENU_QUIT);
	glutAttachMenu(GLUT_RIGHT_BUTTON);

	glutCreateMenu(edit_menu);
	glutAddMenuEntry("delete", EDIT_NONE);
	glutAddMenuEntry("recreate", EDIT_RECREATE);
	glutAddMenuEntry("clear board", EDIT_CLEAR);
	glutAddMenuEntry("whites move", EDIT_WHITES_MOVE);
	glutAddMenuEntry("blacks move", EDIT_BLACKS_MOVE);
	glutAddMenuEntry("black pawn", EDIT_BPAWN);
	glutAddMenuEntry("black knight", EDIT_BKNIGHT);
	glutAddMenuEntry("black bishop", EDIT_BBISHOP);
	glutAddMenuEntry("black rook", EDIT_BROOK);
	glutAddMenuEntry("black queen", EDIT_BQUEEN);
	glutAddMenuEntry("black king", EDIT_BKING);
	glutAddMenuEntry("white pawn", EDIT_WPAWN);
	glutAddMenuEntry("white knight", EDIT_WKNIGHT);
	glutAddMenuEntry("white bishop", EDIT_WBISHOP);
	glutAddMenuEntry("white rook", EDIT_WROOK);
	glutAddMenuEntry("white queen", EDIT_WQUEEN);
	glutAddMenuEntry("white king", EDIT_WKING);
	glutAttachMenu(GLUT_MIDDLE_BUTTON);
}

void start_display(int argc,char *argv[])
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);

	reset_view();

	glutCreateWindow("KnightCap");
	glutReshapeWindow(window_size, window_size);
	glutDisplayFunc(draw_all);
	glutMouseFunc(mouse_func);
	glutMotionFunc(motion_func);
	glutIdleFunc(idle_func);
	
	/* set other relevant state information */
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_LINE_SMOOTH);
	
	set_cursor(GLUT_CURSOR_CROSSHAIR);
	create_menus();

	glutMainLoop();
}

#else

void redraw(void)
{
	
}

void draw_all(void)
{
	need_redraw = 0;
	print_board(state->position.board);
}
#endif
