/**
 * A SQL-based sort and filter.  Can extract source data from the document.
 *
 * Requires:
 *	DOM.js
 *	Arrays.js
 *	ParameterBinding.js
 *	TrimQueryUtilities.js
 */
function SortedFilter()
{
	var itsData;
	var itsQueryEngine;
	var itsVisibleRows;
	var itsIndexPropertyName = "index";
	var itsTableName = "data";
	var itsElementGroupPropertyName = "elementGroup";
	var itsElementIDPrefix = "";
	var itsPropertyTransformers;
	var itsWhere;
	var itsOrderBy;
	var itsElementIDAttributeName;


	function compareNumbers(in1,in2)
	{
		return (in1 - in2);
	}


	this.initialize = function()
	{
		var i;

		if (!itsData)
		{
			itsData = DOM.buildElementGroups(
				document,
				[
					null,
					{ extractor: ParameterBinding.compose(
						ParameterBinding.bindParameters(Arrays.assignIndexProperty,[undefined,itsIndexPropertyName]),
						ParameterBinding.bindParameters(DOM.objectWrap,[undefined,itsTableName])) },
					{ extractor: ParameterBinding.bindParameters(DOM.extractPropertiesFromFirstElementInGroup,[
						undefined,
						itsElementGroupPropertyName,
						null,
						itsPropertyTransformers]) }
				],
				itsElementIDPrefix);
		}
		itsQueryEngine = TrimPath.makeQueryLang(TrimQueryUtilities.inferTableDefinitions(itsData[0]));
		itsVisibleRows = new Array();
		for (i = 0; i < itsData.length; i++)
			if (itsData[i][itsTableName][itsElementGroupPropertyName] != undefined)
				itsVisibleRows[i] = itsData[i][itsTableName];

		Publisher(this);
	};


	this.sortAndFilter = function(inWhere,inOrderBy)
	{
		var i;
		var j;
		var theSQL;
		var theOldIndexes;
		var theFullData;
		var theOldElements;
		var theNewElements;

		if (inWhere == itsWhere && inOrderBy == itsOrderBy)
			return;
		for (i = 0; i < itsData.length; i++)
		{
			// Hide the old range.
			if (itsVisibleRows[i])
			{
				theNewElements = Arrays.flatten(Arrays.extractFromElementProperty(itsVisibleRows[i],itsElementGroupPropertyName));
				DOM.setDisplayStyle(theNewElements,"none");
			}

			// Calculate the new range.
			theSQL =
				"SELECT "
					+ itsTableName
				+ ".* FROM "
					+ itsTableName
				+ (inWhere ? " WHERE " + inWhere : "")
				+ (inOrderBy ? " ORDER BY " + inOrderBy : "");
			itsVisibleRows[i] = itsQueryEngine.parseSQL(theSQL).filter(itsData[i]);

			for (var j = 0; j < itsVisibleRows[i].length; j++)
			{
				theRow = itsVisibleRows[i][j];
				//bind element to data structure, if it is not bound yet
				if (theRow[itsElementGroupPropertyName] == undefined)
				{
					var theElements = new Array();
					var theIDs = theRow[itsElementIDAttributeName];
					for (k = 0; k < theIDs.length; k++)
						theElements[k] = document.getElementById(theIDs[k]);
					theRow[itsElementGroupPropertyName] = theElements;

					//add the elments to itsData
					itsData[i][itsTableName][theRow[itsIndexPropertyName]][itsElementGroupPropertyName] = theElements;
				}
			}

			// Display the new range, in its new order.
			theOldIndexes = Arrays.extractFromElementProperty(itsVisibleRows[i],itsIndexPropertyName);
			theOldIndexes.sort(compareNumbers);
			theFullData = itsData[i][itsTableName];

			theOldElements = Arrays.flatten(Arrays.extractFromElementProperty(Arrays.extractByIndexes(theFullData,theOldIndexes),itsElementGroupPropertyName));
			theNewElements = Arrays.flatten(Arrays.extractFromElementProperty(itsVisibleRows[i],itsElementGroupPropertyName));
			DOM.replaceElements(theOldElements,theNewElements);
			DOM.setDisplayStyle(theNewElements,"");
			for (j = 0; j < theOldIndexes.length; j++)
			{
				if (theFullData[theOldIndexes[j]] !== itsVisibleRows[i][j])
				{
					theFullData[theOldIndexes[j]] = itsVisibleRows[i][j];
					theFullData[theOldIndexes[j]][itsIndexPropertyName] = theOldIndexes[j];
				}
			}
		}

		itsWhere = inWhere;
		itsOrderBy = inOrderBy;

		this.notifySubscribers(inWhere,inOrderBy);
	};


	this.setSortAndFilter = function(inWhere,inOrderBy)
	{
		itsWhere = inWhere;
		itsOrderBy = inOrderBy;
	};


	this.getElementIDPrefix = function()
	{
		return (itsElementIDPrefix);
	};


	this.setElementIDPrefix = function(inElementIDPrefix)
	{
		itsElementIDPrefix = inElementIDPrefix;
	};


	this.getIndexPropertyName = function()
	{
		return (itsIndexPropertyName);
	};


	this.setIndexPropertyName = function(inIndexPropertyName)
	{
		itsIndexPropertyName = inIndexPropertyName;
	};


	this.getTableName = function()
	{
		return (itsTableName);
	};


	this.setTableName = function(inTableName)
	{
		itsTableName = inTableName;
	};


	this.getElementGroupPropertyName = function()
	{
		return (itsElementGroupPropertyName);
	};


	this.setElementGroupPropertyName = function(inElementGroupPropertyName)
	{
		itsElementGroupPropertyName = inElementGroupPropertyName;
	};


	this.getVisibleRows = function()
	{
		return (itsVisibleRows);
	};


	this.setPropertyTransformers = function(inPropertyTransformers)
	{
		itsPropertyTransformers = inPropertyTransformers;
	};


	this.getPropertyTransformers = function()
	{
		return (itsPropertyTransformers);
	};


	this.getData = function()
	{
		return (itsData);
	};


	this.setData = function(inData,inElementIDAttributeName)
	{
		var theSectionIndex;
		var theSection;
		var theRowIndex;
		var theRow;
		var theIDs;
		var theElements;
		var i;

		itsElementIDAttributeName = inElementIDAttributeName;

		for (theSectionIndex = 0; theSectionIndex < inData.length; theSectionIndex++)
		{
			theSection = inData[theSectionIndex];

			if (Arrays.isArray(theSection))
			{
				inData[theSectionIndex] = new Object();
				inData[theSectionIndex][itsTableName] = theSection;
			}
			else
				theSection = theSection[itsTableName];
		}

		itsData = inData;
	};
}
