var CONSTRAIN_NO = 0;						// don't constrain by parent's dimensions
var CONSTRAIN_PROPORTIONAL = 1;			// constrain by moving just as much of the element as is needed to show it completely
var CONSTRAIN_SWITCH_ORIENTATION = 2;	// constrain by switching the orientation to the opposite corner of the element.


ElementContainer = Base.extend ({
   element:null,
   constructor:function ( className, elementName ) {
   	if ( !elementName ) {
   		elementName = 'div';
   	}
      this.element = CE ( elementName, className );
   },
   
   
   addTo:function ( el ) {
      el.appendChild (this.element);
		this.doLayout();
   },
   
   add:function ( node ) {
      this.element.appendChild ( node );
   },
   
   setStyle:function(hash){
      Element.setStyle ( 
         this.element,
         hash
      ); 
   },
   doLayout:function () {},
   
   getElement:function () { return this.element; },
	
	getWidth:function ( withLeftMargin, withRightMargin ) {
		return Element.getWidth(this.getElement(), withLeftMargin, withRightMargin );
	},
	
	getHeight:function ( withTopMargin, withBottomMargin ) {
		return Element.getHeight(this.getElement(), withTopMargin, withBottomMargin  );
	}
});

Movable = Base.extend ({
   setPosition:function ( x, y ) {
		switch ( this.constrainByParent ) {
			case CONSTRAIN_NO:
			
				break;
			case CONSTRAIN_PROPORTIONAL:
	         if ( x + this.getWidth ( true, false ) > this.element.offsetParent.offsetWidth ) {
					x = this.element.offsetParent.offsetWidth - this.getWidth ();
	         } else if ( x < 0 ) {
					x = 0;
	         }
				
				if ( y + this.getHeight( true, false ) > this.element.offsetParent.offsetHeight ) {
					y = this.element.offsetParent.offsetHeight - this.getHeight ();
				} else if ( y < 0 ) {
					y = 0;
				}
				break;
			case CONSTRAIN_SWITCH_ORIENTATION:
				if ( x + this.getWidth ( true, false ) > this.element.offsetParent.offsetWidth ) {
					x = x - this.getWidth ( true, false );
				} 
				if ( x < 0 || isNaN(x) ){
					x = 0;
				}
				
				if ( y + this.getHeight ( true, false ) > this.element.offsetParent.offsetHeight ) {
					y = y - this.getHeight ( true, false );
				} 
				if ( y < 0 || isNaN(y) ) {
					y = 0;
				}
				break;
		}
      Element.setPosition(this.element,x,y);
   },
   
   setSize:function (w, h) {
      Element.setSize(this.element,w,h);
		this.doLayout();
   },
   
   setConstrainByParent:function ( constrain ) {
      this.constrainByParent = constrain;
   },
   
   setPositionAdjacent:function ( element, yOrientation, xOrientation ) {
   	var pos = getAbsolutePositionOf(element);
   	var myDim = Element.getDimensions(this.element);
		
		var newX = 0;
		var newY = 0;
		switch ( xOrientation ) {
			case 'right':
				newX = pos.x + (element.offsetWidth || 0) + 5;
				break;
			default:
				newX = pos.x;
		}
		switch ( yOrientation ) {
			case 'top':
				newY = pos.y - (this.element.offsetHeight || 0) -5; 
				break;
			case 'bottom':
			default:
				newY = pos.y + (element.offsetHeight || 0) + 5; 
		}
   	this.setPosition ( newX, newY );
   }
});


Hidable = Base.extend({
	show:function () {
      this.element.style.visibility='visible';
   },
   
   hide:function () {
      this.element.style.visibility='hidden';
   },
	
	isVisible:function (){
		return this.element.style.visibility == 'visible';
	}
});   


Tracable = Base.extend({
	mouseTraceInited:false,
	
	mouseTraceInit:function () {
		if ( typeof this.updateMouseXY != 'function' ) {
			alert ( 'updateMouseXY is not implemented in ' + this.constructor );
			return;
		}
		this.boundGetMouseXY = this.getMouseXY.bindAsEventListener(this);
		this.boundTriggerUpdateMouseXY = this.triggerUpdateMouseXY.bind(this);
		this.mouseTraceInited=true;
		this.mouseX = null;
		this.mouseY = null;		
	},
	
	startMouseTrace:function ( event ) {
		if ( this.isMouseTracing ) {
			return;
		}
		if (!this.mouseTraceInited) 
			this.mouseTraceInit();
		this.isMouseTracing = true;
			
		if ( event ) {
			this.boundGetMouseXY( event );
		}
		Event.observe(document,'mousemove',this.boundGetMouseXY,false);
		//this.timeoutPointer = setInterval ( this.boundTriggerUpdateMouseXY, 50 );
	},
	
	stopMouseTrace:function () {
		this.mouseX = null;
		this.mouseY = null;
		if ( this.isMouseTracing ) {
			Event.stopObserving(document,'mousemove',this.boundGetMouseXY,false);
			//clearTimeout(this.timeoutPointer );
			this.isMouseTracing = false;
		}
	},
	
	getMouseXY:function ( event ) {
		var x = Event.pointerX(event); //.clientX + (document.body.scrollLeft || window.pageXOffset );
		var y = Event.pointerY(event); //event.clientY + (document.body.scrollTop  || window.pageYOffset);
		
		if ( Event.element(event).ownerDocument != this.element.ownerDocument ) {
  			var pos = getAbsolutePositionOf ( Event.element(event).ownerDocument.frame );
  			x += pos.x;
  			y += pos.y;
  		}
		
		if ( this.mouseX != x || this.mouseY != y ) {
			this.mouseMoved = true;
			this.mouseX = x;
			this.mouseY = y;
			this.updateMouseXY ( this.mouseX, this.mouseY );
		} else {
			this.mouseMoved = false;
		}
	},
	
	triggerUpdateMouseXY:function () {
		if ( typeof this.mouseX != 'number' || typeof this.mouseY != 'number' || !this.mouseMoved ) {
			return;
		}
		DEBUG('Trigger update mouse XY @ ' + [this.mouseX,this.mouseY].join ( ',' ) );
		this.updateMouseXY ( this.mouseX, this.mouseY );
	}
});

Draggable = Tracable.extend({
	observerDocuments:[],
	
	initDraggable:function ( dragElement ) {
		this.addObserverDocument(document);
		this.dragElement = dragElement;
		
		this.isDraggable = true;
		this.boundUnlistenDragEvent = this.unlistenDragEvent.bindAsEventListener(this);
		this.dragElement.onmousedown = this.listenDragEvent.bindAsEventListener(this);
	},
	
	addObserverDocument:function ( d ) {
		this.observerDocuments.push ( d );
	},
	
	setDraggable:function (d){
		this.isDraggable = d;
	},
	
	updateMouseXY:function ( x, y ) {
		DEBUG ( 'update @ ' + [x,y].join(',') );
		if ( this.isDraggable )
      	this.setPosition (
				x - this.offsetMouseX, 
				y - this.offsetMouseY
			);
		return false;
   },

   setMousePositionOffset:function ( x, y ) {
		var p = this.element.offsetParent;
		do{
			x += p.offsetLeft;
			y += p.offsetTop;
		} while ( p = p.offsetParent )
		DEBUG('OFFSET @ ' + x + ',' + y );
		
		this.offsetMouseX = x;
      this.offsetMouseY = y;
   },
   
   listenDragEvent:function ( event ) {
		if ( this.isDraggable ) {
	      this.setMousePositionOffset ( event.offsetX || event.layerX, event.offsetY || event.layerY );
			Element.addClassName ( this.element, 'dragged' );
			for ( i in this.observerDocuments ) {
				this.startMouseTrace ();
      		Event.observe(this.observerDocuments[i],'mouseup',this.boundUnlistenDragEvent,false);
     		}
		}
   },
   
   unlistenDragEvent:function ( event ) {
		Element.removeClassName ( this.element, 'dragged' );
		for ( i in this.observerDocuments ) {
			this.stopMouseTrace ();
	      Event.stopObserving(this.observerDocuments[i],'mouseup',this.boundUnlistenDragEvent,false);
		}
   }
});
Draggable.implement ( Movable );
