/*
 * Drawer class
 *
 * Objects of the Drawer class represent drawers. Drawer is
 * a dynamic HTML-component that consists of one or more DrawerContainer
 * objects. Container can be either open or closed. When the container is opened,
 * its contents are displayed to the client. Drawer acts as an interface towards the 
 * client and handles events that get triggered when the client moves mouse over one
 * of the containers' handles. When such an event is triggered, Drawer opens the correct
 * container and closes all non-closed containers. Drawer also takes care that the
 * container doesn't open too much.
 *
 * Initialize object with two parameters: element and options.
 *
 * Parameter element is the containing HTML-element for the entire Drawer.
 * Parameter options is an associative array of Drawer options.
 *
 * Available options:
 * {int} height			Maximum available height for the drawer (null by default)
 * {int} extAmount		Defines how much the container gets opened/closed per each iteration in opening and closing animations
 * {int} extInterval	Defines the interval in milliseconds between each iteration in opening and closing animations
 *
 * Example of usage:
 * var d = new Drawer(document.getElementById('drawer'),{height:300,extAmount:10,extInterval:10,triggerEvent:'click',manualClosing:true});
 *
 * @version 1.0
 * @author Juha Suni / Media Cabinet Oy
 */
function Drawer(element,options){

	this._options = {
		height:300,
		extAmount:10,
		extInterval:10,
		triggerEvent:'mouseover',
		manualClosing:false
	};

	this._elm = element;
	this._containers = new Array();
	this._maxVspace = null;
	this._setFlag = false;
	
	if(options){
		for(var key in options){
			this._options[key] = options[key];
		}
	}
	
	/**
	 * Initializes Drawer object
	 */
	this.init = function(){
		var containers = DomHtml.getElementsByClassName(this._elm,'drw_Ctr');
		var open = null, c;
		
		//parse containers
		for(var i=0;i<containers.length;i++){
			this._newContainer(containers[i]);
			
			if(DomHtml.elementHasClass(containers[i],'open')) open = i;
		}
		
		//count max vertical space that each container can (and will) extend
		this._maxVspace = this._getMaxVspace();
		
		if(this._options.height != null){
			this._elm.style.overflowY = 'hidden';
			this._elm.style.height = this._options.height+'px';
		}
		
		//set max height for the containers
		for(i=0;i<this._containers.length;i++){
			c = this._containers[i];
			c.setMaxHeight(this._maxVspace);
		}
		
		//open marked container
		if(open != null){
			this.openContainer(this._containers[open]);
		}
	}
	
	/**
	 * Opens container
	 *
	 * @param {DrawerContainer} container Container to be opened
	 */
	this.openContainer = function(container){
		//set flag to cancel incomplete operations
		this._setFlag = !this._setFlag;
		
		//start opening new container (and closing non-closed)
		this._extend(this._setFlag,true,container,this._options.extAmount,this._options.extInterval,0);
	}
	
	/**
	 * Closes container
	 *
	 * @param {DrawerContainer} container Container to be closed
	 */
	this.closeContainer = function(container){
		//set flag to cancel incomplete operations
		this._setFlag = !this._setFlag;
		
		//start closing the container
		this._extend(this._setFlag,false,container,this._options.extAmount,this._options.extInterval,0);
	}
	
	/*
	 * Returns true if given container is the last container
	 * in the drawer
	 *
	 * @param {DrawerContainer} container Container to be opened
	 */
	this.isLastContainer = function(container){
		return this._containers[this._containers.length-1] == container;
	}
	
	/*
	 * Adds new container to drawer
	 *
	 * @param {DrawerContainer} container
	 */
	this.addContainer = function(container){
		this._containers[this._containers.length] = container;
	}
	
	/*
	 * Creates a new container from given HTML element
	 *
	 * @param {Object} elm
	 */
	this._newContainer = function(elm){
		var c = new DrawerContainer(elm,this,{triggerEvent:this._options.triggerEvent,manualClosing:this._options.manualClosing});
		this.addContainer(c);
	}
	
	/**
	 * Calculates maximum available vertical space
	 * in pixels that a container can have.
	 */
	this._getMaxVspace = function(){
		if(!this._options.height) return null;
	
		var avail = this._options.height, c;
		for(var i=0;i<this._containers.length;i++){
			c = this._containers[i];
			avail -= c.getClosedHeight();
		}
		
		return avail;
	}
	
	/**
	 * Extends or shrinks container by every iteration
	 *
	 * Flag changes at every openContainer-call so that existing
	 * iteration can be identified and stopped before a new _extend(..)
	 * occurs.
	 *
	 * @param {Drawed} drawer Drawer (parent) object
	 * @param {DrawerContainer} target Container to be opened
	 * @param {int} amount Specifies, in pixels, how much the container is opened at each iteration
	 * @param {int} interval Specifies interval in milliseconds between two iterations
	 * @param {int} iteration Iteration number
	 */
	this._extend = function(flag,doOpen,target,amount,interval,iteration){
		var nonClosed = new Array(), d, i, h, h2, accel = 5;
		var drawer = this;
		
		if(drawer._setFlag != flag) return;
	
		//look for non-closed containers
		for(i=0;i<drawer._containers.length;i++){
			d = drawer._containers[i];
			
			if((d != target || (!doOpen && d == target)) && !d.isClosed()){
				nonClosed[nonClosed.length] = d;
			}
		}
	
		//continue if one or more non-closed containers or target is not yet fully opened
		if(nonClosed.length > 0 || (doOpen && !target.isFullyOpen())){
			h = (amount+iteration*accel);
			
			//close first to make some space
			if(nonClosed.length > 0){
				h = 0;
				h2 = Math.round((amount/nonClosed.length+iteration*accel));
				
				for(i=0;i<nonClosed.length;i++){
					h += h2;
					nonClosed[i].extend(-h2);
				}
			}
			
			//open if not fully opened
			if(doOpen && !target.isFullyOpen()){
				target.extend(h);
			}
			
			//next round
			iteration++;
			setTimeout(function(){drawer._extend(flag,doOpen,target,amount,interval,iteration)},interval);
		}
	}

	this.init();
}

/**
 * DrawerContainer class
 *
 * Objects of DrawerContainer represent containers in a drawer.
 * Each container has a title element and a content element.
 * Title element acts as a handle: it takes user's mouseover event
 * and triggers Drawer's openContainer()-method. Content element
 * contains HTML-data and is hidden by default. Height of a container
 * depends on the available height in the drawer. 
 */
function DrawerContainer(element,parent,options){

	this._elm = element;
	this._options = options;
	this._elements = {title:null,content:null};
	this._parent = parent;
	this._vp = null;
	this._maxHeight = null;
	
	/**
	 * Initializes object
	 */
	this.init = function(){
		var a, cb, thisObj = this, parent = this._parent;
		
		a = DomHtml.getElementsByClassName(this._elm,'drw_CtrTitle');
		
		//fetch title element
		if(a && a[0]) this._elements.title = a[0];
		else throw 'Unable to locate title element';
		
		a = DomHtml.getElementsByClassName(this._elm,'drw_CtrContent');
		
		//fetch content element
		if(a && a[0]) this._elements.content = a[0];
		else throw 'Unable to locate content element';
		
		//get viewport of the content element before shrinking it to 0px height
		this._vp = this.getContentViewport();
		this._elements.content.style.height = '0px';
		this._maxHeight = this._vp.height;
		
		//attach onclick event to title element
		cb = function(e){if(thisObj._options.manualClosing && thisObj.isOpen()) parent.closeContainer(thisObj); else parent.openContainer(thisObj);}
		Events.attachEvent(this.getTitleElm(),this._options.triggerEvent,cb)
	}
	
	/**
	 * Extends container by given amount
	 *
	 * @param {int} amount Amount in pixels
	 */
	this.extend = function(amount){
		var c = this.getContentElm();
		var h = this._parseHeight();
		
		h += amount;
		
		if(h <= 0){
			h = 0;
		}
		else if(h >= this._maxHeight){
			h = this._maxHeight;
		}
		
		c.style.height = h+'px';
		
		return h;
	}
	
	/**
	 * Returns true if container is open (is not closed and
	 * not necessarily fully open)
	 */
	this.isOpen = function(){
		return this._parseHeight() > 0;
	}
	
	/**
	 * Returns true if container is fully open
	 */
	this.isFullyOpen = function(){
		return this._parseHeight() >= this._maxHeight;
	}
	
	/**
	 * Returns true if container is closed
	 */
	this.isClosed = function(){
		return this._parseHeight() <= 0;
	}
	
	/**
	 * Sets maximum height for the container
	 */
	this.setMaxHeight = function(height){
		this._maxHeight = height;
	}
	
	/**
	 * Returns title element of the container
	 */
	this.getTitleElm = function(){
		return this._elements.title;
	}
	
	/**
	 * Returns content element of the container
	 */
	this.getContentElm = function(){
		return this._elements.content;
	}
	
	/**
	 * Returns content viewport
	 *
	 * @return {Object} Object with properties: width, height, left, top
	 */
	this.getContentViewport = function(){
		var vp, h, c = this.getContentElm();
	
		h = c.style.height;
		c.style.visibility = 'hidden';
		c.style.height = 'auto';

		vp = DomHtml.getNodeViewport(c);

		c.style.visibility = 'visible';
		if(h) c.style.height = h;
		
		return vp;
	}
	
	this.getClosedHeight = function(){
		var vp = DomHtml.getNodeViewport(this.getTitleElm());
		return vp.height+1;
	}
	
	/**
	 * Parses integer value of the content element's
	 * height (e.g. "390 px" -> 390)
	 */
	this._parseHeight = function(){
		var c = this.getContentElm();
		var h = parseInt(c.style.height);
		if(isNaN(h)) h = 0;
		
		return h;
	}
	
	this.init();
}