Ext.namespace('Zarafa.core.plugins');

/**
 * @class Zarafa.core.plugins.InputAutoFocusPlugin
 * @extends Object
 * @ptype zarafa.inputautofocusplugin
 *
 * A special plugin which, when placed on a {@link Ext.Panel} will
 * automatically select the first available Input element it can find.
 */
Zarafa.core.plugins.InputAutoFocusPlugin = Ext.extend(Object, {
	/**
	 * @cfg {Ext.Component} autoFocus The component which should
	 * be focused when the {@link #field} has been {@link Ext.Container#afterlayout layed out}
	 * or {@link Ext.Container#activate activated}.
	 */
	autoFocus : undefined,

	/**
	 * The field on which this plugin has been installed
	 * @property
	 * @type Ext.Container
	 */
	field : undefined,

	/**
	 * The extra {@link Ext.Element} which is inserted at the start of the {@link #field container},
	 * this is used to apply a {@link #doCyclicFocus cyclic focus} to prevent the focus
	 * to leave the {@link #field container}.
	 * @property
	 * @type Ext.Element
	 */
	beginFocusEl : undefined,

	/**
	 * The extra {@link Ext.Element} which is inserted at the end of the {@link #field container},
	 * this is used to apply a {@link #doCyclicFocus cyclic focus} to prevent the focus
	 * to leave the {@link #field container}.
	 * @property
	 * @type Ext.Element
	 */
	endFocusEl : undefined,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor : function(config)
	{
		Ext.apply(this, config);
	},

	/**
	 * Called by the {@link Ext.Container} when the plugin is being registerd.
	 * @param {Ext.Container} field The field into which this plugin is installed
	 */
	init : function(field)
	{
		this.field = field;
		field.inputAutoFocusPlugin = this;

		this.field.on('afterrender', this.onAfterRender, this);
		this.field.on('afterlayout', this.onAfterFirstLayout, this, { single: true });
		this.field.on('destroy', this.onDestroy, this);
	},

	/**
	 * Event handler which is fired after the {@link #field} has been {@link Ext.Container#afterrender rendered}.
	 * This will create the {@link #beginFocusEl} and {@link #endFocusEl} elements to enable {@link #doCyclicFocus}
	 * cyclic focussing.
	 * @private
	 */
	onAfterRender : function()
	{
		this.beginFocusEl = Ext.DomHelper.insertBefore(this.field.el, {
			tag : 'a',
			href: '#',
			style: 'position: absolute; text-decoration: none; font-height: 1px; width: 1px; height: 1px; left:-10000px; top:-10000px;'
		}, true);
		this.beginFocusEl.dom.innerHTML = " ";
		this.beginFocusEl.on('focus', this.onLimitFocussed, this);

		this.endFocusEl = Ext.DomHelper.insertAfter(this.field.el, {
			tag : 'a',
			href: '#',
			style: 'position: absolute; text-decoration: none; font-height: 1px; width: 1px; height: 1px; left:-10000px; top:-10000px;'
		}, true);
		this.endFocusEl.dom.innerHTML = " ";
		this.endFocusEl.on('focus', this.onLimitFocussed, this);
		// Check if the field is a child of the Ext.Window, if a focus element exists on it,
		// then we need to intercept its focus and move it to the form
		var win = this.field.findParentByType('window');
		if (win && win.focusEl) {
			win.focusEl.on('focus', this.onDialogFocussed, this);
		}

	},

	/**
	 * Event hander which is fired after the {@link #field} has been {@link Ext.Container#afterlayout layed out}.
	 * This will check if the {@link #field} is a tab in a {@link Ext.TabPanel} or a normal {@link Ext.Container}.
	 * When it is a tab, it will wait for the {@link Ext.Container#activate tab-activation}, otherwise it will
	 * start the {@link #doAutoFocus}.
	 * @private
	 */
	onAfterFirstLayout : function()
	{
		if (Ext.isDefined(this.field.tabEl)) {
			this.field.on('activate', this.onActivate, this);
		} else {
			// This field isn't a tab, but perhaps one of the child elements is...
			var tabs = this.field.findBy(function(cmp) { return Ext.isDefined(cmp.tabEl); });
			if (!Ext.isEmpty(tabs)) {
				for (var i = 0, len = tabs.length; i < len; i++) {
					this.field.mon(tabs[i], 'activate', this.onActivate, this);
				}
			}
		}
	},

	/**
	 * Event handler which is fired after the {@link #field} has been {@link Ext.Container#activate activated}.
	 * This will start the {@link #doAutoFocus}.
	 * @private
	 */
	onActivate : function()
	{
		// Apply a 1ms delay, otherwise we cannot detect which input field
		// is hidden or not.
		this.doAutoFocus.defer(1, this);
	},

	/**
	 * Called when either the {@link #beginFocusEl} or {@link #endFocusEl} has been focussed.
	 * This will start the {@link #doCyclicFocus}.
	 * @param {Ext.EventObject} event The event object
	 * @param {HTMLElement} element The element which was focussed
	 * @private
	 */
	onLimitFocussed : function(event, element)
	{
		this.doCyclicFocus(this.beginFocusEl.dom === element);
	},

	/**
	 * Event handler which is called when the {@link #field} is being destroyed.
	 * This will automatically destroy the {@link #beginFocusEl} and {@link #endFocusEl}.
	 * @private
	 */
	onDestroy : function()
	{
		Ext.destroy(this.beginFocusEl);
		Ext.destroy(this.endFocusEl);
	},

	/**
	 * Change the {@link #autoFocus} property and automatically
	 * have the focus {@link #doAutoFocus} updated to this component.
	 * @param {Mixed} autoFocus The replacement for the {@link #autoFocus} object
	 */
	setAutoFocus : function(autoFocus)
	{
		this.autoFocus = autoFocus;
		this.doAutoFocus();
	},

	/**
	 * Called by {@link #onAfterFirstLayout} and {@link #onActivate}. If {@link #autoFocus} has been
	 * provided, this will apply the focus to that component, otherwise it will search for the first
	 * {@link #isFocusElement focussable element} inside the {@link #field container} (and the sub-containers).
	 * @private
	 */
	doAutoFocus : function()
	{
		var focusCmp;

		if (this.autoFocus) {
			if (Ext.isString(this.autoFocus)) {
				// Perhaps the string is a property on our field
				focusCmp = this.field[this.autoFocus];
				if (!focusCmp) {
					// Perhaps the string is a xtype of a child component
					var children = this.field.findByType(this.autoFocus);
					if (!Ext.isEmpty(children)) {
						focusCmp = children[0];
					}
				}
				if (!focusCmp) {
					// Perhaps the string is an ID of the HTML element
					focusCmp = Ext.get(this.autoFocus);
				}
			} else if (Ext.isElement(this.autoFocus) || Ext.isFunction(this.autoFocus.focus)) {
				focusCmp = this.autoFocus;
			}
		} else {
			focusCmp = this.findFocusElement(this.field);
			// If no focus element was found, then lets try
			// if we can focus on a button instead.
			if (!focusCmp) {
				focusCmp = this.findFocusElement(this.field, false, true);
			}
		}

		if (focusCmp) {
			focusCmp.focus.defer(1, focusCmp);
			if(focusCmp.events['setAutoFocusCursor']){
				focusCmp.fireEvent('setAutoFocusCursor', focusCmp.getEditor());
			}
		}
	},

	/**
	 * Called when the {@link #beginFocusEl} or {@link #endFocusEl} elements have been focussed.
	 * This will apply a cycle to the focus, making sure the focus will not leave the {@link #field}
	 * in which this plugin is installed.
	 * @param {Boolean} inverse Search for the last focussable item in the container rather then
	 * the first.
	 * @private
	 */
	doCyclicFocus : function(inverse)
	{
		var focusCmp;

		focusCmp = this.findFocusElement(this.field, inverse, true);
		if (focusCmp) {
			focusCmp.focus.defer(1, focusCmp);
		}
	},

	/**
	 * Search through the given container to search for the first {@link #isFocusElement focussable element}
	 * inside the given container (and the sub-containers).
	 * @param {Ext.Container} container The container through which we must search to find the first
	 * focussable element
	 * @param {Boolean} inverse True to search for the last Focus Element
	 * @param {Boolean} allowButton Allow a button to be considered an focussable input element
	 * @return {Ext.Component} The focussable component which was found in this container
	 * @private
	 */
	findFocusElement : function(container, inverse, allowButton)
	{
		var objects = [];

		if (container.topToolbar) {
			objects = objects.concat(container.topToolbar.items.items);
		}
		if (container.items) {
			objects = objects.concat(container.items.items);
		}
		if (container.buttons) {
			objects = objects.concat(container.buttons);
		}

		if (inverse) {
			objects.reverse();
		}

		for (var i = 0, len = objects.length; i < len; i++) {
			var item = objects[i];
			var focus;

			if (this.isContainer(item)) {
				if (item.isVisible()) {
					focus = this.findFocusElement(item, inverse, allowButton);
				}
			} else if (this.isFocusElement(item, allowButton)) {
				focus = item;
			}

			if (focus) {
				return focus;
			}
		}
	},

	/**
	 * Check if the given {@link Ext.Component} is a container which contains items through which
	 * we can search for a {@link #isFocusElement focussable element}.
	 * @param {Ext.Component} item The component we are checking if it is a container
	 * @return {Boolean} True if the item is a container, false otherwise
	 * @private
	 */
	isContainer : function(item)
	{
		return (item instanceof Ext.Container || item instanceof Ext.form.CompositeField);
	},

	/**
	 * Check if the given {@link Ext.Component} is a visible input-element which can be focussed.
	 * This includes that the size of the component is non-0, the element itself is visible and
	 * not readonly input element.
	 * @param {Ext.Component} item The component we are checking
	 * @param {Boolean} allowButton Allow a button to be considered an focussable input element
	 * @return {Boolean} True if the item is focussable, false otherwise
	 */
	isFocusElement : function(item, allowButton)
	{
		var el = item.btnEl || item.el;

		if (!el) {
			return false;
		}

		var disallowedType = (allowButton === true) ? /submit|reset|hidden/i : /button|submit|reset|hidden/i;
		var allowedTagname = (allowButton === true) ? /button|input|textarea|select/i : /input|textarea|select/i;

		return (el.dom.style.height !== '0px' &&
				el.dom.style.width !== '0px' &&
				el.dom.style.display !== 'none' &&
				el.dom.style.visibility !== 'hidden' &&
				!el.dom.disabled &&
				!el.dom.getAttribute('readonly') &&
				!disallowedType.test(el.dom.type) &&
				allowedTagname.test(el.dom.tagName));
	},

	/**
	 * Call this when Ext.JS moves the focus to its own focus element a#x-dlg-focus (see source of {@link Ext.Window#render})
	 * Call {@link #doAutoFocus} to move focus to the first form field
	 */
	onDialogFocussed : function()
	{
		this.doAutoFocus();
	}
});

Ext.preg('zarafa.inputautofocusplugin', Zarafa.core.plugins.InputAutoFocusPlugin);