/*
 * GxUI Library 2.0.1
 * Copyright (c) 2009, Artech
 * All rights reserved.
 * 
 * GxUI Library is freely distributable under the terms of the BSD license.
 * 
 */

/*!
* Grid Column AutoWidther
* Version 1.0alpha
* Copyright(c) 2012 Skirtle's Den
* License: http://skirtlesden.com/ux/license
*/
Ext.define('Skirtle.grid.AutoWidther', {
	mixins: { observable: 'Ext.util.Observable' },

	pendingCount: 0,

	constructor: function (config) {
		var me = this;

		Ext.apply(me, config);

		me.addEvents('beforecolumnresize', 'columnresize');
		me.mixins.observable.constructor.call(me);
	},

	autoWidthColumn: function (column, config) {
		var me = this,
            grid = me.grid,
            empty = grid.getStore().getCount() === 0,
            els,
            originalColumnElWidth,
            newWidth;

		config = config || {};

		// Don't resize hidden columns. Arguably this an overly simplistic approach but at least it has good performance
		if (column.isHidden() || !grid.rendered) {
			return false;
		}

		if (empty) {
			if (!Ext.isNumber(newWidth = me.getEmptyWidth(column, config))) {
				return false;
			}
		}
		else {
			newWidth = (config.includeHeader && me.calculateHeaderWidth(column, config)) || 1;

			// Can be multiple, e.g. using the grouping feature
			els = me.getColumnResizers(column, config);

			if (els.length) {
				// Reset the styles on the table so it uses auto sizing, unless this has already been done elsewhere
				me.start();

				Ext.each(els, function (el) {
					el = Ext.fly(el);

					/* This is needed otherwise columns only get wider, not narrower.
					* Using getWidth() or getStyle() doesn't necessarily give us the value we want.
					* Even worse is getAttribute(), which doesn't always return a string.
					*/
					originalColumnElWidth = el.dom.style.width;
					el.setStyle('width', 'auto');

					newWidth = Math.max(el.getWidth(), newWidth);

					// Put it back the way we found it
					el.setStyle('width', originalColumnElWidth);
				});

				// Put the table back the way we found it
				me.end();
			}
		}

		config.newWidth = newWidth;

		if (me.beforeColumnResize(column, config) === false
            || me.fireEvent('beforecolumnresize', me, column, config) === false) {
			return false;
		}

		// flex takes priority over width, so remove it
		if (column.flex) {
			column.flex = null;
		}

		column.setWidth(config.newWidth, /* doLayout */!me.isPending());

		// Required if the column was previously flexed as setWidth thinks the width is already correct
		column.width = config.newWidth;

		me.onColumnResize(column, config);
		me.fireEvent('columnresize', me, column, config);

		return config.newWidth;
	},

	// TODO: Should this be empty?
	beforeColumnResize: function (column, config) {
		if (config.minWidth) {
			config.newWidth = Math.max(config.newWidth, config.minWidth);
		}

		if (config.maxWidth) {
			config.newWidth = Math.min(config.newWidth, config.maxWidth);
		}
	},

	calculateHeaderWidth: function (column, config) {
		// TODO: Verify the need for all the null ternary checks
		var me = this,
            grid = me.grid,
            selector = me.headerCellSelector(column, config),
            triggerSelector = me.headerTriggerSelector(column, config),
            el = grid.getEl(),
            headerCell = el.down(selector),
            trigger = headerCell ? headerCell.down(triggerSelector) : null;

		return headerCell
            ? me.calculateWidth(headerCell) + (trigger ? trigger.getComputedWidth() : 0)
            : false;
	},

	// TODO: Currently only used for the header... is it really necessary?
	calculateWidth: function (cell) {
		var el = Ext.fly(cell);

		return el.getTextWidth() + el.getFrameWidth('lr');
	},

	destroy: function () {
		this.clearListeners();
		this.grid = null;
	},

	// Called when resizing is complete to set the styles on the table elements back to what they were at the start
	end: function () {
		if (! --this.pendingCount) {
			Ext.iterate(this.originalWidths, function (id, width) {
				var tableEl = Ext.fly(id);

				tableEl.setStyle('table-layout', '');
				tableEl.setWidth(width);
			});
		}
	},

	getColumnResizers: function (column, config) {
		// Grab the <th> rows (one per table) that are used to size the columns
		// TODO: can't assume x- prefix
		var els = this.grid.getEl().query('.x-grid-col-resizer-' + column.id);

		// Grouping feature - first table wraps everything and needs to be ignored
		if (els.length > 1 && Ext.fly(els[0]).parent('table').contains(els[1])) {
			els.shift();
		}

		return els;
	},

	getEmptyWidth: function (column, config) {
		if (config.emptyWidth === 'header') {
			return this.calculateHeaderWidth(column, config);
		}

		return config.emptyWidth;
	},

	getTableResizers: function () {
		// TODO: can't assume x- prefix
		var els = this.grid.getView().getEl().query('.x-grid-table-resizer');

		// Grouping feature - first table wraps everything and can be ignored
		if (els.length > 1 && Ext.fly(els[0]).contains(els[1])) {
			els.shift();
		}

		return els;
	},

	headerCellSelector: function (column, config) {
		// TODO: can't assume x- prefix
		// TODO: Should we even use a selector for this?
		return Ext.String.format('#{0} .x-column-header-inner', column.id);
	},

	headerTriggerSelector: function (column, config) {
		// TODO: can't assume x- prefix
		// TODO: Should we even use a selector for this?
		return '.x-column-header-trigger';
	},

	isPending: function () {
		return this.pendingCount !== 0;
	},

	onColumnResize: Ext.emptyFn,

	// Sets the table element for the grid to automatic width... must be reset using endAutoWidth when we're done
	start: function () {
		var me = this;

		if (!me.pendingCount++) {
			me.originalWidths = {};

			Ext.each(me.getTableResizers(), function (tableEl) {
				tableEl = Ext.fly(tableEl);

				me.originalWidths[Ext.id(tableEl)] = tableEl.getWidth();

				tableEl.setStyle({
					'table-layout': 'auto',
					width: 'auto'
				});
			});
		}
	}
});