/**
 * eGroupWare eTemplate2 - Class which contains an management tree for the grid rows
 *
 * @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
	et2_core_common; // for et2_range functions
	et2_core_inheritance;
	et2_dataview_interfaces;
*/

/**
 * The partition node tree manages all rows in a dataview. As a dataview may have
 * many thousands of lines, the rows are organized in an a tree. The leafs of the
 * tree represent the single rows, upper layers represent groups of nodes.
 * Each node has a "height" value and is capable of calculate the exact position
 * of a row and its top and bottom value.
 * Additionaly, a leaf can represent an unlimited number of rows. In this way
 * the partition tree is built dynamically and is also capable of "forgetting"
 * information about the rows by simply reducing the tree nodes at a certain
 * position.
 */

var et2_dataview_IPartitionHeight = new Interface({

	calculateHeight: function() {}

});

/**
 * Abstract base class for partition nodes - contains the code for calculating
 * the top, bottom, height and (start) index of the node
 */
var et2_dataview_partitionNode = Class.extend([et2_dataview_IPartitionHeight,
	et2_dataview_IInvalidatable], {

	init: function(_root) {

		this._root = _root;
		this._parent = null;
		this._pidx = 0;

		// Initialize the temporary storage elements
		this.doInvalidate();
		this._invalid = true;

	},

	destroy: function() {

		// Remove this element from the parent children list
		if (this._parent)
		{
			this._parent.removePIdxNode(this._pidx);
		}

	},

	setParent: function(_parent) {
		if (this._parent != _parent)
		{
			this._parent = _parent;
			this.invalidate();
		}
	},

	setPIdx: function(_pidx) {
		if (this._pidx != _pidx)
		{
			this._pidx = _pidx;
			this.invalidate();
		}
	},

	/**
	 * Invalidates cached values - override the "doInvalidate" function.
	 *
	 * @param _sender is the node wich originally triggerd the invalidation, can
	 * 	be ommited when calling this function.
	 */
	invalidate: function(_sender) {

		// If the _sender parameter is not given, assume that this element is
		// the one which triggered the invalidation
		var origin = typeof _sender == "undefined";

		if ((origin || _sender != this) && !this._invalid)
		{
			this.doInvalidate();
			this._invalid = true;

			// Invalidate the parent node
			if (this._parent)
			{
				// Invalidate the neighbor node
				if (this._pidx < this._parent._children.length - 1)
				{
					this._parent._children[this._pidx + 1].invalidate();
				}

				this._parent.invalidate(origin ? this : _sender);
			}
		}
	},

	/**
	 * Performs the actual invalidation.
	 */
	doInvalidate: function() {
		this._height = false;
		this._posTop = false;
		this._posBottom = false;
		this._startIdx = false;
		this._stopIdx = false;
	},

	/**
	 * Returns the root node of the partition tree
	 */
	getRoot: function() {
		return this._root;
	},

	/**
	 * Returns the height of this node
	 */
	getHeight: function() {
		// Calculate the height value if it is currently invalid
		if (this._height === false)
		{
			this._height = this.calculateHeight();

			// Do a sanity check for the value - if the height wasn't a number
			// it could easily destroy the posTop and posBottom values of the
			// complete tree!
			if (isNaN(this._height))
			{
				this.egw().debug("error", "calculateHeight returned a NaN value!");
				this._height = 0;
			}

			this._invalid = false;
		}

		return this._height;
	},

	/**
	 * Returns the top position of the node in px
	 */
	getPosTop: function() {
		if (this._posTop === false)
		{
			this._posTop = this._accumulateValue(this.getPosTop, this.getPosBottom);

			this._invalid = false;
		}

		return this._posTop;
	},

	/**
	 * Returns the bottom position of the node in px
	 */
	getPosBottom: function() {
		if (this._posBottom === false)
		{
			this._posBottom = this.getPosTop() + this.getHeight();

			this._invalid = false;
		}

		return this._posBottom;
	},

	/**
	 * Returns an range object
	 */
	getRange: function() {
		return {
			"top": this.getPosTop(),
			"bottom": this.getPosBottom()
		};
	},


	/**
	 * Returns true if the node intersects with the given range
	 */
	inRange: function(_ar) {
		return et2_rangeIntersect(this.getRange(), _ar);
	},

	/**
	 * Returns the overall start index of the node
	 */
	getStartIndex: function() {
		if (this._startIdx === false)
		{
			this._startIdx = this._accumulateValue(this.getStartIndex,
				this.getStopIndex);

			this._invalid = false;
		}

		return this._startIdx;
	},

	/**
	 * Returns the overall stop index of the node
	 */
	getStopIndex: function() {
		if (this._stopIdx === false)
		{
			this._stopIdx = this.getStartIndex() + this.getCount();

			this._invalid = false;
		}

		return this._stopIdx;
	},

	/**
	 * Returns the index range object
	 */
	getIdxRange: function() {
		return {
			"top": this.getStartIndex(),
			"bottom": this.getStopIndex()
		};
	},

	/**
	 * Checks whether this element is inside the given index range
	 */
	inIdxRange: function(_idxRange) {
		return et2_rangeIntersect(this.getIdxRange, _idxRange);
	},

	/**
	 * Returns the count of leafs which are below this node
	 */
	getCount: function() {
		return 1;
	},

	/**
	 * Returns the nodes which reside in the given range
	 */
	getRangeNodes: function(_range, _create) {
		if (this.inRange(_range))
		{
			return [this];
		}

		return [];
	},

	/**
	 * Returns the nodes which are inside the given index range
	 */
	getIdxRangeNodes: function(_idxRange, _create) {
		if (this.inIdxRange(_idxRange))
		{
			return [this];
		}

		return [];
	},

	/**
	 * Returns the (maximum) depth of the tree
	 */
	getDepth: function() {
		return 1;
	},

	getAvgHeightData: function(_data) {
		_data.count++;
		_data.height += this.getHeight();
	},

	getNodeIdx: function(_idx, _create) {
		return null;
	},

	getRowProvider: function() {
		return this.getRoot().getRowProvider();
	},

	getDataProvider: function() {
		return this.getRoot().getDataProvider();
	},

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

	_accumulateValue: function(_f1, _f2) {
		if (this._parent)
		{
			if (this._pidx == 0)
			{
				return _f1.call(this._parent);
			}
			else
			{
				return _f2.call(this._parent._children[this._pidx - 1]);
			}
		}

		return 0;
	}

});