// The "right" way to implement the functionality represented by the
// majority of JavaScript libraries that attempt to compensate for
// browser differences seems, to me, to be to use the "Adapter"
// pattern.  
//
// See: Gamma, Helm, Johnson, and Vlissides; "Design
// Patterns: Elements of Reusable Object-Oriented Software", 1st ed.,
// 1994; Addison-Wesley; pp.139-150.
//
// This is an attempt at that.
//
// Since ECMAScript does not provide a mechanism for data-hiding, we
// adopt the convention that the names of internal data begins with an
// underscore. We also get around the classless funniness by adopting
// the Perl aphorism "We'd rather you kept out of our house because
// you're not invited, not because we've a shotgun". 

// Requires Browser.js

WDocument._Default=null;
WDocument.Debug=true;


// Called with a "Document" object (usually just 'document', but maybe
// that in another "Window"), provide for calling with no argument to
// get the "Document" in scope.
function WDocument(d)
{
    this._doc=null; // The browser's DOM object that we're adapting.

    if( d == null ) {
	if( WDocument._Default == null ) {
	    this._doc=document;
	    WDocument._Default = this;
	} else if ( WDocument._Default._doc == document ) {
	    return WDocument._Default;
	} else {
	    // You really shouldn't call this without an argument from
	    // multiple "Window"s, since there's no way of
	    // disambiguating where the call came from since we have
	    // the scope of the HTML file we're included in.
	    if( WDocument.DEBUG ) {
		// Not sure what form the warning should take :o(
		alert('WDocument() [constructor]: Default document assignment failed.');
	    }
	}
    } else {
	this._doc=d;
    }
    this.elementCache = new Object();

    return this;
}

// One of the major purposes of this is to implement
// a 'getElementById' that works everywhere.
WDocument.prototype.getElementById = function(id)
{
    var el;
    if (document.getElementById) {
  	el = this.newElement(this._doc.getElementById(id));
    } else if (document.all) {
	el = this.newElement(this._doc.all[id]);
    } else if (document.layers) {
	el = this.newElement(this._getLayerById(id));
    }
    return el;
}


// _getLayerById() is named by analogy with getElementById, but is
// strictly for Netscape 4.x
WDocument.prototype._getLayerById = function(id, from)
{
    var i;	// iterator
    var l;	// layer sought
    var layers;	// NS4 layers


    if( from == null ) {
	layers = this._doc.layers;
    } else {
	layers = from;
    }

    for (var i=0; i<layers.length; i++) {
	if (layers[i].id == id) {
	    l = layers[i];
	} else if (layers[i].layers.length) {
	    l = this._getLayerById(id, layers[i]);
	}
	// If we've found it, there's no need to keep searching:
	if( l ) {
	    return l;
	}
    }

    // If we get this far, we didn't find it!
    return null;
}

// MSIE 5+ on Windows doesn't correctly handle the CSS2 assertion
//    position: fixed;
// Here, we attempt to fix the problem in a generic way by
// searching for elements with this assertion and adding them
// to a list. We trap the scroll and resize events to reposition
// the elements in the list.
WDocument._FixedElements = new Array();

// Attach event handler for repositioning:
WDocument.prototype.handleFixedPositions = function()
{
    var list=WDocument._FixedElements;

    // Skip searching on old browsers where elements to fix
    // must be registered manually.
    if( Browser.IsIE4 || Browser.IsNS4 ) {
	if( list.length ) {
	    setInterval("WDocument._RepositionFixedElements()", 500);
	    return true;
	}
	return false;
    }

    // We now only have to worry about IE5+ on Windows
    if( (!Browser.IsIE5 || Browser.IsIE5Mac) ) {
	return false;
    }

    var i;
    var win=this._doc.parentWindow;

    // Search for elements with the buggy assertion:
    for(i=0; i<this._doc.all.length; i++) {
	var pos;
	style = Browser.IsIE5 ? this._doc.all[i].currentStyle : this._doc.all[i].style;
	if( style.position=='fixed' ) {
	    el = this.newElement(this._doc.all[i]);
	    el.parentDocument=this;
	    el.fixedLeft = el.left();
	    el.fixedTop  = el.top();
	    el.style.position='absolute';
	    list[list.length]=el;
	}
    }

    // No point registering handlers if theres no elements to fix:
    if( list.length ) {
        win.attachEvent( 'onscroll', WDocument._RepositionFixedElements );
        win.attachEvent( 'onresize', WDocument._RepositionFixedElements );
    }
    return true;
}


// We make this a class method because we seem to lose the value of
// 'this' by the time the event-handler is called, and IE4 needs a
// string in setTimer that can't include 'this' in any case.
WDocument._RepositionFixedElements = function() {
    var self; // This is a pseudo object method
    var el;   // Each element as we iterate
    var i;
    var list=WDocument._FixedElements;

    for(i=0; i<list.length; i++) {
	el=list[i];
	self=el.parentDocument;
	el.moveTo(self.panLeft()+el.fixedLeft, self.scrollTop()+el.fixedTop);
    }
}

// For IE4 we have to explicitly register the elements having fixed
// positions because searching fails. In order for IE4 to be able to
// change the position of something, it *must* have 'position:
// absolute' in the stylesheet *and* be moved under script control;
// sadly, you cannot 'pick up' the stylesheet values in IE4 at all.
WDocument.prototype.fixPositionById = function( id, x, y )
{
    if( !(Browser.IsIE4 || Browser.IsNS4) ) {
	// The element will be autofound when handleFixedPositions is
	// called.
	return false;
    }

    var el=this.getElementById( id );
    if( el == null ) {
	return false;
    }

    var list=WDocument._FixedElements;
    el.parentDocument=this;
    el.moveTo(x,y);
    el.fixedLeft = el.left();
    el.fixedTop  = el.top();
    list[list.length]=el;

    return true;
}

// Get how far the document has been scrolled
WDocument.prototype.scrollTop = function(t)
{
    if( document.layers ) {
	return window.pageYOffset;
    }
    if( document.documentElement && this._doc.documentElement.scrollTop ) {
	return this._doc.documentElement.scrollTop;
    }
    if( document.body && this._doc.body.scrollTop ) {
	return this._doc.body.scrollTop;
    }
    return 0;
}


// Get how far the document has been panned. Using 'pan' rather than
// 'scroll' is me being a bit pedantic.
WDocument.prototype.panLeft = function(l)
{
    if( document.layers ) {
	return window.pageXOffset;
    }
    if( document.documentElement && this._doc.documentElement.scrollLeft ) {
	return this._doc.documentElement.scrollLeft;
    }
    if( document.body && this._doc.body.scrollLeft ) {
	return this._doc.body.scrollLeft;
    }
    return 0;
}


WDocument.prototype.newElement = function(obj)
{
    if( obj == null ) {
	return null;
    }

    if( obj.id && this.elementCache[obj.id] ) {
	return this.elementCache[obj.id];
    }

    var el = new WElement(obj);
    el.parentDocument = this;

    if( obj.id ) {
	el.id = obj.id;
	this.elementCache[obj.id]=el;
    }    
    return el;
}
//============================================================



//============================================================
// Wrapped Element Class
//------------------------------------------------------------

// WElement constructor. All WElements belong to a WDocument,
// just as in the DOM. You shouldn't (but can) create WElements
// directly; instead, use the getElementById() object method of the
// parent document.
function WElement(obj)
{
    if( obj == null ) {
	return null;
    }
	
    this.element = obj;
    if( document.layers ) {
	this.style = obj;
    } else {
	this.style = obj.style;
    }
    return this;
}


//------------------------------------------------------------
// High-level element-positioning methods
//------------------------------------------------------------
WElement.prototype.moveTo = function(x, y)
{
    this.left(x);
    this.top(y);
}


WElement.prototype.moveBy = function(dx, dy)
{
    if( Browser.IsNS4 ) {
	this.element.moveBy(dx,dy);
    } else {
	this.moveTo(this.left()+dx, this.top()+dy);
    }
}

// Occasionally it's handy to be able to set X, Y, and Z at once.
WElement.prototype.moveTo3D = function(x, y, z)
{
    this.moveTo(x,y);
    this.style.zIndex=z;
}

// Move and set z-index at the same time:
WElement.prototype.moveBy3D = function(dx, dy, dz)
{
    this.moveBy(dx,dy);
    this.style.zIndex += dz;
}


// Not really needed since we can uniformly set "this.style.zIndex"
// externally
WElement.prototype.zIndex = function(z)
{
    if( null != z ) {
	this.style.zIndex = z;
    }
    if( this.element.currentStyle ) {
	return this.element.currentStyle.zIndex;
    }
    return this.style.zIndex;
}



//------------------------------------------------------------
// Low-level object-positioning methods
//------------------------------------------------------------

// Set or get left-position
WElement.prototype.left = function(l)
{
    if( null != l ) {
	// We're moving the element:
	if( Browser.IsNS4 ) {
	    this.element.moveTo(l, this.style.top);
	} else if ( Browser.IsIE4 || Browser.IsIE5Mac ) { 
	    this.style.pixelLeft = l;
	} else if ( Browser.IsNS5 || Browser.IsNS6 ) {
	    this.style.left   = l + 'px';
	} else {
	    this.style.left = l;
	}
    } else {
	// Try to use the value in currentStyle (IE6) if the value in
	// style is not set
	if( this.style.left == null || this.style.left == '' ) {
	    return this.element.currentStyle ? parseInt(this.element.currentStyle.left) : NaN;
	}
	return parseInt(this.style.left);
    }
    return l;
}

// Set or get top-position
WElement.prototype.top = function(t)
{
    if( null != t ) {
	// We're moving the element:
	if( Browser.IsNS4 ) {
	    this.element.moveTo(this.style.left, t);
	} else if ( Browser.IsIE4 || Browser.IsIE5Mac ) { 
	    this.style.pixelTop = t;
	} else if ( Browser.IsNS5 || Browser.IsNS6 ) {
	    this.style.top = t + 'px';
	} else {
	    this.style.top = t;
	}
    } else {
	// Try to use the value in currentStyle (IE6) if the value in
	// style is not set
	if( this.style.top == null || this.style.top == '' ) {
	    return this.element.currentStyle ? parseInt(this.element.currentStyle.top) : NaN;
	}
	return parseInt(this.style.top);
    }
}



//------------------------------------------------------------
// Unusual element-positioning methods, added for completeness
//------------------------------------------------------------

// DOMish browsers should grok .right, but don't in general. Sadly,
// the only way of reliably getting this value is with arithmetic.
WElement.prototype.right = function(r)
{
    if( null != r ) {
	this.left(r-this.width());
    } else {
	return this.left()+this.width();
    }
}

// DOMish browsers should grok .bottom, but don't in general. The only
// way of reliably getting this value is with arithmetic.
WElement.prototype.bottom = function(b)
{
    if( null != b ) {
	this.top(b-this.height());
    } else {
	return this.top()+this.height();
    }
}



//------------------------------------------------------------
// Methods related to element size
//------------------------------------------------------------

// Set or get width
WElement.prototype.width = function(w,align)
{
    if( null!=w ) {
	// We need the difference between old and new width for
	// alignment:
	var dw=0;
	if( null!=align && align!='left' ) {
	    dw=this.width()-w;
	}

	// Set the width:
	if( Browser.IsNS4 ) {
	    this.style.clip.width=w;
	} else {
	    this.style.width = w;
	}

	// Adjust position if alignment requested
	if( dw ) {
	    if( align == 'center' ) {
		this.left(this.left()+Math.round(dw/2));
	    } else if ( align == 'right' ) {
		this.left(this.left()+dw);
	    }
	}
    } else {
	if( Browser.IsNS4 ) {
	    return this.style.clip.width;
	}
	// Try to use the value in currentStyle (IE6) if the value in
	// style is not set
	if( this.style.width==null || this.style.width=='' ) {
	    if( this.style.clientWidth ) {
		return parseInt(this.style.clientWidth);
	    }
	    if( this.element.currentStyle ) {
		return parseInt(this.element.currentStyle.width);
	    }
	    return NaN;
	}
	return parseInt(this.style.width);
    }
}


// Set or get width
WElement.prototype.height = function(h, align)
{
    if( null!=h ) {
	// Need current height info for alignment
	var dh=0;
	if( null!=align && align!='left' ) {
	    dh=this.height()-h;
	}

	if( Browser.IsNS4 ) {
	    // Don't think this is possible
	    this.style.clip.height=h;
	} else {
	    this.style.height = h;
	}

	if( dh ) {
	    if( align == 'center' ) {
		this.top(this.top()+Math.round(dh/2));
	    } else if ( align == 'bottom' ) {
		this.top(this.top()+dh);
	    }
	}
    } else {
	// We're getting the height
	if( Browser.IsNS4 ) {
	    return this.style.clip.height;
	}
	// Try to use the value in currentStyle (IE6) if the value in
	// style is not set
	if( this.style.width==null || this.style.width=='' ) {
	    if( this.style.clientWidth ) {
		return parseInt(this.style.clientWidth);
	    }
	    if( this.element.currentStyle ) {
		return parseInt(this.element.currentStyle.width);
	    }
	    return NaN;
	}
	// Not NS4 and 
	return parseInt(this.style.height);
    }
}


WElement.prototype.size = function(w,h,halign,valign)
{
    if(null==w && null==h) {
	return new Array(this.width(), this.height());
    } else {
	if( null!=h ) {
	    this.width(w,halign);
	}
	if( null!=w ) {
	    this.height(h,valign);
	}
    }
    return null;
}


//------------------------------------------------------------
// Methods related to element visibility
//------------------------------------------------------------

WElement.prototype.hide = function()
{
    // Seems to work reliably, but the O'Reilly book uses
    // .visibility='hide' for NS4.x
    this.style.visibility='hidden';
}


WElement.prototype.hidden = function(h)
{
    // Seems to work reliably, but the O'Reilly book uses
    // .visibility='hide' for NS4.x
    if( h ) {
	this.hide();
    } else {
	return !this.isVisible();
    }
}


WElement.prototype.show = function()
{
    // Seems to work reliably, but the O'Reilly book uses
    // .visibility='show' for NS4.x
    this.style.visibility='visible';
}


WElement.prototype.visible = function(v)
{
    if( v ) {
	this.show();
    } else {
	return this.isVisible();
    }
}


// FIXME: Should currentStyle be used for later IEs?
WElement.prototype.isVisible = function()
{
    if( this.style.display == 'none'
	|| this.style.visibility == 'hidden'
	|| this.style.visibility == 'hide' )
    {
	return false;
    }
    return true;
}


WElement.prototype.isHidden = function()
{
    return !this.isVisible();
}
//============================================================


