
var sfAlign_none = 0;
var sfAlign_center = 1;
var sfAlign_middle = 1;
var sfAlign_top = 2;
var sfAlign_bottom = 3;
var sfAlign_left = 4;
var sfAlign_right = 5;

var sfMode_normal = 1;
var sfMode_layout = 2;

if( !__surfaces ) {
	var __surfaces = new Array();
}

if( !__quirks ) {
	var __quirks = false;

	if( typeof document.namespaces != "undefined" )
	{
		// Internet Explorer

		if( document.all[ 0 ].nodeType == 8 ) {
			__quirks = false;
		}
		else {
			__quirks = true;
		}
	}
/*
	else {
		// The others

		if( document.doctype != null ) {
			__quirks = false;
		}
		else {
			__quirks = true;
		}
	}
*/
}

function sfInject( element, debugLog )
{
	var e = getElement( element );

	var sf = new Surface( e, null, debugLog );

	//sf.lock();

	return e;
}

function sfCleanUp()
{
	if( __surfaces.length > 0 ) {
		for( var sf in __surfaces ) {
			__surfaces[ sf ].kill();
			__surfaces[ sf ] = null;
		}

		__surfaces = new Array();
	}
}

function sfPaint( id )
{
	if( !id ) {
		for( var tmpid in __surfaces ) {
			if( __surfaces[ tmpid ] != null) {
				__surfaces[ tmpid ].paint();
			}
		}
	}
	else {
		if( __surfaces[ id ] != null) {
			__surfaces[ id ].paint();
		}
	}
}

function Surface( id, parent, debugLog )
{
	//var self = this;

	// --- internal properties ---

	// mirror properties (locked/unlocked)

	this._Uprop = {
		x: {
			value: 0,
			changed: true
		},
		y: {
			value: 0,
			changed: true
		},
		width: {
			value: 0,
			changed: true
		},
		height: {
			value: 0,
			changed: true
		},
		expand: {
			width: {
				value: false,
				changed: true
			},
			height: {
				value: false,
				changed: true
			}
		},
		optimise: {
			width: {
				value: false,
				changed: true
			},
			height: {
				value: false,
				changed: true
			}
		},
		visible: {
			value: false,
			speed: '',
			changed: true
		 },
		align: {
			
			horz: {
				value: sfAlign_none,
				changed: true
			},
			vert: {
				value: sfAlign_none,
				changed: true
			}
		},
		css: {
			surface: {
				value: '',
				old: '',
				changed: true
			},
			content: {
				value: '',
				old: '',
				changed: true
			}
		},
		text: {
			value: '',
			vAlign: sfAlign_none,
			changed: false
		},
		patch: {
			url: '',
			changed: false,
			patched: false,
			data: '',
			patchId: '',
			arg: null,
			clean: false
		}
	};

	this._Lprop = null;

	this._parentSf = null;

	this._inject = false;

	this._elements = {
		parent: {
			'e': null,
			'display': '',
			'visibility': ''
		},
		surface: {
			'e': null,
			'display': '',
			'visibility': '',
			init: false
		},
		contentWidth: {
			'e': null,
			'display': '',
			'visibility': '',
			init: false
		},
		contentHeight: {	
			'e': null,
			'display': '',
			'visibility': '',
			init: false
		}		
	};

	this._scrollTimer = null;
	
	this._scroll = {
		left: false,
		top: false,
		right: false,
		bottom: false,
		caller: null,
		speed: 4
	};

	this._borders = {
        	cached: false,
		top: {
			value: 0
		},
		left: {
			value: 0
		},
		bottom: {
			value: 0
		},
		right: {
			value: 0
		}
	};

	this._padding = {
		cached: false,
		top: {
			value: 0
		},
		left: {
			value: 0
		},
		bottom: {
			value: 0
		},
		right: {
			value: 0
		}
	};

	this._margin = {
		cached: false,
		top: {
			value: 0
		},
		left: {
			value: 0
		},
		bottom: {
			value: 0
		},
		right: {
			value: 0
		}
	};


	this._callbacks = null;

	/*
	this._events = {
		mouseMove: {
			handler: null,
			changed: false
		},
		mouseDown: {
			handler: null,
			changed: false
		},
		mouseUp: {
			handler: null,
			changed: false
		},
		mouseClick: {
			handler: null,
			changed: false
		}
	}
	*/

	// other internal properties

	this._id = '';
	this._dbg = null;
	this._pipe = null;
	this._children = null;

	this._locked = false;
	this._updated = false;
	this._complete = false;

	this._layoutMode = {
		'mode': 0,
		'recursive': false
	}
/*
	this._layoutModeVars = {

	
		'oldDisplay': '',
		'oldVisibility': ''
	};
*/

	// --- public properties ---

        // --- internal methods ---

	this.construct = function( id, parent, debugLog )
	{
		

		// add this surface to the global surface list
		__surfaces[ id ] = this;

		this._dbg = debugLog;

		this._children = new SurfaceList( this, debugLog );
		this._children.lock();

		this._callbacks = new Array();

		this._layoutMode = {
			'mode': sfMode_normal,
			'recursive': false
		}

		if( parent ) {

			this._id = id;

				
			// there will always be a surface element (unless we inject this surface),
			// so we create one by default
			this._elements.surface.e = createElement( 'div' );			
			this._elements.surface.e.id = this.getId() + '_surfaceElement';

			if( parent.__isSurface ) {
				// we have a parent surface

				this._pipe = parent.__getPipe();

				this._parentSf = parent;
				this._elements.parent.e = parent.__getAttachmentElement();

				parent.getChildren().add( this );
			}
			else {
				// we have a parent element
				
				this._pipe = new SurfacePipe( id + '_pipe', this, debugLog );
				this._elements.parent.e = getElement( parent );
			}

			this._inject = false;
		}
		else {
			// we are injecting the surface into an existing element

			var tmp = getElement( id );

			if( tmp ) {

				this._id = tmp.id;

				this._pipe = new SurfacePipe( id + '_pipe', this, debugLog );

				this._elements.surface.e = tmp;
				this._elements.parent.e = tmp.parentNode;

				this._elements.surface.e.sf = this;	


				this._updated = true;

				this._inject = true;

				this.__retrieveCss( this._elements.surface.e );
			}
			else except( this.__class, 'construct', 'The element \'' + id + '\' does not exist - I cannot be injected into it.', this.getId() );
		}
	}

	this.kill = function()
	{
		this._children.kill();
		this._children = null;
		
		this._pipe.stop();
		this._pipe = null;

		if( this._scrollTimer != null ) {
			this._scrollTimer.stop();
			this._scrollTimer = null;		
		}

		if( this._elements.surface.e != null ) $( this._elements.surface.e ).empty();
		if( this._elements.contentWidth.e != null ) $( this._elements.contentWidth.e ).empty();
		if( this._elements.contentHeight.e != null ) $( this._elements.contentHeight.e ).empty();
	}


	this.__retrieveCss = function( element )
	{

		this.setCssClass();

		this.setX( element.offsetLeft );
		this.setY( element.offsetTop );

		this.setWidth( element.offsetWidth );
		this.setHeight( element.offsetHeight );

		this.setCssClass( element.className );
		this.setContentCssClass( element.className );

		this._updated = true;

		this.getBorder();
		this.getPadding();
		this.getMargin();

	}

	this.__getPipe = function()
	{
		return this._pipe;
	}

	this.__getAttachmentElement = function()
	{
		return this._elements.surface.e;
	}

/*
	this.__exception = function( fname, message )
	{
		var m = '';

		if( this._id != null ) {
			m += '[' + this._id + '] ';
		}

		m += this.__class + '.' + fname + '(): ' + message + '.';
		
		throw m;
	}
*/

	this.__unlocked = function( fname, fn )
	{
		if( !this._locked ) {
			this._updated = false;
			return fn( this );
		}
		else except( this.__class, fname, this.__class + ' must NOT be LOCKED to perform this operation', this.getId() );
	}

	this.__locked = function( fname, fn )
	{
		if( this._locked ) {
			return fn( this );
		}
		else except( this.__class, fname, this.__class + ' MUST be LOCKED to perform this operation', this.getId() );
	}

	this.__updated = function( fname, fn )
	{
		if( this._updated ) {
			return fn( this );
		}
		else except( this.__class, fname, this.__class + ' MUST be UPDATED first, to perform this operation - please call \'update()\' at least once', this.getId() );
	}

	this.__setCallBack = function( cbname, cb, arg1, arg2, arg3 )
	{
		var tmp = {
			"fn": cb,
			"arg1": arg1,
			"arg2": arg2,
			"arg3": arg3
		};

		if( !this._callbacks[ cbname ] ) this._callbacks[ cbname ] = new Queue();

		this._callbacks[ cbname ].queue( tmp );

		

	}

	this.__callback = function( cbname )
	{
		var success = true;

		if( this._callbacks[ cbname ] ) {

			var callback  = null;

			if( this._callbacks[ cbname ].count() > 0 ) {

				for( var c = 0; c < this._callbacks[ cbname ].count(); c++ ) {

					callback = this._callbacks[ cbname ].retrieve();

					if( callback != null ) {
						if( callback.fn != null ) {
							callback.fn( this, callback.arg1, callback.arg2, callback.arg3 );				
						}
						else success = false;
					}
					else success = false;
				}

				
			}
			else success = false;
		}

		return success;
	}

	this.__clearEventListener = function( event )
	{

	}

	this.__addEventListener = function( event, handler )
	{
		var e = this._elements.surface.e;
		addEventListener( e, event, handler );
	}


	// --- public methods ---

	// isSurface is always true
	this.__isSurface = true;
	this.__class = 'Surface';

	this.getChildren = function()
	{
		return this._children;
	}

	// functions to be used when unlocked:

	this.getPipe = function()
	{
		return this.__getPipe();
	}

	this.lock = function( recursive )
	{
		this.__unlocked( 'lock', function( self ) {

			//self._Lprop = self._Uprop;

			self._complete = false;
			self._setComplete = false;

                        self._locked = true;
                        
                        // when this surface is locked, you can add children

                	self._children.unlock();


			// _Uprop --> _Lprop (lock)
			self._Lprop = {
				x: {
					value: self._Uprop.x.value,
					changed: self._Uprop.x.changed
				},
				y: {
					value: self._Uprop.y.value,
					changed: self._Uprop.y.changed
				},
				width: {
					value: self._Uprop.width.value,
					changed: self._Uprop.width.changed
				},
				height: {
					value: self._Uprop.height.value,
					changed: self._Uprop.height.changed
				},

				expand: {
					width: {
						changed: self._Uprop.expand.width.changed,
						value: self._Uprop.expand.width.value
					},
					height: {
						changed: self._Uprop.expand.height.changed,
						value: self._Uprop.expand.height.value
					}
				},

				optimise: {
					width: {
						changed: self._Uprop.optimise.width.changed,
						value: self._Uprop.optimise.width.value
					},
					height: {
						changed: self._Uprop.optimise.height.changed,
						value: self._Uprop.optimise.height.value
					}
				},

				visible: {
					value: false,
					speed: '',
					changed: false
				 },

				align: {

					horz: {
						value: self._Uprop.align.horz.value,
						changed: self._Uprop.align.horz.changed
					},
					vert: {
						value: self._Uprop.align.vert.value,
						changed: self._Uprop.align.vert.changed
					}
				},

				css: {
					surface: {
						value: self._Uprop.css.surface.value,
						old: self._Uprop.css.surface.old,
						changed: self._Uprop.css.surface.changed
					},
					content: {
						value: self._Uprop.css.content.value,
						old: self._Uprop.css.surface.old,
						changed: self._Uprop.css.content.changed
					}
				},
				text: {
					value: self._Uprop.text.value,
					changed: self._Uprop.text.changed,
					vAlign: self._Uprop.text.vAlign
				},
				patch: {
					url: self._Uprop.patch.url,
					changed: self._Uprop.patch.changed,
					patched: self._Uprop.patch.patched,
					data: self._Uprop.patch.data,
					patchId: self._Uprop.patch.patchId,
					arg: self._Uprop.patch.arg,
					clean: self._Uprop.patch.clean
				}
			};


			if( recursive ) self.lockChildren();
		});
	}

	this.lockChildren = function()
	{
		this.__locked( 'lockChildren', function( self ) {

			if( self._children.count() > 0 ) {
				for( var c = 0; c < self._children.count(); c++ ) {

					var child = self._children.get( c );
					if( !child.isLocked() ) child.lock();
	
				}
			}
		});
	}

	this.unlockChildren = function()
	{
		this.__locked( 'unlockChildren', function( self ) {

			if( self._children.count() > 0 ) {
				for( var c = 0; c < self._children.count(); c++ ) {

					var child = self._children.get( c );
					if( child.isLocked() ) child.unlock();
	
				}
			}
		});
	}

	this.isLocked = function()
	{
		return this._locked;
	}

	/*
	this.setId = function( id )
	{

		this.__unlocked( 'setId', function( self ) {
			self._id = id;
		});

	}
	*/

	this.setX = function( x )
	{
		this.__unlocked( 'setX', function( self ) {
                	
                	var v = self._Uprop.x;

                	v.value = x;
                	v.changed = true;
		});

	}

	this.setY = function( y )
	{
		this.__unlocked( 'setY', function( self ) {

                	var v = self._Uprop.y;

                	v.value = y;
                	v.changed = true;

		});

	}

	this.setHeight = function( height )
	{
		this.__unlocked( 'setHeight', function( self ) {

                	var v = self._Uprop.height;

                	v.value = height;
                	v.changed = true;



		});

	}

	this.setWidth = function( width )
	{
		this.__unlocked( 'setWidth', function( self ) {

                	var v = self._Uprop.width;

                	v.value = width;
                	v.changed = true;

		});

	}

	this.setCssClass = function( className )
	{
		this.__unlocked( 'setCssClass', function( self ) {

                	var v = self._Uprop.css.surface;
		
			v.old = v.value;

                	v.value = className;
                	v.changed = true;

                	self._padding.cached = false;
                	self._borders.cached = false;
		});

	}

	this.revertCssClass = function()
	{
		this.__unlocked( 'revertCssClass', function( self ) {

                	var v = self._Uprop.css.surface;

			if( v.old != '' ) {
				self.setCssClass( v.old );
				v.old = '';
			}

		});

	}
	

	this.setContentCssClass = function( className )
	{
		this.__unlocked( 'setContentCssClass', function( self ) {

                	var v = self._Uprop.css.content;

                	v.value = className;
                	v.changed = true;
                	
                	self._padding.cached = false;
                	self._borders.cached = false;

		});

	}

	this.expand = function( width, height )
	{

		this.__unlocked( 'expand', function( self ) {

			

                	var o = self._Uprop.optimise;
                	var e = self._Uprop.expand;

			if( e.width.value != width ) {
				e.width.changed = true;
				e.width.value = width;
			}
                	if( width == true ) o.width.value = false;


			if( e.height.value != height) {
				e.height.changed = true;
				e.height.value = height;
			}
                	if( height == true ) o.height.value = false;

		});

	}

	this.optimise = function( width, height )
	{
		this.__unlocked( 'optimise', function( self ) {

                	var o = self._Uprop.optimise;
                	var e = self._Uprop.expand;

			if( o.width.value != width ) {
				o.width.changed = true;
				o.width.value = width;
			}
			if( width == true ) e.width.value = false;

			if( o.height.value != height ) {
				o.height.changed = true;
				o.height.value = height;
			}
			if( height == true ) e.height.value = false; 
			

		});

	}

	this.hAlign = function( alignment )
	{
		this.__unlocked( 'hAlign', function( self ) {

                	var h = self._Uprop.align.horz;

                	h.value = alignment;
                	h.changed = true;

		});

	}

	this.vAlign = function( alignment )
	{
		this.__unlocked( 'vAlign', function( self ) {

                	var v = self._Uprop.align.vert;

                	v.value = alignment;
                	v.changed = true;
		});

	}

	this.setText = function( string, vAlign )
	{
		this.__unlocked( 'setText', function( self ) {

                	var t = self._Uprop.text;

                	t.value = string;
                	
                	if( ( vAlign == sfAlign_none ) || ( vAlign == sfAlign_top ) || ( vAlign == sfAlign_middle ) || ( vAlign == sfAlign_bottom ) ) {
	                	t.vAlign = vAlign;
	                }
	                else t.vAlign = sfAlign_middle;

                	t.changed = true;


		});

	}

	// functions to be used when locked:

/*
	this.clone = function( newX, newY, fromSurface )
	{

	}
*/

	this.getId = function()
	{
		return this._id;
	}

	this.getX = function()
	{
                return this._Lprop.x.value;
	}

	this.getY = function()
	{
               	return this._Lprop.y.value;
	}

	this.getHeight = function( options )
	{
		var v = this._Lprop.height.value;

		if( options ) {
			if( !options.margin ) {
				v = v - ( this.getMargin().top + this.getMargin().bottom );
			}
			if( !options.border ) {
				v = v - ( this.getBorder().top + this.getBorder().bottom );
			}
			if( !options.padding ) {
				v = v - ( this.getPadding().top + this.getPadding().bottom );
			}
		}

		return v;
	}

	this.getWidth = function( options )
	{
		var v = this._Lprop.width.value;

		if( options ) {
			if( !options.margin ) {
				v = v - ( this.getMargin().left + this.getMargin().right );
			}
			if( !options.border ) {
				v = v - ( this.getBorder().left + this.getBorder().right );
			}
			if( !options.padding ) {
				v = v - ( this.getPadding().left + this.getPadding().right );
			}
		}

		return v;
	}

	this.isVisible = function()
	{
               	return this._Lprop.visible.value;
	}

	this.isExpanded = function()
	{
              	return {
                      	width: this._Lprop.expand.width.value,
			height: this._Lprop.expand.height.value
		}
	}

	this.isOptimised = function()
	{

              	return {
                       	width: this._Lprop.optimise.width.value,
			height: this._Lprop.optimise.height.value
		}
	}

	this.isUpdated = function()
	{
		return this.__locked( 'isUpdated', function( self ) {
			return self._updated;
		});
	}

	this.getCssClass = function()
	{
               	return this._Lprop.css.surface.value;
	}

	this.getContentCssClass = function()
	{
               	return this._Lprop.css.content.value;
	}

	this.getContentWidth = function()
	{
		return this.__updated( 'getContentWidth', function( self ) {
			if( self._elements.contentWidth.e != null ) {
	                       	//return $( self._elements.contentWidth.e ).outerWidth();

				return $( self._elements.contentWidth.e ).width();
			}
			else return 0;
		});
	}

	this.getContentHeight = function()
	{
		return this.__updated( 'getContentHeight', function( self ) {
			if( self._elements.contentHeight.e != null ) {
				//return $( self._elements.contentHeight.e ).outerHeight();

                        	return $( self._elements.contentHeight.e ).height();
			}
			else return 0;
		});
	}

	this.getBorder = function()
	{
		var ret = { top: 0, left: 0, bottom: 0, right: 0 };

		this.__updated( 'getBorders', function( self ) {


                       	if( self._borders.cached == false ) {

                               	var s = self._elements.surface.e;

				var st = computedStyle( s );

                               	ret.top = parseStyleValue( st.borderTopWidth );
                               	ret.bottom = parseStyleValue( st.borderBottomWidth );
                               	ret.left = parseStyleValue( st.borderLeftWidth );
				ret.right = parseStyleValue( st.borderRightWidth );

                                self._borders.top.value = ret.top;
                                self._borders.left.value = ret.left;
                                self._borders.bottom.value = ret.bottom;
                                self._borders.right.value = ret.right;

                                self._borders.cached = true;

		 	}
		 	else {

                              	ret.top = self._borders.top.value,
                              	ret.left = self._borders.left.value,
                              	ret.bottom = self._borders.bottom.value,
                               	ret.right = self._borders.right.value
			}

		});

		return ret;
	}

	this.getPadding = function()
	{
		var ret = { top: 0, left: 0, bottom: 0, right: 0 };


		this.__updated( 'getPadding', function( self ) {



                       	if( self._padding.cached == false ) {

                               	var s = self._elements.surface.e;

				var st = computedStyle( s );

                              	ret.top = parseStyleValue( st.paddingTop );
                               	ret.bottom = parseStyleValue( st.paddingBottom );
                               	ret.left = parseStyleValue( st.paddingLeft );
				ret.right = parseStyleValue( st.paddingRight );

                                self._padding.top.value = ret.top;
                                self._padding.left.value = ret.left;
                                self._padding.bottom.value = ret.bottom;
                                self._padding.right.value = ret.right;

                                self._padding.cached = true;

		 	}
		 	else {
                               	ret.top = self._padding.top.value,
                               	ret.left = self._padding.left.value,
                           	ret.bottom = self._padding.bottom.value,
                             	ret.right = self._padding.right.value
			}
		});

		return ret;

	}

	this.getMargin = function()
	{
		var ret = { top: 0, left: 0, bottom: 0, right: 0 };


		this.__updated( 'getMargin', function( self ) {



                       	if( self._margin.cached == false ) {

                               	var s = self._elements.surface.e;

				var st = computedStyle( s );

                               	ret.top = parseStyleValue( st.marginTop );
                               	ret.bottom = parseStyleValue( st.marginBottom );
                               	ret.left = parseStyleValue( st.marginLeft );
				ret.right = parseStyleValue( st.marginRight );

                                self._margin.top.value = ret.top;
                                self._margin.left.value = ret.left;
                                self._margin.bottom.value = ret.bottom;
                                self._margin.right.value = ret.right;

                                self._margin.cached = true;

		 	}
		 	else {

                           	ret.top = self._margin.top.value,
                              	ret.left = self._margin.left.value,
                             	ret.bottom = self._margin.bottom.value,
                               	ret.right = self._margin.right.value
			}

		});

		return ret;

	}




	this.showChildren = function( speed )
	{
		this.__locked( 'showChildren', function( self ) {

			if( self._children.count() > 0 ) {
				for( var c = 0; c < self._children.count(); c++ ) {

					var child = self._children.get( c );

					child.show( speed );
					child.showChildren( speed );
				
				}
			}


		});
	}

	this.hideChildren = function( speed )
	{
		this.__locked( 'hideChildren', function( self ) {

			if( self._children.count() > 0 ) {
				for( var c = 0; c < self._children.count(); c++ ) {

					var child = self._children.get( c );

					child.hideChildren( speed );
					child.hide( speed );		
				}
			}


		});
	}


	this.show = function( speed )
	{
		this.__locked( 'show', function( self ) {
                	self._Lprop.visible.value = true;

                	if( speed ) {
	                	self._Lprop.visible.speed = speed;
	                }
	                else {
				self._Lprop.visible.speed = '';
			}

                	self._Lprop.visible.changed = true;
                	
                	self.update();
		});
	}

	this.hide = function( speed )
	{
		this.__locked( 'hide', function( self ) {

                	self._Lprop.visible.value = false;

                	if( speed ) {
	                	self._Lprop.visible.speed = speed;
	                }
	                else {
				self._Lprop.visible.speed = '';
			}

                	self._Lprop.visible.changed = true;

                	self.update();
		});
	}


	this.mode = function( mode, recursive )
	{
		this.__locked( 'mode', function( self ) {
                	//self._pipe.queueCmd( sfCmd_mode, , self );

			if( ( mode == sfMode_layout) || ( mode == sfMode_normal ) ) {

				var r;

				if( !recursive ) r = false; r = recursive;

				self._layoutMode = { 'mode': mode, 'recursive': r, 'changed': true }
			}

		});	
	}

	this.__doMode = function( layoutMode )
	{
		if( layoutMode.changed == true ) {

			if( layoutMode.mode == sfMode_layout ) {
	
				// -- set up the surface element --
				var s = this._elements.surface.e;		
				var cH = this._elements.contentHeight.e;
				var cW = this._elements.contentWidth.e;

				this._elements.surface.visibility = 'visible';
				this._elements.surface.display = s.style.display;

				if( cW != null ) this._elements.contentWidth.visibility = 'visible';
				if( cH != null ) this._elements.contentHeight.visibility = 'visible';


				if( this._Lprop.visible.value == true ) {
					$(s).css( 'visibility', 'visible' );

					if( cW != null ) $(cW).css( 'visibility', 'visible' );
					if( cH != null ) $(cH).css( 'visibility', 'visible' );
				}
				else {

					$(s).css( 'visibility', 'hidden' );
				
					if( cW != null ) $(cW).css( 'visibility', 'hidden' );					
					if( cH != null ) $(cH).css( 'visibility', 'hidden' );

				}

				$(s).css( 'display', 'block' );
				if( cH != null ) $(cH).css( 'display', 'block' );
				if( cW != null ) $(cW).css( 'display', 'inline' );
			}

			else if( layoutMode.mode == sfMode_normal ) {

				// -- set up the surface element --
				var s = this._elements.surface.e;	
				var cH = this._elements.contentHeight.e;
				var cW = this._elements.contentWidth.e;

				$(s).css( 'display', this._elements.surface.display );
				$(s).css( 'visibility', 'visible' );

				if( cW != null ) {
					$(cW).css( 'visibility', 'visible' );
					$(cW).css( 'display', 'inline' );
				}

				if( cH != null ) {
					$(cH).css( 'visibility', 'visible' );
					$(cH).css( 'display', 'block' );
				}
			

			}

			if( layoutMode.recursive == true ) {
				if( this._children.count() > 0 ) {
					for( var c = 0; c < this._children.count(); c++ ) {
						var child = this._children.get( c );
						child.__doMode( layoutMode );
					}
				}
			}

			layoutMode.changed = false;
		}
	}


	this.unlock = function( recursive )
	{
		if( !this._locked ) {
			this.__callback( 'unlock' );
		}
		else {
			this.__locked( 'unlock', function( self ) {
				self._pipe.queueCmd( sfCmd_unlock, { "recursive": recursive }, self );
			});
		}
	}



	this.__doUnlock = function( recursive )
	{
		this.__locked( 'doUnlock', function( self ) {

			self._locked = false;


			if( self._children != null ) {
				self._children.lock();
			}

			// do the callback

			// _Lprop --> _Uprop (unlock)
			self._Uprop = {
				x: {
					value: self._Lprop.x.value,
					changed: false
				},
				y: {
					value: self._Lprop.y.value,
					changed: false
				},
				width: {
					value: self._Lprop.width.value,
					changed: false
				},
				height: {
					value: self._Lprop.height.value,
					changed: false
				},
				expand: {
					width: {
						changed: false,
						value: self._Lprop.expand.width.value
					},
					height: {
						changed: false,
						value: self._Lprop.expand.height.value
					}
				},
				optimise: {
					width: {
						changed: false,
						value: self._Lprop.optimise.width.value
					},
					height: {
						changed: false,
						value: self._Lprop.optimise.height.value
					}
				},
				visible: {
					value: self._Lprop.visible.value,
					speed: self._Lprop.visible.speed,
					changed: false
				 },
				align: {

					horz: {
						value: self._Lprop.align.horz.value,
						changed: false
					},
					vert: {
						value: self._Lprop.align.vert.value,
						changed: false
					}
				},
				css: {
					surface: {
						value: self._Lprop.css.surface.value,
						old: self._Lprop.css.surface.old,
						changed: false
					},
					content: {
						value: self._Lprop.css.content.value,
						old: self._Lprop.css.surface.old,
						changed: false
					}
				},
				text: {
					value: self._Lprop.text.value,
					vAlign: self._Lprop.text.vAlign,
					changed: false
				},
				patch: {
					url: self._Lprop.patch.url,
					changed: false,
					patched: self._Lprop.patch.patched,
					data:  self._Lprop.patch.data,
					patchId:  self._Lprop.patch.patchId,
					arg: self._Lprop.patch.arg,
					clean: self._Lprop.patch.clean
				}
			};

                        self.__callback( 'unlock' );

			if( recursive ) self.unlockChildren();
		});
	}

	this.update = function( force )
	{
		this.__locked( 'update', function( self ) {
                	self._pipe.queueCmd( sfCmd_update, { force: force }, self );

			//FIXME: temp - MUST BE QUEUED!!!
			//self.doUpdate( force );
		});
	}

	this.__doUpdate = function( force )
	{
		if( !force ) force = false;

		this.__locked( 'doUpdate', function( self ) {

			var patch = self._Lprop.patch;

			self.onPatch( function( self ) {

				self._updated = true;

				// --- initialisation of the elements ---
				var o, s, cW, cH;

				// -- set up the parent element --
				o = self._elements.parent.e;

				// -- set up the surface element --
				s = self._elements.surface.e;

				// -- set up the contentWidth element
				cW = self._elements.contentWidth.e;

				// -- set up the contentHeight element
				cH = self._elements.contentHeight.e;


				// short-cuts
				var css = self._Lprop.css;
				var text = self._Lprop.text;
				var expand = self._Lprop.expand;
				var optimise = self._Lprop.optimise;
				var align = self._Lprop.align;
				var x = self._Lprop.x;
				var y = self._Lprop.y;
				var width = self._Lprop.width;
				var height = self._Lprop.height;
				var visible = self._Lprop.visible;
		

				if( self._elements.surface.init == false ) {
	

					// -- set up the surface element --

					s = self._elements.surface.e;

					$(s).css( 'overflow', 'hidden' );

					$(s).hide();

					//FIXME!!!
					//$(s).css( 'border', '1px solid #000000' );

/*
					if( self._parentSf != null ) {
						// position absolute if our parent is a surface
						$(s).css( 'position', 'absolute' );
                	        	
						self._elements.parent.e = self._parentSf.__getAttachmentElement();
					}
					else {
						// position relative if our parent is a element
						$(s).css( 'position', 'relative' );
					}
*/

					// -- set up the parent element --

					o = self._elements.parent.e;

					if( self._inject == false ) {

						if( self._parentSf != null ) {
							// position absolute if our parent is a surface
							$(s).css( 'position', 'absolute' );
                	        	
							self._elements.parent.e = self._parentSf.__getAttachmentElement();
						}
						else {

							if( o == document.body ) {
								// position relative if our parent is a element
								$(s).css( 'position', 'absolute' );
							}
							else {
								// position relative if our parent is a element
								$(s).css( 'position', 'relative' );
							}
						}


						if( o != null ) {
							o.appendChild( self._elements.surface.e );
						}
						else except( self.__class, 'doUpdate', 'No parent element found.', self.getId() );
					}

					//alert( 'created ' + self.getId() );

					self._elements.surface.init = true;
				}

				// -- text --

				if( ( text.changed == true ) || ( ( patch.patched == true ) && ( patch.changed == true ) ) ) {
						

					// -- set up the contentHeight element --

					if( ( text.changed == true ) || ( ( patch.clean == false ) && ( patch.changed == true ) ) ) {

						if( self._elements.contentHeight.init == false ) {

							$(s).empty();

							self._elements.contentHeight.e = createElement( 'span' );
							s.appendChild( self._elements.contentHeight.e );
							cH = self._elements.contentHeight.e;
							cH.id = self.getId() + '_contentHeightElement';

							$(cH).css( 'display', 'block' );
							$(cH).css( 'margin', '0px' );
							$(cH).css( 'border', '0px' );
							$(cH).css( 'padding', '0px' );
							//$(cH).css( 'visibility', 'visible' );

							/*
							$(cH).css( 'position', 'relative' );
							$(cH).css( 'top', '10px' );
							$(cH).css( 'background-color', 'red' );
							*/

							self._elements.contentHeight.init = true;
						}

						// -- set up the contentWidth element --
						if(  self._elements.contentWidth.init == false ) {
							self._elements.contentWidth.e = createElement( 'span' );
							cH.appendChild( self._elements.contentWidth.e );
							cW = self._elements.contentWidth.e;
							cW.id = self.getId() + '_contentWidthElement';

							$(cW).css( 'display', 'inline' );
							$(cW).css( 'white-space', 'nowrap' );
							//$(cW).css( 'visibility', 'visible' );

							self._elements.contentWidth.init = true;
						}

						if( text.changed == true ) {
							$(cW).empty().append( text.value );
						}

						text.changed = false;
					}

					// --- patch ---



					if( ( patch.patched == true ) && ( patch.changed == true ) ) {
						patch.changed = false;			

						if( patch.clean == true ) $(s).empty().append( patch.data ); else $(cW).empty().append( patch.data );
					}

				}

				// --- set the layout mode ---
				self.__doMode( self._layoutMode );

				//$(s).empty().append( 'X' );


				// -- css --
				if( ( css.surface.changed == true ) || ( css.content.changed == true ) || ( force == true ) ) {

					if( ( css.surface.changed == true ) || ( force == true ) ) {
						css.surface.changed = false;

						s.className = css.surface.value;

					}

					if( ( css.content.changed == true ) || ( force == true ) ) {
					
						if( cH != null ) {
							css.content.changed = false;
							cH.className = css.content.value;
						}
					}

				}

				// -- expand/optimise in width --



				if( expand.width.value == true ) {


					if( ( expand.width.changed == true ) || ( force == true ) ) {
						// expand in width
						expand.width.changed = false;

						width.changed = true;
						x.changed = true;

						x.value = 0;				

						var tmpW;

						if( self._parentSf ) {
							tmpW = self._parentSf.getWidth();							
						}
						else {
							tmpW = $(o).width();
						}

						width.value = tmpW;
					}
				}
				else if( optimise.width.value == true ) {

					if( ( optimise.width.changed == true ) || ( force == true ) ) {
						optimise.width.changed = false;

						var oldDisplay = $(s).css( 'display' );
						$(s).css( 'display', 'block' );

						$(s).show();

						width.changed = true;

						var m = self.getMargin().left + self.getMargin().right;
						var p = self.getPadding().left + self.getPadding().right;
						var b = self.getBorder().left + self.getBorder().right;

						var max = self.getContentWidth();

						//alert( 'First contentWidth(): ' + self.getContentWidth() );

						if( self._children.count() > 0 ) {
							var sf = null;

							var tmp = 0;
							for( k = 0; k < self._children.count(); k++ ) {

								sf = self._children.get( k );
								tmp = sf.getX() + sf.getWidth();
								if( tmp > max ) max = tmp;
							}
						}

						width.value = max + m + p + b;

						$(s).css( 'display', oldDisplay );
					}

				}

				// -- expand/optimise in height --

				if( expand.height.value == true ) {
					if( ( expand.height.changed == true ) || ( force == true ) ) {
						// expand in height
						
						expand.height.changed = false;

						height.changed = true;
						y.changed = true;

						y.value = 0;

						var tmpH;

						if( self._parentSf ) {
							tmpH = self._parentSf.getHeight();							
						}
						else {
							tmpH = $(o).height();
						}

						height.value = tmpH;
					}

					
				}
				else if( optimise.height.value == true ) {

					if( ( optimise.height.changed == true ) || ( force == true ) ) {
						optimise.height.changed = false;

						var oldDisplay = $(s).css( 'display' );
						$(s).css( 'display', 'block' );

						$(s).show();

						height.changed = true;

						var m = self.getMargin().top + self.getMargin().bottom;
						var p = self.getPadding().top + self.getPadding().bottom;
						var b = self.getBorder().top + self.getBorder().bottom;

						var max = self.getContentHeight();

						if( self._children.count() > 0 ) {
							var sf = null;
							var tmp = 0;

							for( k = 0; k < self._children.count(); k++ ) {
								sf = self._children.get( k );
								tmp = sf.getY() + sf.getHeight();
								if( tmp > max ) max = tmp;
							}
						}

						height.value = max + m + p + b;

						$(s).css( 'display', oldDisplay );
					}

					
				}

				// -- align --

				if( ( align.horz.value != sfAlign_none ) || ( expand.width == false ) ) {

					if( ( align.horz.changed == true ) || ( force == true ) ) {

						x.changed = true;

						var tmpW;

						if( self._parentSf ) {
							tmpW = self._parentSf.getWidth();							
						}
						else {
							tmpW = $(o).width();
						}

						if( align.horz.value == sfAlign_left ) {
							x.value = 0;

						}
						else if( align.horz.value == sfAlign_center ) {
							x.value = ( tmpW / 2 ) - ( self.getWidth() / 2 );

						}
						else if( align.horz.value == sfAlign_right ) {

							x.value = tmpW - self.getWidth()

						}

					}


				}

				if( ( align.vert.value != sfAlign_none ) || ( expand.height == false ) ) {

					if( ( align.vert.changed == true ) || ( force == true ) ) {

						y.changed = true;

						var tmpH;

						if( self._parentSf ) {
							tmpH = self._parentSf.getHeight();							
						}
						else {
							tmpH = $(o).height();
						}


						if( align.vert.value == sfAlign_top ) {

							y.value = 0;
						}
						else if( align.vert.value == sfAlign_center ) {

							y.value = ( tmpH / 2 ) - ( self.getHeight() / 2 );

						}
						else if( align.vert.value == sfAlign_bottom ) {

							y.value = tmpH - self.getHeight()


						}


					}


				}

				// -- x / y --
				if( ( x.changed == true ) || ( force == true ) ) {
					x.changed = false;

					var m = self.getMargin().left;
					$(s).css( 'left', x.value );

				}

				if( ( y.changed == true ) || ( force == true ) ) {
					y.changed = false;

					if( __quirks == false ) {
						var m = self.getMargin().top;
						$(s).css( 'top', y.value + m );
					}
					else {
						$(s).css( 'top', y.value );
					}

				}


				// -- width / height --
				if( ( width.changed == true ) || ( force == true ) ) {

					width.changed = false;



					var m = self.getMargin().left + self.getMargin().right;

					if( __quirks == false ) {


						var b = self.getBorder().left + self.getBorder().right;
						var p = self.getPadding().left + self.getPadding().right;

						$(s).width( width.value - b - p - m );
					}
					else {
						$(s).width( width.value - m );
					}

				}

				if( ( height.changed == true ) || ( force == true ) ) {

					height.changed = false;

					var m = self.getMargin().top + self.getMargin().bottom;

					if( __quirks == false ) {


						var b = self.getBorder().top + self.getBorder().bottom;
						var p = self.getPadding().top + self.getPadding().bottom;

						$(s).height( height.value - b - p - m );
					}
					else {
						$(s).height( height.value - m  );
					}

					

					// -- vertical alignment of text --

					if( text.vAlign == sfAlign_none ) {
					}
					else if( text.vAlign == sfAlign_top ) {
					}
					else if( text.vAlign == sfAlign_middle ) {

						var b = self.getBorder().top + self.getBorder().bottom;
						var p = self.getPadding().top + self.getPadding().bottom;

						$(s).css( 'line-height', ( height.value - b - p - m ) + 'px' );
					}

					else if( text.vAlign == sfAlign_bottom ) {

					}

				}

				/*
				if( modify.optimise.value == true )
				alert( 'Second contentWidth(): ' + self.getContentWidth() );
				*/

				// -- visible --
				if( ( visible.changed == true ) || ( force == true ) ) {

					var cb = null;

					if( visible.value == true ) {

//alert( self.getId() + ' ' + self._callbacks[ 'show' ] );

						//o.appendChild( self._elements.surface.e );
						if( ( visible.speed == 'fast' ) || ( visible.speed == 'medium' ) || ( visible.speed == 'slow' ) ) {

							if( self._callbacks[ 'show' ] != null ) {
								var r = self._callbacks[ 'show' ].retrieve();
								if( r ) cb = r.fn;
							}
						
							

							$(s).fadeIn( visible.speed, cb );
						}
						else {
							$(s).show();
							self.__callback( 'show' );
						}

						self._elements.surface.display = s.style.display;
					}
					else {


						if( ( visible.speed == 'fast' ) || ( visible.speed == 'medium' ) || ( visible.speed == 'slow' ) ) {

							if( self._callbacks[ 'hide' ] != null ) {
								var r = self._callbacks[ 'hide' ].retrieve();
								if( r ) cb = r.fn;
							}

							$(s).fadeOut( visible.speed, cb );

							
							
						}
						else {
							$(s).hide();
							self.__callback( 'hide' );
						}

					}


				}
			


				// -- do callback --

				self.__callback( 'update' );

			});

			

			if( ( patch.changed == true ) && ( patch.patched == false ) ) {

				// --- do patch ---

				var tmp = self._Lprop.patch.arg;

				if( !tmp ) {
					tmp = new Object();
				}

				

				tmp.get = 'patch';
				tmp.sfId = self.getId();

				if( patch.patchId != '' ) {
					tmp.patchId = patch.patchId;
				}


				$.get( patch.url, tmp, function( ret, code ) {
					self.__doPatch( ret, code );
				});

			}

			

		});
	}

	this.patch = function( srcUrl, patchId, arg, clean )
	{
		this.__unlocked( 'patch', function( self ) {
			self._Uprop.patch.patched = false;
			self._Uprop.patch.changed = true;			
			self._Uprop.patch.url = srcUrl;

			if( !clean ) self._Uprop.patch.clean = false; else self._Uprop.patch.clean = clean;

			if( arg ) {
				self._Uprop.patch.arg = arg;
			}
			else self._Uprop.patch.arg = null;

			if( patchId ) {
				self._Uprop.patch.patchId = patchId;
			}
			else {
				self._Uprop.patch.patchId = '';
			}
		});
	}

	this.__doPatch = function( ret, code ) 
	{
		
		

		if( code == 'success' ) {

			var data = parseJSON( ret );			
			var self = __surfaces[ data.sfId ];

			if( !self._Lprop.patch.patched ) {
				self._Lprop.patch.patched = true;
				//self._Lprop.patch.changed = false;

				self._Lprop.patch.data = data.data;

				self.__doUpdate();

				self.__callback( 'patch' );
			}
		}
		else {
			self.setText( 'Error with patch' );
		}
	}

	this.onPatch = function( callback, arg1, arg2, arg3 )
	{
		this.__locked( 'onPatch', function( self ) {

			self.__setCallBack( 'patch', callback, arg1, arg2, arg3 );

			if( ( self._Lprop.patch.changed == false ) || ( self._Lprop.patch.patched == true ) ) {
				self.__callback( 'patch' );
			}
		});
	}

	this.paint = function( full )
	{

                this.__locked( 'paint', function( sf ) {
                	// -- process the pipe --


			sf._pipe.onDone( function( pipe ) {
				sf.__callback( 'paint' );
			});

			sf._pipe.start( full );
		});



	}

	this.complete = function()
	{
		this.__locked( 'complete', function( self ) {			
			self._pipe.queueCmd( sfCmd_complete, null, self );
		});
	}



	this.__doComplete = function()
	{
		this._complete = true;
	}

	this.isComplete = function()
	{
		var ret = this._complete;
		
		if( this._children.count() > 0 ) {

                	var c = 0;

			while( ( ret == true ) && ( c < this._children.count() ) ) {
				ret = this._children.get( c ).isComplete();
                        	c++;
			}
		}

		return ret;
	}


	this.checkComplete = function()
	{
		var ret = this.isComplete();

		if( ret == true ) {

			if( this._dbg ) {
				if( this.__callback( 'complete' ) ) {
					this._dbg.message( this.getId(), 'checkComplete', 'COMPLETED', 'reverseGreen' );
				}
			}

			if( this._parentSf != null ) {
				this._parentSf.checkComplete();
			}		
		}

		return ret;
	}


	this.onPaint = function( callback, arg1, arg2, arg3 )
	{
		this.__locked( 'onPaint', function( self ) {
                	var cb = self._callbacks;
			self.__setCallBack( 'paint', callback, arg1, arg2, arg3 );
		});
	}

	this.onUnlock = function( callback, arg1, arg2, arg3 )
	{
		this.__setCallBack( 'unlock', callback, arg1, arg2, arg3 );
	}




	this.onUpdate = function( callback, arg1, arg2, arg3 )
	{
		this.__locked( 'onUpdate', function( self ) {
			self.__setCallBack( 'update', callback, arg1, arg2, arg3 );
		});

	}

	this.onShow = function( callback, arg1, arg2, arg3 )
	{
		this.__locked( 'onShow', function( self ) {
			self.__setCallBack( 'show', callback, arg1, arg2, arg3 );
		});

	}

	this.onHide = function( callback, arg1, arg2, arg3 )
	{
		this.__locked( 'onHide', function( self ) {
			self.__setCallBack( 'hide', callback, arg1, arg2, arg3 );
		});

	}

	this.onComplete = function( callback, arg1, arg2, arg3 )
	{
		this.__locked( 'onComplete', function( self ) {
			self.__setCallBack( 'complete', callback, arg1, arg2, arg3 );
		});
	}


	this.startScroll = function( left, top, right, bottom, speed, caller )
	{
		var self = this;

		this.__locked( 'startScroll', function( self ) {



                	if( self._scrollTimer == null ) {

	                	self._scroll.left = left;
				self._scroll.top = top;
				self._scroll.right = right;
				self._scroll.bottom = bottom;
				self._scroll.speed = speed;
				self._scroll.caller = caller;


				$.timer( 1, function( timer ) {
                	        	self._scrollTimer = timer;
					self._tickScroll( self );
				});

			}

		});
	}

	this.stopScroll = function()
	{
		var self = this;

		this.__locked( 'stopScroll', function( self ) {
			if( self._scrollTimer != null ) {
	                	self._scrollTimer.stop();
				self._scrollTimer = null;
			}
		});

	}

	this.getScrollLength = function()
	{
		//var s = this._elements.surface.e;

		

		return {
			width: this._elements.surface.e.scrollWidth,
			height: this._elements.surface.e.scrollHeight
		};

	}

	this.getScrollPosition = function()
	{
		var s = this._elements.surface.e;

		return {
			top: s.scrollTop,
			left: s.scrollLeft
		};

	}

	this._tickScroll = function( self )
	{
		var s = self._elements.surface.e;

		//window.status = s.scrollTop;

		if( self._scroll.left == true ) {
                	if( s.scrollLeft > 0 ) s.scrollLeft -= self._scroll.speed;
		}
		else if( self._scroll.right == true ) {
                	if( s.scrollLeft < s.scrollWidth ) s.scrollLeft += self._scroll.speed;
		}


		if( self._scroll.top == true ) {
                       	if( s.scrollTop > 0 ) s.scrollTop -= self._scroll.speed;
		}
		else if( self._scroll.bottom == true ) {
			if( s.scrollTop < s.scrollHeight) s.scrollTop += self._scroll.speed;
		}

		if( self._scroll.caller ) {
			self._scroll.caller.tick( self );
		}

	}

	this.scrollTo = function( x, y )
	{
		var s = this._elements.surface.e;


		if( x ) {
			s.scrollLeft = x;
		}

		if( y ) {
			s.scrollTop = y;
		}


	}

	this.onMouseOver = function( handler )
	{
		this.__locked( 'onMouseOver', function( self ) {
			self.__updated( 'onMouseOver', function( self ) {
				self.__clearEventListener( event_mouseOver );
				self.__addEventListener( event_mouseOver, handler );
			});
		});
	}

	this.onMouseDown = function( handler )
	{
		this.__locked( 'onMouseDown', function( self ) {
			self.__updated( 'onMouseDown', function( self ) {
				self.__clearEventListener( event_mouseDown );
				self.__addEventListener( event_mouseDown, handler );
			});
		});
	}

	this.onMouseUp = function( handler )
	{
		this.__locked( 'onMouseUp', function( self ) {
			self.__updated( 'onMouseUp', function( self ) {
				self.__clearEventListener( event_mouseUp );
				self.__addEventListener( event_mouseUp, handler );
			});
		});
	}

	this.onMouseClick = function( handler )
	{
		this.__locked( 'onMouseClick', function( self ) {
			self.__updated( 'onMouseClick', function( self ) {
				self.__clearEventListener( event_mouseClick );
				self.__addEventListener( event_mouseClick, handler );
			});
		});
	}

	this.onMouseOut = function( handler )
	{
		this.__locked( 'onMouseOut', function( self ) {
			self.__updated( 'onMouseOut', function( self ) {
				self.__clearEventListener( event_mouseOut );
				self.__addEventListener( event_mouseOut, handler );
			});
		});
	}

	this.onMouseMove = function( handler )
	{
		this.__locked( 'onMouseMove', function( self ) {
			self.__updated( 'onMouseMove', function( self ) {
				self.__clearEventListener( event_mouseMove );
				self.__addEventListener( event_mouseMove, handler );
			});
		});
	}


	this.construct( id, parent, debugLog );
}
