/**
 * A {@link Ext.ux.statusbar.StatusBar} plugin that provides automatic error
 * notification when the associated form contains validation errors.
 */
Ext.define('Ext.ux.statusbar.ValidationStatus', {
	extend: 'Ext.Component',
	alias: 'plugin.validationstatus',
	requires: ['Ext.util.MixedCollection'],
	/**
	 * @cfg {String} errorIconCls
	 * The {@link Ext.ux.statusbar.StatusBar#iconCls iconCls} value to be applied
	 * to the status message when there is a validation error.
	 */
	errorIconCls: 'x-status-error',
	/**
	 * @cfg {String} errorListCls
	 * The css class to be used for the error list when there are validation errors.
	 */
	errorListCls: 'x-status-error-list',
	/**
	 * @cfg {String} validIconCls
	 * The {@link Ext.ux.statusbar.StatusBar#iconCls iconCls} value to be applied
	 * to the status message when the form validates.
	 */
	validIconCls: 'x-status-valid',

	/**
	 * @cfg {String} showText
	 * The {@link Ext.ux.statusbar.StatusBar#text text} value to be applied when
	 * there is a form validation error.
	 */
	showText: 'The form has errors (click for details...)',
	/**
	 * @cfg {String} hideText
	 * The {@link Ext.ux.statusbar.StatusBar#text text} value to display when
	 * the error list is displayed.
	 */
	hideText: 'Click again to hide the error list',
	/**
	 * @cfg {String} submitText
	 * The {@link Ext.ux.statusbar.StatusBar#text text} value to be applied when
	 * the form is being submitted.
	 */
	submitText: 'Saving...',

	/**
	 * @private
	 */
	init: function(sb) {
		var me = this;

		me.statusBar = sb;
		sb.on({
			single: true,
			scope: me,
			render: me.onStatusbarRender
		});
		sb.on({
			click: {
				element: 'el',
				fn: me.onStatusClick,
				scope: me,
				buffer: 200
			}
		});
	},

	onStatusbarRender: function(sb) {
		var me = this,
			startMonitor = function() {
				me.monitor = true;
			};

		me.monitor = true;
		me.errors = Ext.create('Ext.util.MixedCollection');
		me.listAlign = (sb.statusAlign === 'right' ? 'br-tr?' : 'bl-tl?');

		if (me.form) {
			// Allow either an id, or a reference to be specified as the form name.
			me.formPanel = Ext.getCmp(me.form) || me.statusBar.lookupController().lookupReference(me.form);
			me.basicForm = me.formPanel.getForm();
			me.startMonitoring();
			me.basicForm.on({
				beforeaction: function(f, action) {
					if (action.type === 'submit') {
						// Ignore monitoring while submitting otherwise the field validation
						// events cause the status message to reset too early
						me.monitor = false;
					}
				}
			});
			me.formPanel.on({
				beforedestroy: me.destroy,
				scope: me
			});
			me.basicForm.on('actioncomplete', startMonitor);
			me.basicForm.on('actionfailed', startMonitor);
		}
	},

	/**
	 * @private
	 */
	startMonitoring: function() {
		this.basicForm.getFields().each(function(f) {
			f.on('validitychange', this.onFieldValidation, this);
		}, this);
	},

	/**
	 * @private
	 */
	stopMonitoring: function() {
		var form = this.basicForm;

		if (!form.destroyed) {
			form.getFields().each(function(f) {
				f.un('validitychange', this.onFieldValidation, this);
			}, this);
		}
	},

	doDestroy: function() {
		Ext.destroy(this.msgEl);
		this.stopMonitoring();
		this.statusBar.statusEl.un('click', this.onStatusClick, this);
		this.callParent();
	},

	/**
	 * @private
	 */
	onFieldValidation: function(f, isValid) {
		var me = this,
			msg;

		if (!me.monitor) {
			return false;
		}
		msg = f.getErrors()[0];
		if (msg) {
			me.errors.add(f.id, { field: f, msg: msg });
		}
		else {
			me.errors.removeAtKey(f.id);
		}
		this.updateErrorList();
		if (me.errors.getCount() > 0) {
			if (me.statusBar.getText() !== me.showText) {
				me.statusBar.setStatus({
					text: me.showText,
					iconCls: me.errorIconCls
				});
			}
		}
		else {
			me.statusBar.clearStatus().setIcon(me.validIconCls);
		}
	},

	/**
	 * @private
	 */
	updateErrorList: function() {
		var me = this,
			msg,
			msgEl = me.getMsgEl();

		if (me.errors.getCount() > 0) {
			msg = ['<ul>'];
			this.errors.each(function(err) {
				msg.push('<li id="x-err-', err.field.id, '"><a href="#">', err.msg, '</a></li>');
			});
			msg.push('</ul>');
			msgEl.update(msg.join(''));
		}
		else {
			msgEl.update('');
		}

		// reset msgEl size
		msgEl.setSize('auto', 'auto');
	},

	/**
	 * @private
	 */
	getMsgEl: function() {
		var me = this,
			msgEl = me.msgEl,
			t;

		if (!msgEl) {
			msgEl = me.msgEl = Ext.DomHelper.append(Ext.getBody(), {
				cls: me.errorListCls
			}, true);
			msgEl.hide();
			msgEl.on('click', function(e) {
				t = e.getTarget('li', 10, true);
				if (t) {
					Ext.getCmp(t.id.split('x-err-')[1]).focus();
					me.hideErrors();
				}
			}, null, { stopEvent: true }); // prevent anchor click navigation
		}
		return msgEl;
	},

	/**
	 * @private
	 */
	showErrors: function() {
		var me = this;

		me.updateErrorList();
		me.getMsgEl().alignTo(me.statusBar.getEl(), me.listAlign).slideIn('b', { duration: 300, easing: 'easeOut' });
		me.statusBar.setText(me.hideText);
		me.formPanel.body.on('click', me.hideErrors, me, { single: true }); // hide if the user clicks directly into the form
	},

	/**
	 * @private
	 */
	hideErrors: function() {
		var el = this.getMsgEl();
		if (el.isVisible()) {
			el.slideOut('b', { duration: 300, easing: 'easeIn' });
			this.statusBar.setText(this.showText);
		}
		this.formPanel.body.un('click', this.hideErrors, this);
	},

	/**
	 * @private
	 */
	onStatusClick: function() {
		if (this.getMsgEl().isVisible()) {
			this.hideErrors();
		}
		else if (this.errors.getCount() > 0) {
			this.showErrors();
		}
	}
});
