/*----------------------------------
Classes :
	1 - Node > Element
	2 - Exception 
	3 - Node > Fragment
	4 - NodeMap
	5 - Node 
	6 - Node > Widget
	7 - Hierarchy definition
----------------------------------*/
var bDebug = bDebug || false

/*----------------------------------
	1 - class Element
----------------------------------*/
Element =function( sTag , sValue ){
	Node.call( this , sTag , sValue , Node.ELEMENT_NODE )
	this.attributes = NodeMap()
	this.tagName = sTag
	}
Element.prototype.extend({
	hasAttribute :function( s ){
		return this.attributes.getNamedItem( s ) !== null
		},
	getAttribute :function( s ){
		var o = this.getAttributeNode( s )
		return o ? o.value : null
		},
	setAttribute :function( sName , sValue , bReadOnly ){
		var oOld = this.attributes.getNamedItem( sName )
		if( oOld && oOld.readOnly ) return Exception( "NO_MODIFICATION_ALLOWED_ERR" )
		this.setAttributeNode({ name: sName , value: sValue , readOnly: bReadOnly || false })
		return oOld ? oOld.value : null
		},
	removeAttribute :function( s ){
		var o = this.attributes.getNamedItem( s )
		if( o && o.readOnly ) return Exception( "NO_MODIFICATION_ALLOWED_ERR" )
		this.attributes.removeNamedItem( s )
		return o.value
		},
	getAttributeNode :function( s ){
		return this.attributes.getNamedItem( s )
		},
	setAttributeNode :function( o ){
		var s = o.name, mOld
		if( this.hasAttribute( s )) mOld = this.removeAttribute( s )
		this.attributes.setNamedItem( s , o )
		return mOld ? { name : s , value: mOld , readOnly: false } : null
		}
	})

/*----------------------------------
	2 - class Exception 
----------------------------------*/
Exception =function( s ){
	var sLanguage = sLanguage || "fr"
	if( bDebug || false )
		throw new Error ( Exception.getMessage( s ))
	}
Exception.extend({
	messages : {},
	UNDEFINED_ERR : 0,
	HIERARCHY_REQUEST_ERR : 3,
	NO_MODIFICATION_ALLOWED_ERR : 7,
	NOT_FOUND_ERR : 8,
	INUSE_ATTRIBUTE_ERR : 10,
	DUPLICATE_ID : 20,
	getMessage :function( s ){
		return s + "\n" + Exception.messages[ sLanguage ][ Exception[s] || 0 ]
		}
	})
Exception.messages.fr ={
	0:  "Tentative d'ajout ou de suppression d'un noeud inexistant.",
	3:  "Un noeud a été inséré à un endroit non autorisé.",
	7:  "Tentative de modification d'un objet non autorisé.",
	8:  "Tentative d'accès à un noeud inexistant.",
	10: "Tentative d'ajout d'un attribut déjà utilisé dans un autre objet.",
	20: "Identifiant déjà utilisé."
	}

/*----------------------------------
	3 - class Fragment
----------------------------------*/
Fragment =function( a ){
	Node.call( this , null , null , Node.FRAGMENT_NODE )
	this.childNodes = a || []
	}

/*----------------------------------
	4 - NodeMap
----------------------------------*/
NodeMap = function(){
	var aKeys = []
	, aValues = []
	, bReadOnly = bReadOnly || false
	, oList = {}
	return {
		length: 0,
		getKeys :function(){
			return aKeys
			},
		getNamedItem :function( s ){
			return aValues[ oList[ s ]] || null
			},
		item: function( n ){
			for( var i = 0 , ni = aKeys.length ; i < ni ; i++ )
				if( i == n ) return aValues[ oList[ aKeys[ i ]]]
			return null
			},
		removeNamedItem :function( s ){
			if( oList[ s ] != undefined ){
				var n = oList[ s ]
				var mValue = aValues[ n ]
				delete oList[ s ]
				aKeys.remove( s )
				aValues[ n ] = null
				this.length--
				return mValue
				}
			return null
			},
		setNamedItem :function( sName , mNewValue ){
			if( bReadOnly )return Exception ( "NO_MODIFICATION_ALLOWED_ERR" )
			if( arguments.length > 1 ){
				if( ! in_array( sName , aKeys )) aKeys.push( sName )
				var mOldValue = this.removeNamedItem( sName )
				, n = oList[ sName ] = aValues.length 
				aValues[ n ] = mNewValue
				this.length++
				return mOldValue
				}
			}
		}
	}
	
/*----------------------------------
	5 - class Node 
----------------------------------*/
Node =function( s1 , s2 , s3 ){
	if( s1 ) this.nodeName = s1
	if( s2 ) this.nodeValue = s2
	if( s3 ) this.nodeType = s3
	this.childNodes = []
	}
Node.extend({
	ELEMENT_NODE : 1 ,
	WIDGET_NODE : 2 ,
	FRAGMENT_NODE : 3
	}) 
Node.prototype.extend({
	attributes : null,
	nodeName : null,
	nodeValue : null,
	nodeType : null,
	parentNode : null,
	firstChild : null,
	lastChild : null,
	previousSibling : null,
	nextSibling : null,
	ownerWidget : null,
	
	appendChild :function( o ){
		if( ! o ) return Exception( "UNDEFINED_ERR" )
		if( this.readOnly ) return Exception( "NO_MODIFICATION_ALLOWED_ERR" )
		if( o.nodeType == Node.FRAGMENT_NODE ){
			var a = o.childNodes
			for( var i = 0, n = a.length ; i < n ; i++ )
				this.appendChild( a[i])
			return o
			}
		if( o === this ) Exception( "HIERARCHY_REQUEST_ERR" )
		if( o.isAncestor( this )) Exception( "HIERARCHY_REQUEST_ERR" )
		var o2 = o.parentNode
		if( o2 ) o = o2.removeChild ( o )
		o.ownerWidget = this.ownerWidget
		o.nextSibling = null
		o.parentNode = this
		o.previousSibling = this.lastChild
		if( o2 = this.lastChild ) o2.nextSibling = o
		if( this.childNodes.length == 0 ) this.firstChild = o 
		this.lastChild = o
		this.childNodes.push( o )
		if(  this.ownerWidget ) this.ownerWidget.addId( o )
		return o
		},
	cloneNode :function( bDeep ){
		if( this.nodeType == Node.FRAGMENT_NODE ){
			var a = this.childNodes
			for( var i = 0 , aClones = [] , n = a.length ; i < n ; i++ )
				aClones [i] = a [i].cloneNode( bDeep )
			return new this.constructor( aClones )
			}
		var aExclude = [ "ownerWidget" , "childNodes" , "nextSibling" , "previousSibling" , "firstChild" , "lastChild" , "parentNode" ]
		, Clone =function( o , b ){
			this.constructor = o.constructor
			for(var s in o ){
				var m = o[s]
				if( m && ( typeof m == "object" ))
					this[s] = ( in_array( s , aExclude ) || m.ownerDocument )
						? null
						: new Clone ( m , true )
					else this[s] = m
				}
			if( o.childNodes ){
				if( ! b ) this.childNodes = []
					else {
						var a = [] , ni = o.childNodes.length
						for( var i = 0 ; i < ni ; i++ ) a[i] = new Clone ( o.childNodes[i] , b )
						for( var i = 0 ; i < ni ; i++ )
							a[i].extend({
								nextSibling : i < ni - 1 ? a[ i + 1 ] : null,
								previousSibling : i > 0 ? a[ i - 1 ] : null,
								parentNode : this
								})
						this.extend({
							firstChild : a[0] || null,
							lastChild : a[ a.length - 1 ] || null,
							childNodes : a
							})
						}
				}
			this.ownerWidget = o.ownerWidget || null
			}
		var o = new Clone ( this , bDeep )
		return o
		},
	getElementsByTagName :function( s ){
		var a = []
		, o = this.firstChild
		while( o ){
			if( o.nodeName == s || s == "*" ) a.push( o )
			a = a.concat( o.getElementsByTagName( s ))
			o = o.nextSibling
			}
		return a
		},
	hasChildNodes :function(){
		var a = this.childNodes
		return a ? a.length > 0 : false
		},
	insertBefore :function( o , oRefChild ){
		var a
		if( ! o ) return Exception( "UNDEFINED_ERR" )
		if( this.readOnly ) return Exception( "NO_MODIFICATION_ALLOWED_ERR" )
		if( ! oRefChild ) return this.appendChild( o )
		if( o.nodeType == Node.FRAGMENT_NODE ){
			a = o.childNodes
			for( var i = 0, n = a.length ; i < n ; i++ )
				this.insertBefore( a[i] , oRefChild )
			return o
			}
		if( o === this ) Exception( "HIERARCHY_REQUEST_ERR" )
		if( o.isAncestor( this )) Exception( "HIERARCHY_REQUEST_ERR" )  
		if( oRefChild.parentNode !== this ) Exception( "NOT_FOUND_ERR" )
		var a, o2
		if( o2 = o.parentNode ) o = o2.removeChild( o )
		if( o2 = oRefChild.previousSibling ) o2.nextSibling = o 
		oRefChild.previousSibling = o
		if( ! o2 ) this.firstChild = o 
		o.ownerWidget = this.ownerWidget
		o.parentNode = this
		o.previousSibling = o2
		o.nextSibling = oRefChild
		a = this.childNodes
		for( var i = 0, n = a.length ; i < n ; i++ ) if( a[i] === oRefChild ) break
		this.childNodes = a.slice( 0 , i ).concat( [ o ]).concat( a.slice( i , a.length ))
		if(  this.ownerWidget ) this.ownerWidget.addId( o )
		return o
		},
	isAncestor :function( o ){
		while( o = o.parentNode ) if( o === this ) return true
		return false
		},
	isRoot :function(){
		return this.parentNode === this.ownerWidget
		},
	removeChild :function( o ){
		if( ! o ) return Exception( "UNDEFINED_ERR" )
		if( this.readOnly ) return Exception( "NO_MODIFICATION_ALLOWED_ERR" )
		if( o.parentNode != this ) return Exception( "NOT_FOUND_ERR" )
		var o1 = o.nextSibling, o2 = o.previousSibling
		if( o1 ) o1.previousSibling = o2
		if( o2 ) o2.nextSibling = o1
		var a = this.childNodes , bFound = false
		for( var i = 0, n = a.length ; i < n ; i++ )
			if( a [i] === o ){
				bFound = ( a.splice( i , 1 ))[0]
				break
				}
		this.firstChild = a[0]
		this.lastChild = a[ a.length - 1 ]
		this.childNodes = a
		if(  this.ownerWidget ) this.ownerWidget.removeId( o )
		if( bFound ){
			o.ownerWidget = null
			o.parentNode = null
			o.nextSibling = null
			o.previousSibling = null
			return o
			} return Exception( "NOT_FOUND_ERR" )
		},
	replaceChild :function( o1 , o2 ){
		this.insertBefore( o1 , o2 )
		return this.removeChild( o2 )
		}
	})

/*----------------------------------
	6 - class Widget
----------------------------------*/
Widget =function( mName , mValue ){
	Node.call( this , mName || null , mValue || null , Node.WIDGET_NODE )
	var oNodes =  NodeMap ()
	, checkId = function( o , s ){
		if( o.id ) oNodes[ s ]( o.id , o )
		for( var o = o.firstChild ; o ; o = o.nextSibling ) checkId( o , s )
		}
	this.ownerWidget = this
	this.extend({
		getKeys :function(){ return oNodes.getKeys() },
		addId :function( o ){ checkId( o ,"setNamedItem" )},
		eachNodes :function( f ){ for( var i = 0 , ni = oNodes.length ; i < ni ; i++ ) f( oNodes.item( i ))},
		getElementById :function( nId ){ return oNodes.getNamedItem( nId ) || null },
		removeId :function( o ){ checkId( o , "removeNamedItem" )}
		})
	}
	
/*----------------------------------
	7 - Super class definition
----------------------------------*/
Element.extend( Node )
Fragment.extend( Node )
Widget.extend( Node )

TREE_LOADED = true
