/* COMP3004 Coursework Two *************************************************/
/* "Alice in the Machine"                                                  */
/* Controls:                                                               */
/*  Use the mouse to change the angle of the camera ('mouselook').         */
/*  F - Freeze/unfreeze (pause). While frozen, the mouse is released.      */
/*  Y - change mouse y-axis inversion.                                     */
/*  WASD or arrows - Forwards, slide left, backwards, slide right.         */
/*  Left mouse, right mouse - Forwards, backwards.                         */
/*  Space, X - Move camera directly up / down                              */
/*  T - Start the automatic tour. Can be interrupted by other movement.    */
/*  O - Output the current camera co-ordinates to the terminal.            */
/*  P - Position the camera for the screenshot and freeze.                 */
/*  H - Reposition the camera at the help screen.                          */
/*  Q - Quit.                                                              */
/* Dependancies:                                                           */
/*  Requires GLUT 3.6 (API version 4). Without functions provided by this, */
/*  smooth keyboard movement is not possible, as it would depend on the    */
/*  repeat rate. This is a GLUT limitation.                                */
/* Philip Richard Boulain *** <prb102@ecs.soton.ac.uk> *** 3 December 2004 */

/* Note: The reason that "move directly down" is X, not the standard Ctrl, */
/* is that GLUT cannot detect Ctrl on its own, even with glutGetModifiers. */
/* Note: Left-handed co-ordinate system; ergo, X is left-right, Y is       */
/* up-down, and Z is in-out.                                               */
/* Note: World units are in metres.                                        */

/* Note: FOV-related stuff is post-submission tinkering [1/2] */

/* Linked library definitions */
#ifdef USLEEP         /* Decent build enviroment? */
 #define _GNU_SOURCE  /* Tell glibc to bother to actually implement UNIX features. */
 #include <unistd.h>  /* For usleep() */
#endif
#include <stdlib.h>   /* For exit() */
#include <stdio.h>    /* For printf() */
#include <math.h>     /* For sin(), cos(), and M_PI */
#include <time.h>     /* For time(), to initialize the RNG */
#include "getglut.h"  /* For GLUT */
#include "smalloc.h"  /* Safer malloc() */
#include "universe.h" /* Global data structure */
#include "vertex.h"   /* For polycount */

#ifndef M_PI /* Not provided by all math.h implementations */
#define M_PI 3.14159265358979323846264338327
#endif
#define DEG_TO_RAD M_PI / 180.0

#define WINDOW_TITLE "Alice in the Machine"
#define VIEW_DISTANCE 48.0
#define MOUSE_SENSITIVITY 0.25
#define MOVEMENT_SPEED 0.005
#define LOCK_Y /* Undefine this to allow turning the camera upsidedown */

/* Global data */
universe_t universe; /* Global instance of universe data */
enum direction {FORWARDS, BACKWARDS, LEFT, RIGHT, UP, DOWN};
int windoww,  windowh;  /* Full window width and height */
int hwindoww, hwindowh; /* Half window width and height */
long int tourprogress; /* Time elapsed in tour, -1 for none */

int fov;

struct {
	/* The state of various keys for which GLUT offers only press/release events. */
	/* (The up events are a GLUT API 4 addition, in genuine GLUT 3.6 and up.) */
	/* Also stores the current mouse location, as GLUT only offers a mechanism to */
	/* get its location during a callback for its actual motion.                  */
	char w, a, s, d;
	char up, down, left, right; /* Arrow keys */
	char space, x;
	char lmb, rmb;
	int mousex, mousey; /* Window-relative */
	char dirtymouse; /* A dirty mouse is one which hasn't been centred nicely. */
                         /* Used to prevent rotation at start/after unfreeze.      */
} keystates;
void initkeystates(void) {
	keystates.w = 0; keystates.a = 0; keystates.s = 0; keystates.d = 0;
	keystates.up = 0; keystates.down = 0; keystates.left = 0; keystates.right = 0;
	keystates.space = 0; keystates.x = 0; keystates.lmb = 0; keystates.rmb = 0;
	keystates.mousex = hwindoww; keystates.mousey = hwindowh;
	keystates.dirtymouse = 1;
}

/* Freeze/unfreeze time */
void freeze(char frozen) {
	universe.frozen = frozen;
	if(universe.frozen) {
		glutSetCursor(GLUT_CURSOR_INHERIT);
		keystates.dirtymouse = 1;
		printf("Frozen   - your mouse has been released. Press F to resume.\n");
	} else {
		glutSetCursor(GLUT_CURSOR_NONE);
		printf("Unfrozen - your mouse has been captured. Press F to release it.\n");
	}
}

/* Move the camera in the given direction, collisions notwithstanding */
void move(enum direction d, GLfloat distance) {
	double xa, ya; /* No camera rotation around Z */
	double cx; /* cos(xa) cache */

	if(universe.frozen) freeze(0); /* Unfreeze automatically */
	/* Interrupt tour automatically */
	tourprogress = -1;

	/* Direct up/down motion is easy */
	       if(d == UP) {
		universe.camera.y -= distance;
	} else if(d == DOWN) {
		universe.camera.y += distance;
	} else {
		/* Maths time! */
		/* Because of GL's annoying tendancy to use degrees, convert */
		/* back to sane rotation units (radians).                    */
		/* (xa is calculated later, to avoid unnecessary converts)   */
		ya = universe.camera.ya * DEG_TO_RAD;
		/* Directional adjustments */
		if(d == BACKWARDS || d == RIGHT)
			{ distance *= -1.0; } /* Other direction along vector */
		if(d == LEFT || d == RIGHT) {
			/* Strafing modifies ya of motion */
			ya -= M_PI * 0.5;
			/* And causes no vertical motion */
			xa = 0.0;
			cx = 1.0;
		} else {
			/* Finish converting xa */
			xa = universe.camera.xa * DEG_TO_RAD;
			cx = cos(xa);
		}
		/* Move the camera by a vector of length distance which has */
		/* the given rotation around each axis (always 0 for Z).    */
		universe.camera.x += distance * cos(xa) *-sin(ya);
		universe.camera.y += distance * sin(xa);
		universe.camera.z += distance * cos(xa) * cos(ya);
	}
	
	/* Collision detection */
	world_clipcam(&universe.camera);
}

void display(void); /* Prototype for up-and-coming function... */
/* GLUT callback: window reshaped */
void reshaped(int w, int h) {
	glViewport(0, 0, w, h);

	/* Store dimensions and midpoint */
	 windoww = w;
	 windowh = h;
	hwindoww = w / 2;
	hwindowh = h / 2;
	
	/* Call display() to reset the perspective view */
	display();
}

/* GLUT callback: display items */
void display(void) {
	GLfloat lightposition[4];
	
	/* You can't actually see the 'void' anywhere, so there's no need to clear colour */
	/* (if you *did* get outside, this would cause a "Hall of Mirrors" effect).       */
	/* However, the depth buffer must still be cleared for correct occlusion.         */
	glClear(GL_DEPTH_BUFFER_BIT); /* But not GL_COLOR_BUFFER_BIT */

	/* Position camera */
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(fov, ((GLfloat) windoww/(GLfloat) windowh), /* FOV, aspect */
	               0.01, VIEW_DISTANCE); /* near clip, far clip */
	/* Use GL matrix transforms to position/rotate the camera. */
	glRotatef(universe.camera.xa, 1.0, 0.0, 0.0);
	glRotatef(universe.camera.ya, 0.0, 1.0, 0.0);
/*	glRotatef(universe.camera.za, 0.0, 0.0, 1.0); Always zero */
	glTranslatef(universe.camera.x, universe.camera.y, universe.camera.z);
	gluLookAt(0.0, 0.0, 0.0,  /* Camera */
	          0.0, 0.0, 1.0,  /* Subject */
		  0.0, 1.0, 0.0); /* Up */
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	/* Keep light 0 with the camera */
	lightposition[0] = universe.camera.x;
	lightposition[1] = universe.camera.y;
	lightposition[2] = universe.camera.z;
	lightposition[3] = 1.;
	glLightfv(GL_LIGHT0, GL_POSITION, lightposition);
	
	/* Draw current world */
	world_draw(universe.world, &universe.camera);

	glFlush();
	glutSwapBuffers();
}

/* GLUT callback: keypress */
void keypress(unsigned char key, int x, int y) {
	switch(key) {
		case 'w': case 'W': keystates.w = 1; break;
		case 'a': case 'A': keystates.a = 1; break;
		case 's': case 'S': keystates.s = 1; break;
		case 'd': case 'D': keystates.d = 1; break;
		case ' ':       keystates.space = 1; break;
		case 'x': case 'X': keystates.x = 1; break;
		case 'f': case 'F':
			freeze(!universe.frozen); break;
		case 'y': case 'Y':
			universe.yinvert = !universe.yinvert;
			printf("Mouselook Y invert %s.\n", universe.yinvert ? "on" : "off");
			break;
		case 't': case 'T':
			printf("Starting tour -- please keep your arms inside the car at all times.\n");
			tourprogress = 0; break;
		case 'o': case 'O': /* Display co-ordinates of camera */
			printf("Camera at (%+.2f, %+.2f, %+.2f), rotation (%+i, %+i, %+i).\n",
			universe.camera.x, universe.camera.y, universe.camera.z,
			(int) universe.camera.xa,(int) universe.camera.ya,(int) universe.camera.za);
			break;
		case 'p': case 'P':
			/* Position camera for screenshot (use movable) */
			movable_moveto(&universe.camera, &universe.scrnpos);
			freeze(1);
			break;
		case 'h': case 'H':
			/* Reposition camera at start (help screen) (use movable) */
			movable_moveto(&universe.camera, &universe.helppos); break;
		case 'q': case 'Q':
			printf("Bye! (Total memory used: %lu KB [%lu KB in textures])\n",
			       smalloc_used()/1024, texture_memusage()/1024);
			glFinish();
			exit(0);
			break;
		case '1': fov -= 5; printf("FOV: %i\n", fov); break;
		case '2': fov += 5; printf("FOV: %i\n", fov); break;
		default:
			printf("Unrecognized key. Press H for help or Q to quit.\n");
	}
}

/* GLUT callback: keypress released */
void ukeypress(unsigned char key, int x, int y) {
	switch(key) {
		case 'w': case 'W': keystates.w = 0; break;
		case 'a': case 'A': keystates.a = 0; break;
		case 's': case 'S': keystates.s = 0; break;
		case 'd': case 'D': keystates.d = 0; break;
		case ' ':       keystates.space = 0; break;
		case 'x': case 'X': keystates.x = 0; break;
	}
}

/* GLUT callback: 'special' keypress */
void skeypress(int key, int x, int y) {
	switch(key) {
		case GLUT_KEY_UP   : keystates.up    = 1; break;
		case GLUT_KEY_LEFT : keystates.left  = 1; break;
		case GLUT_KEY_DOWN : keystates.down  = 1; break;
		case GLUT_KEY_RIGHT: keystates.right = 1; break;
		default:
			printf("Unrecognized key. Press H for help or Q to quit.\n");
	}
}

/* GLUT callback: 'special' keypress released */
void sukeypress(int key, int x, int y) {
	switch(key) {
		case GLUT_KEY_UP   : keystates.up    = 0; break;
		case GLUT_KEY_LEFT : keystates.left  = 0; break;
		case GLUT_KEY_DOWN : keystates.down  = 0; break;
		case GLUT_KEY_RIGHT: keystates.right = 0; break;
	}
}

/* GLUT callback: mouse key pressed or released */
void mousepress(int button, int state, int x, int y) {
	switch(button) {
		case GLUT_LEFT_BUTTON:
			keystates.lmb = (state == GLUT_DOWN ? 1 : 0); break;
		case GLUT_RIGHT_BUTTON:
			keystates.rmb = (state == GLUT_DOWN ? 1 : 0); break;
	}
}

/* GLUT callback: mouse motion */
void mousemove(int x, int y) {
	keystates.mousex = x;
	keystates.mousey = y;
}

/* GLUT callback: idle actions */
void idle(void) {
	/* Note: The last coursework used floats with fractional seconds for timing. */
	/* This one uses integer milliseconds. */
	char newtitle[48];
	int elapsed;
	int diffx, diffy;
	int timenow = glutGet(GLUT_ELAPSED_TIME); /* Since GLUT was initialized */

	/* Calculate the time elapsed since the last animation (idle() call) */
	elapsed = timenow - universe.lastidle;

	/* Same 100FPS limit code as coursework 1. This is doubly important, */
	/* as not only animation but mouselook rotation may lose accuracy    */
	/* otherwise.                                                        */
	if ( elapsed < 10 ) {
		/* Sleep for five hundred microseconds (0.5 milliseconds) */
		#ifdef USLEEP /* ...*if* usleep() is availible. */
		usleep(500);
		#endif

		/* Return, and let this be called again later. */
		return;
	}

	if(!universe.frozen) {
		/* Measure mouse movement and recenter pointer */
		if(keystates.dirtymouse) {
			/* Ignore mouse for now -- it needs to be recentered first */
			keystates.dirtymouse = 0;
		} else {
			diffx = keystates.mousex - hwindoww;
			diffy = keystates.mousey - hwindowh;
			if(universe.yinvert) { diffy *= -1.0; }
			/* Remember, xa etc. are rotations AROUND that axis, so */
			/* looking left-right (X) is a rotation around the *Y*  */
			/* axis.                                                */
			universe.camera.ya += diffx * MOUSE_SENSITIVITY;
			universe.camera.xa += diffy * MOUSE_SENSITIVITY;
			/* Normalize, to keep the exponent small (higher acc.)  */
#ifdef LOCK_Y /* Remember, Y look is rotation around X */
			if(universe.camera.xa >   90.0) { universe.camera.xa  =  90.0; }
			if(universe.camera.xa <  -90.0) { universe.camera.xa  = -90.0; }
#else
			if(universe.camera.xa >  360.0) { universe.camera.xa -= 360.0; }
			if(universe.camera.xa < -360.0) { universe.camera.xa += 360.0; }
#endif
			if(universe.camera.ya >  360.0) { universe.camera.ya -= 360.0; }
			if(universe.camera.ya < -360.0) { universe.camera.ya += 360.0; }
		}
		/* This function is a bit new, but works in genuine GLUT 3.7.1. */
		/* (Requires API version 4; present from GLUT 3.4 onwards.) */
		glutWarpPointer(hwindoww, hwindowh);
	}
	
	/* Move camera as appropriate */
	/* Not possible while frozen, BUT still call move(), which will autounfreeze. */
	if(keystates.w || keystates.up || keystates.lmb)
		{ move(FORWARDS,  elapsed * MOVEMENT_SPEED); }
	if(keystates.s || keystates.down || keystates.rmb)
		{ move(BACKWARDS, elapsed * MOVEMENT_SPEED); }
	if(keystates.a || keystates.left)
		{ move(LEFT,      elapsed * MOVEMENT_SPEED); }
	if(keystates.d || keystates.right)
		{ move(RIGHT,     elapsed * MOVEMENT_SPEED); }
	if(keystates.space)
		{ move(UP,        elapsed * MOVEMENT_SPEED); }
	if(keystates.x)
		{ move(DOWN,      elapsed * MOVEMENT_SPEED); }

	if(!universe.frozen) {
		/* Animate current world, giving it the calculated elapsed time */
		world_animate(universe.world, elapsed);
	}
	
	/* Touring? */
	if(tourprogress != -1) {
		/* Interpolate along a linked list of movables with time information */
		/* Get right tour node */
		movable *m, * n;
		long int s, t;
		GLfloat p, q;
		m = universe.tour; n = 0; s = 0; t = 0;
		while(tourprogress >= t && m != 0) {
			s = t;
			t += m->duration;
			n = m;
			m = m->next;
		}
		if(m == 0) {
			/* Tour over */
			tourprogress = -1;
			printf("End of tour. Please watch your step when disembarking.\n");
		} else {
			/* Time   for this stretch is from s to t (t - s == n->duration) */
			/* Motion for this stretch is from n to m                        */
			p = ((float) (tourprogress - s)) / (t - s); /* Progress through this */
			q = 1.0 - p;

			universe.camera.x  = (n->x  * q) + (m->x  * p);
			universe.camera.y  = (n->y  * q) + (m->y  * p);
			universe.camera.z  = (n->z  * q) + (m->z  * p);
			universe.camera.xa = (n->xa * q) + (m->xa * p);
			universe.camera.ya = (n->ya * q) + (m->ya * p);
			
			tourprogress += elapsed;
		}
	}

	/* Redraw, step FPS count */
	display();
	universe.fps++;

	/* Note when we last idled */
	universe.lastidle = timenow;
	
	/* Test for new second; update FPS display if it is */
	if(timenow > universe.lastsecond) {
#ifdef _GNU_SOURCE  /* snprintf is, rather annoyingly, GNU-specific */
		snprintf(newtitle, 48, "%s - %iFPS%s",
			WINDOW_TITLE, universe.fps, universe.frozen ? " (Frozen)" : "");
#else /* Oooh...potential buffer overrun vunerabilities! */
		sprintf( newtitle,     "%s - %iFPS%s",
			WINDOW_TITLE, universe.fps, universe.frozen ? " (Frozen)" : "");
#endif
		glutSetWindowTitle(newtitle);
		universe.lastsecond = timenow + 1000;
		universe.fps = 0;
	}
}

/* Entry point */
int main(int argc, char** argv) {
	GLfloat t[4], s[4]; /* Temporary arrays */
	printf("COMP3004 Principles of Computer Graphics   Coursework 2\n");
	printf("Philip Richard Boulain         <prb102@ecs.soton.ac.uk>\n");
	
	fov = 45;

	printf("Initializing GLUT");
	glutInit(&argc, argv);                                    printf(".");
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(800, 600);
	glutCreateWindow(WINDOW_TITLE);                           printf(".");
	/* Set triangle creation direction */
	glFrontFace(GL_CW);
	glEnable(GL_DEPTH_TEST);                                  printf(".");
	/* Alpha transparency support */
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);        printf(".");
	/* Backface culling for speed, and to make bad normals obvious */
	glEnable(GL_CULL_FACE);
	glCullFace(GL_BACK);                                      printf(".");
	/* Texturing */
	glEnable(GL_TEXTURE_2D);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); printf(".");
	/* Lighting */
	glEnable(GL_LIGHTING);
	glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 1); /* Use camera loc */
	glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1);                printf(".");
	/* Light 0 is positioned with the camera */
	glEnable(GL_LIGHT0);
	glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION,  0.75);
	glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION,    0.05);
	glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.05);      printf(".");
	/* Light 1 is the 'emission' of the help screen */
	glEnable(GL_LIGHT1);
	glLightf(GL_LIGHT1, GL_CONSTANT_ATTENUATION,  0.50);
	glLightf(GL_LIGHT1, GL_LINEAR_ATTENUATION,    0.10);
	glLightf(GL_LIGHT1, GL_QUADRATIC_ATTENUATION, 0.10);
	t[0] = 0.; t[1] = 0.; t[2] = -1.; t[3] = 1.;
	glLightfv(GL_LIGHT1, GL_POSITION, t);
	s[0] = 0.75; s[1] = 0.75; s[2] = 1.; s[3] = 1.;
	glLightfv(GL_LIGHT1, GL_DIFFUSE, s);                      printf(".");
	/* Lights 2 and 3 are the 'fake' help rooms either side */
	glEnable(GL_LIGHT2);
	glLightf(GL_LIGHT2, GL_CONSTANT_ATTENUATION,  0.50);
	glLightf(GL_LIGHT2, GL_LINEAR_ATTENUATION,    0.10);
	glLightf(GL_LIGHT2, GL_QUADRATIC_ATTENUATION, 0.10);
	t[0] = 0.; t[1] = 6.; t[2] = -1.; t[3] = 1.;
	glLightfv(GL_LIGHT2, GL_POSITION, t);
	glLightfv(GL_LIGHT2, GL_DIFFUSE, s);                      printf(".");
	glEnable(GL_LIGHT3);
	glLightf(GL_LIGHT3, GL_CONSTANT_ATTENUATION,  0.50);
	glLightf(GL_LIGHT3, GL_LINEAR_ATTENUATION,    0.10);
	glLightf(GL_LIGHT3, GL_QUADRATIC_ATTENUATION, 0.10);
	t[0] = 0.; t[1] =-6.; t[2] = -1.; t[3] = 1.;
	glLightfv(GL_LIGHT3, GL_POSITION, t);
	glLightfv(GL_LIGHT3, GL_DIFFUSE, s);                      printf(".");
	/* Lights 4 and 5 are in the infinity room */
	s[0] = 0.75; s[1] = 1.; s[2] = 0.75; s[3] = 1.;
	glEnable(GL_LIGHT4);                                      printf(".");
	glLightf(GL_LIGHT4, GL_CONSTANT_ATTENUATION,  0.50);
	glLightf(GL_LIGHT4, GL_LINEAR_ATTENUATION,    0.10);
	glLightf(GL_LIGHT4, GL_QUADRATIC_ATTENUATION, 0.10);
	glEnable(GL_LIGHT5);
	glLightf(GL_LIGHT5, GL_CONSTANT_ATTENUATION,  0.50);
	glLightf(GL_LIGHT5, GL_LINEAR_ATTENUATION,    0.10);
	glLightf(GL_LIGHT5, GL_QUADRATIC_ATTENUATION, 0.10);
	t[0] = 0.; t[1] = 2.; t[2] = 18.; t[3] = 1.;
	glLightfv(GL_LIGHT5, GL_POSITION, t);
	glLightfv(GL_LIGHT5, GL_DIFFUSE, s);                      printf(".");
	/* Lights 6, 7 and re-used 4 are the tea-room mushrooms */
	glEnable(GL_LIGHT6);
	glLightf(GL_LIGHT6, GL_CONSTANT_ATTENUATION,  0.50);
	glLightf(GL_LIGHT6, GL_LINEAR_ATTENUATION,    0.10);
	glLightf(GL_LIGHT6, GL_QUADRATIC_ATTENUATION, 0.10);
	s[0] = 1.00; s[1] = 0.00; s[2] = 0.00; s[3] = 1.;
	glLightfv(GL_LIGHT6, GL_DIFFUSE, s);                      printf(".");
	glEnable(GL_LIGHT7);
	glLightf(GL_LIGHT7, GL_CONSTANT_ATTENUATION,  0.50);
	glLightf(GL_LIGHT7, GL_LINEAR_ATTENUATION,    0.10);
	glLightf(GL_LIGHT7, GL_QUADRATIC_ATTENUATION, 0.10);
	s[0] = 0.00; s[1] = 1.00; s[2] = 0.00; s[3] = 1.;
	glLightfv(GL_LIGHT7, GL_DIFFUSE, s);                      printf(".");
	/* Misc */
	glutSetCursor(GLUT_CURSOR_NONE); /* Hide mouse pointer */ printf(".");
	/* Callbacks */
	glutReshapeFunc(reshaped);
	glutDisplayFunc(display);
	glutIgnoreKeyRepeat(1); /* New to GLUT 3.6 and up */
	glutKeyboardFunc(   keypress);
	glutKeyboardUpFunc(ukeypress);
	glutSpecialFunc(   skeypress);
	glutSpecialUpFunc(sukeypress);
	glutMouseFunc(mousepress);
	glutMotionFunc(mousemove); /* Mouse buttons down */
	glutPassiveMotionFunc(mousemove); /* Buttons up */
	glutIdleFunc(idle);                                       printf("done\n");

	/* Universe initialization must come AFTER GLUT initialization now, */
	/* else glGenTextures(), which is used in texture loading, which is */
	/* used in world initialization, which is used in universe init,    */
	/* won't do anything.                                               */
	
	printf("Initializing universe...");
	srand(time(0)); /* Lets have some chaos */
	universe_init(&universe);
	hwindoww = 400; hwindowh = 300; /* Need guesses for these BEFORE initkeystates() */
	initkeystates();
	tourprogress = -1; /* Don't start with the tour */
	printf("done, over %li polygons.\n", polycount);

	printf("Entering GLUT main loop now.\n");
	glutMainLoop();

	return 0;
}
