NPL.Prototype.__ElementSet__.IMS = function ( pCommandStr )
{
	//	'autoscroll'
	if ( pCommandStr == 'autoscroll' )
	{
		//	Use the same algorithm from the gallery code.
		NPL_IMS._autoscroll_add(this);
	}
	//	'align-middle'
	else if ( pCommandStr == 'align-middle' )
	{
		NPL_IMS._align_middle(this);
	}
	else if ( pCommandStr == 'align-center' )
	{
		NPL_IMS._align_center(this);
	}
}

if ( typeof NPL_IMS != 'undefined' )
{
	if ( typeof _NPL_Dbg != 'undefined' ) { _NPL_Dbg('NPLib: "IMS" already defined.'); }
}
else
{
	var NPL_IMS = function ()
	{
		var _autoscrollers = [];
		var _onresize = [];
		var _fudge = 15;

		return {
		
			_autoscroll_add: function ( pElement )
			{
				_autoscrollers.push(pElement);
				//	Interval period should probably be adjusted dynamically, depending on the number
				//	of elements we're watching for &etc.
				if ( _autoscrollers.length == 1 ) { window.setInterval(NPL_IMS._autoscroll_interval, 70); }
			},
			
			_autoscroll_remove: function ( pElement )
			{
			},
			
			_autoscroll_interval: function ()
			{
				var _mouseX = NPL.Mouse.x();
				var _mouseY = NPL.Mouse.y();
				for ( var _x = 0; _x < _autoscrollers.length; _x++ )
				{
					var _element = _autoscrollers[_x];
					//	Check mouse coordinates against various element coordinates.
					if ( _mouseX > (_element.left()-_fudge) && _mouseY > (_element.top()-_fudge) )
					{
						if ( _mouseX < (_element.left()+_element.width()+_fudge) && _mouseY < (_element.top()+_element.height()+15) )
						{
							//	OK, mouse inside an autoscroll'd element. Get to work.
							//	First, calculate the mouse position relative to the element's (displayed) width and height.
							//	FIXME: Opera 8 has a bug here because it appears to support scrollHeight vs. height() correctly,
							//	but not scrollWidth. No idea WTF it's doing.
							//	Opera 9 appears to have fixed this. Yay Opera.
							if ( _element.scrollHeight() > _element.height() )
							{
								var _scrollFudgeY = Math.floor(_element.height()*.08);
								var _ratioY = (_mouseY - _element.top() - _scrollFudgeY) / (_element.height() - (2*_scrollFudgeY));
								_ratioY = _ratioY > 1 ? 1 : _ratioY < 0 ? 0 : _ratioY;
								var _scrollDeltaY = _element.element().scrollTop - Math.floor((_element.scrollHeight() - _element.height()) * _ratioY);
								var _scrollDirectionY = _scrollDeltaY < 0 ? 1 : -1;
								_scrollDeltaY *= _scrollDirectionY * -1;
								if ( _scrollDeltaY < 5 )
								{
									if ( _ratioY == 0 )
									{
										_element.element().scrollTop = 0;
									}
									else if ( _ratioY == 1 )
									{
										_element.element().scrollTop = _element.scrollHeight() - _element.height();
									}
								}
								else
								{
									if ( _scrollDeltaY > 250 ) { _scrollDeltaY = 250; }
									_element.element().scrollTop = _element.element().scrollTop + _scrollDirectionY * Math.floor(.004*_scrollDeltaY*_scrollDeltaY+6);
								}
							}
							if ( _element.scrollWidth() > _element.width() )
							{
								var _scrollFudgeX = Math.floor(_element.width()*.08);
								var _ratioX = (_mouseX - _element.left() - _scrollFudgeX) / (_element.width() - (2*_scrollFudgeX));
								_ratioX = _ratioX > 1 ? 1 : _ratioX < 0 ? 0 : _ratioX;
								var _scrollDeltaX = _element.element().scrollLeft - Math.floor((_element.scrollWidth() - _element.width()) * _ratioX);
								var _scrollDirectionX = _scrollDeltaX < 0 ? 1 : -1;
								_scrollDeltaX *= _scrollDirectionX * -1;
								if ( _scrollDeltaX < 5 )
								{
									if ( _ratioX == 0 )
									{
										_element.element().scrollLeft = 0;
									}
									else if ( _ratioX == 1 )
									{
										_element.element().scrollLeft = _element.scrollWidth() - _element.width();
									}
								}
								else
								{
									//	Danger: Math with "magic values" ahead. Proceed with caution.
									if ( _scrollDeltaX > 250 ) { _scrollDeltaX = 250; }
									_element.element().scrollLeft = _element.element().scrollLeft + _scrollDirectionX * Math.floor(.004*_scrollDeltaX*_scrollDeltaX+6);
								}
							}
						}
					}
				}
			},
			
			_align_middle: function ( pElement )
			{
				//	Sets an element in the vertical center of its parent.
				var xParent = pElement.element().parentNode;
				if ( typeof xParent != 'undefined' )
				{
					xParent = NPL.Select(xParent);
					xMargin = Math.floor((xParent.height() - pElement.height())/2);
					pElement.style('margin-top', xMargin+'px');
					pElement.style('margin-bottom', xMargin+'px');
				}
			},
			
			_align_center: function ( pElement )
			{
				//	Sets an element in the horizontal center of its parent.
				var xParent = pElement.element().parentNode;
				if ( typeof xParent != 'undefined' )
				{
					xParent = NPL.Select(xParent);
					xMargin = Math.floor((xParent.width() - pElement.width())/2);
					pElement.style('margin-left', xMargin+'px');
					pElement.style('margin-bottom', xMargin+'px');
				}
			},
			
			_ims_expression: function ( pString, pParentExpression )
			{
				//	operator @: Return lExpression as value if rExpression evaluates to true, otherwise null
				//	operator -: Return rExpression subtracted from lExpression
				//	operator +: Return lExpression plus rExpression
				//	operator >: Return true as value if lExpression is more than rExpression
				//	operator <: Return true as value if lExpression is less than rExpression
				//	operator ==: Return true as value if lExpression is equal to rExpression
				this.state = 0;
				// -1: Error
				//	0: Init
				//	1: Getting numeric lExpression
				//	2: Getting numeric lExpression units
				//	3: Getting reference lExpression
				//	4: Expecting reference lExpression property open parens
				//	5: Getting reference lExpression property
				//	6: Done getting reference lExpression property
				//	7: Getting operator
				//	8: Parsing rExpression
				this.operator = '';
				this.value = '';				//	Numeric, or true, or false
				this.valueUnits = '';
				this.reference = '';			//	Could be a selector or an object (window, page) or a variable.
				this.referenceProperty = '';	//	Specific property of the selector or object or variable to use.
				this.lExpression = null;		//	Is never an expression object.
				this.rExpression = null;		//	Might be an expression object.
				this.error_description = '';
				this.i = 0;
				
				/*
				
					expression(value,
									  ^--- expression(value,
														     ^--- expression(value,...
				
				*/
				
				this.parse_error = function ( pError )
				{
					this.state = -1;
					this.error_description = pError;
				};
				
				this.evaluate = function ()
				{
				};
				
				for ( ; this.i < pString.length; this.i++ )
				{
					switch ( pString[this.i] )
					{
					case'0': case'1': case'2': case'3': case'4': case'5': case'6': case'7': case'8': case'9':
						switch ( this.state )
						{
							case 0:	//	Init
								this.state = 1;
								this.value = 0;
							case 1:	//	Getting numeric lExpression
								this.value = this.value * 10 + +pString[i];
								break;
							case 2:	case 3:
								this.parse_error('did not expect to find a number here.');
								break;
						}
						break;
					case'<': case'>': case'=': case'+': case'-': case'@':
						break;
					case'$':
						switch ( this.state )
						{
							case 0:	//	Init
								this.state = 3;
								break;
							default:
								this.parse_error("wasn't expecting a $ here.")
						}
						break;
					case'"':
						switch ( this.state )
						{
							case 0:	//	Init
								this.reference += pString[this.i];
								while ( ++(this.i) < pString.length )
								{
									this.reference += pString[this.i];
									if ( pString[this.i] == '"' ) { break; }
								}
								if ( this.i >= pString.length )
								{
									this.parse_error('reached end of expression while looking for a closing quote.');
								}
								else
								{
									this.state = 4;
								}
								break;
							default:
								this.parse_error("wasn't expecting a quote mark here.");
						}
					case'(':
						switch ( this.state )
						{
							case 0:	case 1: case 2:
								this.parse_error("didn't expect to find opening parentheses here.");
								break;
							case 3: case 4:
								this.state = 5;
								break;
							case 5:	//	Getting reference lExpression property
								this.state = -1;
								this.error_description = "nested parentheses aren't supported.";
								break;
						}
					case')':
						switch ( this.state )
						{
							//	Init, getting numeric value, or getting numeric value units.
							case 0: case 1: case 2:
								this.state = -1;
								this.error_description = "didn't expect to find closing parentheses here.";
								break;
							//	Getting reference lExpression or expecting an open parentheses for an lExpression property.
							case 3: case 4:
								this.state = -1;
								this.error_description = 'expecting an open parentheses but found a closing parentheses instead.';
								break;
							case 5:	//	Getting reference lExpression property
								if ( this.referenceProperty == '' )
								{
									this.state = -1;
									this.error_description = 'found a closing parentheses without a property.';
								}
								else
								{
									this.state = 6;
								}
								break;
							case 6: //	Done with reference lExpression property
								this.state = -1;
								this.error_description = 'too many closing parentheses.';
								break;
						}
					case' ':
						switch ( this.state )
						{
							case 0:
								break;
							case 1:
								//	LEFT OFF HERE. This isn't correct.
								this.state++;
						}
					default:
						switch ( this.state )
						{
							case 1:	//	Getting numeric lExpression
								this.state = 2;
							case 2:	//	Getting numeric lExpression units
								this.valueUnits += pString[i];
								break;
							case 0:	//	Init
								this.state = 3;
							case 3:	//	Getting reference lExpression
								this.reference += pString[i];
								break;
						}
						break;
					}
				}
				//	Validate the contents of the expression here; i.e., certain kinds of references require properties, etc.
			},
			
			_parse_expressions: function ( pSelector, pString )
			{
				//	A complex parser for the various ways that IMS supports describing the dimensions of a thing.
				//	This is an early first attempt; future version should evaluate pString as a series of nested expressions:
				//		i.e., (expr)[@(expr)], (expr)[@(expr)] ...
				//	That would allow things like, "10 - (reference)px", where "10" is considered an expression, and "-(reference)px" is another expression.
				var xExpressionObject = function(){ return {operator: ''} };
				var xExpressions = [];
				var xExpression = new xExpressionObject;
				var xAccumulatorObject = function(){ return {state: 0, value: 0, expression: null, is_conditional: false, units: '', reference: '', reference_property: '', conditional_operator: '', conditional_reference: '', conditional_value: 0, conditional_units: '', error_description: '', error_position: '' }};
				var xAccumulator = new xAccumulatorObject;
				xReturnValue = [];
				var xInQuotes = false, xInParens = false;
				//	Accumulator state:
				//	   -1: error
				//		0: init
				//		1: getting value expression (the expression before an @)
				//		2: getting units for value expression
				//		3: getting reference
				//		4: getting reference property
				//		5: getting conditional operator
				//		6: getting conditional value
				//		7: getting conditional value units
				//		8: getting conditional reference
				//		9: getting conditional reference property
				for ( var i = 0; i < pString.length; i++ )
				{
					switch ( pString[i] )
					{
						case'0': case'1': case'2': case'3': case'4': case'5': case'6': case'7': case'8': case'9':
							switch ( xAccumulator.state )
							{
								case 0:	//	Init
									xAccumulator.state = 1;
								case 1:	//	Getting value
									xAccumulator.value = xAccumulator.value * 10 + +pString[i];
									break;
								case 2:	//	Getting value units
									//	Signal a syntax error: user followed a "units" with a number.
									xAccumulator.state = -1;
									xAccumulator.error_description = 'expected end-of-expression or @ but found a number instead.';
									break;
								case 3:	//	Getting reference
									//	Signal a syntax error: user followed an @ symbol with a number.
									xAccumulator.state = -1;
									xAccumulator.error_description = 'expected a reference or an operator but found a number instead.';
									break;
								case 4:	//	Getting conditional operator (<,=,>)
									xAccumulator.state = 5;
								case 5:	//	Getting conditional value
									xAccumulator.conditional_value = xAccumulator.conditional_value * 10 + +pString[i];
									break;
							}
							break;
						case'@':
							switch ( xAccumulator.state )
							{
								case 0:	//	Init
									xAccumulator.state = -1;
									xAccumulator.error_description = 'expected a value or a reference but found ' + pString[i] + ' instead.';
									break;
								case 1:	//	Getting value
								case 2:	//	Getting value units
									if ( xAccumulator.is_conditional )
									{
										//	Already encountered an @.
										xAccumulator.state = -1;
										xAccumulator.error_description = 'too many @s.';
									}
									else
									{
										xAccumulator.is_conditional = true;
										xAccumulator.state = 3;
									}
									break;
								case 3:	//	Getting reference
									xAccumulator.state = -1;
									xAccumulator.error_description = 'expected <, >, or =, but found ' + pString[i] + ' instead.';
									break;
								case 4:	//	Getting conditional operator
									xAccumulator.state = -1;
									xAccumulator.error_description = 'expected a value or reference but found ' + pString[i] + ' instead.';
									break;
								case 5: //	Getting conditional value
									xAccumulator.state = -1;
									xAccumulator.error_description = 'expected a reference, a comma followed by a new condition, or the end of the line, but found ' + pString[i] + ' instead.';
									break;
							}
							break;
						case'<':
						case'=':
						case'>':
							switch ( xAccumulator.state )
							{
								case 0:	//	Init
									xAccumulator.state = -1;
									xAccumulator.error_description = 'expected a value or a reference but found ' + pString[i] + ' instead.';
									break;
								case 1:	//	Getting value
									xAccumulator.state = -1;
									xAccumulator.error_description = 'expected units, end of line, or @ but found ' + pString[i] + ' instead.';
									break;
								case 2:	//	Getting value units
									xAccumulator.state = -1;
									xAccumulator.error_description = 'expected @ or end of line but found ' + pString[i] + ' instead.';
									break;
								case 3:	//	Getting reference
									xAccumulator.state = 4;
								case 4: //	Getting conditional operator
									xAccumulator.conditional_operator += pString[i];
									break;
								case 5:	//	Getting conditional value
									xAccumulator.state = -1;
									xAccumulator.error_description = 'expected a reference, a comma followed by a new condition, or the end of the line, but found ' + pString[i] + ' instead.';
									break;
							}
							break;
						case' ':
							//	If not inside quotes, then ignore.
							break;
						case'"':
							//	Handle quotes.
							break;
						case',':
							//	Handle new condition.
							xReturnValue[xReturnValue.length] = xAccumulator;
							xAccumulator = new xAccumulatorObject;
							break;
						case'(':
							switch ( xAccumulator.state )
							{
								case 0:	//	Init
									xAccumulator.state = -1;
									xAccumulator.error_description = 'expected a reference, variable, or number but found a property instead.';
									break;
								case 1:	//	Getting value
								case 2:	//	Getting value units
									xAccumulator.state = -1;
									xAccumulator.error_description = 'expected units or @ but found a property instead.';
									break;
								case 3:	//	Getting reference
									if ( xAccumulator.reference == '' )
									{
										xAccumulator.state = -1;
										xAccumulator.error_description = 'expected a variable or reference but found a property instead.';
										break;
									}
									xAccumulator.state =4;
								case 4:	//	Getting reference property
									if ( xInParens == false )
									{
										xInParens = true;
									}
									else
									{
										xAccumulator.state = -1;
										xAccumulator.error_description = "sorry, nested parentheses aren't supported.";
									}
									break;
							}
							break;
						default:
							switch ( xAccumulator.state )
							{
								case 0:	//	Init
									xAccumulator.state = 3;
									xAccumulator.conditional_reference += pString[i];
									break;
								case 1:	//	Getting value
									xAccumulator.state = 2;
								case 2:	//	Getting value units
									xAccumulator.units += pString[i];
									break;
								case 3:	//	Getting reference
									xAccumulator.conditional_reference += pString[i];
									break;
								case 4:	//	Getting conditional operator
									xAccumulator.state = 5;
								case 5:	//	Getting conditional value
									xAccumulator.conditional_value += +pString[i];
									break;
							}
					}
					if ( xAccumulator.state == -1 )
					{
						xAccumulator.error_position = i;
						xAccumulator.error_description = 'Syntax error at character ' + (i + 1) + ' ("' + pString[i] + '"): ' + xAccumulator.error_description;
						i = pString.length;
					}
				}
				xReturnValue[xReturnValue.length] = xAccumulator;
				return xReturnValue;
			}
			
		}
	}();
	
	var _imsElements = NPL.Select('head script[type="text/ims"]');
	if ( _imsElements.count > 0 )
	{
		_imsElements = _imsElements.count == 1 ? [_imsElements.innerHtml()] : _imsElements.innerHtml();
		var xIMSCode = '';
		var xSelector, xProperty, xValue;
		for ( var x in _imsElements )
		{
			x = NPL.CSS.ParseCSS(_imsElements[x]);
			if ( x.length > 0 )
			{
				for ( xSelector in x )
				{
					for ( xProperty in x[xSelector] )
					{
						switch ( xProperty )
						{
							case 'autoscroll':
								NPL.Page.OnLoad(new Function('NPL.Select("' + xSelector + '").IMS("autoscroll")'));
								break;
							case 'font-size':
								gReturnValue = NPL_IMS._parse_dimensions(xSelector, x[xSelector][xProperty]);
								break;
						}
					}
				}
			}
		}
	}

}
