Ext.ns('Nexts.grid');

/**
 * @class Nexts.grid.RowActioner
 * @extends Ext.util.Observable
 *
 * CSS rules from Nexts.RowActions.css are mandatory
 *
 * Important general information: Actions are identified by iconCls. Wherever an <i>action</i>
 * is referenced (event argument, callback argument), the iconCls of clicked icon is used.
 * In another words, action identifier === iconCls.
 *
 * Creates new RowActions plugin
 * @constructor
 * @param {Object} config The config object
 */
Nexts.grid.RowActioner = function(config) {
	Ext.apply(this, config);

	this.renderer = this.renderer.createDelegate(this);
};

Ext.apply(Nexts.grid.RowActioner.prototype, {

	// configuration options

	/**
	 * @cfg {Array} actions Mandatory. Array of action configuration objects. The following
	 * configuration options of action are recognized:
	 *
	 * - @cfg {String} id
	 *
	 * - @cfg {Function} handler
	 *
	 * - @cfg {Object} scope
	 *
	 * - @cfg {String} iconIndex Optional, however either iconIndex or iconCls must be
	 *   configured. Field name of the field of the grid store record that contains
	 *   css class of the icon to show. If configured, shown icons can vary depending
	 *   of the value of this field.
	 *
	 * - @cfg {String} iconCls. css class of the icon to show. It is ignored if iconIndex is
	 *   configured. Use this if you want static icons that are not base on the values in the record.
	 *
	 * - @cfg {Boolean} hide Optional. True to hide this action while still have a space in 
	 *   the grid column allocated to it. IMO, it doesn't make too much sense, use hideIndex instead.
	 *
	 * - @cfg (string} hideIndex Optional. Field name of the field of the grid store record that
	 *   contains hide flag (falsie [null, '', 0, false, undefined] to show, anything else to hide).
	 *
	 * - @cfg {String} qtipIndex Optional. Field name of the field of the grid store record that 
	 *   contains tooltip text. If configured, the tooltip texts are taken from the store.
	 *
	 * - @cfg {String} tooltip Optional. Tooltip text to use as icon tooltip. It is ignored if 
	 *   qtipIndex is configured. Use this if you want static tooltips that are not taken from the store.
	 *
	 * - @cfg {String} qtip Synonym for tooltip
	 *
	 * - @cfg {String} textIndex Optional. Field name of the field of the grids store record
	 *   that contains text to display on the right side of the icon. If configured, the text
	 *   shown is taken from record.
	 *
	 * - @cfg {String} text Optional. Text to display on the right side of the icon. Use this
	 *   if you want static text that are not taken from record. Ignored if textIndex is set.
	 *
	 * - @cfg {String} style Optional. Style to apply to action icon container.
	 */

	/**
	 * @cfg {String} actionEvnet Event to trigger actions, e.g. click, dblclick, mouseover (defaults to 'click')
	 */
	actionEvent: 'click',

	/**
	 * @cfg {Boolean} autoWidth true to calculate field width for iconic actions only.
	 */
	autoWidth: true,

	id: 'actioner',
	
	width: 25,
	
	/**
	 * @cfg {String} dataIndex - Do not touch!
	 * @private
	 */
	dataIndex: '',

	/**
	 * @cfg {Array} groupActions Array of action to use for group headers of grouping grids.
	 * These actions support static icons, texts and tooltips same way as actions. There is one
	 * more action config recognized:
	 * - @cfg {String} align Set it to 'left' to place action icon next to the group header text.
	 *   (defaults to undefined = icons are placed at the right side of the group header.
	 */

	/**
	 * @cfg {String} header Actions column header
	 */
	header: '',

	/**
	 * @cfg {Boolean} menuDisabled No sense to display header menu for this column
	 */
	menuDisabled: true,
	
	hideable: false,

	/**
	 * @cfg {Boolean} sortable Usually it has no sense to sort by this column
	 */
	sortable: false,

	fixed: true,
	
	/**
	 * @cfg {String} tplGroup Template for group actions
	 * @private
	 */
	tplGroup: '<tpl for="actions">'
		+ '<div aid="{id}" class="ne-grid-grow-action-item<tpl if="\'right\'===align"> ne-action-right</tpl> '
		+ '{cls}" style="{style}" qtip="{qtip}">{text}</div>'
		+ '</tpl>',

	/**
	 * @cfg {String} tplRow Template for row actions
	 * @private
	 */
	tplRow: '<div class="ne-grid-row-action">'
		+ '<tpl for="actions">'
		+ '<div aid="{id}" class="ne-grid-row-action-item {cls} <tpl if="text">'
		+ 'ne-grid-row-action-text</tpl>" style="{hide}{style}" qtip="{qtip}">'
		+ '<tpl if="text"><span qtip="{qtip}">{text}</span></tpl></div>'
		+ '</tpl>'
		+ '</div>',

	/**
	 * @cfg {String} hideMode How to hide hidden icons. Valid values are: visibility and display
	 * (defaluts to visibility).
	 */
	hideMode: 'visiblity',

	/**
	 * @cfg {Number} widthIntercept constant used for auto-width calculation
	 */
	//widthIntercept: 4

	/**
	 * @cfg {Number} widthSlope constant used for auto-width calculation
	 */
	widthSlope: 21,


	// methods

	/**
	 * Init function
	 * @param {Ext.grid.GridPanel} grid Grid this plugin is in
	 */
	init: function(grid) {
		this.grid = grid;
		
		// setup template
		if (!this.tpl) {
			this.tpl = this.processActions(this.actions);
		}

		if (!this.widthIntercept) {
			this.widthIntercept = Ext.isSafari ? 4 : 2;
		}

		// calculate width
		if (this.autoWidth) {
			this.width =  this.widthSlope * this.actions.length + this.widthIntercept + 2;
			this.fixed = true;
		}

		grid.on('render', function() {
			this.grid.getView().mainBody.on(this.actionEvent, this.onClick, this);
		}, this);
	
		if (this.fixed) {
			grid.on('beforestaterestore', this.applyState, this);
		}

		// body click handler
		var view = grid.getView();

		// actions in grouping grids support
		if (view.groupTextTpl && this.groupActions) {
			view.interceptMouse = view.interceptMouse.createInterceptor(function(e) {
				if (e.getTarget('.ne-grid-grow-action-item')) {
					return false;
				}
			});
			view.groupTextTpl = '<div class="ne-grid-grow-action-text">' + view.groupTextTpl +'</div>' 
				+ this.processActions(this.groupActions, this.tplGroup).apply();
		}
	},

	applyState: function(p, s) {
		if (s && s.columns) {
			for (var i = 0; i < s.columns.length; i++) {
				if (s.columns[i].id == this.id) {
					s.columns[i].width = this.width;
					return;
				}
			}
		}
	},

	renderer: function(value, cell, record, row, col, store) {
		if (this.rowspan) {
			cell.cellAttr = 'rowspan="' + this.rowspan + '"';
		}
		cell.css += (cell.css ? ' ' : '') + 'ne-grid-row-action-cell';
		return this.tpl.apply(this.getData(value, cell, record, row, col, store));
	},

	/**
	 * Returns data to apply to template. Override this if needed.
	 * @param {Mixed} value 
	 * @param {Object} cell object to set some attributes of the grid cell
	 * @param {Ext.data.Record} record from which the data is extracted
	 * @param {Number} row row index
	 * @param {Number} col col index
	 * @param {Ext.data.Store} store object from which the record is extracted
	 * @returns {Object} data to apply to template
	 */
	getData: function(value, cell, record, row, col, store) {
		return record.data || {};
	},


	/**
	 * Processes actions configs and returns template.
	 * @param {Array} actions
	 * @param {String} template Optional. Template to use for one action item.
	 * @return {String}
	 * @private
	 */
	processActions: function(actions, template) {
		var acts = [];

		// actions loop
		Ext.each(actions, function(a, i) {
			if (!a.id) {
				a.id = Ext.id();
			}
			
			// data for intermediate template
			var o = {
				id: a.id,
				cls: a.iconIndex ? '{' + a.iconIndex + '}' : (a.iconCls ? a.iconCls : ''),
				qtip: a.qtipIndex ? '{' + a.qtipIndex + '}' : (a.tooltip || a.qtip ? a.tooltip || a.qtip : ''),
				text: a.textIndex ? '{' + a.textIndex + '}' : (a.text ? a.text : ''),
				hide: a.hideIndex
					? '<tpl if="' + a.hideIndex + '">' 
						+ ('display' === this.hideMode ? 'display:none' :'visibility:hidden') + ';</tpl>' 
					: (a.hide ? ('display' === this.hideMode ? 'display:none' :'visibility:hidden;') : ''),
				align: a.align || 'right',
				style: a.style ? a.style : ''
			};
			acts.push(o);

		}, this); // eo actions loop

		var xt = new Ext.XTemplate(template || this.tplRow);
		return new Ext.XTemplate(xt.apply({actions:acts}));
	},

	/**
	 * Grid body actionEvent event handler
	 * @private
	 */
	onClick: function(e, target) {
		var view = this.grid.getView();
		var aid = false;

		// handle row action click
		var row = e.getTarget('.x-grid3-row');
		var col = view.findCellIndex(target.parentNode.parentNode);

		if (target.className.indexOf('ne-grid-row-action-item') != -1) {
			aid = target.getAttribute('aid');
		}
		if (row && aid) {
			this.invokeAction(this.actions, aid, row.rowIndex, col);
		}

		// handle group action click
		if (target.className.indexOf('ne-grid-grow-action-item') != -1) {
			// get groupId
			var group = view.findGroup(target);
			var groupId = group ? group.id.replace(/ext-gen[0-9]+-gp-/, '') : null;

			// get matching records
			var records;
			if (groupId) {
				var re = new RegExp(groupId.escapeRegExp());
				records = this.grid.store.queryBy(function(r) {
					return r._groupId.match(re);
				});
				records = records ? records.items : [];
			}
			aid = target.getAttribute('aid');

			this.invokeGroupAction(this.groupActions, aid, records, groudId);
		}
	},
	
	invokeAction: function(actions, aid, row, col) {
		Ext.each(actions, function(a, i) {
			// call callback if any
			if (a.id == aid) {
				if (a.handler) {
					var record = this.grid.store.getAt(row);
					a.handler.call(a.scope || this, this.grid, record, row.rowIndex, col, a);
					return false;
				}
			}
		}, this);
	},

	invokeGroupAction: function(actions, aid, records, gid) {
		Ext.each(actions, function(a, i) {
			// call callback if any
			if (a.id == aid) {
				if (a.handler) {
					var record = this.grid.store.getAt(row);
					a.handler.call(a.scope || this, this.grid, records, gid, a);
					return false;
				}
			}
		}, this);
	}
});

