/**
 * Part of JMF Library core
 * This file contains layout class definition used 
 * for building user interface. It is somehow wrapper
 * for DOM document structure.
 * SVN: $Id: layout.js 6163 2008-08-14 14:36:31Z mjezierski $  
 */

JMF.registerLib('JMF.Layout','$Id: layout.js 6163 2008-08-14 14:36:31Z mjezierski $');
JMF.requireLib([
   'JMF.Exception',
   'JMF.Events',
   'JMF.HTMLElement'
]);

/**
 * @member JMF.Layout
 * @constructor
 * @param {Object} props
 * @param {String} tag Tag name, if null div tag will be used
 * css:css string
 * style:{}
 * attributes:{}
 * id:id string - required
 * innerHTML:HTML string
 */
JMF.Layout = function(props,tag) {
   if(!props) {
   	throw new JMF.Exception(JMF.Exception.EX_INVPARAMS);
   }
   
   //auto id generation
   props.id = props.id || JMF.uId();
   this.id = props.id;
    
   props.attributes = props.attributes || props.attr || {};

   this.cEvent = this.cEvent || {};
   
   JMF.Layout.Objects[this.id] = this;
   
   props.attributes.id = props.id;

   
	this.children = new JMF.Data.OrderedList();
	
	if(!tag) {
		tag = 'div';
	}
	
	this.DOMRef = JMF.HTMLElement(document.createElement(tag));

   if(props.innerHTML || props.iHTML) {
      this.DOMRef.iHTML(props.innerHTML || props.iHTML);
   }
   
   this.css(props.css);
   this.cssStyle(props.style);
   this.attr(props.attributes || props.attr);
	
	//controllers array used for dispatching controller events
	this.controllers = [];
	
	return this; 
};

/**
 * Sets HTML style
 * @member JMF.Layout
 * @param {String} css style string 
 */
JMF.Layout.prototype.css = function(cssString) {
   this.DOMRef.css(cssString);	
	return this;
};

/**
 * Sets HTML style
 * @member JMF.Layout
 * @param {Object} Object with style props
 */
JMF.Layout.prototype.cssStyle = function(styleObject) {
   this.DOMRef.cssStyle(styleObject);
	return this;
};

/**
 * Sets HTML attributes
 * @member JMF.Layout
 * @param {Object} Object with attributes' props
 */
JMF.Layout.prototype.attr = function(attributesObject) {
   this.DOMRef.attr(attributesObject);
   return this;
};

/**
 * Appends HTML object to DOM
 * @member JMF.Layout
 * @param {Object/String} HTML object or id. If left undefined document.body will be used
 */
JMF.Layout.prototype.appendDOM = function(domTarget) {
	if(!domTarget) {
		domTarget = document.body;
	} else {
		domTarget = JMF.HTMLElement(domTarget);
	}
	this.DOMRef = domTarget.appendChild(this.DOMRef); 
};

/**
 * Appends child to Layout objects tree and also Layout DOM to DOM tree 
 * @member JMF.Layout
 * @param {Object} Instance of JMF.Layout to add
 * @throws JMF.Exception.EX_INVPARAMS when id is in use
 */
JMF.Layout.prototype.appendChild = function(jmfLayoutObject) {
	if(this.children.getById(jmfLayoutObject.id)) {
      throw new JMF.Exception(JMF.Exception.EX_INVPARAMS,'Object ID "'+jmfLayoutObject.id+'" already in use. Use replaceChild instead.');
	}
   
	this.children.push(jmfLayoutObject);
	this.DOMRef.appendChild(jmfLayoutObject.DOMRef);
	
	//parent
	jmfLayoutObject.parent = this;
	
	return this;
};

/**
 * Inserts a child before child with given id
 * @member JMF.Layout
 * @param {Object} childNode JMF layout object to insert into tree
 * @param {Mixed} sibling Node to insert before or node id. If null or undefined, childNode will be inserted at the beginning
 * @return {Object} this
 */
JMF.Layout.prototype.insertBefore = function(childNode,sibling) {
	childNode.parent = this;
	if(typeof(sibling)==='undefined' || sibling === null) {
      sibling = this.children.getByPos(0) || null;
      if(sibling) {
      	sibling = sibling.data;
      } 		
	}
	
	if(typeof(sibling) === 'string') {
		sibling = this.children.getById(sibling).data;
	}
	
	if(sibling === null) {
		this.children.add(childNode.id,null,childNode);
		this.DOMRef.appendChild(childNode);
		return this;
	}
	
	this.children.add(childNode.id,sibling.id,childNode);
	this.DOMRef.insertBefore(childNode.DOMRef,sibling.DOMRef);
};

/**
 * Removes child from Layout objects tree and also Layout DOM from DOM tree 
 * @member JMF.Layout
 * @param {Object} Instance of JMF.Layout to remove
 * @throws JMF.Exception.EX_INVPARAMS when there id no such child in structure
 */
JMF.Layout.prototype.removeChild = function(jmfLayoutObject) {
   //check if arg is ok
   if(!jmfLayoutObject) {
   	return;
   }

	//remove from children list
	this.children.remove(jmfLayoutObject.id);
	
	//remove from dom Tree
	if(jmfLayoutObject.DOMRef.parentNode === this.DOMRef)
	{
	  this.DOMRef.removeChild(jmfLayoutObject.DOMRef);
	}

   //delete from global reference
	delete JMF.Layout.Objects[jmfLayoutObject.id];
	
	delete jmfLayoutObject.parent;  
};

JMF.Layout.prototype.clear = function() {
	this.children.clear();
	this.DOMRef.clear();
};

/**
 * Adds event listener to object
 * This method supports custom events
 * @member JMF.Layout
 * @param {String} evt Event name
 * @param {Function} handler Event handler
 * @return {Object} this
 * @throws JMF.Exception.EX_INVPARAMS if handler is no type of function
 */
JMF.Layout.prototype.addListener = function(evt,handler) {
   if(typeof handler !== 'function') {
     throw new JMF.Exception(JMF.Exception.EX_INVPARAMS,'Event handler must be function.');
   }
   
   if(JMF.Events.isCustomEvent(evt)) {
   	if(!this.cEvent[evt]) {
   		this.cEvent[evt] = [];
   	}
   	
   	//mark handler with guid
   	JMF.Events.getGUID(handler);
   	
   	this.cEvent[evt].push(handler);
   	return this;
   }

   this.DOMRef.addListener(evt,handler);
   return this;
};

/**
 * Removes event listener from object
 * This method supports custom events
 * @member JMF.Layout
 * @param {String} evt Event name
 * @param {Function} handler Event handler previously added with addListener
 * @return {Object} this
 * @throws JMF.Exception.EX_INVPARAMS if handler is no type of function
 */
JMF.Layout.prototype.removeListener = function(evt,handler) {
   if(typeof handler !== 'function') {
     throw new JMF.Exception(JMF.Exception.EX_INVPARAMS,'Event handler must be function.');
   }

   if(JMF.Events.isCustomEvent(evt)) {
   	if(!this.cEvent[evt]) {
   		return this;
   	}
   	
   	for(var i=0;i<this.cEvent[evt].length;i++) {
   	  if(this.cEvent[evt].$$GUID === handler.$$GUID) {
   	     this.cEvent[evt].splice(i,1);
   	     break;
   	  }	
   	}
   	return this;
   }
   this.DOMRef.removeListener(evt,handler);
};

/**
 * Sets innerHTML property for object
 * NOTE: This function is only for testing purposes, do not use it
 * @member JMF.Layout
 * @param {String} innerHTML
 * @return {Object} this
 */
JMF.Layout.prototype.iHTML = function(innerHTML) {
   this.DOMRef.iHTML(innerHTML);
   return this;
};

/**
 * Dispatches event down or up structure tree
 * @param {Object} evt Event object created by JMF.Events.create 
 * @param {Boolean} Direction If true event will be dispatched to parent nodes, else to child nodes
 * @param {Mixed} If boolean false event will be dispatched immediately, if boolean true or number event dispatch will be delayed (for boolean true delay is 0ms)
 * @return {Object} this 
 */
JMF.Layout.prototype.dispatchEvent = function(evt,direction,async) {
   if(async) {
   	var instance = this;
   	setTimeout(function(){instance.dispatchEvent(evt,direction);},typeof(async)==='number'?async:0);
   	return;
   }

   if(!JMF.Events.isCustomEvent(evt.type)) {
      return this;   	
   }

   if(evt.$$stopPropagation) {
   	return this;
   }

   //local handlers call
   if(this.cEvent[evt.type]) {
   	for(var i=0;i<this.cEvent[evt.type].length;i++) {
   		this.cEvent[evt.type][i](evt);
   		if(evt.$$stopPropagation) {
   			return this;
   		}
   	}
   }
      
  if(!direction) {
      //call children handlers
      var child;
      this.children.iResetPos();
      while((child = this.children.iterate())) {
         child.data.dispatchEvent(evt);
         if(evt.$$stopPropagation) {
            return this;
         }
      }
   }
   
   if(direction && this.parent) {
      this.parent.dispatchEvent(evt,direction);	
   }
   
   return this;
};

/**
 * Sets css class
 * @member JMF.Layout
 * @param {String} className Css class name
 * @return this
 */
JMF.Layout.prototype.cssClass = function(className) {
	this.DOMRef.cssClass(className);
	return this;
};

/**
 * Sets value propety for input objects
 * Wrapper for this.DOMRef.value property
 * @member JMF.Layout
 * @param {String} value Value of input field
 * @return {Object} this
 */
JMF.Layout.prototype.setValue = function(value) {
	this.DOMRef.value = value;
	return this;
};

/**
 * Empty destructor - recursively calls children destructors
 * @member JMF.Layout
 * Experimental 
 */
JMF.Layout.prototype.destroy = function() {
	var child;
	this.children.iResetPos();
	while((child = this.children.iterate())) {
      child.data.destroy();
		this.removeChild(child.data);
	}
};

/**
 * Attaches external controller
 * Controller should implement process(event) method
 * @member JMF.Layout
 * @param {Object} controller Controller object
 */
JMF.Layout.prototype.attachController = function(controller) {
	this.controllers.push(controller);
};

/**
 * Returns current controllers
 * @member JMF.Layout
 * @param {Number} idx optional controller index
 */
JMF.Layout.prototype.getControllers = function(idx) {
	if(undefined !== idx) {
		return this.controllers[idx];
	}
	return this.controllers;
};

/**
 * Dispatches event to external controller
 * @member JMF.Layout
 * @param {Object} evt Event object created by JMF.Events.create
 */
JMF.Layout.prototype.dispatchControllerEvent = function(evt) {
	for(var i=0;i<this.controllers.length;i++) {
	  	this.controllers[i].process(evt);
	  	if(evt.$$stopPropagation) {
	  		break;
	  	}	
	}
};

//Global reference array
JMF.Layout.Objects = {};