#include "seeEdit.h"

//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// seeEdit's global variables
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
int seInitialized=INVALID;
int numQueries = 0;
int nextCellID = 1;
seq QM[MAX_QUERIES]; //Query master

void redisplay_all(void);
GLuint parentWindow, queryWindow;
GLuint seWinWidth, seWinHeight;
GLint selection = CLOSED;

GLint seLock    = OPEN;
GLint cell_move = CLOSED;
GLint cell_x    = INVALID;
GLint show_bbox = CLOSED;

//Default font; change using sesesetfont
char defaultFormat[16] = "%.2f";
GLvoid *font_style = GLUT_BITMAP_TIMES_ROMAN_10;
int font_size = 10;
int interactiveCallback = 0;
int (* seCallback)() = NULL;

float seBackgroundColor[3] = {0.f,0.f,0.f};
float seTextColor[3] = {0.f,1.f,0.5f};
float seInfoColor[3] = {1.f,1.f,0.f};
float seBboxColor[3] = {1.f,1.f,0.f};

void query_display(void);

//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// seeEdit's working functions
//
// The following functions have been lightly modified.
// For the original functions see:
// Nate's OpenGL tutorials: http://www.xmission.com/~nate/opengl.html
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
void cell_print(cell* cell) {
	printf ("  Cell: {ID=%d, (x,y)=(%d,%d), (min,max)=(%.2f,%.2f),\n",
		cell->id,cell->x,cell->y,cell->min,cell->max);
	printf ("         value=%.2f, step=%.2f,format=%s\n",
		cell->value[0], cell->step,cell->format);
	printf ("         info=%s}\n",	cell->info);
}

void cell_draw(cell* cell)
{
	glColor3fv(seTextColor);
    if (selection == cell->id) {
        glColor3fv(seInfoColor);
        sedrawstr(H_OFFSET, (int)(QM[numQueries-1].cells[0].y+font_size),
			cell->info);
        //glColor3ub(255, 0, 0);
    }
    sedrawstr(cell->x, cell->y, cell->format, cell->value[0]);
}

//<CF> - must use same equations as cell_hit
void cell_draw_bbox(cell* cell)
{
	//<CF> - adjust to make sure clickable range of each cell is appropriate
	glColor3fv(seBboxColor);
	glBegin(GL_LINE_LOOP);
		glVertex2d (cell->right, cell->top);
		glVertex2d (cell->left,  cell->top);
		glVertex2d (cell->left,  cell->bottom);
		glVertex2d (cell->right, cell->bottom);
	glEnd();
}

int cell_hit(cell* cell, int x, int y)
{
	//<CF> - adjust to make sure clickable range of each cell is appropriate
    if (x > cell->left && x < cell->right &&
        y > cell->top  && y < cell->bottom  )
        return cell->id;
    return 0;
}

int cell_update(cell* cell, int updateX, int updateY)
{
    if (selection != cell->id)
        return 0;
 
	//Move instead of update
	if (seLock==OPEN && cell_move==OPEN) {
		cell->x=cell_x;

		//update bbox
		cell->left   = cell->x;
		cell->right  = cell->x+2*font_size;
		return 0;
	}

	//Update big movements (up/down)
    cell->value[0] += updateY * cell->step;
	//Update small movements (left/right)
    cell->value[0] += updateX * cell->step * 0.1f;
    
    if (cell->value[0] < cell->min)
        cell->value[0] = cell->min;
    else if (cell->value[0] > cell->max) 
        cell->value[0] = cell->max;

	return 1;
}

void cell_vector(float* dst, cell* cell, int num)
{
    while (--num >= 0)
        dst[num] = cell[num].value[0];
}

void printHelp() {
	printf("\nKEYBOARD MENU (seeEdit 1.0)\n");
	printf("  NOTE: When mousing over interface window only\n");
	printf("  I - Interactive query on move or on release\n");
	printf("  L - Locks/Unlocks draggable numbers with middle mouse button\n");
	printf("      Type 'P' to print copyable code for the current layout\n");
	printf("  B - Show/hide bounding boxes for selectable areas\n");
	printf("  P - Print all information for all cells\n");
	printf("  R - Reset all cells to 0\n");
	printf("  H - This handy/dandy help menu\n");
	printf("  Q/ESC - Quits the application\n");
	printf("\n");
}

void query_keyboard(unsigned char key, int x, int y)
{
	int i,j;
    switch (key) {
	case 'I':
    case 'i':
		if(interactiveCallback==0) interactiveCallback=1;
		else if (interactiveCallback==1) interactiveCallback=0;
        break;
	case 'L':
    case 'l':
		if(seLock==CLOSED) {
			seLock=OPEN;
			printf("  Unlocked: Numbers movable with middle mouse button\n");
		}
		else {
			seLock=CLOSED;
			printf("  Locked: Numbers locked in place\n");
		}
        break;
    case 'B':
    case 'b':
		if(show_bbox==CLOSED) show_bbox=OPEN;
		else show_bbox=CLOSED;
        break;
    case 'P':
    case 'p':
		printf("ALL CELLS: \n");
		for(i=0;i<numQueries;i++) {
			for(j=0;j<QM[i].numVars;j++) {
				if(seLock==CLOSED) cell_print(&(QM[i].cells[j]));
				else {
					printf("\tseloc(%d, %d, %d, %d);",i,j,
						QM[i].cells[j].x,QM[i].cells[j].y);
					if(j%3==0) printf("\n");
				}
			}
			printf("\n");
		}
        break;
    case 'R':
    case 'r':
		for(i=0;i<numQueries;i++) {
			for(j=0;j<QM[i].numVars;j++) {
				QM[i].cells[j].value[0]=0.f;
			}
		}
        break;
    case 'H':
    case 'h':
		printHelp();
        break;
	case 'Q':
	case 'q':
    case 27:
        exit(0);
    }
    
    glutPostRedisplay();
	// Used when main SE is main window controlling render window
    //redisplay_all();
}

void query_reshape(int width, int height)
{
	seWinWidth = width;
	seWinHeight= height;
    glViewport(0, 0, width, height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0, width, height, 0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glClearColor(seBackgroundColor[0],seBackgroundColor[1],
		 seBackgroundColor[2],1.f);
}

void query_display(void)
{
	int i,j;

	//Save state
	GLboolean lightingOn,texturingOn;

	lightingOn  = glIsEnabled(GL_LIGHTING);			// lighting on?
	texturingOn = glIsEnabled(GL_TEXTURE_2D);		// texturing on?
	if (lightingOn) glDisable(GL_LIGHTING);
	if (texturingOn) glDisable(GL_TEXTURE_2D);
	glPushAttrib(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	//Draw the query
	for(i=0;i<numQueries;i++) {
		glColor3f(QM[i].color[0], QM[i].color[1], QM[i].color[2]);
		sedrawstr(H_OFFSET, QM[i].cells[0].y, QM[i].queryText);
	}

	//Draw the interactive cells
	for(i=0;i<numQueries;i++) {
		for(j=0;j<QM[i].numVars;j++) {
			cell_draw(&(QM[i].cells[j]));
		}
	}
	if(show_bbox==OPEN) {
		for(i=0;i<numQueries;i++) {
			for(j=0;j<QM[i].numVars;j++) {
				cell_draw_bbox(&(QM[i].cells[j]));
			}
		}
	}
    
    if (!selection) {
        glColor3fv(seInfoColor);
        sedrawstr(H_OFFSET, (int)(QM[numQueries-1].cells[0].y+font_size),
			(char*)"Click on the arguments and move the mouse to modify values.");
    }

	//Reload state
	glPopAttrib();
	glEnable(GL_LIGHTING);
	if (texturingOn) glEnable(GL_TEXTURE_2D);
	if (lightingOn) glEnable(GL_LIGHTING);

    glutSwapBuffers();
}

int old_x, old_y;
void query_mouse(int button, int state, int x, int y)
{
	static int cell_updated = 0;
	int i,j;
    selection = 0;
    
	if (seLock==OPEN && button==GLUT_MIDDLE_BUTTON) {
		cell_move = OPEN;
		cell_x = x;
	} else {
		cell_move = CLOSED;
	}
    if (state == GLUT_DOWN) {
    /* mouse should only hit _one_ of the cells, so adding up all
        the hits just propagates a single hit. */
		for(i=0;i<numQueries;i++) {
			for(j=0;j<QM[i].numVars;j++) {
				selection += cell_hit(&(QM[i].cells[j]), x, y);
			}
		}
		if(selection!=0) cell_updated = 1;
    }
	old_x = x;
    old_y = y;

	//printf("%d %d %d %d\n",seCallback,selection,state,interactiveCallback);
	if(seCallback!=NULL && cell_updated==1 &&
	   state == GLUT_UP && interactiveCallback==0) {
		seCallback();
		cell_updated=0;
	}

    glutPostRedisplay();
	// Used when main SE is main window controlling render window
    //redisplay_all();
}

void query_motion(int x, int y)
{
	int i,j,cellsUpdated=0;

	for(i=0;i<numQueries;i++) {
		for(j=0;j<QM[i].numVars;j++) {
			//Note: OpenGL x increases as you go right but
			//      OpenGL y increases as you go down (inverse this for user)
			cellsUpdated+=cell_update(&(QM[i].cells[j]), x-old_x, old_y-y);
		}
	}

	old_x = x;
    old_y = y;
	cell_x = x;

	if(seCallback!=NULL && cellsUpdated!=0 && interactiveCallback==1) {
		seCallback();
	}

    glutPostRedisplay();
	// Used when main SE is main window controlling render window
    //redisplay_all();
}

void redisplay_all(void)
{
    glutSetWindow(parentWindow);
    glutPostRedisplay();
    glutSetWindow(queryWindow);
    glutPostRedisplay();
}

void sedraw() {
	glutSetWindow(queryWindow);
    glutPostRedisplay();
	glutSetWindow(parentWindow);
}

//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// Main API Functions
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
int seinit(const int winID,
		   const int x, const int y,
		   const int width, const int height) {
	int i;

	if(width<=0 || height<=0) {
		perror(" seinit(): need positive width/height");
		return INVALID;
	}

	//Initialize glut
	parentWindow = winID;
	queryWindow = glutCreateSubWindow(parentWindow, x,y, width,height);
    glutReshapeFunc(query_reshape);
    glutDisplayFunc(query_display);
    glutMotionFunc(query_motion);
    glutMouseFunc(query_mouse);
    glutKeyboardFunc(query_keyboard);

	glutSetWindow(queryWindow);
	//Initialize state for this subwindow
	glShadeModel(GL_SMOOTH);							// Enable Smooth Shading
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);	// Fast Perspective Calculations
	glClearColor(seBackgroundColor[0],seBackgroundColor[1],
		     seBackgroundColor[2],1.f);
	glEnable(GL_COLOR_MATERIAL);
	glutSetWindow(parentWindow);

	//Initialize seeEdit query structure
	for(i=0;i<MAX_QUERIES;i++) {
		QM[i].mode    = CLOSED;
		QM[i].numVars = 0;
		QM[i].vars    = NULL;
		QM[i].cells   = NULL;
	}
	seInitialized = OPEN;

	//Warning: If the font is changed, then you will likely need to
	//make modifications to code marked "<CF>"
    sesetfont((char*)"helvetica", 12);
	seWinWidth = width;
	seWinHeight= height;

	return queryWindow;
}

int seadd(char* str, int numVars, float* vars) {
	int i,j, strWidth,charHeight, querySlot=INVALID;
	int minSize,maxSize, seSize;
	int sizeDefaultHelp = 256;
	float rMin, rMax, dStep, dSpace;
	char *defaultHelp, seSpace[MAX_QUERY_LENGTH];

	//char* dFormat = "%.2f";
	float r=1.f, g=1.f, b=1.f;

	//Error checking - find valid seq, make sure it can be read, installed,
	//  and space allocated for the filename
	if(seInitialized==INVALID) {
		printf("  seInitialized() has not been called\n");
		return INVALID;
	}
	if(vars==NULL) return INVALID;

	//Trying to be smarter
	rMin=FLT_MAX;	rMax=FLT_MIN;
	for(i=0;i<numVars;i++) {
		if(vars[i]<rMin) rMin=vars[i];
		if(vars[i]>rMax) rMax=vars[i];
	}
	dStep=(rMax-rMin)/100.f;
	sprintf(seSpace,defaultFormat,rMin);
	minSize=glutBitmapLength(font_style, (const unsigned char *)seSpace);
	sprintf(seSpace,defaultFormat,rMax);
	maxSize=glutBitmapLength(font_style, (const unsigned char *)seSpace);
	if(minSize<maxSize) {
		seSize=maxSize;
		dSpace=rMax;
	}
	else {
      seSize=minSize;
		dSpace=rMin;
	}

	//If you want to directly specify a default
	//rMin=0.f; rMax=1.f; dStep=0.01f;

	for(i=0;i<MAX_QUERIES;i++) {
		if(QM[i].mode==CLOSED) {

			//Initialize a query for the given data
			QM[i].mode     = INUSE;
			QM[i].color[0] = r;
			QM[i].color[1] = g;
			QM[i].color[2] = b;
			QM[i].numVars  = numVars;
			QM[i].vars = vars;

			//Parse the format string like a printf
			//<CF> - add/remove spaces to make room for new data format & font
			//QM[i].queryText = sesubstr(DELIMITER,"      ",str);
			QM[i].queryText = sebuildquery(str,defaultFormat,dSpace);

			//Malloc interactive cells
			QM[i].cells = (cell*)malloc(numVars*sizeof(cell));
			if(QM[i].cells==NULL) {
				perror("malloc seadd()");
				return INVALID;
			}
			defaultHelp = (char*)malloc(sizeDefaultHelp*sizeof(char));
			if(defaultHelp==NULL) {
				perror("malloc seadd()");
				return INVALID;
			}

			//Init interactive cells
			for(j=0;j<numVars;j++) {
				QM[i].cells[j].value  = NULL;
				QM[i].cells[j].info   = NULL;
				QM[i].cells[j].format = NULL;

				QM[i].cells[j].id  = nextCellID++;
				
				QM[i].cells[j].value = &(QM[i].vars[j]);

				//Init location intelligently
				//selocguess(i,j,str,defaultFormat,dSpace); //set location
            selocspace(i,j,str,defaultFormat,dSpace); //set location

				//set bounding box
				strWidth = seSize;
				charHeight = font_size;
				//set bounding box based on current value
				//sprintf(testStr,defaultFormat,vars[j]);
				QM[i].cells[j].left   = QM[i].cells[j].x;
				QM[i].cells[j].right  = QM[i].cells[j].x+strWidth;
				QM[i].cells[j].top    = QM[i].cells[j].y-7*charHeight/5;
				QM[i].cells[j].bottom = QM[i].cells[j].y+charHeight/5;

				sprintf(defaultHelp,"This is Query %d, Var %d",i,j);
				seinfo(i,j,defaultHelp);

				seset(i,j,rMin,rMax,dStep,defaultFormat);

				//Debug: look at all cell data as they're added
				//cell_print(&(QM[i].cells[j]));
			}

			free(defaultHelp);
			querySlot=i;
			break;
		}
	}

	if(querySlot!=INVALID) {
		numQueries++;
	}

	return querySlot;//failure (INVALID=-1) if no more query slots available
}

void registerCallback(int (* seQueryChange)()) {
	seCallback = seQueryChange;
}


//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// seeEdit's edit functions - change format and location of displayed stuff
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
int validQuery(int queryID) {
	if(seInitialized==INVALID) {
		printf("  seInitialized() has not been called\n");
		return INVALID;
	}
	if(queryID<0 || queryID>MAX_QUERIES) {
		printf("  seinfo(): No such query\n");
		return INVALID;
	}
	if(QM[queryID].mode==CLOSED) {
		printf("  seinfo(): No such query\n");
		return INVALID;
	}
	return 0;
}

int validVariable(int queryID,int variableID) {
	if(seInitialized==INVALID) {
		printf("  seInitialized() has not been called\n");
		return INVALID;
	}
	if(variableID<0) {
		printf("  seinfo(): No such variable\n");
		return INVALID;
	}
	if(QM[queryID].numVars<=variableID) {
		printf("  seinfo(): No such variable\n");
		return INVALID;
	}
	return 0;
}

int selocspace(int queryID, int variableID, const char* str, const char* dFormat, float maxVal) {
   char addT[MAX_QUERY_LENGTH];
   int i, strWidth, queryStartSize;
   unsigned int xPos, yPos;

   //Error checking
	if(validQuery(queryID)==INVALID) return INVALID;
	if(validVariable(queryID,variableID)==INVALID) return INVALID;

   //The work
   // base string
   for(i=0;i<(int)strlen(str);i++) {
      if(str[i]=='%') {
         addT[i]='\0';
         break;
      }
      addT[i]=str[i];
   }
	queryStartSize=glutBitmapLength(font_style,(const unsigned char *)addT);
   sprintf(addT,dFormat,maxVal);
	strWidth = glutBitmapLength(font_style, (const unsigned char *)addT);

   xPos = H_OFFSET+queryStartSize+variableID*strWidth;
	yPos = (unsigned int)(V_OFFSET+(queryID+1)*1.25*font_size);

	//If guessing puts data outside screen, put on edges
	// to allow for middle-click dragging for proper placement
	if(xPos<0 || xPos>seWinWidth) xPos = seWinWidth-2*H_OFFSET;
	if(yPos<0 || yPos>seWinHeight) yPos = 2*V_OFFSET;

	//Important part: set the location
	QM[queryID].cells[variableID].x = xPos;
	QM[queryID].cells[variableID].y = yPos;
   return 0;
}

int selocguess(int queryID, int variableID, const char* str, const char* dFormat, float maxVal) {
	char *baseStr, *preStr, *postStr, *beginStr, *currStr, *query;
	char addT[MAX_QUERY_LENGTH], addN[MAX_QUERY_LENGTH];
	int i,matches,strWidth,numSpaces,querySize;
	unsigned int xPos,yPos;

	//Error checking
	if(validQuery(queryID)==INVALID) return INVALID;
	if(validVariable(queryID,variableID)==INVALID) return INVALID;

	currStr = (char*)malloc((MAX_QUERY_LENGTH)*sizeof(char));
	beginStr = (char*)malloc((MAX_QUERY_LENGTH)*sizeof(char));
	if(currStr==NULL || beginStr==NULL) {
		perror("selocguess"); return INVALID;
	}
	strcpy(beginStr,str);
	baseStr = beginStr;
	preStr = beginStr;
   postStr = beginStr;

	//Always assume maximum value for each %se
	sprintf(addN,dFormat,maxVal);
	if(strlen(addN)>20) strcpy(addN,"      ");

	//Make sure cells fit on screen
	query=sesubstr(DELIMITER,addN,str);
	querySize=glutBitmapLength(font_style,(const unsigned char *)query);
	numSpaces=strlen(addN);
	while(querySize>(int)(seWinWidth-2*H_OFFSET)) {
		numSpaces--;
		if(numSpaces<0) break;
		addN[numSpaces]='\0';
		free(query);
		query=sesubstr(DELIMITER,addN,str);
		querySize=glutBitmapLength(font_style,(const unsigned char *)query);
	}

	//The actual work
	currStr[0]='\0';//starts blank
	matches = 0;
	while( (postStr=strstr(postStr,DELIMITER)) != NULL ) {
		
		//Find location of substring start
		for(i=0;i<(int)strlen(preStr);i++)
			if( &(preStr[i]) == postStr )
				break;

		//Get the new text part
		memcpy(addT,preStr,i*sizeof(char));
		addT[i]='\0';

		//Add the new parts to the current query
		strcat(currStr,addT);

		if(matches==variableID) break;
		else strcat(currStr,addN);

		//Prepare for next variable
		preStr = postStr+strlen(DELIMITER)*sizeof(char);
		postStr = &(postStr[strlen(DELIMITER)]);
		matches++;	
	}

	//Important part: set the location
	strWidth = glutBitmapLength(font_style, (const unsigned char *)currStr);
	xPos = H_OFFSET+strWidth;
	yPos = (unsigned int)(V_OFFSET+(queryID+1)*1.25*font_size);

	//If guessing puts data outside screen, put on edges
	// to allow for middle-click dragging for proper placement
	if(xPos<0 || xPos>seWinWidth) xPos = seWinWidth-2*H_OFFSET;
	if(yPos<0 || yPos>seWinHeight) yPos = 2*V_OFFSET;

	QM[queryID].cells[matches].x = xPos; //matches incorrect value?
	QM[queryID].cells[matches].y = yPos; //matches incorrect value?

	free(currStr); //SIGSEG Ubuntu (needs cleanup)
	free(beginStr); //SIGSEG Ubuntu (needs cleanup)
	return 0;
}

int seloc(int queryID, int variableID, int x, int y) {
	int xDiff;

	//Error checking
	if(validQuery(queryID)==INVALID) return INVALID;
	if(validVariable(queryID,variableID)==INVALID) return INVALID;

	//The actual work
	QM[queryID].cells[variableID].x = x;
	QM[queryID].cells[variableID].y = y;

	xDiff = QM[queryID].cells[variableID].right -
		    QM[queryID].cells[variableID].left;
	QM[queryID].cells[variableID].left   = x;
	QM[queryID].cells[variableID].right  = x+xDiff;
	QM[queryID].cells[variableID].top    = y-7*font_size/5;
	QM[queryID].cells[variableID].bottom = y+font_size/5;

	return 0;
}

int seset(int queryID, int variableID,
		  float min, float max, float step, const char* format) {
	//Error checking
	if(validQuery(queryID)==INVALID) return INVALID;
	if(validVariable(queryID,variableID)==INVALID) return INVALID;

	//The actual work
	QM[queryID].cells[variableID].min  = min;
	QM[queryID].cells[variableID].max  = max;
	QM[queryID].cells[variableID].step = step;
	QM[queryID].cells[variableID].format = 
		(char*)malloc( (strlen(format)+1) * sizeof(char));
	if(QM[queryID].cells[variableID].format==NULL) {
		perror("malloc seset()");
		return INVALID;
	}
	strcpy(QM[queryID].cells[variableID].format,format);

	return 0;
}

int sesetall(int queryID, 
			 float min, float max, float step, const char* format) {
	int i;

	//Error checking
	if(validQuery(queryID)==INVALID) return INVALID;

	//The actual work
	for(i=0;i<QM[queryID].numVars;i++) {
		QM[queryID].cells[i].min  = min;
		QM[queryID].cells[i].max  = max;
		QM[queryID].cells[i].step = step;
		QM[queryID].cells[i].format = 
			(char*)malloc( (strlen(format)+1) * sizeof(char));
		if(QM[queryID].cells[i].format==NULL) {
			perror("malloc sesetall()");
			return INVALID;
		}
		strcpy(QM[queryID].cells[i].format,format);
	}

	return 0;
}

int seinfo(int queryID, int variableID, const char* str) {
	//Error checking
	if(validQuery(queryID)==INVALID) return INVALID;
	if(validVariable(queryID,variableID)==INVALID) return INVALID;

	//The actual work
	if(QM[queryID].cells[variableID].info!=NULL) {
		free(QM[queryID].cells[variableID].info);
	}
	QM[queryID].cells[variableID].info =
		(char*)malloc((strlen(str)+1)*sizeof(char));
	if(QM[queryID].cells[variableID].info==NULL) {
		perror("malloc seinfo()");
		return INVALID;
	}
	strcpy(QM[queryID].cells[variableID].info,str);

	return 0;
}

int seminmaxall(int queryID, float min, float max) {
	int i;

	//Error checking
	if(validQuery(queryID)==INVALID) return INVALID;

	//The actual work
	for(i=0;i<QM[queryID].numVars;i++) {
		QM[queryID].cells[i].min  = min;
		QM[queryID].cells[i].max  = max;
	}

	return 0;
}

int seminmax(int queryID, int variableID, float min, float max) {
	//Error checking
	if(validQuery(queryID)==INVALID) return INVALID;
	if(validVariable(queryID,variableID)==INVALID) return INVALID;

	//The actual work
	QM[queryID].cells[variableID].min  = min;
	QM[queryID].cells[variableID].max  = max;

	return 0;
}

int seformatdefault(const char* format) {
	if(strlen(format)>16) {
		printf("Format too long\n");
		return INVALID;
	}
	else strcpy(defaultFormat,format);

	return 0;
}

int seformatall(int queryID, const char* format) {
	int i;

	//Error checking
	if(validQuery(queryID)==INVALID) return INVALID;

	//The actual work
	//Set for all variables of the given query
	for(i=0;i<QM[queryID].numVars;i++) {
		if(QM[queryID].cells[i].format!=NULL)
			free(QM[queryID].cells[i].format);
		QM[queryID].cells[i].format = 
			(char*)malloc( (strlen(format)+1) * sizeof(char));
		if(QM[queryID].cells[i].format==NULL) {
			perror("malloc seformatall()");
			return INVALID;
		}
		strcpy(QM[queryID].cells[i].format,format);
		//cell_print(&(QM[queryID].cells[i]));
	}

	return 0;
}

int seformat(int queryID, int variableID, const char* format) {
	//Error checking
	if(validQuery(queryID)==INVALID) return INVALID;
	if(validVariable(queryID,variableID)==INVALID) return INVALID;

	//The actual work
	free(QM[queryID].cells[variableID].format);
	QM[queryID].cells[variableID].format = 
		(char*)malloc( (strlen(format)+1) * sizeof(char));
	if(QM[queryID].cells[variableID].format==NULL) {
		perror("malloc seformat()");
		return INVALID;
	}
	strcpy(QM[queryID].cells[variableID].format,format);

	return 0;
}

int sestepall(int queryID, float step) {
	int i;

	//Error checking
	if(validQuery(queryID)==INVALID) return INVALID;

	//The actual work
	for(i=0;i<QM[queryID].numVars;i++) {
		QM[queryID].cells[i].step  = step;
	}

	return 0;
}

int sestep(int queryID, int variableID, float step) {
	//Error checking
	if(validQuery(queryID)==INVALID) return INVALID;
	if(validVariable(queryID,variableID)==INVALID) return INVALID;

	//The actual work
	QM[queryID].cells[variableID].step  = step;

	return 0;
}

int secolor(int queryID, float r, float g, float b) {

	//Check to see if changing default color states
	if(queryID==SE_BACKGROUND) {
		seBackgroundColor[0]=r;
		seBackgroundColor[1]=g;
		seBackgroundColor[2]=b;
		glutSetWindow(queryWindow);
		glClearColor(seBackgroundColor[0],seBackgroundColor[1],
			     seBackgroundColor[2],1.f);
		glutSetWindow(parentWindow);
		return SE_BACKGROUND;
	}
	else if (queryID==SE_TEXT) {
		seTextColor[0]=r;
		seTextColor[1]=g;
		seTextColor[2]=b;
		return SE_TEXT;
	}
	else if (queryID==SE_INFO) {
		seInfoColor[0]=r;
		seInfoColor[1]=g;
		seInfoColor[2]=b;
		return SE_INFO;
	}
	else if (queryID==SE_BBOX) {
		seBboxColor[0]=r;
		seBboxColor[1]=g;
		seBboxColor[2]=b;
		return SE_BBOX;
	}

	//Error checking
	if(validQuery(queryID)==INVALID) return INVALID;

	//The actual work
	QM[queryID].color[0]=r;
	QM[queryID].color[1]=g;
	QM[queryID].color[2]=b;

	return 0;
}

//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// seeEdit's helper functions
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

void sesetfont(char* name, int size)
{
    font_style = GLUT_BITMAP_HELVETICA_10;
    if (strcmp(name, "helvetica") == 0) {
        if (size == 12) 
            font_style = GLUT_BITMAP_HELVETICA_12;
        else if (size == 18)
            font_style = GLUT_BITMAP_HELVETICA_18;
    } else if (strcmp(name, "times roman") == 0) {
        font_style = GLUT_BITMAP_TIMES_ROMAN_10;
        if (size == 24)
            font_style = GLUT_BITMAP_TIMES_ROMAN_24;
    } else if (strcmp(name, "8x13") == 0) {
        font_style = GLUT_BITMAP_8_BY_13;
    } else if (strcmp(name, "9x15") == 0) {
        font_style = GLUT_BITMAP_9_BY_15;
    }
	font_size = size;
}

void sedrawstr(GLuint x, GLuint y, char* format, ...)
{
    va_list args;
    char buffer[255], *s;
    
    va_start(args, format);
    vsprintf(buffer, format, args);
    va_end(args);
    
    glRasterPos2i(x, y);
    for (s = buffer; *s; s++)
        glutBitmapCharacter(font_style, *s);
}

char* sesubstr(const char* search, const char* replace, const char* str) {
	int i,j,match,matches,newStringSize;
	int currIdx,currPos;
	int* pos;
	char* newString;

	//This entire function is inefficient and is easily doable in
	//STL or with the string class, but I wanted to stay with
	//simple ANSI C character pointers
	matches=0;
	for(i=0;i<(int)strlen(str);i++) {
		match=0;
		for(j=0;j<(int)strlen(search);j++) {
			if(str[i+j]!=search[j]) match++;
		}
		if( match==0 ) { //replace substring
			matches++;
		}
	}

	//Find beginning position of all substring matches
	pos=(int*)malloc(matches*sizeof(int));
	if( pos==NULL) {
		perror("malloc substr()");
		return NULL;
		//exit(INVALID);
	}
	matches=0;
	for(i=0;i<(int)strlen(str);i++) {
		match=0;
		for(j=0;j<(int)strlen(search);j++) {
			if(str[i+j]!=search[j]) match++;
		}
		if( match==0 ) { //replace substring
			pos[matches]=i;
			matches++;
		}
	}

	//Create just enough space for our new string
	newStringSize = strlen(str) + matches*(strlen(replace)-strlen(search)) + 1;
	newString     = (char*)malloc(newStringSize*sizeof(char));
	if( newString==NULL) {
		perror("malloc substr()");
		return NULL;
		//exit(INVALID);
	}

	currIdx=currPos=0;
	for(i=0;i<newStringSize;i++) {
		if(currIdx==pos[currPos]) {
			for(j=0;j<(int)strlen(replace);j++)
				newString[i+j]=replace[j];
			i+=(int)(strlen(replace)-1);
			currIdx+=strlen(search);
			currPos++;
		} else {
			newString[i]=str[currIdx];
			currIdx++;
		}
	}
	free(pos);
	return newString;
}

char* sebuildquery(const char* str, const char* dformat, float maxVal) {
	int maxValSize,spaceSize,numSpaces,querySize;
	char maxValStr[MAX_QUERY_LENGTH],spaceStr[MAX_QUERY_LENGTH];
	char* query;

	//Always assume maximum value for each %se
	sprintf(maxValStr,dformat,maxVal);
	maxValSize=glutBitmapLength(font_style, (const unsigned char *)maxValStr);
	spaceSize=0; spaceStr[0]='\0';
	while(spaceSize<maxValSize) {
		strcat(spaceStr," ");
		spaceSize=glutBitmapLength(font_style,(const unsigned char *)spaceStr);
	}

	//Make sure query fits on screen
	query=sesubstr(DELIMITER,spaceStr,str);
	querySize=glutBitmapLength(font_style,(const unsigned char *)query);
	numSpaces=strlen(spaceStr);
	while(querySize>(int)(seWinWidth-2*H_OFFSET)) {
		numSpaces--;
		if(numSpaces<0) break;
		spaceStr[numSpaces]='\0';
		free(query);
		query=sesubstr(DELIMITER,spaceStr,str);
		querySize=glutBitmapLength(font_style,(const unsigned char *)query);
	}
	
	return query;
}
