//================================================
// Recordset Class
//================================================
function RecordSet()
{
	var THIS = this;

	//==============================================
	// Load an XML string.
	//==============================================
	this.LoadXML = function LoadXML(XML, DataTable)
	{
		THIS.RowFilter = '';
		THIS.DataTableName = DataTable;
		
		if (XML == undefined)
			return;
		
		XML = XML.replace(/&/gi, '&#38;');
		
		// Create the XML DOM document.
		if (THIS.XMLDoc == null)
			THIS.CreateDOMDocument();
		
		// Check if the DOM document has been created. 
		if (THIS.XMLDoc == null)
		{
			alert('ERROR: The XML DOM document has not been created.');
			return;
		}
		
		// Load the XML document into the DOM document.
		THIS.XMLDoc.async = false;
		
		// Check the browser.
		if (THIS.IE)
		{
			THIS.XMLDoc.loadXML(XML);
		}
		else
		{
			if (THIS.Parser == undefined)
				THIS.Parser = new DOMParser();

			THIS.XMLDoc = THIS.Parser.parseFromString(XML, "text/xml");
		}
		
		// Create local storage.
		THIS.CreateLocalStorage(DataTable);
		return;
	}
	
	//==============================================
	// Create the XML DOM document.
	//==============================================
	this.CreateDOMDocument = function CreateDOMDocument()
	{
		// IE.
		if (window.ActiveXObject)
		{
			THIS.IE = true;
			THIS.XMLDoc = new ActiveXObject('Microsoft.XMLDOM');
		}
		else
		{
			THIS.IE = false;
			THIS.XMLDoc = document.implementation.createDocument('', '',null);
		}
		return;
	}
	
	//==============================================
	// Create the local storage array.
	//==============================================
	this.CreateLocalStorage = function CreateLocalStorage(DataTable)
	{
		// Get the required data table.
		if (DataTable == null)
		{
			if (THIS.XMLDoc.documentElement.childNodes.length == 0)
				return;
		
			if (THIS.IE)
			{
				DataTable = THIS.XMLDoc.documentElement.childNodes[0].nodeName;
			}
			else
			{		
				if (THIS.XMLDoc.documentElement.childNodes.length == 1)
				{
					DataTable = THIS.XMLDoc.documentElement.childNodes[0].nodeName;
				}
				else
				{
					DataTable = THIS.XMLDoc.documentElement.childNodes[1].nodeName;
				}
			}
		}

		THIS.RecordPointer = 0;
		THIS.RecordCount = 0;
		THIS.FieldStructure = new Array();
		THIS.MasterRows = new Array();
		THIS.Rows = new Array();
		THIS.DataTable = THIS.XMLDoc.getElementsByTagName(DataTable);
		
		// Check if there are any rows.
		if (THIS.DataTable.length == 0)
			return;
		
		// Loop through the XML and create the rows array.
		var objLastRow = THIS.DataTable[0].parentNode.lastChild;
		var objRow = THIS.DataTable[0].parentNode.firstChild;
		var blnBuildStructure = true;
		
		// Loop through the rows.
		while (true)
		{
			var objLastField = objRow.lastChild;
			var objField = objRow.firstChild;
			var intStop = 0;
			var aFields = new Object();
			
			// Check the node type is an element.
			if (objRow.nodeType == 1 && objRow.nodeName == DataTable)
			{
				// Loop through the fields.
				while (true)
				{
					// Check the node type.
					if (objField != null && objField.nodeType == 1)
					{
						if (objField.childNodes.length > 0)
						{
							aFields[objField.nodeName] = objField.childNodes[0].nodeValue;
						}
						else
						{
							aFields[objField.nodeName] = '';
						}
						
						if (blnBuildStructure)
							THIS.FieldStructure.push(objField.nodeName);
					}
					
					// Check if this is the last field.
					if (objField == objLastField)
						break;
						
					// Get the next field.
					objField = objField.nextSibling;
				}

				blnBuildStructure = false;

				// Append the row to the rows array.
				THIS.Rows.push(aFields);
			}
			
			// Clear the fields.
			aFields = null;
			
			// Check if this is the last row.
			if (objRow == objLastRow)
				break;
						
			// Get the next row.
			objRow = objRow.nextSibling;
		}
		
		// Destroy objects.
		THIS.XMLDoc = null;
		delete THIS.XMLDoc;
		
		// Update the record count.
		THIS.RecordCount = THIS.Rows.length;
	
		// Move to the first record.
		THIS.MoveFirst();
		return;
	}
	
	//==============================================
	// GetFieldValues.
	//==============================================
	this.GetFieldValues = function()
	{
		// Check if the master rows have been defined.
		if (THIS.Rows == undefined)
			return;

		// Check if the row exists.
		if (THIS.Rows[THIS.RecordPointer] == null)
			return;
			
		// Clear the current field values.
		for(var intField = 0; intField < THIS.FieldStructure.length; intField++)
			this[THIS.FieldStructure[intField]] = '';

		// Update the field values.
		for(var intField = 0; intField < THIS.FieldStructure.length; intField++)
		{
			if (THIS.Rows[THIS.RecordPointer][THIS.FieldStructure[intField]] != undefined)
				this[THIS.FieldStructure[intField]] = THIS.Rows[THIS.RecordPointer][THIS.FieldStructure[intField]];
		}
		return;
	}
	
	//==============================================
	// Add columns.
	//==============================================
	this.AddColumns = function AddColumns(Columns)
	{
		Columns = Columns.replace(/, /gi, ',');
		var aColumns = Columns.split(',');
			
		// Check if there are any columns.
		if (aColumns.length > 0)
		{
			// Check if the rows array exists.
			if ((THIS.Rows == undefined) || (THIS.Rows == null))
				THIS.Rows = new Array();
			
			// Check if the rows array exists.
			if ((THIS.MasterRows == undefined) || (THIS.MasterRows == null))	
				THIS.MasterRows = new Array();
			
			// Check if the field structure array exists.
			if ((THIS.FieldStructure == undefined) || (THIS.FieldStructure == null))
				THIS.FieldStructure = new Array();

			// Add the columns.
			for(var intColumn = 0; intColumn < aColumns.length; intColumn++)
				THIS.FieldStructure.push(aColumns[intColumn]);
		}
		return;
	}
	
	//==============================================
	// Add a column.
	//==============================================
	this.AddColumn = function AddColumn(ColumnName)
	{
		THIS.FieldStructure.push(ColumnName);
		return;
	}
	
	//==============================================
	// New row.
	//==============================================
	this.NewRow = function NewRow(Index, Row)
	{
		// Check if the field structure exists.
		if (THIS.FieldStructure.length == 0)
		{
			if ((Row == undefined) || (Row == null))
			{
				alert('Field structure hasn\'t been defined.');
				return false;
			}
			else
			{
				// Use the field structure from the row being imported.
				for(Field in Row)
					THIS.FieldStructure.push(Field);
			}
		}
		
		// Check if the user has defined an index.
		if (Index == null)
			Index = THIS.Rows.length;
		
		// Create the field structure.
		var _Fields = new Object();
		for(_Field in THIS.FieldStructure)
		{
			var strFieldName = THIS.FieldStructure[_Field].toString();
			
			if ((Row == undefined) || (Row == null))
			{
				_Fields[strFieldName] = null;
			}
			else
			{
				_Fields[strFieldName] = Row[strFieldName];
			}
		}
		
		// Cut the current row array into before and after arrays.
		var _RowsBefore = THIS.Rows.slice(0, (Index));
		var _RowNew = new Array();
		var _RowsAfter = THIS.Rows.slice(Index);
		
		// Append the fields to the new row.
		_RowNew = _Fields;
		
		// Concatenate the array rows.
		var _Concat = new Array();
		THIS.Rows = _Concat.concat(_RowsBefore, _RowNew, _RowsAfter);
		
		// Update the record count.
		THIS.RecordCount = THIS.Rows.length;
		
		// Clear objects.
		_RowsBefore = null;
		_RowNew = null;
		_RowsAfter = null;
		_Concat = null;
		
		THIS.RecordPointer = Index;
		
		// Get the field values.
		this.GetFieldValues();
		return THIS.Rows[THIS.RecordPointer];
	}
	
	//==============================================
	// Import row.
	//==============================================
	this.ImportRow = function ImportRow(Row)
	{
		var strFilter = THIS.RowFilter;
		THIS.ResetFilter();
		var aNewRow = THIS.NewRow(null, Row);
		
		// Check if a filter is required.
		if (strFilter != '')
			THIS.Filter(strFilter);
		return;
	}
	
	//==============================================
	// Delete row.
	//==============================================
	this.DeleteRow = function DeleteRow(Index)
	{
		// Check if the user has specified an index.
		if (Index == null)
			Index = THIS.RecordPointer;
	
		// Remove the row from the master rows array.
		if (THIS.MasterRows != undefined)
		{
			for(var intCounter = 0; intCounter < THIS.MasterRows.length; intCounter++)
			{
				if (THIS.MasterRows[intCounter] == THIS.Rows[Index])
				{
					THIS.MasterRows.splice(intCounter, 1);
					break;
				}
			}
		}
		
		THIS.Rows.splice(Index, 1);
		
		// Update the record count.
		THIS.RecordCount = THIS.Rows.length;
		
		THIS.RecordPointer--;
		return THIS.Rows[THIS.RecordPointer];
	}
	
	//==============================================
	// AbsolutePosition.
	//==============================================
	this.AbsolutePosition = function AbsolutePosition(Index)
	{
		// Check if an index has been passed.
		if (Index == null)
			return THIS.RecordPointer;
			
		// An index has been passed, move the pointer.
		THIS.RecordPointer = Index;
		this.GetFieldValues();
		return THIS.Rows[THIS.RecordPointer];
	}
	
	//==============================================
	// CurrentRow.
	//==============================================
	this.CurrentRow = function()
	{
		return THIS.Rows[THIS.RecordPointer];
	}
	
	//==============================================
	// MoveFirst.
	//==============================================
	this.MoveFirst = function()
	{
		THIS.RecordPointer = 0;
		this.GetFieldValues();
		return THIS.Rows[THIS.RecordPointer];
	}
	
	//==============================================
	// MovePrevious
	//==============================================
	this.MovePrevious = function()
	{
		// Check the position of the record pointer.
		if (THIS.RecordPointer == 0)
			return;
			
		THIS.RecordPointer--;
		this.GetFieldValues();
		return THIS.Rows[THIS.RecordPointer];
	}
	
	//==============================================
	// MoveNext
	//==============================================
	this.MoveNext = function()
	{
		THIS.RecordPointer++;
		
		// Check the position of the record pointer.
		if (THIS.RecordPointer == THIS.Rows.length)
			return false;
			
		// Check the record pointer hasn't passed the number of rows.
		if (THIS.RecordPointer > THIS.Rows.length)
			return false;

		this.GetFieldValues();
		return THIS.Rows[THIS.RecordPointer];
	}
	
	//==============================================
	// MoveLast.
	//==============================================
	this.MoveLast = function()
	{
		THIS.RecordPointer = (THIS.Rows.length - 1);
		this.GetFieldValues();
		return THIS.Rows[THIS.RecordPointer];
	}
	
	//==============================================
	// EOF.
	//==============================================
	this.EOF = function()
	{
		if (THIS.Rows == undefined)
			return true;

		return (THIS.RecordPointer == THIS.Rows.length);
	}

	//==============================================
	// Copy the recordset.
	//==============================================
	this.Copy = function Copy()
	{
		var objCopy = {};
		
		for (Property in this)
			objCopy[Property] = this[Property];

		return objCopy;
	}
	
	//==============================================
	// Reset the filter.
	//==============================================
	this.ResetFilter = function ResetFilter()
	{
		THIS.RowFilter = '';

		// The filter is being clear, copy the master rows back to the rows array.
		if (THIS.MasterRows != null)
		{
			if (THIS.MasterRows.length > 0)
			{
				THIS.Rows = THIS.MasterRows.slice(0);
				THIS.RecordCount = THIS.Rows.length;
				THIS.MasterRows = null;

				THIS.RecordPointer = 0;
				this.GetFieldValues();
				
				// Move to the first record.
				THIS.MoveFirst();
			}
		}
		return;
	}

	//==============================================
	// Filter.
	//==============================================
	this.Filter = function(Filter)
	{
		// Check if a filter condition has been passed.
		if (Filter == '')
		{
			// Reset the filter.
			THIS.ResetFilter();
			return;
		}
		
		// Check if a filter has already been set.
		if (THIS.RowFilter != '')
			THIS.ResetFilter();
		
		// Split the filter condition into the required filter fields.
		THIS.RowFilter = Filter;
		THIS.RowFilter = THIS.RowFilter.replace(/\[/gi, '');
		THIS.RowFilter = THIS.RowFilter.replace(/\]/gi, '');
		//THIS.RowFilter = THIS.RowFilter.replace(/ /gi, ''); Why is a space being removed?
		THIS.RowFilter = THIS.RowFilter.replace(/AND/g, ' && ');
		THIS.RowFilter = THIS.RowFilter.replace(/OR/g, ' || ');
		THIS.RowFilter = THIS.RowFilter.replace(/ AND /g, ' && ');
		THIS.RowFilter = THIS.RowFilter.replace(/ OR /g, ' || ');
		THIS.RowFilter = THIS.RowFilter.replace(/!=/gi, '\']!=');
		THIS.RowFilter = THIS.RowFilter.replace(/=/gi, '\']==');
		THIS.RowFilter = THIS.RowFilter.replace(/</gi, '\']<');
		THIS.RowFilter = THIS.RowFilter.replace(/>/gi, '\']>');
		THIS.RowFilter = THIS.RowFilter.replace(/^/gi, 'this[\'');
		THIS.RowFilter = THIS.RowFilter.replace(/(&& )+/gi, '&& this[\'');
		THIS.RowFilter = THIS.RowFilter.replace(/(\|\| )+/gi, '|| this[\'');
		THIS.RowFilter = THIS.RowFilter.replace(/this\[\'\(/gi, '(this[\'');
		THIS.RowFilter = THIS.RowFilter.replace(/\)\']/gi, '\'])');
		THIS.RowFilter = THIS.RowFilter.replace(/!\']==/gi, '!=');
		THIS.RowFilter = THIS.RowFilter.replace(/this\[\' /gi, 'this[\'');
		THIS.RowFilter = THIS.RowFilter.replace(/this\[\'\(/gi, '(this[\''); // fix "this['QuestionID']==723  && this['AxleTyreID']==0  && this['(Code']=="SERV"  || this['Code']=="NONA")"
		
		//alert('Filter: -\n\n' + THIS.RowFilter);
		
		// Store all the rows in the master rows array.
		THIS.MasterRows = THIS.Rows.slice(0);
		var aMatchedRows = new Array();

		// Loop through the recordset.
		this.MoveFirst();
		while (!this.EOF())
		{
			if (eval(THIS.RowFilter))
				aMatchedRows = aMatchedRows.concat(THIS.Rows[THIS.RecordPointer]);
				
			this.MoveNext();
		}
		
		THIS.Rows = aMatchedRows.slice(0);
		THIS.RecordCount = THIS.Rows.length;
		THIS.RecordPointer = 0;
		
		aMatchedRows = null;
		this.GetFieldValues();
		
		// Move to the first record.
		THIS.MoveFirst();
		return THIS.RecordCount;
	}
	
	//==============================================
	// Create a XML document from the recordset.
	//==============================================
	this.ToXML = function ToXML()
	{
		var objXMLDoc = null;

		// IE.
		if (window.ActiveXObject)
		{
			objXMLDoc = new ActiveXObject('Microsoft.XMLDOM');
			objXMLDoc.async = false;
			objXMLDoc.loadXML('<Data/>');
		}
		else
		{
			//Firefox, Mozilla, Opera, etc.
			objXMLDoc = document.implementation.createDocument('', '',null);
			objXMLDoc.async = false;
			objParser = new DOMParser();
			objXMLDoc = objParser.parseFromString('<Data/>', "text/xml");
		}
		
		// Loop through the rows.
		for(var intRow = 0; intRow < THIS.Rows.length; intRow++)
		{
			// Create a new XML element.
			var objRowElement = objXMLDoc.createElement(THIS.DataTableName);
			
			// Loop through the fields.
			for(var intField = 0; intField < THIS.FieldStructure.length; intField++)
			{
				if (THIS.Rows[intRow][THIS.FieldStructure[intField]] != null)
				{
					var objFieldElement = objXMLDoc.createElement(THIS.FieldStructure[intField]);
					var objFieldElementText = objXMLDoc.createTextNode(THIS.Rows[intRow][THIS.FieldStructure[intField]]);

					// Append the field element text to the field element node.
					objFieldElement.appendChild(objFieldElementText);

					// Append the field to the row element.
					objRowElement.appendChild(objFieldElement);
				}
			}
			
			// Append the row element to the XML document.
			objXMLDoc.childNodes[0].appendChild(objRowElement);
		}
		return objXMLDoc;
	}

	//==========================================
	// Return a String representation of the given document
	//==========================================
	this.ToString = function ToString()
	{
		var objXMLDoc = THIS.ToXML();

		if (objXMLDoc.xml)
		{
			return objXMLDoc.xml;
		}
		else
		{
			try
			{
				return new XMLSerializer().serializeToString(objXMLDoc);
			}
			catch (exc)
			{
				return 'Unable to get XML';
			}
		}
		
		// Destroy objects.
		objXMLDoc = null;
		delete objXMLDoc;
		return;
	}

	//==========================================
	// Sort the recordset.
	//==========================================
	this.Sort = function Sort(FieldName, Ascending, IsNumeric)
	{
		SortFieldName = FieldName;
		
		// Check if sorting a numeric field.
		if ((IsNumeric == undefined) ? false : IsNumeric)
		{
			THIS.Rows.sort(((Ascending == undefined) ? NumericSortAsc : ((Ascending) ? NumericSortAsc : NumericSortDesc)));
		}
		else
		{
			THIS.Rows.sort(((Ascending == undefined) ? StringSortAsc : ((Ascending) ? StringSortAsc : StringSortDesc)));
		}
		return;
	}

	//==========================================
	// Numeric sort compare handler.
	//==========================================
	function NumericSortAsc(a, b)
	{
		return Number(b[SortFieldName]) - Number(a[SortFieldName]);
	}

	//==========================================
	// Numeric sort compare handler.
	//==========================================
	function NumericSortDesc(a, b)
	{
		return Number(a[SortFieldName]) - Number(b[SortFieldName]);
	}

	//==========================================
	// String sort compare handler.
	//==========================================
	function StringSortAsc(a, b)
	{
		return a[this.SortFieldName] > b[this.SortFieldName];
	}

	//==========================================
	// String sort compare handler.
	//==========================================
	function StringSortDesc(a, b)
	{
		return b[this.SortFieldName] > a[this.SortFieldName];
	}

	return;
}
