/**
 * eGroupWare eTemplate2 - Class which contains the "grid" base class
 *
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
 * @package etemplate
 * @subpackage dataview
 * @link http://www.egroupware.org
 * @author Andreas Stöckel
 * @copyright Stylite 2011
 * @version $Id$
 */

"use strict";

/*egw:uses
	jquery.jquery;
	et2_core_common;

	et2_dataview_interfaces;
	et2_dataview_view_partitionTree;
	et2_dataview_view_row;
	et2_dataview_view_spacer;
	et2_dataview_view_rowProvider;
*/

/**
 * Determines how many pixels the view range of the gridview is extended.
 */
var ET2_GRID_VIEW_EXT = 25;

/**
 * Determines the timeout after which the scroll-event is processed.
 */
var ET2_GRID_SCROLL_TIMEOUT = 25;

var partitionTree = null;

var et2_dataview_grid = Class.extend(et2_dataview_IViewRange, {

	/**
	 * Creates the grid.
	 *
	 * @param _parent is the parent grid class - if null, this means that this
	 * 	is the outer grid which manages the scrollarea. If not null, all other
	 * 	parameters are ignored and copied from the given grid instance.
	 * @param _outerId is the id of the grid container it uses for the css
	 * 	classes.
	 * @param _columnIds is the id of the individual columns used for the css
	 * 	classes.
	 * @param _avgHeight is the starting average height of the column rows.
	 */
	init: function(_parent, _outerId, _columnIds, _dataProvider, _rowProvider, 
		_avgHeight) {

		// If the parent is given, copy all other parameters from it
		if (_parent != null)
		{
			this._outerId = _parent._outerId;
			this._columnIds = _parent._columnIds;
			this._dataProvider = _parent._dataProvider;
			this._avgHeight = _parent._partitionTree.getAverageHeight();
			this._rowProvider = _parent._rowProvider;
		}
		else
		{
			// Otherwise copy the given parameters
			this._outerId = _outerId;
			this._columnIds = _columnIds;
			this._dataProvider = _dataProvider;
			this._rowProvider = _rowProvider;
			this._avgHeight = _avgHeight;

			this._scrollHeight = 0;
			this._scrollTimeout = null;
		}

		// The "treeChanged" variable is called whenever the viewrange has been
		// set - changing the viewrange is the function which causes new elements
		// to be generated and thus the partition tree to degenerate
		this._treeChanged = false;

		// Count of elements which are buffered at top and bottom of the viewrange
		// before they get replaced with placeholders
		this._holdCount = 50;

		// The current range holds the currently visible nodes
		this._currentRange = et2_range(0, 0);

		// Build the grid outer nodes
		this._createNodes();

		// Create the partition tree object which is used to organize the tree
		// items.
		this._partitionTree = new et2_dataview_partitionTree(this._dataProvider, 
			this._rowProvider, this._avgHeight, this.innerTbody);

		// Setup the "rebuild" timer - it rebuilds the complete partition tree
		// if any change has been done to it. Rebuilding the partition tree is
		// necessary as the tree structure happens to degenerate.
		var self = this;
		this._rebuildTimer = window.setInterval(function() {
			self._checkTreeRebuild();
		}, 10 * 1000);
	},

	destroy: function() {
		// Stop the scroll timeout
		if (this._scrollTimeout)
		{
			window.clearTimeout(this._scrollTimeout);
		}

		// Stop the rebuild timer
		window.clearInterval(this._rebuildTimer);

		// Free the partition tree
		this._partitionTree.free();
	},

	clear: function() {
		// Free the partition tree and recreate it
		this._avgHeight = this._partitionTree.getAverageHeight();
		this._partitionTree.free();
		this._partitionTree = new et2_dataview_partitionTree(this._dataProvider, 
			this._rowProvider, this._avgHeight, this.innerTbody);

		// Set the viewrange again
		this.setViewRange(this._currentRange);
	},

	/**
	 * The setViewRange function updates the range in which columns are shown.
	 */
	setViewRange: function(_range) {
		this._treeChanged = true;

		// Copy the given range
		this._currentRange = et2_bounds(_range.top, _range.bottom);

		// Display all elements in the given range
		var nodes = this._partitionTree.getRangeNodes(_range);

		for (var i = 0; i < nodes.length; i++)
		{
			if (nodes[i].implements(et2_dataview_IViewRange))
			{
				nodes[i].setViewRange(_range);
			}
		}

		// Calculate the range of the actually shown elements
		var displayTop = _range.top;
		var displayBottom = _range.bottom;

		if (nodes.length > 0)
		{
			displayTop = nodes[0].getPosTop();
			displayBottom = nodes[nodes.length - 1].getPosBottom();
		}

		// Hide everything except for _holdCount elements at the top and bottom
		// of the viewrange
		var ah = this._partitionTree.getAverageHeight();
		var reduceHeight = ah * this._holdCount;

		if (displayTop > reduceHeight)
		{
			this._partitionTree.reduceRange(et2_bounds(0, displayTop - reduceHeight));
		}

		if (displayBottom + reduceHeight < this._partitionTree.getHeight())
		{
			this._partitionTree.reduceRange(et2_bounds(displayBottom + reduceHeight,
				this._partitionTree.getHeight()));
		}
	},

	/**
	 * Updates the scrollheight
	 */
	setScrollHeight: function(_height) {
		this._height = _height;

		// Update the height of the outer container
		if (this.scrollarea)
		{
			this.scrollarea.height(_height);
		}

		// Update the viewing range
		this.setViewRange(et2_range(this._currentRange.top, this._height));
	},

	/**
	 * Returns the JQuery outer DOM-Node
	 */
	getJNode: function() {
		return this.outerCell;
	},

	/* ---- PRIVATE FUNCTIONS ---- */

	/**
	 * Checks whether the partition tree has to be rebuilt and if yes, does
	 * that.
	 */
	_checkTreeRebuild: function() {
		if (this._treeChanged)
		{
			var depth = this._partitionTree.getDepth();
			var count = this._partitionTree.getManagedCount();

			// Check whether the depth of the tree is very unproportional
			// regarding to the count of elements managed in it
			if (count < Math.pow(ET2_PARTITION_TREE_WIDTH, depth - 1))
			{
				et2_debug("info", "Rebuilding dataview partition tree");
				this._partitionTree.rebuild();
				et2_debug("info", "Done.");
			}

			// Reset the "treeChanged" function.
			this._treeChanged = false;
		}
	},

	/**
	 * Creates the grid DOM-Nodes
	 */
	_createNodes: function() {
		this.outerCell = $j(document.createElement("td"))
			.addClass("frame")
			.attr("colspan", this._columnIds.length + (this._parent ? 0 : 1));

		// Create the scrollarea div if this is the outer grid
		this.scrollarea = null;
		if (this._parent == null)
		{
			this.scrollarea = $j(document.createElement("div"))
				.addClass("egwGridView_scrollarea")
				.scroll(this, function(e) {

						// Clear any older scroll timeout
						if (e.data._scrollTimeout)
						{
							window.clearTimeout(e.data._scrollTimeout);
						}

						// Set a new timeout which calls the setViewArea
						// function
						e.data._scrollTimeout = window.setTimeout(function() {
							if (typeof ET2_GRID_PAUSE != "undefined")
								return;

							e.data.setViewRange(et2_range(
								e.data.scrollarea.scrollTop() - ET2_GRID_VIEW_EXT,
								e.data._height + ET2_GRID_VIEW_EXT * 2
							));
						}, ET2_GRID_SCROLL_TIMEOUT);
					})
				.height(this._scrollHeight)
				.appendTo(this.outerCell);
		}

		// Create the inner table
		var table = $j(document.createElement("table"))
			.addClass("egwGridView_grid")
			.appendTo(this.scrollarea ? this.scrollarea : this.outerCell);

		this.innerTbody = $j(document.createElement("tbody"))
			.appendTo(table);
	}

});