/**
 * Copyright (c) 2006-2015, JGraph Holdings Ltd
 * Copyright (c) 2006-2015, draw.io AG
 */
/**
 * Class: mxSwimlaneManager
 * 
 * Manager for swimlanes and nested swimlanes that sets the size of newly added
 * swimlanes to that of their siblings, and propagates changes to the size of a
 * swimlane to its siblings, if <siblings> is true, and its ancestors, if
 * <bubbling> is true.
 * 
 * Constructor: mxSwimlaneManager
 *
 * Constructs a new swimlane manager for the given graph.
 *
 * Arguments:
 * 
 * graph - Reference to the enclosing graph. 
 */
function mxSwimlaneManager(graph, horizontal, addEnabled, resizeEnabled)
{
	this.horizontal = (horizontal != null) ? horizontal : true;
	this.addEnabled = (addEnabled != null) ? addEnabled : true;
	this.resizeEnabled = (resizeEnabled != null) ? resizeEnabled : true;

	this.addHandler = mxUtils.bind(this, function(sender, evt)
	{
		if (this.isEnabled() && this.isAddEnabled())
		{
			this.cellsAdded(evt.getProperty('cells'));
		}
	});
	
	this.resizeHandler = mxUtils.bind(this, function(sender, evt)
	{
		if (this.isEnabled() && this.isResizeEnabled())
		{
			this.cellsResized(evt.getProperty('cells'));
		}
	});
	
	this.setGraph(graph);
};

/**
 * Extends mxEventSource.
 */
mxSwimlaneManager.prototype = new mxEventSource();
mxSwimlaneManager.prototype.constructor = mxSwimlaneManager;

/**
 * Variable: graph
 * 
 * Reference to the enclosing <mxGraph>.
 */
mxSwimlaneManager.prototype.graph = null;

/**
 * Variable: enabled
 * 
 * Specifies if event handling is enabled. Default is true.
 */
mxSwimlaneManager.prototype.enabled = true;

/**
 * Variable: horizontal
 * 
 * Specifies the orientation of the swimlanes. Default is true.
 */
mxSwimlaneManager.prototype.horizontal = true;

/**
 * Variable: addEnabled
 * 
 * Specifies if newly added cells should be resized to match the size of their
 * existing siblings. Default is true.
 */
mxSwimlaneManager.prototype.addEnabled = true;

/**
 * Variable: resizeEnabled
 * 
 * Specifies if resizing of swimlanes should be handled. Default is true.
 */
mxSwimlaneManager.prototype.resizeEnabled = true;

/**
 * Variable: moveHandler
 * 
 * Holds the function that handles the move event.
 */
mxSwimlaneManager.prototype.addHandler = null;

/**
 * Variable: moveHandler
 * 
 * Holds the function that handles the move event.
 */
mxSwimlaneManager.prototype.resizeHandler = null;

/**
 * Function: isEnabled
 * 
 * Returns true if events are handled. This implementation
 * returns <enabled>.
 */
mxSwimlaneManager.prototype.isEnabled = function()
{
	return this.enabled;
};

/**
 * Function: setEnabled
 * 
 * Enables or disables event handling. This implementation
 * updates <enabled>.
 * 
 * Parameters:
 * 
 * enabled - Boolean that specifies the new enabled state.
 */
mxSwimlaneManager.prototype.setEnabled = function(value)
{
	this.enabled = value;
};

/**
 * Function: isHorizontal
 * 
 * Returns <horizontal>.
 */
mxSwimlaneManager.prototype.isHorizontal = function()
{
	return this.horizontal;
};

/**
 * Function: setHorizontal
 * 
 * Sets <horizontal>.
 */
mxSwimlaneManager.prototype.setHorizontal = function(value)
{
	this.horizontal = value;
};

/**
 * Function: isAddEnabled
 * 
 * Returns <addEnabled>.
 */
mxSwimlaneManager.prototype.isAddEnabled = function()
{
	return this.addEnabled;
};

/**
 * Function: setAddEnabled
 * 
 * Sets <addEnabled>.
 */
mxSwimlaneManager.prototype.setAddEnabled = function(value)
{
	this.addEnabled = value;
};

/**
 * Function: isResizeEnabled
 * 
 * Returns <resizeEnabled>.
 */
mxSwimlaneManager.prototype.isResizeEnabled = function()
{
	return this.resizeEnabled;
};

/**
 * Function: setResizeEnabled
 * 
 * Sets <resizeEnabled>.
 */
mxSwimlaneManager.prototype.setResizeEnabled = function(value)
{
	this.resizeEnabled = value;
};

/**
 * Function: getGraph
 * 
 * Returns the graph that this manager operates on.
 */
mxSwimlaneManager.prototype.getGraph = function()
{
	return this.graph;
};

/**
 * Function: setGraph
 * 
 * Sets the graph that the manager operates on.
 */
mxSwimlaneManager.prototype.setGraph = function(graph)
{
	if (this.graph != null)
	{
		this.graph.removeListener(this.addHandler);
		this.graph.removeListener(this.resizeHandler);
	}
	
	this.graph = graph;
	
	if (this.graph != null)
	{
		this.graph.addListener(mxEvent.ADD_CELLS, this.addHandler);
		this.graph.addListener(mxEvent.CELLS_RESIZED, this.resizeHandler);
	}
};

/**
 * Function: isSwimlaneIgnored
 * 
 * Returns true if the given swimlane should be ignored.
 */
mxSwimlaneManager.prototype.isSwimlaneIgnored = function(swimlane)
{
	return !this.getGraph().isSwimlane(swimlane);
};

/**
 * Function: isCellHorizontal
 * 
 * Returns true if the given cell is horizontal. If the given cell is not a
 * swimlane, then the global orientation is returned.
 */
mxSwimlaneManager.prototype.isCellHorizontal = function(cell)
{
	if (this.graph.isSwimlane(cell))
	{
		var style = this.graph.getCellStyle(cell);
		
		return mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, 1) == 1;
	}
	
	return !this.isHorizontal();
};

/**
 * Function: cellsAdded
 * 
 * Called if any cells have been added.
 * 
 * Parameters:
 * 
 * cell - Array of <mxCells> that have been added.
 */
mxSwimlaneManager.prototype.cellsAdded = function(cells)
{
	if (cells != null)
	{
		var model = this.getGraph().getModel();

		model.beginUpdate();
		try
		{
			for (var i = 0; i < cells.length; i++)
			{
				if (!this.isSwimlaneIgnored(cells[i]))
				{
					this.swimlaneAdded(cells[i]);
				}
			}
		}
		finally
		{
			model.endUpdate();
		}
	}
};

/**
 * Function: swimlaneAdded
 * 
 * Updates the size of the given swimlane to match that of any existing
 * siblings swimlanes.
 * 
 * Parameters:
 * 
 * swimlane - <mxCell> that represents the new swimlane.
 */
mxSwimlaneManager.prototype.swimlaneAdded = function(swimlane)
{
	var model = this.getGraph().getModel();
	var parent = model.getParent(swimlane);
	var childCount = model.getChildCount(parent);
	var geo = null;
	
	// Finds the first valid sibling swimlane as reference
	for (var i = 0; i < childCount; i++)
	{
		var child = model.getChildAt(parent, i);
		
		if (child != swimlane && !this.isSwimlaneIgnored(child))
		{
			geo = model.getGeometry(child);
			
			if (geo != null)
			{	
				break;
			}
		}
	}
	
	// Applies the size of the refernece to the newly added swimlane
	if (geo != null)
	{
		var parentHorizontal = (parent != null) ? this.isCellHorizontal(parent) : this.horizontal;
		this.resizeSwimlane(swimlane, geo.width, geo.height, parentHorizontal);
	}
};

/**
 * Function: cellsResized
 * 
 * Called if any cells have been resizes. Calls <swimlaneResized> for all
 * swimlanes where <isSwimlaneIgnored> returns false.
 * 
 * Parameters:
 * 
 * cells - Array of <mxCells> whose size was changed.
 */
mxSwimlaneManager.prototype.cellsResized = function(cells)
{
	if (cells != null)
	{
		var model = this.getGraph().getModel();
		
		model.beginUpdate();
		try
		{
			// Finds the top-level swimlanes and adds offsets
			for (var i = 0; i < cells.length; i++)
			{
				if (!this.isSwimlaneIgnored(cells[i]))
				{
					var geo = model.getGeometry(cells[i]);

					if (geo != null)
					{
						var size = new mxRectangle(0, 0, geo.width, geo.height);
						var top = cells[i];
						var current = top;
						
						while (current != null)
						{
							top = current;
							current = model.getParent(current);
							var tmp = (this.graph.isSwimlane(current)) ?
									this.graph.getStartSize(current) :
									new mxRectangle();
							size.width += tmp.width;
							size.height += tmp.height;
						}
						
						var parentHorizontal = (current != null) ? this.isCellHorizontal(current) : this.horizontal;
						this.resizeSwimlane(top, size.width, size.height, parentHorizontal);
					}
				}
			}
		}
		finally
		{
			model.endUpdate();
		}
	}
};

/**
 * Function: resizeSwimlane
 * 
 * Called from <cellsResized> for all swimlanes that are not ignored to update
 * the size of the siblings and the size of the parent swimlanes, recursively,
 * if <bubbling> is true.
 * 
 * Parameters:
 * 
 * swimlane - <mxCell> whose size has changed.
 */
mxSwimlaneManager.prototype.resizeSwimlane = function(swimlane, w, h, parentHorizontal)
{
	var model = this.getGraph().getModel();
	
	model.beginUpdate();
	try
	{
		var horizontal = this.isCellHorizontal(swimlane);
		
		if (!this.isSwimlaneIgnored(swimlane))
		{
			var geo = model.getGeometry(swimlane);
			
			if (geo != null)
			{
				if ((parentHorizontal && geo.height != h) || (!parentHorizontal && geo.width != w))
				{
					geo = geo.clone();
					
					if (parentHorizontal)
					{
						geo.height = h;
					}
					else
					{
						geo.width = w;
					}

					model.setGeometry(swimlane, geo);
				}
			}
		}

		var tmp = (this.graph.isSwimlane(swimlane)) ?
				this.graph.getStartSize(swimlane) :
				new mxRectangle();
		w -= tmp.width;
		h -= tmp.height;
		
		var childCount = model.getChildCount(swimlane);
		
		for (var i = 0; i < childCount; i++)
		{
			var child = model.getChildAt(swimlane, i);
			this.resizeSwimlane(child, w, h, horizontal);
		}
	}
	finally
	{
		model.endUpdate();
	}
};

/**
 * Function: destroy
 * 
 * Removes all handlers from the <graph> and deletes the reference to it.
 */
mxSwimlaneManager.prototype.destroy = function()
{
	this.setGraph(null);
};
