#include "ParticlePlot.h"

//****************************************************************************************

void* viewerThreadFunc(void* viewer) {
	((ParticlePlot*) viewer)->run();
	
	return NULL;
}

//****************************************************************************************

ParticlePlot::ParticlePlot(const char* title, int width, int height) : GlutWindow2d(title, width, height) {
	viewerThreadID = NULL;
	vertices = NULL;
	colors = NULL;
	size = 0;
	pthread_mutex_init(&dataLock, NULL);
	
	followPositionFlag = false;
	cameraCenterPosition.x = 0;
	cameraCenterPosition.y = 0;
	refX = 0;
	refY = 0;
	for(int i = 0; i < 4; i++) {
		mapBoundaries[i].x = 0;
		mapBoundaries[i].y = 0;
	}
	
}

//****************************************************************************************

/*! Basic destructor */
ParticlePlot::~ParticlePlot() {
	if(vertices != NULL) {
		delete [] vertices;
	}
	vertices = NULL;
	
	if(colors != NULL) {
		delete [] colors;
	}
	colors = NULL;
	
	pthread_mutex_destroy(&dataLock);
}

//****************************************************************************************

/*!
  This function reinitializes the plotter by removing all reference data.
*/
void ParticlePlot::reinitPlotter() {
	if(vertices != NULL) {
		delete [] vertices;
	}
	vertices = NULL;
	
	if(colors != NULL) {
		delete [] colors;
	}
	colors = NULL;
	size = 0;
	
	followPositionFlag = false;
	cameraCenterPosition.x = 0;
	cameraCenterPosition.y = 0;
	refX = 0;
	refY = 0;
	
	// pthread_mutex_lock(&dataLock);
	referencePath.clear();
	vehiclePath.clear();
	//pthread_mutex_unlock(&dataLock);
	
}

//****************************************************************************************

/*!
  This function will spawn the viewer as a separate thread and start the viewer.
*/
void ParticlePlot::spawnAsThread() {
	viewerThreadID = new pthread_t;
	pthread_create(viewerThreadID, NULL, viewerThreadFunc, (void*) this);
}

//****************************************************************************************

/*!
  This function needs to be overriden to draw the scene.  This function is called from
  the display function automatically.
*/

void ParticlePlot::draw() {
	glPushMatrix();
	
	positionCamera();
	
	glPushMatrix();
	
	pthread_mutex_lock(&dataLock);
	
	//if the user wants to follow a certain point, then translate over that position
	if(followPositionFlag) {
		glTranslated(-cameraCenterPosition.x, -cameraCenterPosition.y, 0);
	}
	
	glColor3f(1.0, 0.0, 1.0);
	
	
	
	drawAxes();
	if(vertices != NULL) {
		//set lighting
		//glEnable(GL_LIGHTING);
		//glEnable(GL_LIGHT0);
		//GLfloat al[] = {0.2, 0.2, 0.2, 1.0};
		//glLightModelfv(GL_LIGHT_MODEL_AMBIENT, al);
		//GLfloat light_specular[]={-1.0,1.0,-1.0,1.0};
		//glLightfv(GL_LIGHT0,GL_SPECULAR,light_specular);
		//GLfloat light_position[] = {-1.0,1.0,-1.0,0.0};
		//glLightfv(GL_LIGHT0,GL_POSITION,light_position);
		
		//draw the map
		glEnableClientState(GL_VERTEX_ARRAY);
		glEnableClientState(GL_COLOR_ARRAY);
		
		glColorPointer(3, GL_DOUBLE, 0, colors);
		glVertexPointer(3, GL_DOUBLE, 0, vertices);
		glDrawArrays(GL_QUADS, 0, numQuads * 4);
		
		glDisableClientState(GL_VERTEX_ARRAY);
		glDisableClientState(GL_COLOR_ARRAY);
		
		//draw the particles
		glColor3f(1.0, 1.0, 1.0);
		glBegin(GL_POINTS);
		for(int i = 0; i < MAX_PARTICLES; i++) {
			glVertex3d(particles[i].y, particles[i].x, 0.0);
		}
		glEnd();
		
		//draw the map boundaries
		glColor3f(1.0, 1.0, 1.0);
		glBegin(GL_LINE_LOOP);
		for(int i = 0; i < 4; i++) {
			glVertex2d(mapBoundaries[i].y, mapBoundaries[i].x);
		}
		glEnd();
	}
	
	if(vehiclePath.size() > 1) {
		//draw vehicle path
		glColor3f(1, 1, 1);
		glBegin(GL_LINES);
		for(unsigned int i = 0; i < vehiclePath.size() - 1; i++) {
			glVertex2d(vehiclePath[i].y - this->refY, vehiclePath[i].x - this->refX);
			glVertex2d(vehiclePath[i + 1].y - this->refY, vehiclePath[i + 1].x - this->refX);
		}
		glEnd();
	}
	//draw reference path
	if(referencePath.size() > 1) {
		glColor3f(0.5, 0.5, 0.5);
		glBegin(GL_LINES);
		for(unsigned int i = 0; i < referencePath.size() - 1; i++) {
			glVertex2d(referencePath[i].y - this->refY, referencePath[i].x - this->refX);
			glVertex2d(referencePath[i + 1].y - this->refY, referencePath[i + 1].x - this->refX);
		}
		glEnd();
	}
	
	char temp[64];
	sprintf(temp, "(%0.2f, %0.2f)", mapCoords1.x, mapCoords1.y);
	drawText2d(temp, mapCoords1.x, mapCoords1.y - 10, 1, 1, 1, NULL);
	
	if(displayString.size() != 0) {
		drawText(displayString.c_str(), 0, 0.005, 1, 1, 1, GLUT_BITMAP_HELVETICA_12);
	}
	
	pthread_mutex_unlock(&dataLock);
	
	glPopMatrix();
	
	// If the user is changing the zoom factor, draw the zoom circle
	if(drawZoomCircleFlag) {
		drawZoomCircle();
	}
	glPopMatrix();
}

void ParticlePlot::setData(Matrix& heightValues, double* xPos, double* yPos, particleT* currParticles, const double& refX,
						   const double& refY) {
	pthread_mutex_lock(&dataLock);
	
	//set internal refX/refY
	this->refX = refX;
	this->refY = refY;
	
	setMap(heightValues, xPos, yPos);
	
	setParticles(currParticles);
	
	pthread_mutex_unlock(&dataLock);
}

void ParticlePlot::setDataMapOnly(Matrix& heightValues, double* xPos, double* yPos, const double& refX, const double& refY) {
	pthread_mutex_lock(&dataLock);
	
	//set internal refX/refY
	this->refX = refX;
	this->refY = refY;
	
	setMap(heightValues, xPos, yPos);
	
	pthread_mutex_unlock(&dataLock);
}

//****************************************************************************************

void ParticlePlot::setMap(Matrix& heightValues, double* xPos, double* yPos) {
	//default cell size, this is only used on the boundary
	double defaultCellSize = 1.0;
	
	int index = 0; //temporary variable to store which cell we are drawing now
	int posI, posJ;
	int numEntriesPerQuad = 12; //number of vertices times the number of points. i.e 4*3
	
	//heightValues matrix has North as x, East as y: need to flip for visualization
	heightValues = heightValues.t();
	
	//temp variables to hold the interp color
	float r;
	float g;
	float b;
	
	double minHeight = 1e100;
	double maxHeight = -1e100;
	
	int numRows = heightValues.Nrows();
	int numCols = heightValues.Ncols();
	
	//distance to the adjacent cells
	double diffPrevRow;
	double diffPrevCol;
	double diffNextRow;
	double diffNextCol;
	
	//if the arrays have already been created, delete them.
	if(vertices != NULL) {
		delete [] vertices;
	}
	vertices = new double [numRows * numCols * 4 * 3];
	if(colors != NULL) {
		delete [] colors;
	}
	colors = new double [numRows * numCols * 4 * 3];
	
	//assign the number of quads that are being draw as well as the size of the vectors
	numQuads = numCols * numRows;
	size = numRows * numCols * 4 * 3;
	
	
	//change xPos, yPos vectors to be relative to refX, refY
	for(int i = 0; i < numCols; i++) {
		xPos[i] -= refX;
	}
	for(int j = 0; j < numRows; j++) {
		yPos[j] -= refY;
	}
	
	//find the min and max of the height values
	for(int i = 1; i <= numRows; i++) {
		for(int j = 1; j <= numCols; j++) {
			minHeight = min(fabs(heightValues(i, j)), minHeight);
			maxHeight = max(fabs(heightValues(i, j)), maxHeight);
		}
	}
	if(USE_CONST_SCALE) {
		minHeight = MIN_H;
		maxHeight = MAX_H;
	}
	
	for(int i = 1; i <= numRows; i++) {
		for(int j = 1; j <= numCols; j++) {
			posI = i - 1;
			posJ = j - 1;
			
			//calculate the size of each cell, this just ensures that each quad will line up exactly
			//with the previous one.
			if(posI > 0) {
				diffPrevCol = yPos[posI] - yPos[posI - 1];
			} else {
				diffPrevCol = defaultCellSize;
			}
			
			if(posJ > 0) {
				diffPrevRow = xPos[posJ] - xPos[posJ - 1];
			} else {
				diffPrevRow = defaultCellSize;
			}
			
			if(posI < numRows - 1) {
				diffNextCol = yPos[posI + 1] - yPos[posI];
			} else {
				diffNextCol = defaultCellSize;
			}
			
			if(posJ < numCols - 1) {
				diffNextRow = xPos[posJ + 1] - xPos[posJ];
			} else {
				diffNextRow = defaultCellSize;
			}
			
			
			//calculate the vertices
			vertices[numEntriesPerQuad * index] = yPos[posI] + diffNextCol / 2.0;
			vertices[numEntriesPerQuad * index + 1] = xPos[posJ] - diffPrevRow / 2.0;
			vertices[numEntriesPerQuad * index + 2] = 0.0;
			
			vertices[numEntriesPerQuad * index + 3] = yPos[posI] - diffPrevCol / 2.0;
			vertices[numEntriesPerQuad * index + 4] = xPos[posJ] - diffPrevRow / 2.0;
			vertices[numEntriesPerQuad * index + 5] = 0.0;
			
			vertices[numEntriesPerQuad * index + 6] = yPos[posI] - diffPrevCol / 2.0;
			vertices[numEntriesPerQuad * index + 7] = xPos[posJ] + diffNextRow / 2.0;
			vertices[numEntriesPerQuad * index + 8] = 0.0;
			
			vertices[numEntriesPerQuad * index + 9] = yPos[posI] + diffNextCol / 2.0;
			vertices[numEntriesPerQuad * index + 10] = xPos[posJ] + diffNextRow / 2.0;
			vertices[numEntriesPerQuad * index + 11] = 0.0;
			
			//calculate the colors
			/*
			r = -1.0*(fabs(heightValues(i,j))-minHeight)/(maxHeight-minHeight) + 1.0;
			g = 0.5;
			b = 1.0*(fabs(heightValues(i,j))-minHeight)/(maxHeight-minHeight);
			*/
			computeRGB(r, g, b, heightValues(i, j), minHeight, maxHeight);
			//cout<<r<<" "<<g<<" "<<b<<endl;
			//assign the colors
			colors[numEntriesPerQuad * index] = r;
			colors[numEntriesPerQuad * index + 1] = g;
			colors[numEntriesPerQuad * index + 2] = b;
			
			colors[numEntriesPerQuad * index + 3] = r;
			colors[numEntriesPerQuad * index + 4] = g;
			colors[numEntriesPerQuad * index + 5] = b;
			
			colors[numEntriesPerQuad * index + 6] = r;
			colors[numEntriesPerQuad * index + 7] = g;
			colors[numEntriesPerQuad * index + 8] = b;
			
			colors[numEntriesPerQuad * index + 9] = r;
			colors[numEntriesPerQuad * index + 10] = g;
			colors[numEntriesPerQuad * index + 11] = b;
			
			index++;
		}
	}
	
	//set the follow position
	cameraCenterPosition.x = (yPos[0] - 0.5);
	cameraCenterPosition.y = (xPos[0] - 0.5);
	
	//set the coordinate to display on the screen
	mapCoords1.x = yPos[0];
	mapCoords1.y = xPos[0];
	
	//change xPos, yPos vectors back to be non-relative to refX, refY
	for(int i = 0; i < numCols; i++) {
		xPos[i] += this->refX;
	}
	for(int j = 0; j < numRows; j++) {
		yPos[j] += this->refY;
	}
	
}

//************************************************************************

void ParticlePlot::setParticles(particleT* currParticles) {
	int i;
	for(i = 0; i < MAX_PARTICLES; i++) {
		particles[i].x = currParticles[i].position[0] - this->refX;
		particles[i].y = currParticles[i].position[1] - this->refY;
		particles[i].z = currParticles[i].position[2];
	}
}

//************************************************************************

/*!
  Appends this x,y position onto the vehicle path
*/
void ParticlePlot::addVehiclePath(const double& x, const double& y) {
	pthread_mutex_lock(&dataLock);
	
	Point2d point;
	point.x = x;
	point.y = y;
	vehiclePath.push_back(point);
	
	pthread_mutex_unlock(&dataLock);
	
}
//************************************************************************

/*!
  Appends this x,y onto the reference path
*/
void ParticlePlot::addReferencePath(const double& x, const double& y) {
	pthread_mutex_lock(&dataLock);
	
	Point2d point;
	point.x = x;
	point.y = y;
	referencePath.push_back(point);
	
	pthread_mutex_unlock(&dataLock);
}

//************************************************************************
/*!
  Set current map boundaries
*/
void ParticlePlot::setMapBoundary(const double& minX, const double& maxX,
								  const double& minY, const double& maxY) {
	mapBoundaries[0].x = minX - this->refX;
	mapBoundaries[0].y = minY - this->refY;
	
	mapBoundaries[1].x = minX - this->refX;
	mapBoundaries[1].y = maxY - this->refY;
	
	mapBoundaries[2].x = maxX - this->refX;
	mapBoundaries[2].y = maxY - this->refY;
	
	mapBoundaries[3].x = maxX - this->refX;
	mapBoundaries[3].y = minY - this->refY;
	
}

//************************************************************************

/*!
  Sets the display string
*/
void ParticlePlot::setString(const std::string& str) {
	pthread_mutex_lock(&dataLock);
	
	displayString = str;
	
	pthread_mutex_unlock(&dataLock);
}

//************************************************************************

/*!
  This function is called when an ascii key is pressed.
  @param key The key that was pressed
  @param x The x location of the cursor when the key was pressed
  @param y The y location of the cursor when the key was pressed
*/
void ParticlePlot::processNormalKeys(unsigned char key, int x, int y) {
	switch(key) {
	
		case 'f':
			//turns on or off followModeFlag
			followPositionFlag = !followPositionFlag;
			
			
			break;
	}
	
	GlutWindow2d::processNormalKeys(key, x, y);
}


//************************************************************************

//*! Draws the axes on the screen
void ParticlePlot::drawAxes() {
	double delta = 100;
	double dist = delta;
	
	double xDelta = 0;
	double yDelta = 0;
	if(followPositionFlag) {
		xDelta = cameraCenterPosition.x;
		yDelta = cameraCenterPosition.y;
	}
	
	glColor3f(0.5, 0.5, 0.5);
	glBegin(GL_LINES);
	
	//*****************************************************
	//draw the horizontal and vertical axes at (0,0)
	glVertex2f(-windowWidth / 2.0 * zoomFactor + panXOffset + xDelta, 0.0);
	glVertex2f(windowWidth / 2.0 * zoomFactor + panXOffset + xDelta, 0.0);
	
	glVertex2f(0.0, -windowHeight / 2.0 * zoomFactor + panYOffset + yDelta);
	glVertex2f(0.0, windowHeight / 2.0 * zoomFactor + panYOffset + yDelta);
	//*****************************************************
	
	//*****************************************************
	//draw the horizontal lines
	glColor3f(0.15, 0.15, 0.15);
	while(dist < windowHeight / 2.0 * zoomFactor + fabs(yDelta) + fabs(panYOffset)) {
		glVertex2f(-windowWidth / 2.0 * zoomFactor + panXOffset + xDelta, -dist);
		glVertex2f(windowWidth / 2.0 * zoomFactor + panXOffset + xDelta, -dist);
		glVertex2f(-windowWidth / 2.0 * zoomFactor + panXOffset + xDelta, dist);
		glVertex2f(windowWidth / 2.0 * zoomFactor + panXOffset + xDelta, dist);
		
		dist += delta;
	}
	
	//draw the vertical lines
	dist = delta;
	while(dist < windowWidth / 2.0 * zoomFactor + fabs(xDelta) + fabs(panXOffset)) {
		glVertex2f(-dist, -windowHeight / 2.0 * zoomFactor + panYOffset + yDelta);
		glVertex2f(-dist, windowHeight / 2.0 * zoomFactor + panYOffset + yDelta);
		glVertex2f(dist, -windowHeight / 2.0 * zoomFactor + panYOffset + yDelta);
		glVertex2f(dist, windowHeight / 2.0 * zoomFactor + panYOffset + yDelta);
		
		dist += delta;
	}
	//******************************************************
	glEnd();
	
}
