DHTMLApi = {
	
	CSS : {
		getStyle: function (element, name) {
			var styleObj;
			if (element.style[name]) return element.style[name];
			if (element.currentStyle) {	
				name=name.replace(/-([a-z])/g, function (matched) {
					return matched.toUpperCase().slice(1, matched.length);
				});
				return element.currentStyle[name];
			}
			if (document.defaultView && document.defaultView.getComputedStyle) {
				name=name.replace(/([A-Z])/g,"-$1");
				name=name.toLowerCase();
				styleObj=document.defaultView.getComputedStyle(element,"");
				return styleObj && styleObj.getPropertyValue(name);
			} else {
				return null;
			}
		},
		
		setProperties: function (element, properties) {
			var oldProperties={};
			for (var i in properties) {
				oldProperties[i]=element.style[i];
				element.style[i]=properties[i];
			}
			return oldProperties;
		},
		
		setClass:function (element,addClassesArray,removeClassesArray) {
			var currentClasses=new Array();
			var newClasses=new Array();
			var removedClasses=new Array();
			findWordsExp=new RegExp("\\w+", "g");
			var result;
			while ((result= findWordsExp.exec(element.className))!= null) currentClasses.push(result[0]); 
			for (var i=0; i<addClassesArray.length; i++) {
				var classExists=false;
				for (var j=0; j<currentClasses.length; j++) {
					if (currentClasses[j]==addClassesArray[i]) {
						classExists=true;
						break;
					}					
				}
				if (!classExists) newClasses.push(addClassesArray[i]);
			}
			currentClasses=currentClasses.concat(newClasses);
			if (removeClassesArray==null) return removedClasses;
			for (var i=0; i<removeClassesArray.length; i++) {
				for (var j=0; j<currentClasses.length; j++) {
					if (currentClasses[j]==removeClassesArray[i]) {
						removedClasses=removedClasses.concat(currentClasses.splice(j,1));
						break;
					}					
				}
			}
			element.className=currentClasses.join(" ");
			return removedClasses;
		}
	},
	
	Position : {
		getXPosOnPage: function (element) {
			return element.offsetParent?element.offsetLeft+DHTMLApi.Position.getXPosOnPage(element.offsetParent):element.offsetLeft;
		},
		
		getYPosOnPage: function (element) {
			return element.offsetParent?element.offsetTop+DHTMLApi.Position.getYPosOnPage(element.offsetParent):element.offsetTop;
		},
		
		getXPosInElement: function (element,container) {
			return DHTMLApi.Position.getXPosOnPage(element)-DHTMLApi.Position.getXPosOnPage(container);
		},
		
		getYPosInElement: function (element,container) {
			return DHTMLApi.Position.getYPosOnPage(element)-DHTMLApi.Position.getYPosOnPage(container);
		},
		
		setXPosOnPage: function (element,posX) {
			var propertiesArray={};
			if (element.parentNode!=document.body) {
				element=element.parentNode.removeChild(element);
				document.body.appendChild(element);
			}
			if (DHTMLApi.CSS.getStyle(element,"position")!="absolute") {
				propertiesArray={position: "absolute"};
			}
			propertiesArray.left=posX+"px";
			DHTMLApi.CSS.setProperties(element,propertiesArray);
		},
		
		setYPosOnPage: function (element,posY) {
			var propertiesArray={};
			if (element.parentNode!=document.body) {
				element=element.parentNode.removeChild(element);
				document.body.appendChild(element);
			}
			if (DHTMLApi.CSS.getStyle(element,"position")!="absolute") {
				propertiesArray={position: "absolute"};
			}
			propertiesArray.top=posY+"px";
			DHTMLApi.CSS.setProperties(element,propertiesArray);
		},
		
		setXPos: function (element, posX, relativeToElement) {
			if (relativeToElement==undefined) {
				element.style.left=posX+"px";
			} else {
				if(DHTMLApi.CSS.getStyle(element.parentNode,"position")=="static"){
					element.parentNode.style.position="relative";
				}
				element.style.position="absolute";
				element.style.left=(posX-DHTMLApi.Position.getXPosInElement(element.parentNode,relativeToElement))+"px";
			}
		},
		
		setYPos: function (element, posY, relativeToElement) {
			if (relativeToElement==undefined) {
				element.style.top=posY+"px";
			} else {
				if(DHTMLApi.CSS.getStyle(element.parentNode,"position")=="static"){
					element.parentNode.style.position="relative";
				}
				element.style.position="absolute";
				element.style.top=(posY-DHTMLApi.Position.getYPosInElement(element.parentNode,relativeToElement))+"px";
			}
		}
		
	},
	
	Size : { 
		
		getElementWidth : function (element) {
			var tempProperties, width;
			if (DHTMLApi.CSS.getStyle(element, "display" ) != "none") {
				return element.offsetWidth || parseInt(DHTMLApi.CSS.getStyle(element, "width"));
			}
			tempProperties=DHTMLApi.CSS.setProperties(element, {display: "", visibility: "hidden", position: "absolute"});
			width=element.clientWidth || parseInt(DHTMLApi.CSS.getStyle(element, "width"));
			DHTMLApi.CSS.setProperties(element,tempProperties);
			return width;		
		},
		
		getElementHeight : function (element) {
			var tempProperties, height;
			if (DHTMLApi.CSS.getStyle(element, "display" ) != "none") {
				return element.offsetHeight || parseInt(DHTMLApi.CSS.getStyle(element, "height"));
			}
			tempProperties=DHTMLApi.CSS.setProperties(element, {display: "", visibility: "hidden", position: "absolute"});
			height=element.clientHeight || parseInt(DHTMLApi.CSS.getStyle(element, "height"));
			DHTMLApi.CSS.setProperties(element,tempProperties);
			return height;
		},
		
		getPageWidth: function () {
			return document.body.scrollHeight;
		},
		
		getPageHeight: function () {
			return Math.max(document.body.scrollHeight,document.body.offsetHeight);
		}
	
	},
	
	Visibility: {
		
		show: function (element) {
			element.style.display=element.__display__ || '';
		},
		
		hide: function (element) {
			var currentDisplay=DHTMLApi.CSS.getStyle(element,"display");
			if (currentDisplay != 'none') element.__display__=currentDisplay;
			element.style.display='none';
		},
		
		setOpacity: function (element,percent) {
			if (element.filters) {	
				element.style.filter='alpha(opacity='+percent+')';
			} else {
				element.style.opacity=percent/100;
			}
		}
		
	},
	
	Browser : {
		
		getViewportWidth: function () {
			return self.innerWidth || (document.documentElement && document.documentElement.clientWidth) || document.body.clientWidth;
		},
		
		getViewportHeight: function () {
			return self.innerHeight || (document.documentElement && document.documentElement.clientHeight) || document.body.clientHeight;
		},
		
		getScrollX: function () {
			return self.pageXOffset || (document.documentElement && document.documentElement.scrollLeft) || document.body.scrollLeft;
		},
		
		getScrollY: function () {
			return self.pageYOffset || (document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop;
		}
		
	}
};

MousePositionOnPage = {
	getX: function (mouseEvent) {
		return mouseEvent.pageX || mouseEvent.clientX+DHTMLApi.Browser.getScrollX();
	},
	getY: function (mouseEvent) {
		return mouseEvent.pageY || mouseEvent.clientY+DHTMLApi.Browser.getScrollY();
	}
};

function DOMEventHandle(element, type, handler) {
	this.element=element;
	this.type=type;
	this.handler=handler;
}

DOMEvent = {	

	// public
	
	addDomListener: function (element, type, handler) {
		var handlers;
		if (!handler.__id__) handler.__id__ = DOMEvent.currentId++;
		if (!element.events) element.events = {};
		handlers = element.events[type];
		if (!handlers) {
    		handlers = element.events[type] = {};
		    if (element["on" + type]) {
      			handlers[0] = element["on" + type];
    		}
  		}
		handlers[handler.__id__] = handler;
		element["on" + type] = DOMEvent.handleEvent;
		return new DOMEventHandle(element, type, handler);
	},
	
	removeListener: function (domEventHandleObj) {
		var element,type,handler;
		element=domEventHandleObj.element;
		type=domEventHandleObj.type;
		handler=domEventHandleObj.handler;
		if (element.events && element.events[type]) {
   			delete element.events[type][handler.__id__];
  		}
	},
	
	preventDefault: function (eventObj) {
		if (eventObj.preventDefault) {
			eventObj.preventDefault();
		} else {
			eventObj.returnValue=false;
		}
	},
	
	stopPropagation: function (eventObj) {
		if (eventObj.stopPropagation) {
			e.stopPropagation();
		} else {
			e.cancelBubble=true;
		}
	},
	
	// private
	
	currentId: 1,
	
	handleEvent: function (event) {
  		var handlers;
		event = event || window.event;
  		handlers = this.events[event.type];
  		for (var i in handlers) {
    		this.__handleEvent = handlers[i];
    		this.__handleEvent(event);
  		}
		this.__handleEvent=null;
	}
};

////////////////////
// Class Rollover //
////////////////////

function Rollover(imageId,rolloverPicPath) {
	var rolloverImage,rolloverImgSrc,rolloutImgSrc;

	this.imageObject=document.getElementById(imageId);
	rolloverImage= new Image();
	rolloverImage.src=rolloverPicPath;
	this.rolloutImageSrc=this.imageObject.src;
	this.rolloverImageSrc=rolloverPicPath;

	rolloverImgSrc=this.rolloverImageSrc;
	rolloutImgSrc=this.rolloutImageSrc;
	this.imageObject.onmouseover= function () {
		this.src=rolloverImgSrc;
	}
	this.imageObject.onmouseout= function () {
		this.src=rolloutImgSrc;
	}	
}

function implementsInterface() {
	var prototypeObj=new Object();
	for (var i=0; i<arguments.length; i++) {
		for (var method in arguments[i]) {
			prototypeObj[method]=arguments[i][method];
		}	
	}
	return prototypeObj;
}

//////////////////////////
// Interface Observable //
//////////////////////////

Observable = {
	addListener : function(listenerObj) {
		if (typeof this.listeners == 'undefined') {
			this.listeners=new Array();
		}
		for (var i=0; i<this.listeners.length; i++) {
			if (this.listeners[i]==listenerObj) return;
		}
		this.listeners[this.listeners.length]=listenerObj;
	},

	removeListener : function(listenerObj) {
		for (var i=0; i<this.listeners.length; i++) {
			if (this.listeners[i]==listenerObj) {
				this.listeners.splice(i,1);
				return;
			}
		}
	},
	
	notifyListeners : function (eventName,eventObj) {
		if (typeof this.listeners == 'undefined') return;
		for (var i=0; i< this.listeners.length; i++) {
			if (typeof this.listeners[i][eventName]!="undefined") {
				this.listeners[i][eventName](eventObj);
			}
		}
	}
}

/////////////////////////////////////////////
// Class Draggable (implements Observable) //
/////////////////////////////////////////////

/************************************
 listenerObj must implement methods:	
 onDragStop(DraggableEventObj event)
 onDrag(DraggableEventObj event)
************************************/

DraggableMode= {HORIZONTAL : 1,VERTICAL : 2,HORIZONTAL_VERTICAL : 3};

function Draggable(dragObj,dragHandleObj,boundingObj,dragMode) {
	this.dragObj=dragObj;
	if (dragHandleObj==null) {
		this.dragHandle=this.dragObj;
	} else {
		this.dragHandle=dragHandleObj;
	}
	this.mode=dragMode;
	this.boundingObj = boundingObj;			
	this.boundingBox = this.getBoundingBox();
	
	this.mousePosXOnHandle=null;
	this.mousePosYOnHandle=null;
	
	this.mouseDownHandler=null;
	this.mouseUpHandler=null;
	this.mouseMoveHandler=null;
};

Draggable.prototype = implementsInterface(Observable);

Draggable.prototype.set=function() {
	var obj=this;
	
	function dragStart(e) {
		obj.boundingBox = obj.getBoundingBox();
		obj.mousePosXOnHandle=MousePositionOnPage.getX(e)-DHTMLApi.Position.getXPosOnPage(obj.dragObj);
		obj.mousePosYOnHandle=MousePositionOnPage.getY(e)-DHTMLApi.Position.getYPosOnPage(obj.dragObj);
		obj.mouseMoveHandler=DOMEvent.addDomListener(document, "mousemove" , drag);
		obj.mouseUpHandler=DOMEvent.addDomListener(document, "mouseup" , dragEnd);
		DOMEvent.preventDefault(e);
	}
	
	function dragEnd(e) {
		DOMEvent.removeListener(obj.mouseMoveHandler);
		DOMEvent.removeListener(obj.mouseUpHandler);
		obj.notifyListeners("onDragStop",obj.getDraggableEventObj());
	}
	
	function drag(e) {
		var dragObjPosX,dragObjPosY;
		DOMEvent.preventDefault(e);
		dragObjPosX=obj.calculatePosX(MousePositionOnPage.getX(e));
		dragObjPosY=obj.calculatePosY(MousePositionOnPage.getY(e));
		if (obj.mode!=DraggableMode.VERTICAL) {
			if (obj.boundingBox.tlX<=dragObjPosX && dragObjPosX<=obj.boundingBox.brX) {
				DHTMLApi.Position.setXPos(obj.dragObj, dragObjPosX-obj.boundingBox.tlX, obj.boundingObj);
			} else if (obj.boundingBox.tlX>dragObjPosX) {
				DHTMLApi.Position.setXPos(obj.dragObj, 0, obj.boundingObj);
			} else {
				DHTMLApi.Position.setXPos(obj.dragObj, obj.boundingBox.brX-obj.boundingBox.tlX, obj.boundingObj);
			}
		}		
		if (obj.mode!=DraggableMode.HORIZONTAL) {
			if (obj.boundingBox.tlY<=dragObjPosY && dragObjPosY<=obj.boundingBox.brY) {
				DHTMLApi.Position.setYPos(obj.dragObj, dragObjPosY-obj.boundingBox.tlY, obj.boundingObj);
			} else if (obj.boundingBox.tlY>dragObjPosY) {
				DHTMLApi.Position.setYPos(obj.dragObj, 0, obj.boundingObj);
			} else {
				DHTMLApi.Position.setYPos(obj.dragObj, obj.boundingBox.brY-obj.boundingBox.tlY, obj.boundingObj);
			}
		}
		obj.notifyListeners("onDrag",obj.getDraggableEventObj());
	}
	
	this.mouseDownHandler=DOMEvent.addDomListener(this.dragHandle, "mousedown" , dragStart);
};

Draggable.prototype.unset=function() {
	DOMEvent.removeListener(this.mouseDownHandler);
	DOMEvent.removeListener(this.mouseMoveHandler);
	DOMEvent.removeListener(this.mouseUpHandler);	
};

Draggable.prototype.getXBounds=function() {
	return (this.boundingBox.brX-this.boundingBox.tlX);
}

Draggable.prototype.getYBounds=function() {
	return (this.boundingBox.brY-this.boundingBox.tlY);
}

Draggable.prototype.setDragObjXPos=function(xPos) {
	DHTMLApi.Position.setXPos(this.dragObj, xPos, this.boundingObj);
}

Draggable.prototype.setDragObjYPos=function(yPos) {
	DHTMLApi.Position.setYPos(this.dragObj, yPos, this.boundingObj);
}

// private

Draggable.prototype.calculatePosX=function (mousePosX) {
	return mousePosX-this.mousePosXOnHandle;
};

Draggable.prototype.calculatePosY=function (mousePosY) {
	return mousePosY-this.mousePosYOnHandle;
};

Draggable.prototype.getBoundingBox= function () {
	return {
		tlX: DHTMLApi.Position.getXPosOnPage(this.boundingObj), 
		tlY: DHTMLApi.Position.getYPosOnPage(this.boundingObj),
		brX: DHTMLApi.Position.getXPosOnPage(this.boundingObj)+DHTMLApi.Size.getElementWidth(this.boundingObj)-DHTMLApi.Size.getElementWidth(this.dragObj),
		brY: DHTMLApi.Position.getYPosOnPage(this.boundingObj)+DHTMLApi.Size.getElementHeight(this.boundingObj)-DHTMLApi.Size.getElementHeight(this.dragObj)};
}

Draggable.prototype.getDraggableEventObj=function() {
	return new DraggableEventObj(DHTMLApi.Position.getXPosInElement(this.dragObj,this.boundingObj), DHTMLApi.Position.getYPosInElement(this.dragObj,this.boundingObj), this.boundingBox.brX-this.boundingBox.tlX, this.boundingBox.brY-this.boundingBox.tlY);
}

/////////////////////////////
// Class DraggableEventObj //
/////////////////////////////

function DraggableEventObj (objPosX, objPosY, boundWidth, boundHeight) {
	this.posX=objPosX;
	this.posY=objPosY;
	this.boundWidth=boundWidth;
	this.boundHeight=boundHeight;
}

///////////////////
// Class VSlider //
///////////////////

/************************************
 listenerObj must implement methods:	
 onChange(ChangeEventObj event)
************************************/

function VSlider(dragObj,boundingObj,topValue,bottomValue,initValue) {
	this.topValue=topValue;
	this.bottomValue=bottomValue;
	this.draggableObject=new Draggable(dragObj,null,boundingObj,DraggableMode.VERTICAL);
	this.draggableObject.set();
	this.draggableObject.addListener(this);
	if (typeof initValue!= 'undefined') {
		this.setValue(initValue);
	}
}

VSlider.prototype = implementsInterface(Observable);

VSlider.prototype.getValue=function(draggableEventObj) {
	return this.topValue+(draggableEventObj.posY/draggableEventObj.boundHeight)*1.0*(this.bottomValue-this.topValue); 
}

VSlider.prototype.setBoundaryValues=function(topValue,bottomValue) {
	this.topValue=topValue;
	this.bottomValue=bottomValue;
}

VSlider.prototype.setValue=function(value) {
	var dragObjYPos=Math.round((value-this.topValue)*this.draggableObject.getYBounds()/(this.bottomValue-this.topValue));
	this.draggableObject.setDragObjYPos(dragObjYPos);
}

VSlider.prototype.onDragStop = function (draggableEventObj) {
	this.notifyListeners("onChange",new SliderEventObj(this.getValue(draggableEventObj)));
}

VSlider.prototype.onDrag = function (draggableEventObj) {
	this.notifyListeners("onChange",new SliderEventObj(this.getValue(draggableEventObj)));
}

function SliderEventObj (value) {
	this.value=value;
}

////////////////////////
// Class BlockHScroll //
////////////////////////

function VScroll(containerElement,contentElement,sliderbarElement,sliderElement,viewport,upButton,downButton) {
	this.containerElement=containerElement;
	this.contentElement=contentElement;
	this.sliderbarElement=sliderbarElement;
	this.sliderElement=sliderElement;
	this.upButton=upButton;
	this.downButton=downButton;
	this.viewportWidth=viewport.width;
	this.viewportHeight=viewport.height;
	this.viewportPosX=viewport.x;
	this.viewportPosY=viewport.y;
	this.invisibleContentHeight=null;
	this.slider=null;
	this.mask=this.buildMask();
	this.animation=new Animation.SmoothVMove(this.contentElement, this.mask);
	this.initSlider();
	this.initButtons();
}

VScroll.prototype.buildMask=function() {
	var maskDiv=document.createElement("div");
	DHTMLApi.CSS.setProperties(maskDiv, {position: "absolute", left: this.viewportPosX+"px", top: this.viewportPosY+"px", width: this.viewportWidth+"px", height: this.viewportHeight+"px", overflow:'hidden'});
	this.contentElement=this.containerElement.replaceChild(maskDiv,this.contentElement);
	maskDiv.appendChild(this.contentElement);
	return maskDiv;
}

VScroll.prototype.initSlider=function() {
	var obj=this;
	this.invisibleContentHeight=DHTMLApi.Size.getElementHeight(this.contentElement)-this.viewportHeight;
	this.slider=new VSlider(this.sliderElement,this.sliderbarElement,0,-this.invisibleContentHeight,0);
	this.slider.onDragStop = function (draggableEventObj) {
		var value=this.getValue(draggableEventObj);
		obj.animation.setPosition(value);
		this.notifyListeners("onChange",new SliderEventObj(value));
	}
	this.slider.onDrag=function (draggableEventObj) {
		if (obj.animation.getAnimationStep()===null || obj.animation.getAnimationStep()>2) {
			obj.animation.setPosition(this.getValue(draggableEventObj));
		}
		this.notifyListeners("onChange",new SliderEventObj(this.getValue(draggableEventObj)));
	}
	
}

VScroll.prototype.initButtons=function() {
	var obj=this;
	var mousedownInterval;
	
	scrollDown=function() {
		var targetPosY;
		if ((obj.getCurrentScrollPosition()-10) < -obj.invisibleContentHeight) {
			targetPosY=-obj.invisibleContentHeight;
		} else {
			targetPosY=obj.getCurrentScrollPosition()-10;
		}
		obj.slider.setValue(targetPosY);
		obj.animation.setPosition(targetPosY);
	}
	
	scrollUp=function() {
		var targetPosY;
		if ((obj.getCurrentScrollPosition()+10) >= 0) {
			targetPosY=0;
		} else {
			targetPosY=obj.getCurrentScrollPosition()+10;
		}
		obj.slider.setValue(targetPosY);
		obj.animation.setPosition(targetPosY);
}
	
	DOMEvent.addDomListener(this.downButton,"mousedown",function() {
		mousedownInterval=window.setInterval(scrollDown,30);
	});
	
	DOMEvent.addDomListener(this.downButton,"mouseup",function() {
		window.clearInterval(mousedownInterval);
	});
	
	DOMEvent.addDomListener(this.upButton,"mousedown",function() {
		mousedownInterval=window.setInterval(scrollUp,30);
	});
	
	DOMEvent.addDomListener(this.upButton,"mouseup",function() {
		window.clearInterval(mousedownInterval);
	});
}

VScroll.prototype.getCurrentScrollPosition=function() {
	return DHTMLApi.Position.getYPosInElement(this.contentElement,this.mask);
}

Animation= { SmoothVMove : SmoothVMove }

function SmoothVMove(movingObject, relativeToObject) {
	this.movingObject=movingObject;
	this.relativeToObject=relativeToObject;
	this.currentYPos=DHTMLApi.Position.getYPosInElement(movingObject,relativeToObject);
	this.targetYPos=null;
	this.interval=null;
	this.numOfAnimationStep=null;
}

SmoothVMove.prototype.setPosition=function (targetPosition) {
	window.clearInterval(this.interval);
	var animationObject=this;
	this.targetYPos=targetPosition;
	this.numOfAnimationStep=0;
	this.animate();
	this.interval=window.setInterval(function() {animationObject.animate()},50);
}

SmoothVMove.prototype.animate=function () {
	var stepDistance=(this.targetYPos-this.currentYPos)/3;
	if (Math.abs(stepDistance)>0.3) {
		this.currentYPos+=stepDistance;
		DHTMLApi.Position.setYPos(this.movingObject, this.currentYPos, this.relativeToObject);
		return ++this.numOfAnimationStep;
	} else {
		this.currentYPos=this.targetYPos;
		DHTMLApi.Position.setYPos(this.movingObject, this.currentYPos, this.relativeToObject);
		window.clearInterval(this.interval);
		this.numOfAnimationStep=null;
		return false;
	}
}

SmoothVMove.prototype.getAnimationStep=function () {
	return this.numOfAnimationStep;
}
