//  References:
//  http://www.howtocreate.co.uk/tutorials/javascript/objects -- JavaScript objects, private/public properties

/*

    OK. After several hours of frustrating trial-and-error, objects can now be
    "extended", while keeping their private bits private. Doing this in JavaScript
    was not hard, just unclear.
    
    Objects which can have functions dynamically added to them -- can be "extended" --
    have a meta Prototype object stored in the NPL object: NPL.Prototype.__ObjectName__
    
    You can use, "NPL.Prototype.__ObjectName__.myfunction =..." to add a function
    to that object's class. Note that any objects which are instantiated before
    the function is added will not have that function added to them. It doesn't
    work like Javascript's built-in prototype class that way.
    
    I can not find a way to get private methods and properties to work while
    keeping Javascript's built-in prototype functionality, so I have duplicated
    it.
    
    Also, dynamically-added functions do not have access to the object's private
    properties and methods. There does not appear to be a way to overcome this
    in Javascript, but it's considered correct behavior anyway.
    
    You can freely add public properties, however, which will stay local to the
    object that inherits them.

*/

if ( typeof NPL !== 'undefined' )
{
    if ( typeof _NPL_Dbg != 'undefined' ) { _NPL_Dbg('NPLib: "NPL" already defined.'); }
}
else
{
    //  NPL is a singleton which uses closures for some private properties and methods.
    //  There _is_ a way to instantiate another instance of this thing, but that would be a silly thing to do.
    //  All of the NPL functions are encapsulated here to protect them from namespace collisions.
    //  This little library is fully buzzword compliant. :-(
    var NPL = function ()
    {
        //  http://www.hunlock.com/blogs/Closing_The_Book_On_Javascript_Closures
        //  Private properties.
        _uidcount = 1;
        //  Private methods.
        //  Public:
        return {
        
            Mouse: function ()
            {
                _x = 0; _y = 0;
                __get_Mouse_XY = function ( pEvent )
                {
                    if ( typeof pEvent == 'undefined' )
                    {
                        if ( typeof window.event != 'undefined' )
                        {
                            if ( typeof window.event.clientX != 'undefined' )
                            {
                                __get_Mouse_XY = function() 
                                {
                                    _x = window.event.clientX+document.body.scrollLeft+document.documentElement.scrollLeft;
                                    _y = window.event.clientY+document.body.scrollTop+document.documentElement.scrollTop;
                                }
                            }
                        }
                    }
                    else
                    {
                        if ( typeof pEvent.pageX != 'undefined' )
                        {
                            __get_Mouse_XY = function(e){_x=e.pageX;_y=e.pageY;}
                        }
                    }
                };
                document.onmousemove = function (pEvent) { NPL.Mouse.update(pEvent) };
                if ( window.captureEvents ) { window.captureEvents(Event.MOUSEMOVE); }
                return {
                
                    x: function() { return _x; },
                    
                    y: function() { return _y; },
                    
                    update: function ( pEvent ) { __get_Mouse_XY(pEvent); }
                
                }
            }(),
            
            Page: function ()
            {
                //  Private properties.
                _onload = window.onload; window.onload = function () { NPL.Page.LaunchInits() };
                _loaded = false;
                _initfunctions = [];
                return {
                
                    OnLoad: function ( pInitFunction )
                    {
                        _initfunctions[_initfunctions.length] = pInitFunction;
                    },

                    LaunchInits: function ()
                    {
                        if ( ! _loaded )
                        {
                            for ( var xInit = 0; xInit < _initfunctions.length; xInit++ ) { _initfunctions[xInit](); }
                            delete _initfunctions; 
                            if ( _onload ) { _onload(); }
                            delete _onload;
                            _loaded = true;
                        }
                    },
                
                    Width: function ()
                    {
                        document.width ? parseInt(document.width) : parseInt(document.body.clientWidth);
                    }
                }
            }(),
                                
            NewUID: function ()
            {
                var xDate = new Date();
                return (++_uidcount).toString() + xDate.getTime().toString().substring(6) + (1000 + Math.floor(Math.random() * 1000)).toString();
            },
            
            Select: function ( pSelector )
            {
                return new ElementSet(pSelector);
            },
            
            Prototype: function ()
            {
                return {
                    
                    __ElementSet__: function ()
                    {                        
                        return this;
                    }()
                    
                }
            }()
        }
    }();
}

function Point ( pX, pY )
{
    return {
        x: pX,
        y: pY
    }
}

function Bounds ( pLeft, pRight, pTop, pBottom )
{
    var left = pLeft, right = pRight, top = pTop, bottom = pBottom;
    this.CoordsInBounds = CoordsInBounds;
    
    function CoordsInBounds ( pX, pY )
    {
        return ( pX < this.left || pX > this.right || pY < this.top || pY > this.bottom ) ? false : true;
    }
}

function ElementSet ( pSelector )
{
	var _id = '';                  //  ID of the elements we're looking for.
    var _class = '';               //  ClassName of the elements we're looking for.
	var _tag = '';                 //  TagName of the elements we're looking for.
	var _elements = [document];    //  Init xMatches to the document element.
	var _matches, i, x;
	pSelector += ' ';	//	Append a space; used in the for loop below on the last selector.
	for ( i = 0; i < pSelector.length; i++ )
	{
		switch ( pSelector.charAt(i) )
		{
			case '#':
			    _id = pSelector.substring(++i, i=pSelector.indexOf(' ', i));
			    i--;
				break;
			case '.':
			    _class = pSelector.substring(++i, i=pSelector.indexOf(' ', i));
                i--;
				break;
			case ' ':
                if ( _id )
                {
                    //  Enforce unique element IDs. See also: http://www.w3.org/TR/REC-html40/struct/global.html#h-7.5.2
                    _elements = [];
                    _matches = document.getElementById(_id);
                    if ( _matches )
                    {
                        if ( (!_tag) || (_tag && (_matches.tagName.toLowerCase() == _tag.toLowerCase())) )
                        {
                            _elements.push(_matches);
                        }
                    }
                }
                else if ( _class )
                {
                    //  First get all of the descendant elements of each of the currently-matched
                    //  elements whose tag match xSearchTag.
                    _matches = [];
                    if ( ! _tag ) { _tag = '*'; }
                    for ( x in _elements )
                    {
                    	_iesucks = _elements[x].getElementsByTagName(_tag);
                    	if ( typeof _iesucks == 'object' )
                    	{
                    		//	IE sucks. :-(
                    		//	See also http://www.codingforums.com/archive/index.php/t-152936.html
                    		//	and http://www.nczonline.net/blog/2007/12/13/ie-com-reers-its-ugly-head/
                    		//	Even better, I also apparently can not use "for ( y in _iesucks )",
                    		//	because Internet Explorer does indeed munch much ass.
                    		for ( y = 0; y < _iesucks.length; y++ ) { _matches.push(_iesucks[y]); }
                    	}
                    	else
                    	{
                    		_matches = _matches.concat(Array.prototype.slice.call(_iesucks));
                    	}
                    }
                    //  Now clear out the old _elements heap.
                    _elements = [];
                    //  ...And rebuild it from the new set of matches, where the xSearchClass matches what we're looking for.
                    for ( x in _matches )
                    {
                        if ( _matches[x]['className'] == _class )
                        {
                        	_elements.push(_matches[x]);
                        }
                    }
                    //  Whew.
                }
                else if ( _tag )
                {
                    //  Similar to above.
                    _matches = [];
                    for ( var x = 0; x < _elements.length; x++ )
                    {
                    	_iesucks = _elements[x].getElementsByTagName(_tag);
                    	if ( typeof _iesucks == 'object' )
                    	{
                    		for ( y = 0; y < _iesucks.length; y++ ) { _matches.push(_iesucks[y]); }
                    	}
                    	else
                    	{
                        	_matches = _matches.concat(Array.prototype.slice.call(_iesucks));
                        }
                    }
                    _elements = _matches.slice(0);
                }				
				_id = _class = _tag = '';
				break;
			default:
                _tag += pSelector.charAt(i);
		}
	}
	if ( (_elements.length == 1) && (_elements[0] === document) )
	{
	   //  Do not return the document element.
	   _elements = [];
	}
	
	/*
	   
	   The following section of code is a little "interesting", but not that bad
	   once you get an idea of what's going on.
	   
	   NPL is lazy -- it won't do anything unless it's asked to. On the other hand,
	   once it's asked to do something, it tries to do that thing as efficiently as
	   possible.
	   
	   So, the first time an ElementSet is created, it sets itself up with a couple
	   of functions -- __get_E_Opacity and __set_E_Opacity -- designed to bootstrap
	   opacity handling.
	   
	   The first time that the opacity() function is called, the bootstrapped functions
	   get called, which in turn determine the required opacity syntax for the host
	   browser. Then they save a copy of the new opacity functions into the NPL object,
	   and finally they overwrite themselves so that the next time they get called,
	   they use the new code tailored to the host browser.
	   
	   From that point on, any new ElementSet objects that get created will find the
	   code stored in the NPL object, and use those functions instead of the bootstrap
	   version.
	   
	*/
	
	if ( typeof NPL.__set_E_Opacity != 'undefined' )
	{
        __get_E_Opacity = NPL.__get_E_Opacity;
        __set_E_Opacity = NPL.__set_E_Opacity;
    }
    else
    {
    	__set_E_Opacity = function(e,o)
    	{
    	    // See if another ElementSet has set this up while this ElementSet
    	    // was instantiated.
    	    if ( typeof NPL.__get_E_Opacity != 'undefined' )
    	    {
    	        __get_E_Opacity = NPL.__get_E_Opacity;
    	        __set_E_Opacity = NPL.__set_E_Opacity;
    	        if ( typeof o != 'undefined' )
    	        {
    	           NPL.__set_E_Opacity(e,o);
    	        }
    	        else
    	        {
    	           return NPL.__get_E_Opacity(e);
    	        }
    	    }
    	    else
    	    {
                if (e.style)
                {
                    if ( typeof e.style.MozOpacity != 'undefined' )
                    {
                        NPL.__get_E_Opacity = function(e){return e.style ? e.style.MozOpacity : 1};
                        NPL.__set_E_Opacity = function(e,o){if (e.style){e.style.MozOpacity = o}};
                    }
                    else if ( typeof e.style.opacity != 'undefined' )
                    {
                        NPL.__get_E_Opacity = function(e){return e.style ? e.style.opacity : 1};
                        NPL.__set_E_Opacity = function(e,o){if (e.style){e.style.opacity = o}};
                    }
                    else if ( typeof e.style.filter != 'undefined' )
                    {
                        NPL.__get_E_Opacity = function(e){return e.style ? e.style.filter.match(/([0-9]+)/) / 100 : 1};
                        NPL.__set_E_Opacity = function(e,o){if (e.style){o.style.filter = "alpha(opacity=" + o*100 + ")"}};
                    }
                    else
                    {
                        NPL.__get_E_Opacity = function(e){return 1;};
                        NPL.__set_E_Opacity = function(e,o){};
                    }
                    __get_E_Opacity = NPL.__get_E_Opacity;
                    __set_E_Opacity = NPL.__set_E_Opacity;
                    if ( typeof o != 'undefined' )
                    {
                        NPL.__set_E_Opacity(e,o);
                    }
                    else
                    {
                        return NPL.__get_E_Opacity(e);
                    }
                }
            }
    	};
    	__get_E_Opacity = __set_E_Opacity;
    }
	
	if ( typeof NPL.__get_E_Style != 'undefined' )
	{
	   __get_E_Style = NPL.__get_E_Style;
	   __set_E_Style = NPL.__set_E_Style;
	}
	else
	{
	   __set_E_Style = function(e,a,v)     //  element, attribute, value
	   {
	       if ( typeof NPL.__get_E_Style != 'undefined' )
	       {
	           __get_E_Style = NPL.__get_E_Style;
	           __set_E_Style = NPL.__set_E_Style;
	           if ( typeof v != 'undefined' )
	           {
	               NPL.__set_E_Style(e,a,v);
	           }
	           else
	           {
	               return NPL.__get_E_Style(e,a);
	           }
	       }
	       else
	       {
    	       if ( document.defaultView && document.defaultView.getComputedStyle )
    	       {
    	           //  Notes on below:
    	           //  Mozilla does not use camelCase for its selectors.
    	           //  (Some versions of) Safari will literally hang if getComputedStyle returns null.
    	           NPL.__get_E_Style = function(e,a){var s = document.defaultView.getComputedStyle(e, null);return s === null ? '' : s[a] ? s[a] : ''};
    	           NPL.__set_E_Style = function(e,a,v){e.style[a] = v};
    	       }
    	       else if ( e.currentStyle )
    	       {
                    //  Notes on below:
                    //  The anonymous function in replace accepts the string (x) and the first sub string match (y), and then returns
                    //  the string to replace the match with. This is due to IE's (correct) use of camelCase for its css selectors. 
                    //  Other browsers ("IE") use camelCase for their selectors: http://www.quirksmode.org/dom/getstyles.html
                    //  Replace: https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Global_Objects/String/Replace
                    NPL.__get_E_Style = function(e,a){a=a.replace(/\-(.)/g, function(x, y){return y.toUpperCase()});return e.currentStyle[a] ? e.currentStyle[a] : ''};
                    NPL.__set_E_Style = function(e,a,v){a=a.replace(/\-(.)/g, function(x, y){return y.toUpperCase()});e.style[a] = v};
    	       }
    	       else
    	       {
    	           NPL.__get_E_Style = function(e,a){return ''};
    	           NPL.__set_E_Style = function(e,a,v){};
    	       }
    	       __get_E_Style = NPL.__get_E_Style;
    	       __set_E_Style = NPL.__set_E_Style;
    	       if ( typeof v != 'undefined' )
    	       {
    	           NPL.__set_E_Style(e,a,v);
    	       }
    	       else
    	       {
    	           return NPL.__get_E_Style(e,a);
    	       }
           }
	   };
	   __get_E_Style = __set_E_Style;
	}
	
	__set_E_Onclick = function(e,v)
	{
		e.onclick = v;
	}
	
	__get_E_Onclick = function(e)
	{
		return e.onclick;
	}
		
	//     _for_all_do and _for_all_get are meta functions with horrible syntax
	//     and wonderful utility. Pass them a function as the first parameter and
	//     up to four other parameters, and they'll call the function with the
	//     four parameters on each of the elements in the set.
	_for_all_do = function(f, a, b, c, d)
	{
	   for ( x in _elements ) { f(_elements[x], a, b, c, d); }
	};
	
	_for_all_get = function(f)
	{
	   if ( _elements.length < 2 ) { return _elements.length < 1 ? [] : f(_elements[0]); }
	   var xReturnValue = [];
	   for ( x in _elements ) { xReturnValue[x] = f(_elements[x]); }
	   return xReturnValue;
	};
	
	delete _id;
	delete _class;
	delete _tag;
	delete _matches;
	delete i;
	delete x;
	var _counter = 0;
	
	// Merge in dynamic functions from the prototype for this object.
    for ( var x in NPL.Prototype.__ElementSet__ )
    {
        this[x] = NPL.Prototype.__ElementSet__[x];
    }
	
    this.length = _elements.length;
	
    this.element = function ( pElementIndex )
    {
		if ( typeof pElementIndex != 'undefined' ) { _counter = pElementIndex; }
		//	Internet Explorer has a problem parsing the next line:
		//	if ( _counter < _elements.length ) { return _elements[_counter++]; }
		//	...and people wonder why my code is often a little strange.
		if ( _counter < _elements.length )
		{
			_iesucks = _elements[_counter];
			_counter++;
			return _iesucks;
		}
		_counter = 0;
		return null;
    };
	   
    this.elements = function ()
    {
        return _elements.length > 0 ? _elements.splice() : [];
    };
    
    this.width = function ( pWidth )
    {
        //  http://www.quirksmode.org/dom/getstyles.html
        if ( typeof pWidth == 'undefined' ) { return _for_all_get(function(e){return e.offsetWidth}); }
        _for_all_do(function(e,w){e.style.width = w}, pWidth); 
    };
    
    this.height = function ( pHeight )
    {
        if ( typeof pHeight == 'undefined' ) { return _for_all_get(function(e){return e.offsetHeight}); }
        _for_all_do(function(e,h){e.style.height = h}, pHeight);
    };

    this.style = function ( pStyleAttr, pStyleValue )
    {
        //  Return (or set) the values for pStyleAttr for each element.
        //  Note that IE will return "auto" for the CSS width of any element
        //  that has not actually had its width set by CSS. You might want to use
        //  the width() function instead.
        if ( typeof pStyleValue == 'undefined' ) { return _for_all_get(function(e, a){return __get_E_Style(e, a)}, pStyleAttr); }
        _for_all_do(function(e, a, b){__set_E_Style(e, a, b)}, pStyleAttr, pStyleValue);
    };
    
    this.opacity = function ( pOpacity )
    {
        if ( typeof pOpacity == 'undefined' ) { return _for_all_get(function(e){return __get_E_Opacity(e)}); }
        _for_all_do(function(e,o){__set_E_Opacity(e, o)}, pOpacity);
    };
    
    this.onclick = function ( pFunction )
    {
    	if ( typeof pFunction == 'undefined' ) { return _for_all_get(function(e){return __get_E_Onclick(e)}); }
    	_for_all_do(function(e,v){__set_E_Onclick(e,v)}, pFunction);
    }
    
    this.click = function ()
    {
    	_for_all_do(function(e){__do_E_Click(e)});
    }
    
    return this;
}

