//================================================
// Recordset Class
//================================================
function Recordset()
{
	// Private variables.
	var _IE = true;
	var _RecordPointer = 0;
	var _FieldStructure = new Array();
	var _DataTableName = '';
	var _DataTableContainer = '';
	var _XMLDoc = null;
	
	var _SortField = "";
	var _SortAscending = true;
	var _SortEval = false;
	
	// Public variables.
	this.RecordCount = 0;
	this.MasterRows = new Array();
	this.Rows = new Array();
	
	// Start of public functions and procedures
	
	//==============================================
	// Load an XML file.
	//==============================================
	this.LoadFile = function(File, DataTable, DisplayErrors)
	{
		var _blnFileLoaded = false;
		
		// Check if the 'DisplayErrors' variable has been defined, set it to true if it hasn't.
		if (DisplayErrors == undefined)
			DisplayErrors = true;

		// Create the XML DOM document.
		_XMLDoc = CreateDOMDocument();
		
		// Check if the DOM document has been created. 
		if (_XMLDoc == null)
		{
			if (DisplayErrors)
				alert('ERROR: The XML DOM document has not been created.');

			return false;
		}
		
		// Load the XML document into the DOM document.
		_XMLDoc.async = false;

		// Check if a data table name has been passed.
		if (DataTable == null)
		{
			window.status = 'Loading please wait...';
		}
		else
		{
			window.status = 'Loading \'' + DataTable + '\', please wait...';
		}

		try
		{
//			if (!_IE)
//			{
//				if (File.indexOf(':') > 0)
//					File = 'file:///' + File.replace(/\\/g, '//');
//			}

			_blnFileLoaded = _XMLDoc.load(File);
			
			// Check if the file has been loaded.
			if (!_blnFileLoaded)
			{
				if (DisplayErrors)
					alert('Unable to load \'' + File + '\'.');

				return false;
			}
		}
		catch(e)
		{
			if (_IE)
			{
				if (DisplayErrors)
					alert('ERROR: ' + e.message + '\nFile: \'' + File + '\'.');
			}
			else
			{
				if (DisplayErrors)
					alert('ERROR: ' + e + '\nFile: \'' + File + '\'.');
			}
			return false;
		}
		
		// Create local storage.
		if (_blnFileLoaded)
			this.CreateLocalStorage(DataTable);

		return true;
	}
	
	//==============================================
	// Load an XML string.
	//==============================================
	this.LoadXML = function(XML, DataTable, CreateLocalStorage)
	{
		var _CreateLocalStorage = true;
		
		// Check if local storage should be created.
		if (CreateLocalStorage != null)
			_CreateLocalStorage = CreateLocalStorage;
	
		if (XML == "<NewDataSet />")
			_CreateLocalStorage = false;
		
		// Create the XML DOM document.
		_XMLDoc = CreateDOMDocument();
		
		// Check if the DOM document has been created. 
		if (_XMLDoc == null)
		{
			alert('ERROR: The XML DOM document has not been created.');
			return;
		}
		
		// Load the XML document into the DOM document.
		_XMLDoc.async = false;
		
		// Check if a data table name has been passed.
		if (DataTable == null)
		{
			window.status = 'Loading please wait...';
		}
		else
		{
			window.status = 'Loading \'' + DataTable + '\', please wait...';
		}
		
		// Check the browser.
		if (_IE)
		{
			_XMLDoc.loadXML(XML);
		}
		else
		{
			var _Parser = new DOMParser();
			_XMLDoc = _Parser.parseFromString(XML, "text/xml");
		}
		
		// Create local storage.
		if (_CreateLocalStorage)
		{
			this.CreateLocalStorage(DataTable);
		}
		else
		{
			window.status = 'Done';
		}
		return;
	}
	
	//==============================================
	// Create the local storage array.
	//==============================================
	this.CreateLocalStorage = function(DataTable)
	{
		// Get the required data table.
		if (DataTable == null)
		{
			if (_XMLDoc.documentElement.childNodes.length == 0)
			{
				return;
			}
		
			if (_IE)
			{
				DataTable = _XMLDoc.documentElement.childNodes[0].nodeName;
			}
			else
			{		
				if (_XMLDoc.documentElement.childNodes.length == 1)
					DataTable = _XMLDoc.documentElement.childNodes[0].nodeName;
				else
					DataTable = _XMLDoc.documentElement.childNodes[1].nodeName;
			}
		}

		window.status = 'Loaded \'' + DataTable + '\', storing locally, please wait...';

		var _DataTable = _XMLDoc.getElementsByTagName(DataTable);
		
		// Check if there are any rows.
		if (_DataTable.length == 0)
		{
			return;
		}
		
		// Store the data table container.
		_DataTableName = DataTable;
		_DataTableContainer = _DataTable[0].parentNode.nodeName;
		
		// Loop through the XML and create the rows array.
		var _LastRow = _DataTable[0].parentNode.lastChild;
		var _Row = _DataTable[0].parentNode.firstChild;
		
		// Loop through the rows.
		while (true)
		{
			var _LastField = _Row.lastChild;
			var _Field = _Row.firstChild;
			var _Stop = 0;
			var _Fields = new Object();
			
			// Check the node type is an element and the node name is the required table.
			if (_Row.nodeType == 1 && _Row.nodeName == DataTable)
			{
				// Loop through the fields.
				while (true)
				{
					// Check the node type.
					if (_Field != null && _Field.nodeType == 1)
					{
						if (_Field.childNodes.length > 0)
						{
							_Fields[_Field.nodeName] = _Field.childNodes[0].nodeValue;
						}
						else
						{
							_Fields[_Field.nodeName] = null;
						}
					}
					
					// Check if this is the last field.
					if (_Field == _LastField)
						break;
						
					// Get the next field.
					_Field = _Field.nextSibling;
				}
				
				// Check if the field structure has been created.
				if (_FieldStructure.length == 0)
				{
					_FieldStructure = new Array(_Fields.length);
					for (var _FieldCounter = 0; _FieldCounter < _Fields.length; _FieldCounter++)
						_FieldStructure[_FieldCounter] = _Fields[_Fields];
				}
				
				// Append the row to the rows array.
				this.Rows[this.Rows.length] = _Fields;
			}
			
			// Check if this is the last row.
			if (_Row == _LastRow)
				break;
						
			// Get the next row.
			_Row = _Row.nextSibling;
		}
		
		// Destory the XML doc.
		_XMLDoc = null;
		
		// Update the record count.
		this.RecordCount = this.Rows.length;
		
		// Clear objects.
		_DataTable = null;
	
		// Move to the first record.
		this.MoveFirst();
		
		window.status = 'Done';
		return;
	}
	
	//==============================================
	// New row.
	//==============================================
	this.NewRow = function(Index)
	{
		// Check if the field structure exists.
		if (_FieldStructure.length == 0)
		{
			alert('ERROR: Field structure is not defined.');
			return false;
		}
		
		// 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 _FieldStructure)
		{
			_Field[_FieldStructure[_Field]] = null;
		}
		
		// 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;
		
		_RecordPointer = Index;
		
		// Get the field values.
		this.GetFieldValues();
		return this.Rows[_RecordPointer];
	}
	
	//==============================================
	// Delete row.
	//==============================================
	this.DeleteRow = function(Index)
	{
		// Check if the user has specified an index.
		if (Index == null)
			Index = _RecordPointer;
	
		this.Rows.splice(Index, 1);
		_RecordPointer--;
		this.RecordCount--;
		return;
	}
	
	//==============================================
	// AbsolutePosition.
	//==============================================
	this.AbsolutePosition = function(Index)
	{
		// Check if an index has been passed.
		if (Index == null)
			return _RecordPointer;
			
		// An index has been passed, move the pointer.
		_RecordPointer = Index;
		this.GetFieldValues();
		return this.Rows[_RecordPointer];
	}
	
	//==============================================
	// CurrentRow.
	//==============================================
	this.CurrentRow = function()
	{
		return this.Rows[_RecordPointer];
	}
	
	//==============================================
	// MoveFirst.
	//==============================================
	this.MoveFirst = function()
	{
		_RecordPointer = 0;
		this.GetFieldValues();
		return this.Rows[_RecordPointer];
	}
	
	//==============================================
	// MovePrevious
	//==============================================
	this.MovePrevious = function()
	{
		// Check the position of the record pointer.
		if (_RecordPointer == 0)
			return;
			
		_RecordPointer--;
		this.GetFieldValues();
		return this.Rows[_RecordPointer];
	}
	
	//==============================================
	// MoveNext
	//==============================================
	this.MoveNext = function()
	{
		_RecordPointer++;
		
		// Check the position of the record pointer.
		if (_RecordPointer == this.Rows.length)
			return false;
			
		// Check the record pointer hasn't passed the number of rows.
		if (_RecordPointer > this.Rows.length)
			return false;

		this.GetFieldValues();
		return this.Rows[_RecordPointer];
	}
	
	//==============================================
	// MoveLast.
	//==============================================
	this.MoveLast = function()
	{
		_RecordPointer = (this.Rows.length - 1);
		this.GetFieldValues();
		return this.Rows[_RecordPointer];
	}
	
	//==============================================
	// EOF.
	//==============================================
	this.EOF = function()
	{
		return (_RecordPointer == this.Rows.length);
	}
	
	//==============================================
	// GetFieldValues.
	//==============================================
	this.GetFieldValues = function()
	{
		// Update the field values.
		for(_Field in this.Rows[_RecordPointer])
		{
			this[_Field] = this.Rows[_RecordPointer][_Field];
		}
		return;
	}

	//==============================================
	// Filter.
	//==============================================
	this.Filter = function(Filter)
	{
		// Check if a filter condition has been passed.
		if (Filter == '')
		{
			// The filter is being clear, copy the master rows back to the rows array.
			if (this.MasterRows.length > 0)
			{
				this.Rows = this.MasterRows.slice(0);
				this.RecordCount = this.Rows.length;
				this.MasterRows = null;

				_RecordPointer = 0;
				this.GetFieldValues();
			}
			return;
		}
		
		// Split the filter condition into the required filter fields.
		var _Filter = Filter;

		_Filter = Filter.replace(/\[/gi, '');
		_Filter = _Filter.replace(/\]/gi, '');
		_Filter = _Filter.replace(/ /gi, '');
		_Filter = _Filter.replace(/AND/g, ' && ');
		_Filter = _Filter.replace(/OR/g, ' || ');
		_Filter = _Filter.replace(/ AND /g, ' && ');
		_Filter = _Filter.replace(/ OR /g, ' || ');
		_Filter = _Filter.replace(/=/gi, '\']==');
		_Filter = _Filter.replace(/^/gi, 'this[\'');
		_Filter = _Filter.replace(/(&& )+/gi, '&& this[\'');
		_Filter = _Filter.replace(/(\|\| )+/gi, '|| this[\'');
		
		//alert('Recordset.Filter():\n\n' + _Filter);
		
		// Store all the rows in the master rows array.
		this.MasterRows = this.Rows.slice(0);
		this.MatchedRows = new Array();

		// Loop through the recordset.
		this.MoveFirst();
		while (!this.EOF())
		{
			if (eval(_Filter))
				this.MatchedRows = this.MatchedRows.concat(this.Rows[_RecordPointer]);
				
			this.MoveNext();
		}
		
		this.Rows = this.MatchedRows.slice(0);
		this.RecordCount = this.Rows.length;
		
		_RecordPointer = 0;
		this.GetFieldValues();
		return;
	}
	
	//==============================================
	// Return the recordset as a XML string.
	//==============================================
	this.ToXMLString = function(RootNodeName)
	{
		var _XMLString = '';

		// Create a XML document from the recordset.
		this.XML(RootNodeName);
		
		// Check the browser.
		if (_IE)
		{
			_XMLString = _XMLDoc.xml;
		}
		else
		{
			_XMLString = (new XMLSerializer()).serializeToString(_XMLDoc);
		}
		
		return _XMLString;
	}
	
	//==============================================
	// Save the recordset to a file.
	//==============================================
	this.SaveToFile = function(File)
	{
		if (this.Rows.length == 0)
		{
			alert('ERROR: Unable to save an empty recordset.');
			return;
		}

		// Create a XML document from the recordset.
		this.XML();
		
		// Check the browser.
		if (_IE)
		{
			try
			{
				_XMLDoc.save(File);
				return true;
			}
			catch(e)
			{
				alert(e.description + '\n\n' + File);
				return false;
			}
		}
		else
		{
			return Save(File, (new XMLSerializer()).serializeToString(_XMLDoc));
		}
		return false;
	}
	
	//==============================================
	// Save raw XML to a file.
	//==============================================
	this.SaveRawXMLToFile = function(XML, File)
	{
		// Create a XML document from the recordset.
		this.XML();
		
		// Check the browser.
		if (_IE)
		{
			_XMLDoc.loadXML(XML);
		}
		else
		{
			var _Parser = new DOMParser();
			_XMLDoc = _Parser.parseFromString(XML, "text/xml");
		}
		
		// Check the browser.
		if (_IE)
		{
			try
			{
				_XMLDoc.save(File);
				return true;
			}
			catch(e)
			{
				alert(e.description + '\n\n' + File);
				return false;
			}
		}
		else
		{
			return Save(File, (new XMLSerializer()).serializeToString(_XMLDoc));
		}
		return false;
	}
	
	//==============================================
	// Sort the rows about a column, and in a specified direction
	//==============================================
	this.Sort = function(FieldName, Ascending, IsNumeric)
	{
		if (Ascending != null)
			_SortAscending = Ascending;
		else
			_SortAscending = true;
			
		if (IsNumeric != null)
			_SortEval = IsNumeric;
		else
			_SortEval = false;
			
		_SortField = FieldName;
		
		if (_SortEval == true)
		{
			if (_SortAscending != false)
				this.Rows.sort(SortAscendingEval);
			else
				this.Rows.sort(SortDescendingEval);
		}
		else
		{
			if (_SortAscending != false)
				this.Rows.sort(SortAscending);
			else
				this.Rows.sort(SortDescending);
		}
			
		return this;
	}
	
	/******************* Private row comparers for sort ********************/
	
	function SortAscending(a, b)
	{
		if (a[_SortField] == b[_SortField])
			return 0;
			
		if (a[_SortField] > b[_SortField])
			return 1;
			
		return -1;
	}
	
	function SortDescending(a, b)
	{
		return SortAscending(b, a);
	}
	
	function SortAscendingEval(a, b)
	{
		try
		{
			if (eval(a[_SortField]) == eval(b[_SortField]))
				return 0;
				
			if (eval(a[_SortField]) > eval(b[_SortField]))
				return 1;
				
			return -1;
		}
		catch(exc)
		{
			return 0;
		}
	}
	
	function SortDescendingEval(a, b)
	{
		return SortAscendingEval(b, a);
	}
	

	/***********************************************************************/
	
	// End of public functions and procedures
	
	// Start of private functions and procedures
	
	//==============================================
	// Create a XML document from the recordset.
	//==============================================
	this.XML = function(RootNodeName)
	{
		if (RootNodeName == null)
			RootNodeName = _DataTableContainer;
	
		if (RootNodeName != "")
			this.LoadXML('<' + RootNodeName + '></' + RootNodeName + '>', '', false);
		else
			this.LoadXML('', '', false);
		
		if (this.Rows.length == 0)
		{
			if (_DataTableContainer == "")
				return "<NewDataSet />";
			else
				return "<" + RootNodeName + " />";
		}
		
		// Loop through the rows.
		for (Row in this.Rows)
		{
			// Create a new XML element.
			var _RowElement = _XMLDoc.createElement(_DataTableName);
			
			// Loop through the fields.
			for (Field in this.Rows[Row])
			{
				if (this.Rows[Row][Field] != null)
				{
					var _FieldElement = _XMLDoc.createElement(Field);
					var _FieldElementText = _XMLDoc.createTextNode(this.Rows[Row][Field]);

					// Append the field element text to the field element node.
					_FieldElement.appendChild(_FieldElementText);

					// Append the field to the row element.
					_RowElement.appendChild(_FieldElement);
				}
			}
			
			// Append the row element to the XML document.
			_XMLDoc.childNodes[0].appendChild(_RowElement);
		}
		return;
	}
	
	//==============================================
	// Save the XML to a local file.
	//==============================================
	function Save(File, Content)
	{
		//alert(File + '\n\n' + Content);
		
		try
		{
			netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
		}
		catch(e)
		{
			alert("ERROR: Permission to save file was denied.");
			return false;
		}
		
		try
		{
			var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
			file.initWithPath(File);
			
			if (file.exists() == false)
			{
				file.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 420);
			}
			
			var outputStream = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance( Components.interfaces.nsIFileOutputStream );
			/* Open flags 
			#define PR_RDONLY       0x01
			#define PR_WRONLY       0x02
			#define PR_RDWR         0x04
			#define PR_CREATE_FILE  0x08
			#define PR_APPEND      0x10
			#define PR_TRUNCATE     0x20
			#define PR_SYNC         0x40
			#define PR_EXCL         0x80
			*/
			/*
			** File modes ....
			**
			** CAVEAT: 'mode' is currently only applicable on UNIX platforms.
			** The 'mode' argument may be ignored by PR_Open on other platforms.
			**
			**   00400   Read by owner.
			**   00200   Write by owner.
			**   00100   Execute (search if a directory) by owner.
			**   00040   Read by group.
			**   00020   Write by group.
			**   00010   Execute by group.
			**   00004   Read by others.
			**   00002   Write by others
			**   00001   Execute by others.
			**
			*/
			outputStream.init( file, 0x04 | 0x08 | 0x20, 420, 0 );
			var output = Content;
			var result = outputStream.write( output, output.length );
			outputStream.close();
		}
		catch(e)
		{
			return false;
		}
		return true;
	}
	
	//==============================================
	// Create the XML DOM document.
	//==============================================
	function Evalutor(Filter)
	{
		if (Filter.match('>=') != null)
			return Filter.indexOf('>=');
			
		if (Filter.match('<=') != null)
			return Filter.indexOf('<=');
			
		if (Filter.match('>') != null)
			return Filter.indexOf('>');
			
		if (Filter.match('<') != null)
			return Filter.indexOf('<');
			
		if (Filter.match('!=') != null)
			return Filter.indexOf('!=');
			
		if (Filter.match('=') != null)
			return Filter.indexOf('=');
	}
	
	//==============================================
	// Create the XML DOM document.
	//==============================================
	function CreateDOMDocument()
	{
		try //Internet Explorer
		{
			_IE = true;
			var _XMLDoc = new ActiveXObject('Microsoft.XMLDOM');
		}
		catch(e)
		{
			try //Firefox, Mozilla, Opera, etc.
			{
				_IE = false;
				var _XMLDoc = document.implementation.createDocument('', '',null);
			}
			catch(e)
			{
				alert(e.message);
			}
		}
		return _XMLDoc;
	}
	
	// End of private functions and procedures

	// End of class.
	return;
}
