/*
	This heading must remain intact at all times.
	Copyright (c) 2009 Mark Mason. All rights reserved.

	File:	Q-Cogo-Misc.js
	Use:	To provide miscellaneous operations for Q-Cogo, <http://www.q-cogo.com/>.
	Ver:	1.1 (Beta)

	Created by Mark Mason. Latest version available from <http://www.q-cogo.com/>.



	This file is part of Q-Cogo.
	
	Q-Cogo is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.
	
	Q-Cogo is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	
	You should have received a copy of the GNU General Public License
	along with Q-Cogo.  If not, see <http://www.gnu.org/licenses/>.



	FUNCTION LISTING

	GetPointList
	SameCoords
	SamePoint
	ParseDMS
	ParsePoints
	CheckDecimal
	CheckPoint
	CheckBlank
	FormatDMS
	FormatDecimal
	FormatGrade
	FormatStn
	FormatString
	FormatPtList
	SetPoint
	AddPoint
	SortPoints
	OutputPoints
	DeletePoint
	RewritePoints
	OutputCogo
	Round
*/



// *******************************************************************************************************************************

function GetPointList(PtList, Multiple, Radial) {

// Gets a matrix of points as requested by a '-' and ',' separated list string, or a single point request
// Input:  Point list, '-' and ',' separated, multiple points flag (1 to allow many points), radial points flag (1 if list includes radial points)
// Output: The resulting points matrix or the single point

	var check = 0;

// Parse the existing points file contents into a points matrix and eliminate spaces

	var pointsMatrix = ParsePoints();

	PtList = PtList.replace(/\s+/g,'');

// If all contents indicated, return all existing points in their current order

	if (PtList == '*.*' && Multiple) {

		PtList = new Array(0);

		for (i=0; i<pointsMatrix.length - 1; i++) {

			PtList[i] = pointsMatrix[i];

		}

		return (PtList);

	}

	PtList = PtList.split(',');

// If more than one point indicated, separate string into array of single points and point ranges

	if (Multiple) {

// Format every element in point list to match values in point records, parse ranges and replace with point lists

		var Range = 0;
		var k = 0;
		var Index1 = -1;
		var Index2 = -1;
		var Asterisk = '';

		for (var j=0; j<PtList.length; j++) {

			if (PtList[j].search(/\-/) < 0) {

				PtList[j] = ((PtList[j]) ? FormatString(PtList[j], 1, Radial) : 'Error_one_or_more_points_contain_null_values');

			}

			else {

				Range = PtList[j].split('-');

// Replace empty range values with empty string flag (much longer string than legal point)

				Range[0] = ((Range[0]) ? FormatString(Range[0], 1, Radial) : 'Error_one_or_more_points_contain_null_values');
				Range[1] = ((Range[1]) ? FormatString(Range[1], 1, Radial) : 'Error_one_or_more_points_contain_null_values');

				Range.sort(sortRange);
				Range.sort(sortRangeNumeric);

				pointsMatrix.sort(sortByPt);
				pointsMatrix.sort(sortByPtNumeric);

				for (k=0; k<pointsMatrix.length; k++) {

					if (Range[0].replace(/\*/g,'') == pointsMatrix[k][0]) {

						Index1 = k;

					}

					else if (Range[1].replace(/\*/g,'') == pointsMatrix[k][0]) {

						Index2 = k;

					}

				}

				PtList.splice(j, 1, Range[0]);

				if (Index1 > -1 && Index2 > -1) {

					for (k=Index1+1; k<Index2; k++) {

						PtList.splice(j + 1, 0, pointsMatrix[k][0]);
						j++;

					}

				}

				PtList.splice(j + 1, 0, Range[1]);
				j++;

			}

		}

	}

// Eliminate duplicates / empty strings, and get point for every element in point list

	for (var j=0; j<PtList.length; j++) {

		var PreAsterisk = '';
		var PostAsterisk = '';

		if (PtList[j] == 'Error_one_or_more_points_contain_null_values') {

			PtList.splice(j, 1);
			j--;

		}

		else {

			for (var i=j; i<PtList.length; i++) {

				if (i != j && PtList[i] == PtList[j] && PtList[i].search(/\*/) < 0) {

					PtList.splice(i, 1);
					i--;
				
				}

			}

			check = 0;

			for (i=0; i<pointsMatrix.length; i++) {

				if ((pointsMatrix[i][0] == PtList[j].replace(/\*/g,'') && Radial) || (pointsMatrix[i][0] == PtList[j])) {

					if (PtList[j].search(/\*/) == PtList[j].length - 1) {

						PostAsterisk = '*';

					}

					if (PtList[j].search(/\*/) == 0) {

						PreAsterisk = '*';

					}

					check = 1;
					PtList[j] = [pointsMatrix[i][0], pointsMatrix[i][1], pointsMatrix[i][2], pointsMatrix[i][3], pointsMatrix[i][4]];
					PtList[j][0] = PreAsterisk + PtList[j][0] + PostAsterisk;

					break;

				}

			}

// Alert and return error if point not found

			if (!check) {

				if (Radial) {

					alert('Point \"' + PtList[j].replace(/\*/g,'') + '\" not found!');

				}

				else {

					alert('Point \"' + PtList[j] + '\" not found!');

				}

				return check;

			}

		}

	}

	return ((Multiple) ? PtList : PtList[0]);

}

// *******************************************************************************************************************************

function SameCoords(Pt1, Pt2, Error) {

// Checks to see if two given points have the same northing and easting
// Input:  The two point vectors to check
// Output: 0 if the points are different, 1 if they are identical, plus the appropriate alert boxes

	var check = 0;

	if (Pt1[1] == Pt2[1] && Pt1[2] == Pt2[2]) {

		alert(Error + ' points have identical northings and eastings!');
		check = 1;

	}

	return check;

}

// *******************************************************************************************************************************

function SamePoint(Pt1, Pt2, Error) {

// Checks to see if two given points are the same
// Input:  The two point names to check, and the error message to display
// Output: 0 if the points are different, 1 if they are identical, plus the appropriate alert boxes

	var check = 0;

	if (Pt1 == Pt2) {

		alert(Error + ' points are identical!');
		check = 1;

	}

	return check;

}

// *******************************************************************************************************************************

function ParseDMS(Value) {

// Changes a DMS angle (DDD.MMSSSS...) into a radian angle suitable for calculations
// Input:  DMS angle of the form DDD.MMSSSS...
// Output: The radian angle, or X if it cannot be parsed, plus appropriate alert boxes

	var check = 1;
	var negative = 0;

// Check that input is a number

	if (isNaN(parseFloat(Value))) {

		check = 'X';		
		alert('DMS angles must be numbers of the form DDD.MMSS (Many Seconds digits allowed)');
		return check;

	}

	Value = parseFloat(Value);

// If value is negative, set to positive and flag as negative

	if (Value < 0) {

		Value = -1 * Value;
		negative = 1;

	}

	var D = Math.floor(Value);
	var M = Math.floor(Round((Value - D) * 100, 10));
	var S = (Value - D - M / 100) * 10000;

// Check that user entered minutes and seconds are less than 60

	if (M >= 60 || S >= 60) {

		check = 'X';		
		alert('DMS minutes and seconds must be less than 60');
		return check;

	}

	Value = D + (M / 60) + (S / 3600);
	Value = Value * Math.PI / 180;

// If value is flagged as negative, set to negative

	if (negative) {

		Value = -1 * Value;

	}

	return Value;

}

// *******************************************************************************************************************************

function ParsePoints() {

// Converts the standard input file from the GUI into a matrix of points
// Input:  None
// Output: 2D matrix of points and lines

// Get points contents from text box

	var pointsContents = document.getElementById('pointsContents').value;

// Remove "\n" and multiple spaces from Points Contents and split into a vector

	pointsContents = pointsContents.replace(/\n/g,'');
	pointsContents = pointsContents.replace(/\s+/g,' ');
	var pointsVector = pointsContents.split(' ');

// Find length of points matrix, and initialize
// Initializing vector length will speed up handling of larger vectors

	var lP = (pointsVector.length - 1) / 5;
	var pointsMatrix = new Array(lP);

// Read data into 2D matrix

	for (var i=0; i<=lP; i++) {

		var tempPoint = new Array(5);
		tempPoint[0] = pointsVector[0 + i*5];
		tempPoint[1] = pointsVector[1 + i*5];
		tempPoint[2] = pointsVector[2 + i*5];
		tempPoint[3] = pointsVector[3 + i*5];
		tempPoint[4] = pointsVector[4 + i*5];

		pointsMatrix[i] = tempPoint;

	}

// Return 2D matrix

	return pointsMatrix;

}

// *******************************************************************************************************************************

function CheckDecimal(Value) {

// Checks that a decimal input can be used
// Input:  Original value
// Output: 1 if OK, 0 if not, and the appropriate alert boxes

	var check = 1;

	if (isNaN(parseFloat(Value))) {

		check = 0;		
		alert('Distances, coordinates and scale factors must be numbers');

	}

	return check;

}

// *******************************************************************************************************************************

function CheckPoint(Value) {

// Checks that a point name is suitable (no commas, spaces, or dashes) and is not already in use
// Input:  Original value
// Output: 1 if OK, 0 if not, and the appropriate confirm box

	var check = 1;
	var replace = 0;

// Check and alert if name contains illegal characters

	if (Value.search(/\,/) >= 0 || Value.search(/\-/) >= 0 || Value.search(/\*/) >= 0) {

		check = 0;
		alert('Point names must not contain commas, asterisks or dashes');

	}

	if (check) {

// Format input to match values in point records

		Value = FormatString(Value, 1, 0);

// Get point records matrix

		var pointsMatrix = ParsePoints();

// Check all point names against new value

		for (var i=0; i<pointsMatrix.length; i++) {

			if (pointsMatrix[i][0] == Value) {

// If point found, ask user before editing the point

				var N = pointsMatrix[i][1];
				var E = pointsMatrix[i][2];
				var Z = pointsMatrix[i][3];
				N = FormatDecimal(N, 0);
				N = N.replace(/\s+/g,'');
				E = FormatDecimal(E, 0);
				E = E.replace(/\s+/g,'');
				Z = FormatDecimal(Z, 0);
				Z = Z.replace(/\s+/g,'');

				var Message = 'Point \"' + pointsMatrix[i][0] + '\" exists!\n\n';
				Message += '     N:\t\t' + N + '\n';
				Message += '     E:\t\t' + E + '\n';
				Message += '     Z:\t\t' + Z + '\n';
				Message += '     Desc:\t\t' + pointsMatrix[i][4] + '\n\n';
				Message += 'Edit this point?';

				check = confirm(Message);

				if (check == 1) {

					DeletePoint(Value);

				}

			}

		}

	}

	return check;

}

// *******************************************************************************************************************************

function CheckBlank(Value, errorString) {

// Checks that a field isn't blank
// Input:  Field Value
// Output: 1 if OK, 0 if not, and the appropriate alert box

	var check = 1;

	if (!Value) {

		check = 0;		
		alert('Enter a' + errorString);

	}

	return check;

}

// *******************************************************************************************************************************

function FormatDMS(Value) {

// Converts given radian string into a formatted DMS value (no standard width)
// Input:  Original radian string
// Output: DMS formatted string

// Change string into float DD value

	Value = parseFloat(Value);
	Value = Value * 180 / Math.PI;

// Ensure value is between 0 and 360

	while (Value < 0) {

		Value += 360;

	}

	Value = Value % 360;

// Find D, M, and S (S to specified precision)

	var valuePrecision = document.getElementById('APrecision').value;
	valuePrecision = parseInt(valuePrecision);
	var valueWidth = 8 + valuePrecision;
	
	D = Math.floor(Value);
	M = Math.floor((Value - D) * 60);
	S = (Value - D - M / 60) * 3600;
	S = Round(S, valuePrecision);

// Ensure mimutes and seconds are less than 60
// Ensure degrees are between 0 and 360

	if (S == 60) {

		M += 1;
		S = 0;

	}

	if (M == 60) {

		D += 1;
		M = 0;

	}

	D = D % 360;

// Add leading spaces to convert unit to uniform spacing

	D = D + '';
	
	for (var i=D.length; i<=3; i++) {

		D = ' ' + D;

	}

// Add leading zero to minutes (if needed)

	M = M + '';

	if (M.length < 2) {

		M = '0' + M;

	}

// Add leading zero (if needed)
// Add decimal (if needed) and add trailing zeros to meet working precision

	S = S + '';

	if (S.search(/\./) < 0) {

		S = S + '.';

	}

	if (S.search(/\./) < 2) {

		S = '0' + S;

	}

	SZeros = S.length - S.search(/\./);

	for (var j=SZeros; j<=valuePrecision; j++) {

		S = S + '0';

	}

	return D + '\u00B0' + M + '\'' + S + '\"';

}

// *******************************************************************************************************************************

function FormatArea(Value) {

// Converts given area string into a standard width value
// Input:  Original value
// Output: Formatted value

// Set width of columns and working number precision

	var valuePrecision = parseInt(document.getElementById('AreaPrecision').value);

// Round value to working precision

	Value = parseFloat(Value);
	Value = Round(Value, valuePrecision);

// Add decimal (if needed) and add trailing zeros to meet working precision

	Value = Value + '';

	if (Value.search(/\./) < 0) {

		Value = Value + '.';

	}

	valZeros = Value.length - Value.search(/\./);

	for (var j=valZeros; j<=valuePrecision; j++) {

		Value = Value + '0';

	}

	return Value + ' sq.';

}

// *******************************************************************************************************************************

function FormatDecimal(Value, Precise) {

// Converts given decimal string into a standard width value
// Input:  Original value, Precise switch (0 for rounded value, 0 for 10 decimal place precise value)
// Output: Formatted value

// Set width of columns and working number precision

	var valuePrecision = 10;

	if (!Precise) {

		valuePrecision = parseInt(document.getElementById('DPrecision').value);

	}

	var valueWidth = 8 + valuePrecision;

// Round value to working precision

	Value = parseFloat(Value);
	Value = Round(Value, valuePrecision);

// Add decimal (if needed) and add trailing zeros to meet working precision

	Value = Value + '';

	if (Value.search(/\./) < 0) {

		Value = Value + '.';

	}

	valZeros = Value.length - Value.search(/\./);

	for (var j=valZeros; j<=valuePrecision; j++) {

		Value = Value + '0';

	}

// Add enough spaces to value to create uniform length value

	valSize = Value.length;

	for (var i=valSize; i<=valueWidth; i++) {

		Value = ' ' + Value;

	}

	return Value;

}

// *******************************************************************************************************************************

function FormatGrade(Rise, Run) {

// Converts given rise and run into a standard width % value
// Input:  Rise and run of slope
// Output: Formatted grade

// Set width of columns and working number precision

	var valuePrecision = parseInt(document.getElementById('GPrecision').value);

	var valueWidth = 7 + valuePrecision;

// Round values to working precision

	Rise = parseFloat(Rise);
	Run = parseFloat(Run);
	var Grade = 100 * Rise / Run;
	Grade = Round(Grade, valuePrecision);

	if (Grade > 10000 || Grade < -10000 || isNaN(Grade)) {

		Grade = '      N/A';
		return Grade;

	}

	else {

// Add decimal (if needed) and add trailing zeros to meet working precision

		Grade = Grade + '';

		if (Grade.search(/\./) < 0) {

			Grade = Grade + '.';

		}

		valZeros = Grade.length - Grade.search(/\./);

		for (var j=valZeros; j<=valuePrecision; j++) {

			Grade = Grade + '0';

		}

// Add enough spaces to value to create uniform length value

		valSize = Grade.length;

		for (var i=valSize; i<=valueWidth; i++) {

			Grade = ' ' + Grade;

		}

		return Grade + '%';

	}

}

// *******************************************************************************************************************************

function FormatStn(Value) {

// Converts given stationing string into a value of the form 1 + 234.567 (to specified precision)
// Input:  Value to be formatted
// Output: Formatted value

// Round value to current working precision and remove spaces

	Value = FormatDecimal(Value, 0);
	Value = Value.replace(/\s+/g,'');

// Format as a standard stationing value

	var Sep = ' + ';

	if (Value.search(/\-/) >= 0) {

		Value = Value.substring(1);
		Sep = ' - ';

	}

	var DP = Value.search(/\./);

	if (DP > 2) {

		var Val1 = Value.substring(0, DP - 2);
		var Val2 = Value.substring(DP - 2);

		Value = Val1 + Sep + Val2;

	}

	else if (DP <= 2) {

		for (var i=DP; i<2; i++) {

			Value = '0' + Value;

		}

		Value = '0' + Sep + Value;

	}

	return Value;

}

// *******************************************************************************************************************************

function FormatString(Value, Short, Radial) {

// Converts given string into a standard width value
// Input:  Original value, short flag (1 for short no-spaces output), radial flag (1 to allow extra '*' character at start / end)
// Output: New value

// Check for radial symbol

	var PreAsterisk = '';
	var PostAsterisk = '';

	if (Radial && Value.search(/\*/) == Value.length - 1) {

		Value = Value.replace(/\*/g,'');
		PostAsterisk = '*';

	}

	else if (Radial && Value.search(/\*/) == 0) {

		Value = Value.replace(/\*/g,'');
		PreAsterisk = '*';

	}

// Set width of columns and truncate value if necessary

	var valueWidth = 8;
	Value = Value.replace(/\s+/g,'_');
	Value = Value.substring(0, valueWidth);
	Value = PreAsterisk + Value + PostAsterisk;

// If short flag not set, add enough spaces to value to create uniform length value

	if (!Short) {

		for (var i=Value.length; i<=valueWidth; i++) {

			Value = Value + ' ';

		}

	}

	return Value;

}

// *******************************************************************************************************************************

function FormatPtList(PtList, Sort) {

// Converts given point list into a "," and "-" separated string ("-" denotes all points used between sorted points matrix elements)
// Input:  Point list to format, sort flag (1 to provide sorted output, 0 to leave point order as is)
// Output: Formatted point list

// Load points matrix and remove blank row, then sort

	var pointsMatrix = ParsePoints();
	var lP = pointsMatrix.length - 2;
	pointsMatrix.splice(lP + 1, 1);
	pointsMatrix.sort(sortByPt);
	pointsMatrix.sort(sortByPtNumeric);

	var lL = PtList.length - 1;

// If sorted output required, sort point list

	if (Sort) {

		PtList.sort(sortByPt);
		PtList.sort(sortByPtNumeric);

	}

// Make array of point list element occurences in points matrix

	var PtIndex = new Array(lL);

	for (var i=0; i<=lL; i++) {

		for (var j=0; j<=lP; j++) {

			if (PtList[i][0].replace(/\*/g,'') == pointsMatrix[j][0]) {

				PtIndex[i] = j;
				break;

			}

		}

	}

// Produce formatted string, separating any radial points

	var i1 = 0;
	var i2 = 0;
	var PtString = '';

	while (i1 <= lL) {

		for (i=i1; i<=lL; i++) {

			i2 = i;

			if (i2 - i1 != PtIndex[i2] - PtIndex[i1] || PtList[i2][0].search(/\*/) >= 0) {

				break;

			}

		}

		if (i2 < lL) {

			PtString += ((i2 - i1 > 2) ? PtList[i1][0] + '-' + PtList[i2 - 1][0] : (i2 - i1 == 2) ? PtList[i1][0] + ',' + PtList[i2 - 1][0] : PtList[i1][0]);

			PtString += ',';

		}

		else {

			PtString += (

(i2 - i1 > 2 && (PtIndex[i2] - PtIndex[i2 - 1] != 1 || PtList[i2][0].search(/\*/) >= 0)) ? PtList[i1][0] + '-' + PtList[i2 - 1][0] + ',' + PtList[i2][0]

 : (i2 - i1 > 1 && (PtIndex[i2] - PtIndex[i2 - 1] != 1 || PtList[i2][0].search(/\*/) >= 0)) ? PtList[i1][0] + ',' + PtList[i2 - 1][0] + ',' + PtList[i2][0]

 : (i2 - i1 > 1 && PtIndex[i2] - PtIndex[i2 - 1] == 1) ? PtList[i1][0] + '-' + PtList[i2][0]

 : (i2 - i1 == 1) ? PtList[i1][0] + ',' + PtList[i2][0]

 : PtList[i2][0]);

			break;

		}

		if (PtList[i2][0].search(/\*/) < 0) {

			i1 = i;

		}

		else {

			if (i2 > 0) {

				PtString += PtList[i2][0] + ',';

			}

			i1 = i + 1;

		}

	}

	return PtString;

}

// *******************************************************************************************************************************

function SetPoint() {

// Takes a point from the point input boxes and passes it to the AddPoint function
// Input:  None
// Output: The values of the point summary text boxes (1 hidden, 1 display), through the AddPoint function

// Find the contents of the input text boxes and existing file

	var Pt = document.getElementById('Pt').value;
	var N = document.getElementById('N').value;
	var E = document.getElementById('E').value;
	var Z = document.getElementById('Z').value;
	var Desc = document.getElementById('Desc').value;

// Check that field aren't blank

	if (!CheckBlank(Pt, ' point name') || !CheckBlank(N, ' northing') || !CheckBlank(E, 'n easting') || !CheckBlank(Z, 'n elevation') || !CheckBlank(Desc, ' point description'))  {

		return;

	}

// Check that inputs are acceptable, then add point

	if (CheckDecimal(N) && CheckDecimal(E) && CheckDecimal(Z) && CheckPoint(Pt)) {

		AddPoint(Pt, N, E, Z, Desc);

	}

}

// *******************************************************************************************************************************

function AddPoint(Pt, N, E, Z, Desc) {

// Adds a new point to the point summary box, through OutputPoints
// Input:  Point, northing, easting, elevation, description
// Output: The new contents of the text boxes (1 hidden, 1 display), including the new point

// Parse the existing points file contents into a points matrix

	var pointsMatrix = ParsePoints();

// Add the new point to the points matrix
 
	lP = pointsMatrix.length-1;
	pointsMatrix[lP] = [Pt, N, E, Z, Desc];

// Clear point sorting labels

	ClearSort();

// Output the information from the 2D matrix back into the text boxes in the GUI

	OutputPoints(pointsMatrix, lP);

}

// *******************************************************************************************************************************

function ClearSort() {

// Clears the point sorting indicators so none are displayed
// Input:  None
// Output: The cleared point indicators

	var Titles = ['Pt', 'N', 'E', 'Z', 'Desc'];
	var Objects = [document.getElementById('SortPt'), document.getElementById('SortN'), document.getElementById('SortE'), document.getElementById('SortZ'), document.getElementById('SortDesc')];

	for (var l=0; l<=4; l++) {

		Objects[l].innerHTML = Titles[l];

	}

}

// *******************************************************************************************************************************

function SortPoints(j) {

// Sorts the points areas (1 hidden, 1 display) by the label specified
// Input:  column, from 0 to 4
// Output: The updated points listing

// Create arrays of titles and title objects

	var Titles = ['Pt', 'N', 'E', 'Z', 'Desc'];
	var Objects = [document.getElementById('SortPt'), document.getElementById('SortN'), document.getElementById('SortE'), document.getElementById('SortZ'), document.getElementById('SortDesc')];

// Change title to ascending or descending as appropriate

	if (Objects[j].innerHTML == Titles[j]) {

		for (var l=0; l<=4; l++) {

			Objects[l].innerHTML = Titles[l];

		}

		Objects[j].innerHTML = Titles[j] + ' <span class="super">v</span>';

	}

	else if (Objects[j].innerHTML.search(/v/) > 0) {

		Objects[j].innerHTML = Titles[j] + ' <span class="normal">^</span>';

	}

	else if (Objects[j].innerHTML.search(/\^/) > 0) {

		Objects[j].innerHTML = Titles[j] + ' <span class="super">v</span>';

	}

// Load points matrix and remove blank row

	var pointsMatrix = ParsePoints();
	var lP = pointsMatrix.length - 2;
	pointsMatrix.splice(lP + 1, 1);

// Sort points by specified column, separating and sorting numerals in the case of point name or description

	switch (j) {

		case 0:

			pointsMatrix.sort(sortByPt);
			pointsMatrix.sort(sortByPtNumeric);
			break;

		case 1:

			pointsMatrix.sort(sortByN);
			break;

		case 2:

			pointsMatrix.sort(sortByE);
			break;

		case 3:

			pointsMatrix.sort(sortByZ);
			break;

		case 4:

			pointsMatrix.sort(sortByDesc);
			pointsMatrix.sort(sortByDescNumeric);
			break;

		default:
			return;

	}

// If column is to be sorted descending, reverse order of point matrix

	if (Objects[j].innerHTML.search(/\^/) > 0) {

		pointsMatrix = pointsMatrix.reverse();

	}

// Output and sketch points

	OutputPoints(pointsMatrix, lP);

}

// The following functions help to sort the 2D points matrix by its appropriate columns through the sort() method

function sortRange(a, b) {
	var x = a.toLowerCase();
	var y = b.toLowerCase();
	return ((x < y) ? -1 : ((x > y) ? 1 : 0));
}

function sortRangeNumeric(a, b) {
	var x = parseFloat(a);
	var y = parseFloat(b);
	return ((x < y) ? -1 : ((x > y) ? 1 : 0));
}

function sortByPt(a, b) {
	var x = a[0].toLowerCase();
	var y = b[0].toLowerCase();
	return ((x < y) ? -1 : ((x > y) ? 1 : 0));
}

function sortByPtNumeric(a, b) {
	var x = parseFloat(a[0]);
	var y = parseFloat(b[0]);
	return ((x < y) ? -1 : ((x > y) ? 1 : 0));
}

function sortByN(a, b) {
	var x = parseFloat(a[1]);
	var y = parseFloat(b[1]);
	return ((x < y) ? -1 : ((x > y) ? 1 : 0));
}

function sortByE(a, b) {
	var x = parseFloat(a[2]);
	var y = parseFloat(b[2]);
	return ((x < y) ? -1 : ((x > y) ? 1 : 0));
}

function sortByZ(a, b) {
	var x = parseFloat(a[3]);
	var y = parseFloat(b[3]);
	return ((x < y) ? -1 : ((x > y) ? 1 : 0));
}

function sortByDesc(a, b) {
	var x = a[4].toLowerCase();
	var y = b[4].toLowerCase();
	return ((x < y) ? -1 : ((x > y) ? 1 : 0));
}

function sortByDescNumeric(a, b) {
	var x = parseFloat(a[4]);
	var y = parseFloat(b[4]);
	return ((x < y) ? -1 : ((x > y) ? 1 : 0));
}

// *******************************************************************************************************************************

function OutputPoints(pointsMatrix, lP) {

// Outputs given matrix to point summary textbox
// Input:  Points matrix, length of points matrix
// Output: The new contents of the text boxes (1 hidden, 1 display)

// Update hidden precise values textbox

	var pointsString = '';

	for (var i=0; i<=lP; i++) {

		pointsString += FormatString(pointsMatrix[i][0], 0, 0) + ' ';
		pointsString += FormatDecimal(pointsMatrix[i][1], 1) + ' ';
		pointsString += FormatDecimal(pointsMatrix[i][2], 1) + ' ';
		pointsString += FormatDecimal(pointsMatrix[i][3], 1) + '      ';
		pointsString += FormatString(pointsMatrix[i][4], 0, 0) + ' \n';

	}

	PointsField = document.getElementById('pointsContents');
	PointsField.value = pointsString;

// Update display values textbox

	pointsString = '';

	for (i=0; i<=lP; i++) {

		pointsString += FormatString(pointsMatrix[i][0], 0, 0) + ' ';
		pointsString += FormatDecimal(pointsMatrix[i][1], 0) + ' ';
		pointsString += FormatDecimal(pointsMatrix[i][2], 0) + ' ';
		pointsString += FormatDecimal(pointsMatrix[i][3], 0) + '      ';
		pointsString += FormatString(pointsMatrix[i][4], 0, 0) + ' \n';

	}

	pointsString += ' ';

	PointsField = document.getElementById('pointsContentsCopy');
	PointsField.value = pointsString;
	PointsField.scrollTop = PointsField.offsetWidth - PointsField.clientHeight;

// Sketch resulting points

	SketchPoints();

}

// *******************************************************************************************************************************

function DeletePoint(PtList) {

// Deletes the requested point from the point summary text box
// Input:  Point list
// Output: The updated point list, or an alert box if the point list is invalid (through GetPointList)

// If no value passed, get input value from textbox

	var DeleteInput = 0;

	if (!PtList) {

		var PtList = document.getElementById('Delete').value;
		DeleteInput = 1;

	}

// Check to make sure field isn't blank

	if (!CheckBlank(PtList, ' list of points to delete'))  {

		return;

	}

// Get list of points to delete

	if (!(PtList = GetPointList(PtList, 1, 0))) {

		return;

	}

// Get point records matrix

	var pointsMatrix = ParsePoints();

// Check all point names in list against existing points matrix and delete lines when found

	for (var j=0; j<PtList.length; j++) {

		for (var i=0; i<pointsMatrix.length; i++) {

			if (pointsMatrix[i][0] == PtList[j][0]) {

				pointsMatrix.splice(i, 1);

			}

		}

	}

	var Plural = ((PtList.length > 1) ? 's' : '');

	check = ((DeleteInput) ? confirm(PtList.length + ' point' + Plural + ' will be deleted!\n\nDelete point' + Plural + '?') : 1);

	if (check == 1) {

		OutputPoints(pointsMatrix, pointsMatrix.length-2);

			if (DeleteInput) {

				document.getElementById('Delete').value = '';

			}

	}

}

// *******************************************************************************************************************************

function RewritePoints() {

// Refreshes the points textbox to reflect updated point precision
// Input:  None
// Output: Refreshed points listing

// Parse the existing points file contents into a points matrix

	var pointsMatrix = ParsePoints(); 
	lP = pointsMatrix.length-2;

// Output the information from the 2D matrix back into the text boxes in the GUI

	OutputPoints(pointsMatrix, lP);

}

// *******************************************************************************************************************************

function OutputCogo(Results, Log) {

// Adds the results of any type of cogo operation to the appropriate log
// Input:  Results string, textarea to output to
// Output: The values of the appropriate log textarea

// Output values and scroll to the bottom of the output area

	OutputField = document.getElementById(Log);
	OutputField.value += Results;
	OutputField.scrollTop = OutputField.scrollHeight - OutputField.clientHeight;

}

// *******************************************************************************************************************************

function Round(rnum, rlength) {

// Rounds a number to a specified number of decimal places
// Input:  Number to round, number of decimal places
// Output: Rounded Number

	var NewNumber = Math.round(rnum * Math.pow(10, rlength)) / Math.pow(10, rlength);
	return NewNumber;

}

// *******************************************************************************************************************************
