Ext.namespace("Zarafa.core.ui");

/**
 * @class Zarafa.core.ui.View
 * @extends Ext.util.Observable
 * The View class is a component for building views. Views abstract away the details
 * of manipulating HTML and the DOM from underlying controls such as grid panels
 * and {@link Zarafa.calendar.ui.CalendarPanel CalendarPanel}. 
 * 
 * The View class supports convenience methods for creating elements, managing
 * several child views, etc.
 * <p>
 * The createDiv() method can be used to create div elements. These elements are then 
 * automatically destroyed by the destroy() method. 
 * Child views can be added using addChildView(). These are then also destroyed 
 * automatically by the default destroy() implementation.
 */
Zarafa.core.ui.View = Ext.extend(Ext.util.Observable, {
	/**
	 * @cfg {String} baseCls The base CSS class to apply to this panel's element.
	 */
	baseCls : undefined,

	/**
	 * @cfg {String} itemCls The item CSS class to apply to this panel's element.
	 */
	itemCls : undefined,

	/**
	 * @cfg {String} themeCls The CSS theme which must be applied to this view. The name will
	 * be added to the various CSS classes which are generated using {@link #getClassName}.
	 * will be used.
	 */
	themeCls : undefined,

	/**
	 * The parent element for this view
	 * @property
	 * @type Object
	 */
	parentView : undefined,

	/**
	 * The {@link Ext.Element} in which this view has been rendered.
	 * This is passed as argument to {@link #render}. 
	 * @property
	 * @type Ext.Element
	 */
	container : undefined,

	/**
	 * The list of child objects which were created for this view. When
	 * this object is destroyed all registered children will be automatically
	 * cleaned up as well.
	 * @property
	 * @type Array
	 */
	children : undefined,

	/**
	 * The list of child elements which were created by this view using
	 * the {@link #create} and {@link #createDiv} functions. When this
	 * object is destroyed all registered elements will be automatically
	 * cleaned up as well.
	 * @property
	 * @type Array
	 */
	elements : undefined,

	/**
	 * Indicates if this view has been {@link #render rendered}.
	 * @property
	 * @type Boolean
	 */
	rendered : false,

	/**
	 * True if the view has been destroyed already. Read only
	 * @property isDestroyed
	 * @type Boolean
	 */
	isDestroyed: false,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor : function(config)
	{
		Ext.apply(this, config);
		
		this.addEvents(
			/**
			 * @event beforedestroy
			 * Fires before the component is {@link #destroy}ed. Return false from an event handler to stop the {@link #destroy}.
			 * @param {Ext.Component} this
			 */
			'beforedestroy',
			/**
			 * @event destroy
			 * Fires after the component is {@link #destroy}ed.
			 * @param {Ext.Component} this
			 */
			'destroy'
		);

		Zarafa.core.ui.View.superclass.constructor.call(this, config);

		this.init();
	},

	/**
	 * Initialises the view. When a {@link #parentView} is defined,
	 * this will automatically register this view to the given parentView.
	 * @protected 
	 */
	init : function()
	{
		this.children = [];
		this.elements = [];

		if (Ext.isDefined(this.parentView)) {
			this.parentView.addChildView(this);
		}
	},

	/**
	 * Returns the base className which must be applied to all {@link Ext.Element elements} within this
	 * view. This will also be the prefix of any additional CSS classes which will be applied to those elements.
	 * The exact base classname depends on the {@link #baseCls} and the availablity of the {@link #itemCls}.
	 * @return {String} The base class. 
	 * @private
	 */
	getBaseClassName : function()
	{
		return this.itemCls ? (this.baseCls + '-' + this.itemCls) : this.baseCls;
	},

	/**
	 * Helper function for generating a string with CSS class names. Each {@link Ext.Element Element} must
	 * at least contain the {@link #baseCls} CSS class and CSS classes containing the {@link #itemCls} and
	 * {@link #themeCls} but additional names will be generated for specific elements. Example:
	 * <pre>
	 *   baseCls =  'zarafa-calendar';
	 *   getClassName('body', 'image');
	 * </pre>
	 * The outcome will be:
	 * <pre>
	 *   'zarafa-calendar zarafa-calendar-body zarafa-calendar-body-image zarafa-calendar-blue zarafa-calendar-blue-body zarafa-calendar-blue-body-image'
	 * </pre>
	 *
	 * @param {String} name The main name for the CSS element, this will be appended to the {@link #baseCls}.
	 * @param {String/Array} postfix (optional) If provided extra CSS classNames will be generated with the name and postfix. If the postfix
	 * is an array, all elements from the array will be used as postfix.
	 * @return {String} the CSS class names
	 * @param {String/Array} extraCls (optional) If provided extra CSS classNames will be generated with this extraCls. If the postfix
	 * is an array, all elements from the array will be used as extraCls.
	 * @private
	 */
	getClassName : function(name, postfix, extraCls)
	{
		var baseCls = this.getBaseClassName();
		var className = this.baseCls;

		if (!Ext.isEmpty(this.themeCls)) {
			className += ' ' + this.baseCls + '-' + this.themeCls;
		}

		className += ' ' + baseCls + '-' + name;

		if (Ext.isDefined(postfix) && !Array.isArray(postfix)) {
			postfix = [ postfix ];
		}

		if (!Ext.isEmpty(postfix)) {
			for (var i = 0, len = postfix.length; i < len; i++) {
				className += ' ' + baseCls + '-' + name + '-' + postfix[i];
			}
		}

		if (Ext.isDefined(extraCls) && !Array.isArray(extraCls)) {
			extraCls = [ extraCls ];
		}

		if (!Ext.isEmpty(extraCls)) {
			for (var i = 0, len = extraCls.length; i < len; i++) {
				className += ' ' + baseCls + '-' + extraCls[i];
			}
		}

		return className;
	},

	/**
	 * A convenience method for creating DIV elements. The new DIV element will be
	 * wrapped in an {@link Ext.Element} and added to the view as a property called 'name'.
	 * Equivalent to <code>create('div', parent, name, className)</code>.
	 * @param {Mixed} parent element
	 * @param {String} name the name of the property to create
	 * @param {String} className (optional) class name for the new DIV 
	 * @return {Ext.Element} The created div element
	 * @private
	 */
	createDiv : function(parent, name, className)
	{
		return this.create('div', parent, name, className);
	},
	
	/**
	 * A convenience method for creating DOM elements. The new element will be
	 * wrapped in an {@link Ext.Element} and added to the view as a property called 'name'.
	 * Calling <pre>this.create('div', 'test')</pre> for instance will create a new
	 * property <pre>this.test</pre>, and the DIV can be manipulated with code such
	 * as <pre>this.test.setSize(100, 100);</pre>. If a property already exists
	 * with the given name, the property will be converted into an array and
	 * the new element will the appended to that array.
	 * @param {String|Object} tagName type of HTML tag to generate (i.e. 'div', 'img', etc.) 
	 * @param {Mixed} parent element
	 * @param {String} name the name of the property to create
	 * @param {String} className (optional) class name for the new DIV
	 * @return {Ext.Element} The created element
	 * @private
	 */
	create : function(tagName, parent, name, className)
	{
		var config = Ext.isObject(tagName) ? tagName : { tag : tagName };

		Ext.applyIf(config, { cls : className || this.baseCls });

		// create a new HTML element and add it to the parent element
		var element = Ext.get(parent).createChild(config);
		
		// add the element as a property to this
		if (!this[name]) {
			this[name] = element;
		} else if (Array.isArray(this[name])) {
			this[name].push(element);
		} else {
			this[name] = [ this[name], element ];
		}
		
		// add the element to the elements list so that we may destroy it easily later on
		this.elements.push(element);
		
		return element;
	},
	
	/**
	 * Removes a child element from the view and destroys it.
	 * @param {Ext.Element} element DOM element 
	 */
	remove : function(element)
	{
		element = Ext.get(element);
		this.elements.remove(element);
		element.remove();
	},
	
	/**
	 * @return {Ext.Element} The main element of the view. This is the element the
	 * view is rendered <i>into</i>. The container of a view may be shared with other 
	 * views, so manipulating the container of a given view might affect other views
	 * as well. 
	 */
	getContainer : function()
	{
		return this.container;
	},

	/**
	 * Calls 'render' on each of the child views
	 * @param {Ext.Element} container The container into which the children must be rendered. Defaults to {@link #el}.
	 * @private
	 */
	renderChildren : function(container)
	{
		for (var i = 0, len = this.children.length; i < len; i++) {
			this.children[i].render(container || this.container);
		}
	},
	
	/**
	 * Renders the view. 
	 * Rendering a view is done once after construction, and creates all the DOM elements needed for the visual 
	 * representation of the view. 
	 * @param {Ext.Element} container The Ext.Element into which the view must be rendered.
	 */
	render : function(container)
	{
		this.container = container;
		// TODO Can't we call renderChildren here?
		this.rendered = true;
	},
	
	/**
	 * Calls 'layout' on each of the child views.
	 * @private
	 */
	layoutChildren : function()
	{
		for (var i = 0, len = this.children.length; i < len; i++) {
			this.children[i].layout();
		}
	},

	/**
	 * Called just before this View will be {@link #layout layed out}.
	 * @protected
	 */
	onBeforeLayout : Ext.emptyFn,

	/**
	 * Called when this view is being {@link #layout layed out}.
	 * @protected
	 */
	onLayout : Ext.emptyFn,

	/**
	 * Called just after this View has been {@link #layout layed out}.
	 * @protected
	 */
	onAfterLayout : Ext.emptyFn,

	/**
	 * Lays out the view, setting the position and size of the individual DOM elements.
	 */
	layout : function()
	{
		this.onBeforeLayout();
		this.onLayout();
		this.onAfterLayout();
	},

	/**
	 * Calls 'destroy' on each of the child views.
	 * @private
	 */
	destroyChildren : function()
	{
		for (var i = 0, len = this.children.length; i < len; i++) {
			this.children[i].destroy();
		}
	},
	
	/**
	 * Destroys the view, removing all DOM elements generated by render() from the DOM tree and unhooks
	 * any events.
	 */
	destroy : function()
	{
		if (!this.isDestroyed) {
			if (this.fireEvent('beforedestroy', this) !== false) {
				// remove all elements generated with createDiv() from the DOM
				for (var i = 0, len = this.elements.length; i < len; i++) {
					this.elements[i].remove();
				}

				// destroy child views
				this.destroyChildren();

				this.onDestroy();

				this.fireEvent('destroy', this);
				this.purgeListeners();
			}

			this.isDestroyed = true;
		}
	},

	/**
	 * Set the {@link #parentView} field. This will be called by {@link #addChildView}
	 * to update the parent to this view. 
	 * @param {Zarafa.core.ui.View} parentView The parent view
	 * @private
	 */
	setParentView : function(parentView)
	{
		this.parentView = parentView;
	},

	/**
	 * Adds a child view to this view.
	 * @param {Zarafa.core.ui.View} child child view to be added 
	 */
	addChildView : function(child)
	{
		child.setParentView(this);
		this.children.push(child);
		return child;
	},
	
	/**
	 * Removes a child view from this view. 
	 * @param {Zarafa.core.ui.View} child child view to be added
	 * @param {Boolean} destroy if true, destroy the child view 
	 */
	removeChildView : function(child, destroy)
	{
		this.children.remove(child);
		child.setParentView(undefined);
		if (destroy) {
			child.destroy();
		}
	},

	/**
	 * Purge all event listeners.
	 * @private
	 */
	purgeListeners : Ext.Container.prototype.purgeListeners,

	/**
	 * Clear all monitored event listeners. This will call
	 * {@link Ext.util.Observable.un un()} on each previously
	 * registered event handler which was registered with
	 * {@link #mon}.
	 * @private
	 */
	clearMons : Ext.Container.prototype.clearMons,

	/**
	 * Create the tracking object in which we store
	 * each event handler which was registered with {@link #mon}.
	 * @private
	 */
	createMons : Ext.Container.prototype.createMons,

	/**
	 * Adds listeners to any Observable object (or Elements) which are automatically removed when this View
	 * is destroyed.
	 * @param {Observable|Element} item The item to which to add a listener/listeners.
	 * @param {Object|String} ename The event name, or an object containing event name properties.
	 * @param {Function} fn Optional. If the <code>ename</code> parameter was an event name, this
	 * is the handler function.
	 * @param {Object} scope Optional. If the <code>ename</code> parameter was an event name, this
	 * is the scope (<code>this</code> reference) in which the handler function is executed.
	 * @param {Object} opt Optional. If the <code>ename</code> parameter was an event name, this
	 * is the {@link Ext.util.Observable#addListener addListener} options.
	 */
	mon : Ext.Container.prototype.mon,

	/**
	 * Removes listeners that were added by the {@link #mon} method.
	 * @param {Observable|Element} item The item from which to remove a listener/listeners.
	 * @param {Object|String} ename The event name, or an object containing event name properties.
	 * @param {Function} fn Optional. If the <code>ename</code> parameter was an event name, this
	 * is the handler function.
	 * @param {Object} scope Optional. If the <code>ename</code> parameter was an event name, this
	 * is the scope (<code>this</code> reference) in which the handler function is executed.
	 */
	mun : Ext.Container.prototype.mun,

	/**
	 * Called when the View is being destroyed.
	 * @protected
	 */
	onDestroy : Ext.emptyFn
});