//  WddxRecordset extension functions
//  File:       wddxExtensions.js
//  Contributions from:     
//  - Rob Edgar (robedgar@mersey.com.hk)
//  - Simeon Simeonov (simeons@allaire.com)
//  - Nate Weiss (nweiss@icesinc.com)  


//  Utility function to take data from a 2D JavaScript 
//  array and return a corresponding wddxRecordset.
//  Does NOT become a method of new wddxRecordset objects
function ArrayToWddxRecordset(RSArr, colNames, RSObj) {
  var Rows = RSArr.length;
  var ColName;

  // If RSObj was not passed to function, create new WddxRecordset object
  if (arguments.length < 2) {
    if (typeof(RSArr.getRowCount != 'function')) {
      RSObj = new WddxRecordset;
      
      for (i = 0; i < colNames[0].length; i++) {
        RSObj.addColumn(colNames[i]);
      }
      RSObj.addRows(Rows);
    }
  } 

  // Iterate through each cell of array, by Column then by Row
  for (Col = 0; Col < colNames.length; Col++) {
    ColName = colNames[Col];
    for (Row = 0; Row < Rows; Row++) {
      RSObj.setField(Row, ColName, RSArr[Row][ColName]);
    }
  }

  return RSObj;
}



// Function to add columns to a recordset, from an array of strings
function wddxRecordset_addColumns(colNamesAsArray) {
  for (i = 0; i < colNamesAsArray.length; i++) {
    this.addColumn(colNamesAsArray[i]);
  }
}



//  Function to insert a row into a recordset at the
//  specified postition (new row is inserted after).
//  Uses JS 1.2 Array.splice() as shortcut if available.
function wddxRecordset_insertRow(RowNum, arValues) {
  var numRows = this.getRowCount();

  if (RowNum <= numRows) {
    var colNames = this.getFieldNames();
    var isSpliceOK = (typeof(colNames.splice) == 'function');
    if (!isSpliceOK) this.addRows(1);

    for (Col = 0; Col < colNames.length; Col++) {
      if (isSpliceOK) {
        this[colNames[Col]].splice(RowNum, 0, "");
      } else {
        for (Row = numRows; Row > RowNum; Row--) {
          this.setField(Row, colNames[Col], this.getField(Row-1, colNames[Col]));
        }
        this.setField(RowNum, colNames[Col], "");
      }  
    }
    
    if (arguments.length > 1) {
      this.setFields(RowNum, arValues);
    }
    
  }
}



//  Function to remove a row from a recordset.
//  Uses JS 1.2 Array.splice() as shortcut if available.
function wddxRecordset_deleteRow(rowNum) {
  var ColNames = this.getFieldNames();
  var isSpliceOK = (typeof(ColNames.splice) == 'function');  

  for (Col = 0; Col < ColNames.length; Col++) {
    if (isSpliceOK) {
      this[ColNames[Col]].splice(rowNum, 1);
    } else {
      for (Row = rowNum; Row < this.getRowCount(); Row++) {
        this.setField(Row, ColNames[Col], this.getField(Row+1, ColNames[Col]));
      }
      this[ColNames[Col]].length = this[ColNames[Col]].length -1;
    }  
  }
}

 
 
/* 
  Function to add values to a recordset's row all at once
  Becomes a method of all new wddxRecordset objects.
*/  
function wddxRecordset_setFields(RowNum, arValues) {
  var ColNames = this.getFieldNames();
  var numCols = ColNames.length <= arValues.length ? ColNames.length : arValues.length;

  for (var Col = 0; Col < numCols; Col++) {
    this.setField(RowNum, ColNames[Col], arValues[Col]);
  }
}


function wddxRecordset_findValue(ColName, Value, bWholeStrings, bCaseSensitive, bMultiple) {
  if (arguments.length < 2) bWholeStrings = true;
  if (arguments.length < 3) bCaseSensitive = true;
  if (arguments.length < 4) bMultiple = false;

  if (bMultiple) 
    var arFoundRows = new Array();
  else  
    var FoundRow = -1;
  

  if (bWholeStrings) {
    if (bCaseSensitive) {
      for (Row = 0; Row < this.getRowCount(); Row++) {
        if (this.getField(Row, ColName) == Value) { 
          if (bMultiple) 
            arFoundRows[arFoundRows.length] = Row;
          else {
            FoundRow = Row;  
            break;
          }  
        }
      }

    } else {
      Value = Value.toLowerCase();
      for (Row = 0; Row < this.getRowCount(); Row++) {
        if (this.getField(Row, ColName).toLowerCase() == Value) {
          if (bMultiple) 
            arFoundRows[arFoundRows.length] = Row;
          else {
            FoundRow = Row;  
            break;
          }  
        }
      }
    }    

  }  else {
    if (bCaseSensitive) {
      for (Row = 0; Row < this.getRowCount(); Row++) {
        if (this.getField(Row, ColName).indexOf(Value) >= 0) { 
          if (bMultiple) 
            arFoundRows[arFoundRows.length] = Row;
          else {
            FoundRow = Row;  
            break;
          }  
        }
      }

    } else {
      Value = Value.toLowerCase();
      for (Row = 0; Row < this.getRowCount(); Row++) {
        if (this.getField(Row, ColName).toLowerCase().indexOf(Value) >= 0) {
          if (bMultiple) 
            arFoundRows[arFoundRows.length] = Row;
          else {
            FoundRow = Row;  
            break;
          }  
        }
      }
    }    

  }
  
  if (bMultiple) 
    return arFoundRows;
  else
    return FoundRow;
}




//  Function to get column names from a wddxRecordset
//  object.  Returns the names as 1D array of strings.
function wddxRecordset_getFieldNames() {
  var Names = new Array();
  var Rows = this.getRowCount();
  
  // Assume that any object with the right number of rows is a "column"
  for (x in this) {
    if ( (typeof(this[x]) == 'object') && (this[x].length == Rows)) {
      Names[Names.length] = x;
    }
  }   
  
  return Names; 
}



 
//  Function to return normal, JS-style "array of arrays" 
//  from data in a wddxRecordset object.  WddxRS.title[0] 
//  becomes Array[0].title, and so on.  
function wddxRecordset_toArray() {
  var Rec, Col, Row;
  
  // Create the new array (will be a 2D array)
  var Records = new Array();
  // Get the column names for the recordset
  var colNames = this.getFieldNames();
  var RowCount = this.getRowCount();
  
  // For each row in the recordset, add an element to the array
  for (Row = 0; Row < this[colNames[0]].length; Row++) {
    Rec = new Array(RowCount);
    for (Col = 0; Col < colNames.length; Col++) 
      Rec[colNames[Col]] = this[colNames[Col]][Row];
    Records[Row] = Rec;
  }

  // Return array to calling process  
  return Records;
}



// WddxRecordset sorting extension
// Author: Simeon Simeonov (simeons@allaire.com)


///////////////////////////////////////////////////////////////////////////
// wddxRecordsetSimpleComparator_compareAsc(i, j)
// Compares the elements at positions i and j in a recordset column
// Maintains ascending order
function wddxRecordsetSimpleComparator_compareAsc(i, j)
{
	var a = this.rs[this.col][i];
	var b = this.rs[this.col][j];
	return (a == b) ? 0 : (a > b) ? 1 : -1;
}


///////////////////////////////////////////////////////////////////////////
// wddxRecordsetSimpleComparator_compareDesc(i, j)
// Compares the elements at positions i and j in a recordset column
// Maintains descending order
function wddxRecordsetSimpleComparator_compareDesc(i, j)
{
	var a = this.rs[this.col][i];
	var b = this.rs[this.col][j];
	return (a == b) ? 0 : (a > b) ? -1 : 1;
}


///////////////////////////////////////////////////////////////////////////
// wddxRecordsetSimpleComparator(rs, col)
// Constructor for the default comparator object for recordset sorting
// rs : recordset to sort
// col : column to sort
function wddxRecordsetSimpleComparator(rs, col, func)
{
	this.rs = rs;
	this.col = col;
	this.compare = func;
}


///////////////////////////////////////////////////////////////////////////
// _wddxRecordset_sortComparatorFunc(i, j)
// Delegates comparison of rows i and j to the active comparator object
function _wddxRecordset_sortComparatorFunc(i, j)
{
	return _wddxRecordset_activeComparator.compare(i, j);
}


///////////////////////////////////////////////////////////////////////////
// wddxRecordset_sort
// Builds an auxilliary index array, quicksorts based it, and then creates
// the sorted columns based on the sorted index.
// Returns the sorted recordset or null on failure.
//
// There are two versions of the function:
//
// wddxRecordset_sort(cmpObj)
// Sorts using a comparator object. The comparator object must have a 
// compare(i, j) function defined where i and j are two row indexes.
//
// wddxRecordset_sort(sortField)
// wddxRecordset_sort(sortField, sortOrder)
// Sorts the recordset based on an ascending | descending comparison of a
// given field. Ascending sort is specified by "asc", descending by "desc".
// The default order is ascending.
function wddxRecordset_sort()
{
	// Is there anything to sort?
	var rowCount = this.getRowCount();
	if (rowCount < 2) return this;
	
	// Comparator object
	var cmpObj = null;
	
	// Validate arguments and determine sort type
	if (arguments.length == 0 || arguments.length > 2) return null;
	if (typeof(arguments[0]) == "object")
	{
		// Comparator sort?
		if (typeof(arguments[0].compare) == "function")
		{
			cmpObj = arguments[0];
		}
	}
	else if (typeof(arguments[0]) == "string")
	{
		// Simple field sort
		
		// Validate field name
		if (typeof(this[arguments[0]]) == "object")
		{
			// Validate and determine sort order
			var isError = false;
			var isAscending = true;
			if (arguments.length == 2)
			{
				var orderSpec = arguments[1].toLowerCase();
				if (orderSpec == "asc")
				{
					isAscending = true;
				}
				else if (orderSpec == "desc")
				{
					isAscending = false;
				}
				else
				{
					isError = true;
				}
			}
			
			if (! isError)
			{
				cmpObj = new wddxRecordsetSimpleComparator(
					this, 
					arguments[0], 
					isAscending ? 
						wddxRecordsetSimpleComparator_compareAsc : 
						wddxRecordsetSimpleComparator_compareDesc);
			}
		}
	}
	
	// If no comparison object has been established the arguments were invalid
	if (cmpObj == null) return null;
	
	// Create an array of row indexes
	var indexes = new Array(rowCount);
	for (var i = 0; i < rowCount; ++i)
	{
		indexes[i] = i;
	}
	
	// Sort the array
	// __SIM: no check for presence of Array.sort()
	// __SIM: can use quicksort() from http://proxy.uiggm.nsc.ru/doc/WEBPAGES/java/sejava16.htm#listing1614
	_wddxRecordset_activeComparator = cmpObj;
	indexes.sort(_wddxRecordset_sortComparatorFunc);
	
	// Create a sorted recordset
	for (var col in this)
	{
		if (typeof(this[col]) != "function")
		{
			// Create sorted column
			var colArray = new Array(rowCount);
			for (var i = 0; i < rowCount; ++i)
			{
				colArray[i] = this[col][indexes[i]];
			}
			
			// Put it in the recordset
			this[col] = colArray;
		}
	}
	
	return this;
}


// Utility function to provide a "replace" function that can safely
// be called regardless of whether String.replace() from JS 1.2 is available
// This brings core JS 1.1 compatibility, but still requires String.split()
function replaceAny(strString, strFind, strRep) {
  var newString = '';

  if (typeof 'teststring'.replace == 'function') {
    newString = strString.replace(strFind, strRep);
    
  } else {
    tempArray = strString.split(strFind);
    for (var x = 0; x < tempArray.length-1; x++) {
      newString = newString + tempArray[x] + strRep;
    }
    newString = newString + tempArray[tempArray.length-1];
    
  }
  
  return newString;
}



///////////////////////////////////////////////////////////////////////////
// WDDXDeserializeRecordset() 
// Deserializes a <recordset> packet and returns new WddxRecordset object
// Code and concept contributed by Rob Edgar - Thanks, Rob!  :)
// * For WDDX SDK Beta 2, Nate Weiss added:
// * - eliminated JS 1.2-specific code (slice, switch and replace statements)
// * - support for <boolean> elements
// * - support for <char> elements (parseSpecialChars in code)
function wddxRecordset_readFromPacket(WDDXPacket) {

WDDXPacket = WDDXPacket.toString() + '';

// * Optional parseSpecialChars argument determines whether <char> elements are processed
// * If not provided, sniff packet for the presence of a <char> element to obtain default
var parseSpecialChars;
if (arguments.length > 1)
  parseSpecialChars = arguments[1];
else parseSpecialChars = (WDDXPacket.toLowerCase().indexOf('<char code=') > 0);

// * If parsing special characters, convert <char> elements (but not their values) to lowercase
if (parseSpecialChars) {
  WDDXPacket = replaceAny(WDDXPacket, '<CHAR CODE=', '<char code=');
}          

//create a new empty recordset object
//var rs = this;
//now process the xml
//split off the header from the recordset
var data = WDDXPacket.split('<data>');
var xmlheader = data[0];
//the recordset set is in the second element, remove everything after the trailing data
var recordset = data[1].split('</data>');
//split off the recordset header from the data
var nfirstfield = recordset[0].indexOf('>',0);
//recordsetheader = recordset[0].slice(0, nfirstfield);
recordsetheader = recordset[0].substring(0, nfirstfield);
//strip the trailing recordset tag
fields = recordset[0].substring( nfirstfield+1, recordset[0].length - 12);
//split into fields
var fields = fields.split('</field>');

//now process the fields array
for (var i = 0; i < fields.length-1; i++){
  //process a single field
  var field = fields[i];
  var fielddata = field.indexOf('>',0);
  var fieldname = field.substring(0,fielddata);
  fieldname = replaceAny(fieldname, '<field ', 'field').toLowerCase();
  eval(fieldname);
  
  fielddata = field.substring(fielddata+1);
  datatype = fielddata.substring(0, fielddata.indexOf('>',0) + 1);

  datatype = replaceAny(datatype, '<', '</');
  fielddata = fielddata.split(datatype);
  datatype = replaceAny(datatype, '/' ,'');
  fname = new Array;

  // Branch according to datatype element
  if (datatype == '<datetime>') {
    for (var y = 0; y < fielddata.length - 1; y++){
      // Split date string into component parts
      var Value = replaceAny(fielddata[y], datatype,'');
      Value = Value.split('T');
      var dtDateParts = Value[0].split('-');
      var dtTimeParts = Value[1].split(':');
      // create native JS Date object
      fname[y] =  new Date(dtDateParts[0], dtDateParts[1], dtDateParts[2], dtTimeParts[0], dtTimeParts[1], 0); //dtTimeParts[1], dtTimeParts[2]);
  
    }
  
  } else if (datatype == '<number>') {
    for (var y = 0; y < fielddata.length - 1; y++){
      fname[y] = parseFloat(replaceAny(fielddata[y], datatype,''));
  
    }
  
  } else if (datatype == '<boolean>') {
    for (var y = 0; y < fielddata.length - 1; y++){
      fname[y] = ( replaceAny(fielddata[y], datatype,'') == 'true' );
  
    }
  
  } else {
    for (var y = 0; y < fielddata.length - 1; y++){
      fname[y] = replaceAny(fielddata[y], datatype,'');
      
      if (parseSpecialChars) {
        fname[y] = replaceAny(fname[y], "<char code='0D'/>", '\r');
        fname[y] = replaceAny(fname[y], "<char code='0C'/>", '\f');
        fname[y] = replaceAny(fname[y], "<char code='0A'/>", '\n');
        fname[y] = replaceAny(fname[y], "<char code='09'/>", '\t');
      }
  
    }
  
  }
  
  this[fieldname] = fname;  // * Rob's code was: eval('this["'+fieldname+'"]=fname');
}
return 0;
}







// Register the functions, so they become available 
// as methods for all new WddxRecordset objects
registerWddxRecordsetExtension("getFieldNames", wddxRecordset_getFieldNames);
registerWddxRecordsetExtension("toArray", wddxRecordset_toArray);
registerWddxRecordsetExtension("sort", wddxRecordset_sort);
registerWddxRecordsetExtension("addColumns", wddxRecordset_addColumns);
registerWddxRecordsetExtension("deleteRow", wddxRecordset_deleteRow);
registerWddxRecordsetExtension("insertRow", wddxRecordset_insertRow);
registerWddxRecordsetExtension("setFields", wddxRecordset_setFields);
registerWddxRecordsetExtension("findValue", wddxRecordset_findValue);
registerWddxRecordsetExtension("readFromPacket", wddxRecordset_readFromPacket);


