//******************************************************************************
//Viewer			-	Program to interface with external FPGA and view
//							oscilloscope traces
//
//By				-	Yi Yao			http://yyao.ca/
//Date				-	2006-01-05
//******************************************************************************

#include "Viewer.h"


void main(int argc, char **argv) {
	InitScope();
	InitVars();

	//Must be called first. It extracts GLUT specific values from argc and argv
	//Updates the argument list to user specific arguments only on function return
	//Arguments taken out are specific to window operating system (X11, Windows, etc)
	//Parse argc and argc only AFTER glutInit is called
	glutInit(&argc, argv);

	//Both Position and Size are suggestions to the OS, they may be ignored
	//X, Y (use -1, -1 for default location)
	glutInitWindowPosition(0, 0);
	//Width, Height
	glutInitWindowSize(640, 480);

	//Pass in arguements for next opened window
	//GLUT_RGBA and GLUT_RGB (identical)
	//	RGBA mode window.
	//	This is the default if neither GLUT_RGBA nor GLUT_INDEX are specified.
	//GLUT_INDEX
	//	Color index mode window.
	//	This overrides GLUT_RGBA if it is also specified.
	//GLUT_SINGLE
	//	Single buffered window.
	//	This is the default if neither GLUT_DOUBLE or GLUT_SINGLE are specified.
	//GLUT_DOUBLE
	//	Double buffered window.
	//	This overrides GLUT_SINGLE if it is also specified. 
	//GLUT_ACCUM
	//	Window with an accumulation buffer. 
	//GLUT_ALPHA
	//	Window with an alpha component to the color buffer(s). 
	//GLUT_DEPTH
	//	Window with a depth buffer. 
	//GLUT_STENCIL
	//	Window with a stencil buffer. 
	//GLUT_MULTISAMPLE
	//	Window with multisampling support.
	//	If multisampling is not available, a non-multisampling window will automatically be chosen.
	//	Note: both the OpenGL client-side and server-side implementations must support the GLX_SAMPLE_SGIS extension for multisampling to be available. 
	//GLUT_STEREO
	//	Use stereo window. 
	//GLUT_LUMINANCE
	//	Window with a ``luminance'' color model.
	//	This model provides the functionality of OpenGL's RGBA color model, but the green and blue components are not maintained in the frame buffer.
	//	Instead each pixel's red component is converted to an index between zero and glutGet(GLUT_WINDOW_COLORMAP_SIZE)-1 and looked up in a per-window color map to determine the color of pixels within the window.
	//	The initial colormap of GLUT_LUMINANCE windows is initialized to be a linear gray ramp, but can be modified with GLUT's colormap routines.
	//	Note: GLUT_LUMINANCE is not supported on most OpenGL platforms.
	glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);

	//Create window with specified title. A handle is returned to the opened window.
	glutCreateWindow("Yi's über cool serial oscilloscope viewer");

	//Register call back function for redrawing window
	//This applies to the current (last created) window
	//Before this function is called, the current window is set to the the main one
	glutDisplayFunc(DrawMainWindow);

	//Register call back function for idle processing
	//Before this funciton is called, the current window is not changed
	glutIdleFunc(IdleProcessing); 

	//Register call back functions for keystroke handling for the current (main) window
	glutKeyboardFunc(HandleNormalKeys);
	glutSpecialFunc(HandleSpecialKeys);

	glEnable(GL_DEPTH_TEST);

	//Call the eternal loop. This function will never return.
	//It will call all registered call back functions as necessary
	glutMainLoop();
};


void DrawMainWindow(void) {
	float	t, x, y, x2, y2;
	int	i;
	char	TextBuff[80];

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	//Save previous settings
	glPushMatrix();

	//Valid shapes for (glBegin) are:
	//	GL_POINTS			individual points
	//	GL_LINES			pairs of vertices interpreted as individual line segments
	//	GL_LINE_STRIP		series of connected line segments
	//	GL_LINE_LOOP		same as above, with a segment added between last and first vertices
	//	GL_TRIANGLES		triples of vertices interpreted as triangles
	//	GL_TRIANGLE_STRIP	linked strip of triangles
	//	GL_TRIANGLE_FAN		linked fan of triangles
	//	GL_QUADS			quadruples of vertices interpreted as four-sided polygons
	//	GL_QUAD_STRIP		linked strip of quadrilaterals
	//	GL_POLYGON			boundary of a simple, convex polygon

	//Draw all samples
	glColor3fv(Ch1TrClr);
	i = 0;
	while ((i < TimeSettings[CurrTimeSetting].SamplesOnScreen) &&
			(i < NumSamples)) {
		x = -1 + 1.5 * (((float) i) / TimeSettings[CurrTimeSetting].SamplesOnScreen);
		y = (Samples[i] + CurrVerticalShift) / (5 * GetVPerDiv());
		glBegin(GL_POINTS);
			glVertex3f(x, y, 0);
		glEnd();
		if (DispLines) {
			x2 = -1 + 1.5 * (((float) i + 1) / TimeSettings[CurrTimeSetting].SamplesOnScreen);
			y2 = (Samples[i + 1] + CurrVerticalShift) / (5 * GetVPerDiv());
			glBegin(GL_LINES);
				glVertex3f(x, y, 0);
				glVertex3f(x2, y2, 0);
			glEnd();
		};
		i++;
	};

	//Draw x-axis
	glColor3fv(GridClr);				glLineWidth(3);
	glBegin(GL_LINES);
		glVertex3f(-1, 0, 0);			glVertex3f(0.5, 0, 0);
	glEnd();
	//Draw y-axis
	glColor3fv(GridClr);				glLineWidth(3);
	glBegin(GL_LINES);
		glVertex3f(-0.25, -1, 0);		glVertex3f(-0.25, 1, 0);
	glEnd();
	//Draw 10x10 grid
	for (t = -0.8; t < 1; t += 0.2) {			//Bottom to top (horizontal lines)
		glColor3fv(GridClr);			glLineWidth(1);
		glBegin(GL_LINES);
			glVertex3f(-1, t, 0);		glVertex3f(0.5, t, 0);
		glEnd();
	};
	for (t = -0.85; t < 0.5; t += 0.15) {		//Left to right (vertical lines)
		glColor3fv(GridClr);			glLineWidth(1);
		glBegin(GL_LINES);
			glVertex3f(t, -1, 0);		glVertex3f(t, 1, 0);
		glEnd();
	};

	//Display time settings
	strcpy(TextBuff, TimeSettings[CurrTimeSetting].Desc);
	strcat(TextBuff, " per div");
	glColor3fv(TextClr);
	WriteText(TextBuff, 0.05, 0.55, 0.9, 0);

	//Display "Paused" if capture is paused
	if (Paused) {
		glColor3fv(PauseClr);
		WriteText("Paused", 0.05, 0.55, 0.8, 0);
	};

	//Display trigger settings
	glColor3fv(TextClr);
	WriteText("Trigger: None", 0.05, 0.55, 0.7, 0);
	sprintf(TextBuff, "%.3fV", (CurrTriggerLevel * CurrAtten));
	WriteText(TextBuff, 0.05, 0.575, 0.6, 0);

	//Display Ch1 settings
	glColor3fv(TextClr);
	WriteText("Channel 1:", 0.06, 0.55, 0.4, 0);
	sprintf(TextBuff, "Atten: %dX", (int) CurrAtten);
	WriteText(TextBuff, 0.05, 0.575, 0.3, 0);
	sprintf(TextBuff, "%.3fV per div", (GetVPerDiv() * CurrAtten));
	WriteText(TextBuff, 0.05, 0.575, 0.2, 0);
	sprintf(TextBuff, "Offset: %.3fV", (CurrVerticalShift * CurrAtten));
	WriteText(TextBuff, 0.05, 0.575, 0.1, 0);

	//Restore previous settings
	glPopMatrix();

	//Puts rendered scene on front buffer
	//glFlush is called by glutSwapBuffers implicitly
	glutSwapBuffers();
};


float GetVPerDiv(void) {
	switch (CurrAmpLevel) {
		case X1:
			return 1;
			break;
		case X2:
			return 0.5;
			break;
		case X5:
			return 0.2;
			break;
		case X10:
			return 0.1;
			break;
		case X20:
			return 0.05;
			break;
		case X50:
			return 0.02;
			break;
		case X100:
			return 0.01;
			break;
		default:
			return 0;
			break;
	};
};


void HandleNormalKeys(unsigned char key, int x, int y) {
	switch (key) {
		case 27:						//ESC - Exit
			exit(0);
			break;
		case 'a':						//a or A - change attenuation
		case 'A':
			switch ((int) CurrAtten) {
				case 1:
					CurrAtten = 10;
					break;
				case 10:
					CurrAtten = 100;
					break;
				case 100:
					CurrAtten = 1;
					break;
				default:
					CurrAtten = 1;
			};
			break;
		case 'l':						//l or L - toggle line drawing
		case 'L':
			DispLines = !DispLines;
			break;
		case 'p':						//p or P - toggel paused status
		case 'P':
			Paused = !Paused;
			if (Paused) {
				MyScope.DisableCapture();
			} else {
				MyScope.EnableCapture();
			};
			break;
		case 's':						//s or S - save data
		case 'S':
			SaveData();
			break;
		case '=':						//= - increase trigger voltage slowly
			CurrTriggerLevel += 0.001;
			MyScope.SetTriggerLevel(CurrTriggerLevel);
			break;
		case '+':						//+ - increase trigger voltage quickly
			CurrTriggerLevel += 0.05;
			MyScope.SetTriggerLevel(CurrTriggerLevel);
			break;
		case '-':						//- - decrease trigger voltage slowly
			CurrTriggerLevel -= 0.001;
			MyScope.SetTriggerLevel(CurrTriggerLevel);
			break;
		case '_':						//_ - decrease trigger voltage quickly
			CurrTriggerLevel -= 0.05;
			MyScope.SetTriggerLevel(CurrTriggerLevel);
			break;
		default:
			break;
	};

	//Disallow trigger voltage settings outside boundaries
	if (CurrTriggerLevel < -5) {
		CurrTriggerLevel = -5;
		MyScope.SetTriggerLevel(CurrTriggerLevel);
	};
	if (CurrTriggerLevel > 5) {
		CurrTriggerLevel = 5;
		MyScope.SetTriggerLevel(CurrTriggerLevel);
	};
};


//Choices for key are:
//	GLUT_KEY_F1
//	GLUT_KEY_F2
//	GLUT_KEY_F3
//	GLUT_KEY_F4
//	GLUT_KEY_F5
//	GLUT_KEY_F6
//	GLUT_KEY_F7
//	GLUT_KEY_F8
//	GLUT_KEY_F9
//	GLUT_KEY_F10
//	GLUT_KEY_F11
//	GLUT_KEY_F12
//	GLUT_KEY_LEFT
//	GLUT_KEY_UP
//	GLUT_KEY_RIGHT
//	GLUT_KEY_DOWN
//	GLUT_KEY_PAGE_UP
//	GLUT_KEY_PAGE_DOWN
//	GLUT_KEY_HOME
//	GLUT_KEY_END
//	GLUT_KEY_INSERT
void HandleSpecialKeys(int key, int x, int y) {
	int	Modifiers = glutGetModifiers();
	int	CtrlPressed = Modifiers & GLUT_ACTIVE_CTRL;
	int	AltPressed = Modifiers & GLUT_ACTIVE_ALT;
	int	ShiftPressed = Modifiers & GLUT_ACTIVE_SHIFT;

	switch (key) {
		case GLUT_KEY_UP:
			if (AltPressed) {			//Greater vertical zoom
				switch (CurrAmpLevel) {
					case X1:
						CurrAmpLevel = X2;
						break;
					case X2:
						CurrAmpLevel = X5;
						break;
					case X5:
						CurrAmpLevel = X10;
						break;
					case X10:
						CurrAmpLevel = X20;
						break;
					case X20:
						CurrAmpLevel = X50;
						break;
					case X50:
						CurrAmpLevel = X100;
						break;
					case X100:
						CurrAmpLevel = X100;
						break;
					default:
						CurrAmpLevel = X1;
						break;
				};
				MyScope.SetAmplification(CurrAmpLevel);
			} else if (ShiftPressed) {	//Shift trace up quickly (decrease vertical offset)
				CurrVerticalShift += 0.05;
				MyScope.SetVerticalShift(CurrVerticalShift);
			} else {					//Shift trace up slowly (decrease vertical offset)
				CurrVerticalShift += 0.001;
				MyScope.SetVerticalShift(CurrVerticalShift);
			};
			break;
		case GLUT_KEY_DOWN:
			if (AltPressed) {			//Less vertical zoom
				switch (CurrAmpLevel) {
					case X1:
						CurrAmpLevel = X1;
						break;
					case X2:
						CurrAmpLevel = X1;
						break;
					case X5:
						CurrAmpLevel = X2;
						break;
					case X10:
						CurrAmpLevel = X5;
						break;
					case X20:
						CurrAmpLevel = X10;
						break;
					case X50:
						CurrAmpLevel = X20;
						break;
					case X100:
						CurrAmpLevel = X50;
						break;
					default:
						CurrAmpLevel = X1;
						break;
				};
				MyScope.SetAmplification(CurrAmpLevel);
			} else if (ShiftPressed) {	//Shift trace down quickly (increase vertical offset)
				CurrVerticalShift -= 0.05;
				MyScope.SetVerticalShift(CurrVerticalShift);
			} else {					//Shift trace up slowly (increase vertical offset)
				CurrVerticalShift -= 0.001;
				MyScope.SetVerticalShift(CurrVerticalShift);
			};
			break;
		case GLUT_KEY_LEFT:
			if (AltPressed) {			//Zoom out horizontally
				CurrTimeSetting--;
			} else if (ShiftPressed) {	//Shift trace left quickly (decrease horizontal offset)
			} else {					//Shift trace left slowly (decrease horizontal offset)
			};
			break;
		case GLUT_KEY_RIGHT:
			if (AltPressed) {			//Zoom in horizontally
				CurrTimeSetting++;
			} else if (ShiftPressed) {	//Shift trace right quickly (increase vertical offset)
			} else {					//Shift trace right slowly (increase vertical offset)
			};
			break;
		default:
			break;
	};

	//Disallow vertical shift settings outside boundaries
	if (CurrVerticalShift < -5) {
		CurrVerticalShift = -5;
		MyScope.SetVerticalShift(CurrVerticalShift);
	};
	if (CurrVerticalShift > 5) {
		CurrVerticalShift = 5;
		MyScope.SetVerticalShift(CurrVerticalShift);
	};

	//Disallow time zoom settings from going out of bounds
	if (CurrTimeSetting < 0) {
		CurrTimeSetting = 0;
	};
	if (CurrTimeSetting >= TimeSettingsCount) {
		CurrTimeSetting = TimeSettingsCount - 1;
	};
};


void IdleProcessing(void) {
	char	Input[MaxSamples];
	int	BytesRead;
	int	BytesToDiscard;
	int	i;
	float	val;

	//Read in data
	BytesRead = MyScope.ReadData(Input, MaxSamples);

	//If data was recieved, process it
	if (BytesRead) {
/*		//Calculate how many bytes should be discarded to make space for new ones
		BytesToDiscard = BytesRead + NumSamples - MaxSamples;
		if (BytesToDiscard > 0) {		//If there's not enough space in the read buffer to add new values
			for (i = 0; i < BytesToDiscard; i++) {		//	then discard the old
				Samples[i] = Samples[BytesToDiscard + i];
			};
			NumSamples = MaxSamples - BytesToDiscard;	//Set new offset
		};
		//Calculate new values
		for (i = 0; i < BytesRead; i++) {
			val = Input[i];
			if (val < 0) {				//Convert signed char to unsigned char
				val += 256;
			};
			Samples[NumSamples + i] = (val / 25.5) * GetVPerDiv() - CurrVerticalShift;
		};
		NumSamples += BytesRead;
*/
		//Keep reading data in until display buffer is full
		if (NumSamples + BytesRead < MaxSamples) {
			for (i = 0; i < BytesRead; i++) {
				val = Input[i];
				if (val < 0) {				//Convert signed char to unsigned char
					val += 256;
				};
				Samples[NumSamples + i] = (((float) 2)/51)*GetVPerDiv()*((float) val)-5*GetVPerDiv()-CurrVerticalShift;
			};
			NumSamples += BytesRead;
		//Once display buffer is full, discard old data and append with new
		} else {
			//Calculate how many samples to discard
			BytesToDiscard = (NumSamples + BytesRead) - MaxSamples;
			//Discard old data
			for (i = 0; i < NumSamples - BytesToDiscard; i++) {
				Samples[i] = Samples[BytesToDiscard + i];
			};
			//Calculate and record new data
			for (i = 0; i < BytesRead; i++) {
				val = Input[i];
				if (val < 0) {				//Convert signed char to unsigned char
					val += 256;
				};
				Samples[MaxSamples - BytesRead + i] = (((float) 2)/51)*GetVPerDiv()*((float) val)-5*GetVPerDiv()-CurrVerticalShift;
				if (TimeSettings[CurrTimeSetting].SamplesOnScreen != 8000) {
					SaveData();
				};
			};
			NumSamples = MaxSamples;
		};
	};

	DrawMainWindow();
};


void InitScope(void) {
	MyScope.OpenConnection(2);
	MyScope.DisableCapture();
	MyScope.EnableCapture();
	MyScope.SetVerticalShift(CurrVerticalShift);
	MyScope.SetTriggerLevel(CurrTriggerLevel);
	MyScope.SetAmplification(CurrAmpLevel);
};


void InitVars(void) {
	int	i;
	char	*Desc;
	int		SamplesDisplayed;

	for (i = 0; i < MaxSamples; i++) {
		Samples[i] = sin(i / 400.0f);
	};
	NumSamples = MaxSamples;

	for (i = 0; i < TimeSettingsCount; i++) {
		switch (i) {
			case 0:
				Desc = "1ms";
				SamplesDisplayed = 80;
				break;
			case 1:
				Desc = "2ms";
				SamplesDisplayed = 160;
				break;
			case 2:
				Desc = "5ms";
				SamplesDisplayed = 400;
				break;
			case 3:
				Desc = "10ms";
				SamplesDisplayed = 800;
				break;
			case 4:
				Desc = "20ms";
				SamplesDisplayed = 1600;
				break;
			case 5:
				Desc = "50ms";
				SamplesDisplayed = 4000;
				break;
			case 6:
				Desc = "100ms";
				SamplesDisplayed = 8000;
				break;
			default:
				Desc = "unknown";
				SamplesDisplayed = 0;
				break;
		};
		strcpy(TimeSettings[i].Desc, Desc);
		TimeSettings[i].SamplesOnScreen = SamplesDisplayed;
	};
};


void SaveData(void) {
};


void WriteText(char *Text, float Height, float x, float y, float z) {
	const float	CharHeight = 119.05;

	glPushMatrix();

	glTranslatef(x, y, z);
	glScalef(Height / CharHeight / 1.5, Height / CharHeight, Height / CharHeight);

	while (*Text) {
		glutStrokeCharacter(GLUT_STROKE_MONO_ROMAN, (int) *Text);
		Text++;
	};

	glPopMatrix();
};
