/**
 * The class implements the display of a collection of diagram elements and sequence flows .
 */
Ext.define("Terrasoft.controls.Diagram", {
	extend: "Terrasoft.Container",
	alternateClassName: "Terrasoft.Diagram",

	mixins: {
		connectorRemoval: "Terrasoft.ConnectorRemovalMixin"
	},

	statics: {

		/**
   * List of overlapped methods ej.Diagram
   * @type {object}
   */
		custom: {}

	},

	/**
  * Collection of items
  * @type {Terrasoft.Collection}
  */
	items: null,

	/**
  * @inheritdoc Terrasoft.AbstractContainer#initItems
  * @override
  */
	initItems: Ext.emptyFn,

	/**
  * @inheritdoc Terrasoft.AbstractContainer#prepareItems
  * @override
  */
	prepareItems: Ext.emptyFn,

	/**
  * @inheritdoc Terrasoft.AbstractContainer#destroyItems
  * @override
  */
	destroyItems: Ext.emptyFn,

	/**
  * Minimum chart height
  * @type {Number}
  */
	minHeight: 300,

	/**
  * Enables autoscrolling from the chart container
  * @type {Boolean}
  */
	autoScroll: true,

	/**
  * The currently selected item with which the user is working.
  * @type {object}
  */
	selectedItem: null,

	/**
  * Instance of the diagram.
  * @type {ej.Diagram}
  */
	diagram: null,

	/**
  * The initial state of the diagram.
  * @type {object}
  */
	diagramSnap: null,

	/**
  * The initial state of the svg controller.
  * @type {ej.Diagram.SvgContext}
  */
	svgContextSnap: null,

	/**
  * Flag of disabling diagram events.
  * @type {Boolean}
  */
	cancelDiagramEvents: false,

	/**
  * A set of behavior constraints for diagram elements.
  * @type {ej.Diagram.NodeConstraints}
  */
	nodeConstraints: null,

	/**
  * Port configuration for the diagram element
  * @type {Object}
  */
	portDefaultsConfig: null,

	/**
  * Configuring the basic elements of the diagram
  * @type {Object}
  */
	nodeBaseDefaultsConfig: null,
	/**
  * A set of behavior constraints for diagram management flows.
  * @type {ej.Diagram.ConnectorConstraints}
  */
	connectorConstraints: null,

	/**
  * Control flow color
  */
	connectorLineColor: "#A4A4A4",

	/**
  * The color of the selected control flow
  */
	selectionColor: "#E3E9F9",

	/**
  * Color of the port on the control stream
  */
	connectorPortColor: "#5FADEA",

	/**
  * Port radius on the element and control flow
  */
	portRadius: 3.5,

	/**
  * Indents from the boundary of the chart container when autoscrolling is on
  */
	autoScrollBorder: null,

	/**
  * Diagram container Selector
  * @type {String}
  */
	renderToSelector: null,

	/**
  * Diagram container svg selector
  * @type {String}
  */
	svgContainerSelector: null,

	/**
  * Distance between last right element shape and diagram bottom border.
  * @type {Number}
  */
	bottomScrollContentOffset: 0,

	/**
  * Distance between last right element shape and diagram right border.
  * @type {Number}
  */
	rightScrollContentOffset: 0,

	/**
  * The cache of the dimensions of the diagram element. See {@link #getItemBounds}.
  * @private
  * @type {Object[]}
  */
	itemsBounds: null,

	constructor: function () {
		ej.Diagram = ej.datavisualization.Diagram;
		this.svgContextSnap = Terrasoft.deepClone(ej.Diagram.SvgContext);
		this.items = Ext.create("Terrasoft.Collection");
		this.itemsBounds = [];
		this.autoScrollBorder = {
			left: 50,
			top: 50,
			right: 50,
			bottom: 50
		};
		this.nodeBaseDefaultsConfig = {
			constraints: ej.Diagram.NodeConstraints.Delete,
			inputPortCount: 0,
			outputPortCount: 0,
			children: []
		};
		this.portDefaultsConfig = {
			visibility: ej.Diagram.PortVisibility.Connect,
			fillColor: "#FFFFFF",
			shape: ej.Diagram.PortShapes.Circle,
			size: this.portRadius * 2,
			borderColor: this.connectorPortColor,
			borderWidth: 1,
			constraints: ej.Diagram.PortConstraints.Connect
		};
		this.connectorConstraints = ej.Diagram.ConnectorConstraints.Delete | ej.Diagram.ConnectorConstraints.DragSourceEnd | ej.Diagram.ConnectorConstraints.DragTargetEnd | ej.Diagram.ConnectorConstraints.Select;
		this.nodeConstraints = ej.Diagram.NodeConstraints.Select | ej.Diagram.NodeConstraints.Drag | ej.Diagram.NodeConstraints.Delete | ej.Diagram.NodeConstraints.Connect | ej.Diagram.NodeConstraints.Shadow;
		this.initUtils();
		this.callParent(arguments);
	},

	/**
  * Initialize diagram utilities.
  * @protected
  */
	initUtils: function () {
		var utils = this.utils = {};
		utils.svg = this.getSvgUtils();
		utils.labelUtils = this.getLabelUtils(utils.svg);
	},

	/**
  * Gets the utility for working with SVG elements.
  * @protected
  * @return {}
  */
	getSvgUtils: function () {
		return Ext.create("");
	},

	/**
  * Gets the utility for working with the diagram element labels.
  * @param {} svgUtils A utility for working with SVG elements.
  * @protected
  * @return {}
  */
	getLabelUtils: function (svgUtils) {
		return Ext.create("", {
			svgUtils: svgUtils
		});
	},

	/**
  * @inheritdoc Terrasoft.controls.Container#tpl
  */
	tpl: [
	/*jshint white:false */
	"<div id=\"diagram-{id}\" class=\"{wrapClassName}\"></div>"
	/*jshint white:true */
	],

	/**
  * @inheritdoc Terrasoft.Component#getTplData
  * @override
  */
	getTplData: function () {
		var tplData = this.callParent(arguments);
		this.selectors = this.getSelectors();
		return tplData;
	},

	/**
  * @inheritdoc Terrasoft.Component#getSelectors
  * @override
  */
	getSelectors: function () {
		return {
			wrapEl: "#diagram-" + this.id
		};
	},

	/**
  * Ges the state of the diagram
  * @return {jQuery}
  */
	getState: function () {
		var diagram = this.getInstance();
		return diagram.save();
	},

	/**
  * Draws an element on the diagram. The element must be previously added to the diagram, because the method does not register the element.
  * @param {ej.Diagram.Node} item Diagram element.
  * @param {String} parentName The name of the parent element. If the parameter is not set, an error will be generated.
  */
	renderItem: function (item, parentName) {
		var diagram = this.getInstance();
		var svgNode = diagram._svg;
		var parentNode = svgNode.getElementById(parentName);
		if (item.type === "group") {
			ej.Diagram.SvgContext.renderGroup(item, svgNode, parentNode, diagram.nameTable, diagram);
		} else if (item.segments) {
			ej.Diagram.SvgContext.renderConnector(item, svgNode, parentNode);
		} else {
			ej.Diagram.SvgContext.renderNode(item, svgNode, parentNode);
		}
		if (item.isContainer === true) {
			this.iterateItemChildrens(item, function (child) {
				this.renderItem(child, item.name);
			}, this);
		}
	},

	/**
  * Returns the diagram element by its id.
  * @param {String} elementId
  * @return {ej.Diagram.Node}
  */
	getElementById: function (elementId) {
		var diagram = this.getInstance();
		return diagram.findNode(elementId);
	},

	/**
  * Returns the svg element by its identifier.
  * @param {String} id Identifier.
  * @return {SVGElement}
  */
	getSvgElementById: function (id) {
		var diagram = this.getInstance();
		return diagram._svg.getElementById(id);
	},

	/**
  * Adds the css class to the html element passed.
  * @param {HTMLElement} el The element for which you want to add a css class.
  * @param {String []} classes An array of css classes to add.
  */
	addCls: function (el, classes) {
		var classList = el.classList;
		classList.add.apply(classList, classes);
	},

	/**
  * Returns the selected element of the diagram.
  * @return {ej.Diagram.Node}
  */
	getSelectedItem: function () {
		var diagram = this.getInstance();
		return diagram.selectionList[0];
	},

	/**
  * Sorts out all the children of the passed item.
  * @param {ej.Diagram.Node} item The element for which you want to go through child elements.
  * @param {Function} iterator Iterator function.
  * @param {ej.Diagram.Node} iterator.child Child.
  * @param {Number} iterator.index The index of the item in the collection.
  * @param {Object} scope The context for calling the iterator.
  */
	iterateItemChildrens: function (item, iterator, scope) {
		if (item.children) {
			item.children.forEach(function (child, index) {
				if (typeof child === "string") {
					child = this.getElementById(child);
				}
				iterator.call(scope, child, index);
			}, this);
		}
	},

	/**
  * Gets the dimensions and position of the diagram element, taking into account the labels and children.
  * @param {ej.Diagram.Node} item diagram element.
  * @param {Boolean} [updateCache] Cache update flag.
  * @return {Object} The position is returned for the upper-left corner of the element.
  * @return {Number} return.x Shifts the element horizontally.
  * @return {Number} return.y The offset of the element vertically.
  * @return {Number} return.width The width of the element.
  * @return {Number} return.height The height of the element.
  */
	getItemBounds: function (item, updateCache) {
		var itemName = item.name;
		var cachedBounds = this.itemsBounds[itemName];
		if (cachedBounds && updateCache !== true) {
			return cachedBounds;
		}
		var bounds = ej.Diagram.Util.bounds(item);
		var nodeBounds = {
			x: bounds.x,
			y: bounds.y
		};
		item.labels.forEach(function (label) {
			if (label.text) {
				var labelTextEl = this.getSvgElementById(item.name + "_" + label.name);
				if (labelTextEl) {
					var transformValues = this.utils.svg.getSvgElTranslateValues(labelTextEl, ["e", "f"]);
					var bBox = label.bBox;
					if (!bBox) {
						bBox = label.bBox = labelTextEl.getBBox();
					}
					var width = bBox.width;
					var height = bBox.height;
					var x = nodeBounds.x + transformValues.e - width / 2;
					var labelBackgroundEl = this.getSvgElementById(item.name + "_" + label.name + "_lblbg");
					var transformBackgroundValues = this.utils.svg.getSvgElTranslateValues(labelBackgroundEl, ["f"]);
					var y = nodeBounds.y + transformBackgroundValues.f;
					var labelTextElRect = ej.Diagram.Rectangle(x, y, width, height);
					bounds = ej.Diagram.Geometry.union(bounds, labelTextElRect);
				}
			}
		}, this);
		if (item.isContainer === false) {
			this.iterateItemChildrens(item, function (child) {
				var childBounds = this.getItemBounds(child);
				bounds = ej.Diagram.Geometry.union(bounds, childBounds);
			}, this);
		}
		this.itemsBounds[itemName] = bounds;
		return bounds;
	},

	/**
  * Removes the sizes of the element and its parents from the cache.
  * @param {ej.Diagram.Node} item diagram element.
  */
	removeItemBoundsCache: function (item) {
		if (item) {
			delete this.itemsBounds[item.name];
			if (item.parent) {
				var parent = this.getElementById(item.parent);
				this.removeItemBoundsCache(parent);
			}
		}
	},

	/**
  * Hides the standard behavior.
  * @private
  */
	disableDiagramFeatures: function () {
		ej.Diagram.prototype._handleMouseWheel = Ext.emptyFn;
		ej.Diagram.prototype._onContextMenuOpen = Ext.emptyFn;
		ej.Diagram.prototype._initContextMenu = Ext.emptyFn;
		ej.Diagram.prototype._renderContextMenu = Ext.emptyFn;
		ej.Diagram.prototype._createScrollbar = Ext.emptyFn;
	},

	/**
  * Overrides the standard behavior.
  * @private
  */
	customizeMouseMove: function () {
		var renderToSelector = "#" + this.renderToSelector;
		if (!Terrasoft.Diagram.custom.enableAutoScroll) {
			var baseEnableAutoScroll = ej.Diagram.prototype.enableAutoScroll;
			ej.Diagram.prototype.enableAutoScroll = function () {
				if (arguments.length > 0 && arguments[0] === "customized") {
					return true;
				}
				baseEnableAutoScroll.apply(this, arguments);
			};
			Terrasoft.Diagram.custom.enableAutoScroll = true;
		}
		if (!Terrasoft.Diagram.custom._mousemove) {
			var baseMouseMove = ej.Diagram.prototype._mousemove;
			ej.Diagram.prototype._mousemove = function (evt) {
				baseMouseMove.apply(this, arguments);
				if (this.activeTool.inAction && this.enableAutoScroll("customized")) {
					var defAutoScrollDelay = 200;
					var defAutoScrollDelayStep = 10;
					var scrollOffset = 10;
					var viewPort = ej.Diagram.ScrollUtil._viewPort(this);
					var pt = this._mousePosition(evt, true);
					var autoScrollBorder = this.model.pageSettings.autoScrollBorder;
					var container = $(renderToSelector);
					var beginAutoScroll = function (direction, evt) {
						this.canvasBBox = null;
						if (!this._canAutoScroll) {
							this._canAutoScroll = true;
							this._beginAutoScrollDelay = defAutoScrollDelay;
							this._beginAutoScroll(direction, evt);
						}
						this._beginAutoScrollDelay -= defAutoScrollDelayStep;
					}.bind(this);
					var scrollLeft = container.scrollLeft();
					var scrollTop = container.scrollTop();
					if (pt.x - scrollLeft + autoScrollBorder.right >= viewPort.width - scrollOffset) {
						if (this.activeTool.previousPoint && pt.x < this.activeTool.previousPoint.x) {
							this._canAutoScroll = false;
							return;
						}
						beginAutoScroll("right", evt);
					} else if (pt.x - scrollLeft <= autoScrollBorder.left) {
						if (this.activeTool.previousPoint && pt.x > this.activeTool.previousPoint.x) {
							this._canAutoScroll = false;
							return;
						}
						beginAutoScroll("left", evt);
					} else if (pt.y - scrollTop + autoScrollBorder.bottom >= viewPort.height - scrollOffset) {
						if (this.activeTool.previousPoint && pt.y < this.activeTool.previousPoint.y) {
							this._canAutoScroll = false;
							this._autoScrollStartPoint = null;
							return;
						}
						beginAutoScroll("bottom", evt);
					} else if (pt.y - scrollTop <= autoScrollBorder.top) {
						if (this.activeTool.previousPoint && pt.y > this.activeTool.previousPoint.y) {
							this._canAutoScroll = false;
							return;
						}
						beginAutoScroll("top", evt);
					} else {
						this._canAutoScroll = false;
					}
				}
			};
			Terrasoft.Diagram.custom._mousemove = true;
		}
	},

	/**
  * Overrides the standard behavior of the _updateSelectionOnScroll handler .
  * @private
  */
	customizeUpdateSelectionOnScroll: function () {
		if (Terrasoft.Diagram.custom._updateSelectionOnScroll) {
			return;
		}
		var base = ej.Diagram.prototype._updateSelectionOnScroll;
		ej.Diagram.prototype._updateSelectionOnScroll = function (x, y) {
			var tool = this.activeTool;
			if (tool.name === "move" && tool.helper) {
				tool._updateHelperXY(tool.helper, tool.previousPoint, tool.currentPoint);
				ej.Diagram.SvgContext._updateContainerHelper(this);
				tool.currentPoint = ej.Diagram.Point(tool.currentPoint.x + x, tool.currentPoint.y + y);
				this._translate(tool.helper, x, y, this.nameTable);
				tool.previousPoint = ej.Diagram.Point(tool.currentPoint.x, tool.currentPoint.y);
				return;
			}
			base.apply(this, arguments);
		};
		Terrasoft.Diagram.custom._updateSelectionOnScroll = true;
	},

	/**
  * Overrides the standard behavior of the _beginAutoScroll handler .
  * @private
  */
	customizeBeginAutoScroll: function () {
		if (Terrasoft.Diagram.custom._beginAutoScroll) {
			return;
		}
		var renderToSelector = "#" + this.renderToSelector;
		ej.Diagram.prototype._beginAutoScroll = function (option, evt) {
			var delay = this._beginAutoScrollDelay > 0 ? this._beginAutoScrollDelay : 0;
			var callBack = this;
			var left = 0,
			    top = 0;
			var scrollOffset = 10;
			if (this._canAutoScroll) {
				var container = $(renderToSelector);
				var scrollLeft = container.scrollLeft();
				var scrollTop = container.scrollTop();
				switch (option) {
					case "right":
						left = scrollOffset;
						container.scrollLeft(scrollLeft + left);
						break;
					case "left":
						if (scrollLeft === 0) {
							return;
						}
						left -= scrollOffset;
						container.scrollLeft(scrollLeft + left);
						break;
					case "top":
						if (scrollTop === 0) {
							return;
						}
						top -= scrollOffset;
						container.scrollTop(scrollTop + top);
						break;
					case "bottom":
						top = scrollOffset;
						container.scrollTop(scrollTop + top);
						break;
				}
				setTimeout(function () {
					callBack._raiseEvent("autoScrollChange", { delay: delay });
					callBack._updateScrollBar(left, top, option, evt);
				}, delay);
			}
		};
		Terrasoft.Diagram.custom._beginAutoScroll = true;
	},

	/**
  * Overrides the standard behavior of the _setScrollContentSize handler .
  * @private
  */
	customizeSetScrollContentSize: function () {
		var rightOffset = this.rightScrollContentOffset;
		var bottomOffset = this.bottomScrollContentOffset;
		ej.Diagram.ScrollUtil._setScrollContentSize = function (diagram) {
			if (diagram.disableSetScrollContentSize === true) {
				return;
			}
			diagram.viewPortSize = null;
			var viewPort = ej.Diagram.ScrollUtil._viewPort(diagram);
			viewPort = ej.Diagram.Rectangle(diagram._hScrollOffset, diagram._vScrollOffset, viewPort.width, viewPort.height);
			var left = diagram._spatialSearch.pageLeft;
			var right = diagram._spatialSearch.pageRight;
			var top = diagram._spatialSearch.pageTop;
			var bottom = diagram._spatialSearch.pageBottom;
			var diagramArea = ej.Diagram.Rectangle(0, 0, 0, 0);
			diagramArea = this._union(diagramArea, ej.Diagram.Rectangle(0, 0, viewPort.width, viewPort.height));
			diagramArea = this._union(diagramArea, ej.Diagram.Geometry.rect([{
				x: left,
				y: top
			}, {
				x: right,
				y: bottom
			}]));
			var tool = diagram.activeTool;
			var width = diagramArea.width;
			var height = diagramArea.height;
			if (tool && tool.name === "move" && tool.helper) {
				var helper = tool.helper;
				if (tool.currentPoint.x > width) {
					width = helper.offsetX + helper.width;
				}
				if (tool.currentPoint.y > height) {
					height = helper.offsetY + helper.height;
				}
			}
			if (width > viewPort.width) {
				width += rightOffset;
			}
			if (height > viewPort.height) {
				height += bottomOffset;
			}
			ej.Diagram.SvgContext.setSize(diagram._svg, width, height);
		};
	},

	/**
  * Overrides the logic of the drag event of the element in the diagram.
  * @private
  */
	customizeContainerMouseMove: function () {
		var scope = this;
		ej.Diagram.MoveTool.prototype._containerMouseMove = function () {
			var helper = this.helper;
			if (!helper) {
				helper = this.helper = this._getCloneNode(this.selectedObject);
				helper.name = "helper";
				helper.labels = [];
				var bounds = ej.Diagram.Util.bounds(this.selectedObject);
				helper.offsetX = bounds.center.x + this.currentPoint.x - this.previousPoint.x;
				helper.offsetY = bounds.center.y + this.currentPoint.y - this.previousPoint.y;
				ej.Diagram.SvgContext._drawContainerHelper(this.diagram);
				if (this.selectedObject.type !== "pseudoGroup") {
					this._renderHelper();
				}
			} else {
				this._updateHelperXY(helper, this.previousPoint, this.currentPoint);
				ej.Diagram.SvgContext._updateContainerHelper(this.diagram);
			}
		};
		if (!Terrasoft.Diagram.custom._endAction) {
			var base = ej.Diagram.MoveTool.prototype._endAction;
			ej.Diagram.MoveTool.prototype._endAction = function () {
				if (this.helper) {
					var selectedObject = this.selectedObject;
					if (selectedObject.type !== "pseudoGroup") {
						var diagram = this.diagram;
						var startPoint = this.startPoint;
						var endPoint = this.currentPoint;
						var towardsLeft = endPoint.x < startPoint.x;
						var towardsTop = endPoint.y < startPoint.y;
						var bounds = scope.getItemBounds(selectedObject);
						if (towardsLeft) {
							var endpointX = Math.ceil(endPoint.x);
							if (Math.abs(endpointX) <= bounds.width) {
								endPoint.x = Math.ceil(bounds.width / 2);
							}
						}
						if (towardsTop) {
							var endPointY = Math.ceil(endPoint.y);
							if (Math.abs(endPointY) <= bounds.height) {
								endPoint.y = Math.ceil(bounds.height / 2);
							}
						}
						this._updateXY(selectedObject, startPoint, endPoint);
						ej.Diagram.DiagramContext.update(selectedObject, diagram);
						diagram._renderTooltip(selectedObject);
						this._updateSelection();
						diagram._updateSelectionHandle(true);
					}
				}
				base.apply(this, arguments);
			};
			Terrasoft.Diagram.custom._endAction = true;
		}
	},

	/**
  * Overrides the logic of deletion of the parent from descendants.
  * @private
  */
	customizeRemoveChildParent: function () {
		var base = ej.Diagram.MoveTool.prototype._removeChildParent;
		ej.Diagram.MoveTool.prototype._removeChildParent = function (node) {
			if (node.type !== "pseudoGroup") {
				base.apply(this, arguments);
			}
		};
	},

	/**
  * Overrides the standard behavior.
  * @private
  */
	customizeDiagram: function () {
		// TODO # CRM-18298 Give a general view of the customization of the ej.Diagram methods on the products Sales, Marketing, Service
		this.customizeMouseMove();
		this._customizeWrapTextAlign();
		this.customizeUpdateSelectionOnScroll();
		this.customizeBeginAutoScroll();
		this.customizeSetScrollContentSize();
		this.customizeContainerMouseMove();
		this.customizeRemoveChildParent();
		this.customizeSvgImage();
		this.customizeDisConnect();
		this.customizeRemoveConnector();
		this.customizeRecordPinPointChanged();
		this.customizeRecordRotationChanged();
		this.customizeRecordSizeChanged();
		var scope = this;
		var svgContext = ej.Diagram.SvgContext;
		svgContext.updateUserHandles = function (handles, node, svg, isMultipleSelection, isDragging, scale) {
			if (handles && handles.length > 0) {
				for (var handleIndex = 0; handleIndex < handles.length; handleIndex++) {
					var handle = handles[handleIndex];
					if (!(node.toolsConstraint & handle.constraint)) {
						continue;
					}
					if (isMultipleSelection && handle.enableMultiSelection || !isMultipleSelection) {
						if (handle.upateHandle) {
							handle.upateHandle.call(this, handle, node, svg, isDragging, scale, scope);
						} else {
							this._updateHandle(handle, node, svg, isDragging, scale);
						}
					}
				}
			}
		};
		svgContext.renderUserHandles = function (handles, shape, svg, isMultipleSelection, scale, parent) {
			for (var handleIndex = 0; handleIndex < handles.length; handleIndex++) {
				if (isMultipleSelection && handles[handleIndex].enableMultiSelection || !isMultipleSelection) {
					var handle = handles[handleIndex];
					// TODO: CRM-14989 The nodes must know what tools do they need
					if (handle.visible && shape.toolsConstraint & handle.constraint) {
						var isHidden = handle.tool && handle.tool.test && !handle.tool.test(shape);
						if (isHidden) {
							continue;
						}
						if (handle.renderUserHandle) {
							handle.renderUserHandle.call(this, handle, shape, svg, scale, parent, scope);
						} else {
							this._renderUserHandle(handle, shape, svg, scale, parent);
						}
					}
				}
			}
		};
		var baseUpdateConnector = svgContext._updateConnector;
		svgContext._updateConnector = function (connector, svg, diagram) {
			baseUpdateConnector.apply(this, arguments);
			var path = this._findPath(connector, diagram);
			svg.path({
				id: connector.name + "_segments_selection",
				d: path
			});
		};
		var connectorPortColor = this.connectorPortColor;
		var portRadius = this.portRadius;
		svgContext._renderOrthogonalThumb = function (corner, x, y, svg, visibility) {
			var handle = svg.getElementById(svg.document.id + "handle_g");
			var fill = connectorPortColor;
			var attr = {
				"id": corner,
				"class": "segmentEnd",
				"fill": fill,
				"r": portRadius,
				"stroke": connectorPortColor,
				"transform": "translate(" + x + "," + y + ")",
				"visibility": visibility
			};
			handle.appendChild(svg.circle(attr));
		};
		svgContext._updateOrthoThumb = function (corner, x, y, svg, visibility) {
			var attr = {
				"id": corner,
				"visibility": visibility,
				"transform": "translate(" + x + "," + y + ")"
			};
			svg.circle(attr);
		};
		svgContext._updateEndPointCorner = function (corner, x, y, isConnected, svg, isenabled) {
			var fill = isConnected ? "#aad4ff" : "white";
			if (!isenabled) {
				fill = "darkgray";
			}
			var attr = {
				id: corner,
				fill: fill,
				cx: x,
				cy: y
			};
			svg.circle(attr);
		};
		svgContext._renderEndPointCorner = function (corner, x, y, isConnected, svg, isenabled) {
			var handle = svg.getElementById(svg.document.id + "handle_g");
			var fill = isConnected ? "#FFFFFF" : "rgba(0,0,0,0)";
			var stroke = isConnected ? connectorPortColor : "rgba(0,0,0,0)";
			var opacity = isConnected ? 0.75 : 0;
			if (!isenabled) {
				fill = "darkgray";
			}
			var attr = {
				"id": corner,
				"class": corner,
				"opacity": opacity,
				"fill": fill,
				"stroke": stroke,
				"stroke-width": 1,
				"cx": x,
				"cy": y,
				"r": portRadius
			};
			handle.appendChild(svg.circle(attr));
		};
		var _getHandlePosition = svgContext._getHandlePosition;
		svgContext._getHandlePosition = function (handle, node, scale) {
			var position = handle.position;
			if (typeof position === "object") {
				var positionPoints = ej.Diagram.Point();
				var bounds = ej.Diagram.Util.bounds(node);
				positionPoints.x = bounds.center.x + position.x;
				positionPoints.y = bounds.center.y + position.y;
				return positionPoints;
			}
			return _getHandlePosition(handle, node, scale);
		};

		svgContext._renderLabelBackground = function (label, node, text, svg) {
			return scope.utils.labelUtils.getLabelBackgroundEl({
				label: label,
				node: node,
				textNode: text,
				svg: svg
			});
		};

		svgContext.updateGroup = function (group, svg, diagram, layout) {
			if (group.nodeType === Terrasoft.diagram.UserHandlesConstraint.Lane) {
				return;
			}
			var visible = group.visible ? "visible" : "hidden";
			var style = "display:block;";
			if (!group.visible) {
				style = "display:none;";
			}
			svg.g({
				"id": group.name,
				"visibility": "visible",
				"style": "display:block"
			});
			if (group.type === "pseudoGroup") {
				var children = diagram._getChildren(group.children);
				for (var i = 0, len = children.length; i < len; i++) {
					var child = diagram.nameTable[children[i]];
					if (child) {
						if (child.type === "group") {
							this.updateGroup(child, svg, diagram, layout);
						} else if (child.segments) {
							this.updateConnector(child, svg, diagram);
						} else {
							this.updateNode(child, svg, diagram, layout);
						}
					}
				}
			}
			svg.g({
				"id": group.name,
				"visibility": visible,
				"style": style
			});
			this._updateGoupBackground(group, svg);
			this._updateAssociatedConnector(group, svg, diagram);
			this._updateLabels(group, svg);
			this._updatePorts(group, svg);
			var diagramPhases = diagram.model.phases;
			if (group.isSwimlane && diagramPhases && diagramPhases.length > 0) {
				var des = null;
				for (var j = 0; j < diagramPhases.length; j++) {
					des = diagramPhases[j];
					if (des.parent === group.name) {
						this._updatephase(des, diagram);
					}
				}
			}
		};

		var baseRenderFilter = svgContext._renderFilter;
		svgContext._renderFilter = function (node, svg) {
			if (node.constraints & ej.Diagram.NodeConstraints.Shadow) {
				var existingFilter = svg.getElementById(node.name + "_filter");
				if (!existingFilter) {
					baseRenderFilter.apply(this, arguments);
				}
			}
		};

		svgContext._enableSelectedNode = Ext.emptyFn;
		svgContext._disableSelectedNode = Ext.emptyFn;

		var baseRenderPort = svgContext._renderPort;
		svgContext._renderPort = function (node, port, svg) {
			var portElName = node.name + "_" + port.name;
			var portEl = svg.getElementById(portElName);
			if (portEl) {
				return portEl;
			}
			return baseRenderPort.apply(this, arguments);
		};

		svgContext.updateNode = function (node, svg, diagram) {
			var g, bounds;
			if (node.shape.type === "html" && ej.browserInfo().name === "msie") {
				g = document.getElementById(node.name + "_html").getBoundingClientRect();
				bounds = {
					x: g.left,
					y: g.top,
					width: g.width,
					height: g.height
				};
			} else {
				g = svg.getElementById(node.name + "_shape");
			}
			if (g && !bounds) {
				bounds = ej.Diagram.Util.bounds(node);
			}
			this._updateNode(node, svg);
			if (diagram._layoutInAction) {
				return;
			}
			this._updateAssociatedConnector(node, svg, diagram);
			var activeTool = diagram.activeTool;
			if (!activeTool.inAction || activeTool.name !== "move" && activeTool.name !== "rotate") {
				this._updateLabels(node, svg);
				if (bounds && (bounds && bounds.width !== node.width || bounds.height !== node.height)) {
					this._updatePorts(node, svg);
				}
			}
		};
		if (!Terrasoft.Diagram.custom._addSelection) {
			var baseAddSelection = ej.Diagram.prototype._addSelection;
			ej.Diagram.prototype._addSelection = function () {
				if (this.disableDefaultSelection === true) {
					return;
				}
				return baseAddSelection.apply(this, arguments);
			};
			Terrasoft.Diagram.custom._addSelection = true;
		}
		if (!Terrasoft.Diagram.custom._updateCursor) {
			var baseUpdateCursor = ej.Diagram.prototype._updateCursor;
			ej.Diagram.prototype._updateCursor = function () {
				if (this.cursor !== this._currentCursor) {
					this.cursor = this._currentCursor;
					baseUpdateCursor.apply(this, arguments);
				}
			};
			Terrasoft.Diagram.custom._updateCursor = true;
		}
		if (!Terrasoft.Diagram.custom._mousePosition) {
			ej.Diagram.prototype._mousePosition = function (evt, exOffset) {
				var e = this._isTouchEvent(evt);
				var scrollLeft = 0;
				var scrollTop = 0;
				var canvasBBox = this.canvasBBox;
				if (canvasBBox == null) {
					canvasBBox = this.canvasBBox = this._canvas.getBoundingClientRect();
				}
				var layerx = e.clientX + scrollLeft - canvasBBox.left;
				var layery = scrollTop + e.clientY - canvasBBox.top;
				if (!exOffset) {
					layerx = (layerx + this._hScrollOffset) / this._currZoom;
					layery = (layery + this._vScrollOffset) / this._currZoom;
				}
				return new ej.Diagram.Point(Math.round(layerx * 100) / 100, Math.round(layery * 100) / 100);
			};
			Terrasoft.Diagram.custom._mousePosition = true;
		}
		ej.Diagram.ScrollUtil._viewPort = function (diagram) {
			var viewPortSize = diagram.viewPortSize;
			if (viewPortSize != null) {
				return viewPortSize;
			}
			var element = diagram.element[0];
			var eWidth = Math.floor($(element)[0].getBoundingClientRect().width);
			var eHeight = $(element).height();
			var bRect = diagram.element[0].getBoundingClientRect();
			var screenX = window.screenX < 0 ? window.screenX * -1 : window.screenX;
			if (eWidth === 0) {
				eWidth = Math.floor(window.innerWidth - screenX - Math.floor(bRect.left));
			}
			var screenY = window.screenY < 0 ? window.screenY * -1 : window.screenY;
			if (eHeight === 0) {
				eHeight = Math.floor(window.innerHeight - screenY - Math.floor(bRect.top));
			}
			viewPortSize = diagram.viewPortSize = ej.Diagram.Size(eWidth, eHeight);
			return viewPortSize;
		};
		ej.Diagram.ScrollUtil._transform = function (diagram, hOffset, vOffset, canScale) {
			ej.Diagram.SvgContext.transformView(diagram, -hOffset, -vOffset);
			if (canScale) {
				ej.Diagram.SvgContext.scaleContent(diagram, diagram._currZoom);
			}
			ej.Diagram.PageUtil._updatePageSize(diagram);
			ej.Diagram.SvgContext._updateBackground(hOffset, vOffset, diagram._currZoom, diagram);
			ej.Diagram.SvgContext._updateGrid(hOffset, vOffset, diagram._currZoom, diagram);
		};
		if (!Terrasoft.Diagram.custom._updateScrollOffset) {
			ej.Diagram.prototype._updateScrollOffset = function (hScrollOffset, vScrollOffset, canScale) {
				ej.Diagram.ScrollUtil._transform(this, hScrollOffset, vScrollOffset, canScale);
				var diagram = this;
				this._views.forEach(function (viewid) {
					var view = diagram._views[viewid];
					if (view.type === "overview") {
						var ovw = $("#" + viewid).ejOverview("instance");
						if (ovw) {
							ovw._scrollOverviewRect(hScrollOffset, vScrollOffset, diagram._currZoom);
						}
					}
				});
			};
			Terrasoft.Diagram.custom._updateScrollOffset = true;
		}
		ej.Diagram.Util.removeItem = function (array, item) {
			var index;
			while ((index = array.indexOf(item)) >= 0) {
				array.splice(index, 1);
			}
		};
		/*jshint evil: true */
		var replaceColor = function (funcBody) {
			return funcBody.replace(/#07EDE1/g, connectorPortColor);
		};
		svgContext._renderSideAlignmentLines = new Function("return " + replaceColor(svgContext._renderSideAlignmentLines.toString()))();
		svgContext._renderCenterAlignmentLines = new Function("return " + replaceColor(svgContext._renderCenterAlignmentLines.toString()))();
		svgContext._renderSpacingLines = new Function("return " + replaceColor(svgContext._renderSpacingLines.toString()))();
		/*jshint evil: false */
		var self = this;
		ej.Diagram.Util._rotateChildBounds = function (child) {
			var bounds = self.getItemBounds(child, true);
			var padding = 10;
			if (bounds.x - padding > 0 && bounds.y - padding > 0) {
				bounds.x -= padding;
				bounds.y -= padding;
				bounds.height += padding * 2;
				bounds.width += padding * 2;
			}
			return bounds;
		};
	},

	/**
  * Customize wrap text align.
  * @private
  */
	_customizeWrapTextAlign: function () {
		ej.Diagram.SvgContext._wrapTextAlign = this.utils.labelUtils.wrapTextAlign;
	},

	/**
  * Sets the default values for diagram elements
  */
	setDefaults: function () {
		Ext.apply(ej.Diagram.PortDefaults, this.portDefaultsConfig);
		Ext.apply(ej.Diagram.NodeBaseDefaults, this.nodeBaseDefaultsConfig);
	},

	/**
  * Component initialization.
  * @protected
  * @override
  */
	init: function () {
		this.callParent(arguments);
		this.renderToSelector = "diagram-" + this.id;
		this.svgContainerSelector = this.renderToSelector + "_canvas_svg";
		this.customizeDiagram();
		this.customizeRenderNode();
		this.disableDiagramFeatures();
		this.setDefaults();
		this.addEvents(
		/**
   * @event
   * Click event by element
   */
		"click",
		/**
   * @event
   * Double-click event by element
   */
		"doubleClick",
		/**
   * @event
   * Event of changing the selected item
   */
		"selectionChange",
		/**
   * @event
   * Connector change event
   */
		"connectionChange",
		/**
   * @event
   * The mouseUp event
   */
		"mouseUp",
		/**
   * @event
   * The mouseMove event
   */
		"mouseMove",
		/**
   * @event
   * Event for changing the collection of items
   */
		"nodesCollectionChange",
		/**
   * @event
   * Connector collection change event
   */
		"connectorsCollectionChange",
		/**
   * @event
   *  Connector change event. It is generated when a connector has a connection point with a diagram element.
   */
		"connectorsNodesChange",
		/**
   * @event
   * Event of changing the signature of the diagram element.
   */
		"textChange");
	},

	/**
  * Returns the value of the data-item-marker attribute
  */
	getNodeDataItemMarker: function (node) {
		var dataItemMarker = node.name;
		if (!Ext.isEmpty(node.labels) && !Ext.isEmpty(node.labels[0].text)) {
			dataItemMarker += " " + node.labels[0].text;
		}
		return dataItemMarker;
	},

	/**
  * Adds the data-item-marker attribute to the element
  */
	customizeRenderNode: function () {
		var self = this;
		var svgContext = ej.Diagram.SvgContext;
		var baseRenderNode = svgContext.renderNode;
		svgContext.renderNode = function (node, svg) {
			baseRenderNode.apply(this, arguments);
			var dataItemMarker = self.getNodeDataItemMarker(node);
			svg.g({
				id: node.name + "_shape",
				"data-item-marker": dataItemMarker
			});
		};
	},

	/**
  * Returns an existing instance of the diagram.
  * @return {ej.Diagram} diagram.
  */
	getInstance: function () {
		var diagram = this.diagram;
		if (this.isDiagramLoaded()) {
			diagram = this.diagram = $("#diagram-" + this.id).ejDiagram("instance");
		}
		return diagram;
	},

	/**
  * Returns the width of the diagram container.
  * @return {Number}
  */
	getWidth: function () {
		var diagram = this.getInstance();
		var bBox = diagram._svg.document.getBBox();
		return bBox.width;
	},

	/**
  * Returns the height of the diagram container.
  * @return {Number}
  */
	getHeight: function () {
		var height = $(window).height() - $("#diagram-" + this.id).offset().top;
		return Math.max(this.minHeight, height);
	},

	/**
  * Enables auto scrolling of the diagram container.
  */
	setConteinerAutoOverflow: function () {
		var diagramContainer = Ext.get("diagram-" + this.id);
		diagramContainer.setStyle("overflow", "auto");
		diagramContainer.on("scroll", this.onScroll, this);
	},

	/**
  * Scroll event handler for the diagram.
  * @private
  */
	onScroll: function () {
		var diagram = this.getInstance();
		diagram.canvasBBox = null;
	},

	/**
  * A flag that the diagram is loaded
  * @return {Boolean}
  */
	isDiagramLoaded: function () {
		return $("#diagram-" + this.id).hasClass("e-datavisualization-diagram");
	},

	/**
  * Clears the diagram.
  * @private
  */
	clearDiagram: function () {
		if (this.isDiagramLoaded()) {
			var diagram = this.getInstance();
			diagram.clear();
			if (Ext.isIE) {
				//  diagram.clear () in IE10, IE11 does not clean the DOM elements of the htmlLayer node
				Ext.select("[class=\"htmlLayer\"] > *").remove();
			}
		}
	},

	/**
   * Sets the visibility flag for the ports of the diagram node.
   * @private
   * @param {Object} diagram Diagram.
   * @param {Object} node The diagram node.
   * @param {Boolean} hide The visibility sign.
   */
	setPortsVisibility: function (diagram, node, hide) {
		var ports = node.ports || [];
		var len = ports.length;
		var portShape;
		var port;
		var visibility = hide ? "hidden" : "visible";
		for (var i = 0; i < len; ++i) {
			port = ports[i];
			if (port.visibility & ej.Diagram.PortVisibility.Hover || port.visibility & ej.Diagram.PortVisibility.Connect) {
				portShape = diagram._svg.getElementById(node.name + "_" + port.name);
				if (portShape) {
					portShape.setAttribute("visibility", visibility);
				}
			}
		}
	},

	/**
  * Returns a set of custom event handlers for the diagram.
  * @return {Array} A set of custom event handlers for the diagram.
  * @protected
  */
	getUserHandles: function () {
		return [];
	},

	/**
  * Creates ports based on the resulting set of configurations.
  * @param {Array} portsSet A set of port configurations.
  * @return {Array} Set of ports.
  */
	createPorts: function (portsSet) {
		return portsSet.map(function (port) {
			return {
				name: port.name,
				offset: port.offset
			};
		});
	},

	/**
  * Returns the collection of diagram nodes.
  * @return {Array}.
  */
	getNodes: function () {
		var nodes = [];
		nodes = this.items.filterByFn(this.isItemNode, this);
		return nodes.getItems();
	},

	/**
  * Returns the collection with a diagram control flow.
  * @return {Array}.
  */
	getConnectors: function () {
		var connectors = [];
		connectors = this.items.filterByFn(this.isItemConnector, this);
		return connectors.getItems();
	},

	/**
  * Gets the configuration object of settings for the diagram.
  * @protected
  * @returns {Object}
  */
	getDiagramConfig: function () {
		var nodes = this.getNodes();
		var connectors = [];
		if (nodes && nodes.length > 0) {
			connectors = this.getConnectors();
		}
		var diagramConfig = {
			renderTo: "#diagram-" + this.id,
			diagram: {
				nodes: nodes,
				connectors: connectors,
				selectedItems: {
					userHandles: this.getUserHandles()
				},
				constraints: ej.Diagram.DiagramConstraints.Default | ej.Diagram.DiagramConstraints.Zoomable,
				snapSettings: {
					enableSnapToObject: true,
					snapConstraints: ej.Diagram.SnapConstraints.SnapToLines
				},
				selectorConstraints: ej.Diagram.SelectorConstraints.UserHandles | ej.Diagram.SelectorConstraints.Resizer,
				enableVisualGuide: false,
				defaultSettings: {
					connector: {
						constraints: this.connectorConstraints,
						segments: [{
							type: "orthogonal"
						}]
					}
				},
				showTooltip: false,
				pageSettings: {
					scrollLimit: "diagram",
					autoScrollBorder: this.autoScrollBorder
				},
				width: "100%",
				height: this.getHeight()
			}
		};
		if (Ext.isIE) {
			Ext.apply(diagramConfig.diagram.snapSettings, {
				enableSnapToObject: false,
				snapObjectDistance: 1,
				horizontalGridLines: {
					snapInterval: [1]
				},
				verticalGridLines: {
					snapInterval: [1]
				}
			});
		}
		return diagramConfig;
	},

	/**
  * Initializes a diagram.
  * @protected
  */
	initDiagram: function () {
		this.cancelDiagramEvents = true;
		var diagramConfig = this.getDiagramConfig();
		this.clearDiagram();
		$(diagramConfig.renderTo).ejDiagram(Ext.apply(diagramConfig.diagram, {
			"nodeCollectionChange": this.onNodesCollectionChange.bind(this),
			"connectorCollectionChange": this.onConnectorsCollectionChange.bind(this),
			"itemClick": this.onItemClick.bind(this),
			"click": this.onClick.bind(this),
			"doubleClick": this.onDoubleClick.bind(this),
			"textChange": this.onTextChange.bind(this),
			"drag": this.onDrag.bind(this)
		}));
		if (this.autoScroll) {
			this.setConteinerAutoOverflow();
		}
		var diagram = this.getInstance();
		if (!Terrasoft.isEmptyObject(this.diagramSnap)) {
			diagram.load(this.diagramSnap);
		}
		var scope = this;
		diagram._on($(window), "resize", function () {
			var container = document.getElementById(this._id);
			var height = scope.getHeight();
			$(container).height(height);
			this._svg.document.setAttribute("height", height);
			this._updateScrollOffset(0, 0);
		});
		var svgDocument = $(diagram._canvas);
		diagram._on(svgDocument, ej.eventType.mouseUp, this.onDiagramMouseUp.bind(this));
		diagram._on(svgDocument, ej.eventType.mouseMove, this.onDiagramMouseMove.bind(this));
		//TODO Sign up on the event only after the ProcessSchema load

		diagram.model.selectionChange = this.onSelectionChange.bind(this);
		diagram.model.connectionChange = this.onConnectionChange.bind(this);
		this.initConnectorRemovalMixin();
	},

	/**
  * Initialize connector removal mixin.
  * @private
  */
	initConnectorRemovalMixin: function () {
		this.mixins.connectorRemoval.init(this.getInstance());
	},

	/**
  * Called to customize the appearance before rendering the element
  * @param diagram {ej.Diagram}
  * @param node {Object}
  * Example:
  *  itemTemplate: function(node) {
  *   item.labels[0].text = node.name;
  *  }
  */
	nodeTemplate: Ext.emptyFn,

	/**
  * Called to customize the appearance before rendering an element/control flow
  * @param diagram {ej.Diagram}
  * @param item {Object}
  * Example:
  *  itemTemplate: function(diagram, connector) {
  *   connector.labels[0].text = connector.name;
  *  }
  */
	connectorTemplate: Ext.emptyFn,

	/**
  * The event handler for changing the selected item on the chart.
  * @private
  */
	onSelectionChange: function (event) {
		var element = event.element;
		var elementName;
		var i;
		var len;
		if (element && event.changeType === "insert") {
			if (Ext.isArray(element) && element.length > 0) {
				elementName = element[0].name;
			} else {
				elementName = element.name;
			}
		}
		var diagram = this.getInstance();
		var svg = diagram._svg;
		var model = diagram.model;
		var connectors = model.connectors;
		len = connectors.length;
		for (i = 0; i < len; i++) {
			var connector = connectors[i];
			var selected = connector.name === elementName;
			this.updateConnectorSelection(svg, connector, selected);
		}
		this.setPortsVisibilityOnSelectionChange(event, diagram, model.nodes);
		this.fireEvent("selectionChange", event);
	},

	/**
  * Sets the port visibility for the extended elements.
  * @param {Object} event The event object.
  * @param {Object} diagram Instance of the diagram.
  * @param {Array} nodes diagram elements.
  * @protected
  */
	setPortsVisibilityOnSelectionChange: function (event, diagram, nodes) {
		var element = event.element;
		var elementName;
		if (element) {
			elementName = element.name;
		}
		var len = nodes.length;
		for (var i = 0; i < len; i++) {
			var node = nodes[i];
			this.setPortsVisibility(diagram, node, node.name !== elementName);
		}
	},

	/**
  * Updates the connection type in the diagram.
  * @param {Object} svg The element of drawing graphics in the diagram.
  * @param {Object} connector Connection on the diagram.
  * @param {Boolean} selected The characteristic of the selected item.
  */
	updateConnectorSelection: function (svg, connector, selected) {
		var connectorName = connector.name;
		var segments = svg.path({ "id": connectorName + "_segments" });
		var connectorConfig = {
			"id": connectorName + "_segments_selection",
			"fill": "none",
			"color": selected ? this.selectionColor : this.connectorLineColor,
			"stroke-width": 1,
			"stroke-opacity": selected ? 13 : 0,
			"d": segments.attributes.getNamedItem("d").value
		};
		segments.parentNode.appendChild(svg.path(connectorConfig));
	},

	/**
  * The event handler for the control flow change.
  * @private
  */
	onConnectionChange: function (event) {
		var connector = event.element;
		var node = event.connection;
		var port = event.port;
		if (connector && node && port) {
			var isSourcePoint = event.endPoint === "sourceEndPoint";
			var changedProperty = isSourcePoint ? "sourceNode" : "targetNode";
			var currentPort = isSourcePoint ? connector.sourcePort : connector.targetPort;
			var newPort = port.name;
			if (connector[changedProperty] === node.name && currentPort === newPort) {
				return;
			}
			this.fireEvent("connectorsNodesChange", [{
				changedProperty: changedProperty,
				connectorName: connector.name,
				nodeName: node.name,
				port: newPort
			}]);
		}
	},

	/**
  * Double-click event handler for the element in the diagram.
  * @private
  * @param {Object} event Event object.
  */
	onDoubleClick: function (event) {
		//prevent element double click to disable text editing.
		event.cancel = true;
		this.fireEvent("doubleClick", event);
	},

	/**
  * The event handler for the element's signature change on the diagram.
  * @private
  * @param {Object} event Event object.
  */
	onTextChange: function (event) {
		this.fireEvent("textChange", event);
	},

	/**
  * The handler of the click event on the diagram.
  * @private
  * @param {Object} event Event object.
  */
	onClick: function (event) {
		this.fireEvent("click", event);
	},

	/**
  * The click event handler for the element on the diagram.
  * @param {Object} event Event object.
  * @private
  */
	onItemClick: function (event) {
		this.fireEvent("itemClick", event);
	},

	/**
  * The mouse button event handler in the diagram.
  * @private
  */
	onDiagramMouseUp: function (event) {
		Ext.select(".hitTest").set({ "pointer-events": "stroke" });
		this.fireEvent("mouseUp", event);
	},

	/**
  * The handler for the mouse movement event on the diagram.
  * @private
  */
	onDiagramMouseMove: function (event) {
		this.fireEvent("mouseMove", event);
	},

	/**
  * The event handler for moving items on the diagram.
  * @private
  */
	onDrag: function (event) {
		this.fireEvent("drag", event);
	},

	/**
  * The event handler for changing the number of nodes in the diagram.
  * @private
  */
	onNodesCollectionChange: function (event) {
		this.onDiagramItemsChange(event);
		this.fireEvent("nodesCollectionChange", event);
	},

	/**
  * The event handler for changing the number of diagram connectors.
  * @private
  */
	onConnectorsCollectionChange: function (event) {
		this.onDiagramItemsChange(event);
		this.fireEvent("connectorsCollectionChange", event);
	},

	/**
  * The event handler for changing the collection of items on the diagram. Synchronizes the chart elements and the
  * items collection.
  * @param {Object} event The event parameter.
  * @protected
  */
	onDiagramItemsChange: function (event) {
		var changeType = event.changeType;
		if (changeType === "remove") {
			var element = event.element;
			var items = this.items;
			// The collection indexes are rebuilt after the remove event is generated. In the event handler for this event
			// another item is deleted. Due to an incorrectly defined index, the wrong item is deleted.
			items.collection.rebuildIndexMap();
			var elementName = element.name;
			if (items.contains(elementName)) {
				items.removeByKey(elementName);
			}
		}
	},

	/**
  * Creates a structure of the connections to the diagram elements for the transferred connectors and generates a {@link #connectorsNodesChange} event.
  * @param {String []} inEdgesConnectors An array with the names of the connectors for which you want to process incoming connections.
  * @param {String []} outEdgesConnectors An array with the names of the connectors for which you want to process outbound connections.
  */
	linkConnectors: function (inEdgesConnectors, outEdgesConnectors) {
		var eventArgs = [];
		var self = this;
		var incomingConnectors = inEdgesConnectors || [];
		var outcomingConnectors = outEdgesConnectors || [];
		function processConnector(connectorName, changedProperty) {
			var connector = self.getElementById(connectorName);
			if (connector) {
				eventArgs.push({
					changedProperty: changedProperty,
					connectorName: connectorName,
					nodeName: connector[changedProperty]
				});
			}
		}
		incomingConnectors.forEach(function (connectorName) {
			processConnector(connectorName, "targetNode");
		}, this);
		outcomingConnectors.forEach(function (connectorName) {
			processConnector(connectorName, "sourceNode");
		}, this);
		this.fireEvent("connectorsNodesChange", eventArgs);
	},

	/**
  * Bind all the elements to the model
 * @override
 * @param {Terrasoft.data.modules.BaseViewModel} model Data model.
 */
	bind: function (model) {
		this.mixins.bindable.bind.call(this, model);
	},

	/**
  * Returns the configuration of the binding to the model. Implements the {@link Terrasoft.Bindable} mixin interface.
  * @protected
  * @override
  */
	getBindConfig: function () {
		var bindConfig = this.callParent(arguments);
		var diagramBindConfig = {
			items: {
				changeMethod: "onCollectionDataLoaded"
			}
		};
		return Ext.apply(diagramBindConfig, bindConfig);
	},

	/**
  * @inheritdoc Terrasoft.Component#reRender
  * @override
  */
	reRender: function () {
		if (this.allowRerender()) {
			this.callParent(arguments);
		}
	},

	/**
  * @inheritdoc Terrasoft.Component#onAfterRender
  * @protected
  * @override
  */
	onAfterRender: function () {
		this.callParent(arguments);
		this.afterRenderOrAfterReRender();
	},

	/**
  * @inheritdoc Terrasoft.Component#onAfterReRender
  * @protected
  * @override
  */
	onAfterReRender: function () {
		this.callParent(arguments);
		this.diagram = null;
		this.afterRenderOrAfterReRender();
	},

	/**
  * Creates an instance of the diagram. Displays it in the created container.
  * @override
  */
	afterRenderOrAfterReRender: function () {
		this.initDiagram();
		this.cancelDiagramEvents = false;
	},

	/**
  * Updates the dimensions of the diagram.
  */
	updatePageSize: function () {
		var diagram = this.getInstance();
		ej.Diagram.PageUtil._updatePageSize(diagram);
	},

	/**
  * @inheritdoc Terrasoft.Bindable#subscribeForCollectionEvents
  * @protected
  * @override
  */
	subscribeForCollectionEvents: function (binding, property, model) {
		this.callParent(arguments);
		var collection = model.get(binding.modelItem);
		collection.on("dataLoaded", this.onCollectionDataLoaded, this);
		collection.on("add", this.onAddItem, this);
		collection.on("remove", this.onRemoveItem, this);
		collection.on("clear", this.clearDiagram, this);
	},

	/**
  * @inheritdoc Terrasoft.Bindable#unSubscribeForCollectionEvents
  * @protected
  * @override
  */
	unSubscribeForCollectionEvents: function (binding, property, model) {
		if (!model) {
			return;
		}
		var collection = model.get(binding.modelItem);
		collection.un("dataLoaded", this.onCollectionDataLoaded, this);
		collection.un("add", this.onAddItem, this);
		collection.un("remove", this.onRemoveItem, this);
		collection.un("clear", this.clearDiagram, this);
		this.callParent(arguments);
	},

	/**
  * The "dataLoaded" event handler for the Terrasoft.Collection
  * @protected
  */
	onCollectionDataLoaded: function (items, newItems) {
		items = newItems || items;
		if (items && items.getCount() > 0) {
			var isDiagramLoaded = this.isDiagramLoaded();
			var diagram;
			if (isDiagramLoaded) {
				diagram = this.getInstance();
				diagram.disableSetScrollContentSize = true;
				diagram.disableDefaultSelection = true;
			}
			var ownItems = this.items;
			ownItems.suspendEvents(false);
			items.each(function (item) {
				this.onAddItem(item, true);
			}, this);
			ownItems.resumeEvents();
			if (isDiagramLoaded) {
				diagram.disableSetScrollContentSize = false;
				diagram.disableDefaultSelection = false;
				ej.Diagram.ScrollUtil._setScrollContentSize(diagram);
			}
		}
	},

	/**
  * Checks whether the element is a control thread
  * @param {Object} item
  * @returns {boolean}
  */
	isItemConnector: function (item) {
		if (item.sourceNode || item.sourcePoint) {
			return true;
		}
		return false;
	},

	/**
  * Checks whether the element is a node of the diagram.
  * @param {Object} item
  * @returns {boolean}
  */
	isItemNode: function (item) {
		return !this.isItemConnector(item);
	},

	/**
  * The "add" event handler of the Terrasoft.Collection
  * @protected
  * @param {Object} item
  * @param {Boolean} isDataLoaded A flag of the initial boot.
  */
	onAddItem: function (item, isDataLoaded) {
		if (this.isDiagramLoaded()) {
			if (this.isItemConnector(item)) {
				this.connectorTemplate(item);
			} else {
				item.offsetX = item.offsetX + this.diagram._hScrollOffset;
				item.offsetY = item.offsetY + this.diagram._vScrollOffset;
				item.constraints = item.constraints || this.nodeConstraints;
				//create ports when connections to node allowed
				if (item.constraints & ej.Diagram.NodeConstraints.Connect) {
					var portsSet = item.portsSet ? item.portsSet : Terrasoft.diagram.PortsSet.All;
					item.ports = this.createPorts(portsSet);
				}
				this.nodeTemplate(item);
			}
			this.addItem(item, isDataLoaded);
		}
	},

	/**
  * Adds an element to the diagram.
  * @param {ej.Diagram.Node} item The element to add to the diagram.
  */
	addItem: function (item) {
		if (this.isDiagramLoaded()) {
			var diagram = this.getInstance();
			diagram.add(item);
		}
	},

	/**
  * The "remove" event handler for the Terrasoft.Collection
  * @protected
  * @param {Object} item
  */
	onRemoveItem: function (item) {
		if (this.isDiagramLoaded()) {
			var element = this.getElementById(item.name);
			// Hack.  does not generate any event when the connector is automatically redirected to another chart element.
			// Redirection occurs when the node is deleted.
			// Here we save all the nodes of the node before it is deleted. After removing the node for all stored connectors
			// we generate a change event for the (see linkConnectors) mount points.
			var inEdges = null;
			var outEdges = null;
			var elementType = element.type;
			if (elementType === "node") {
				inEdges = element.inEdges;
				outEdges = element.outEdges;
			}
			var diagram = this.getInstance();
			diagram.remove(element);
			if (elementType === "node") {
				this.linkConnectors(inEdges, outEdges);
			}
		}
	},

	/**
  * Destroy the chart and its components.
  * @override
  */
	onDestroy: function () {
		this.callParent(arguments);
		if (this.diagram) {
			this.diagram.destroy();
		}
		this.diagram = null;
		if (this.svgContextSnap) {
			ej.Diagram.SvgContext = Terrasoft.deepClone(this.svgContextSnap);
			this.svgContextSnap = null;
		}
	},

	/**
  * Overrides the creation of the image in the Svg element.
  * @private
  */
	customizeSvgImage: function () {
		ej.Diagram.Svg.prototype.image = function (attr) {
			var element = this.element(attr, "image");
			if (Ext.isGecko) {
				var el = Ext.fly(element);
				el.on("click", function (event) {
					if (event.ctrlKey === true || event.shiftKey === true) {
						event.preventDefault();
					}
				});
			}
			return element;
		};
	},

	/**
  * Updates _disConnect function to reconnect connectors to first non-recursive.
  * @protected
  */
	customizeDisConnect: function () {
		if (Terrasoft.Diagram.custom._disConnect) {
			return;
		}
		var connectorRemoval = this.mixins.connectorRemoval;
		ej.Diagram.prototype._disConnect = function (node, args, isChild) {
			connectorRemoval.disconnect(node, args, isChild);
		};
	},

	/**
  * Updates _removeConnector function to remove connectors with null references.
  * @protected
  */
	customizeRemoveConnector: function () {
		if (Terrasoft.Diagram.custom._removeConnector) {
			return;
		}
		var connectorRemoval = this.mixins.connectorRemoval;
		ej.Diagram.prototype._removeConnector = function (node, args) {
			connectorRemoval.removeConnector(node, args);
		};
		Terrasoft.Diagram.custom._removeConnector = true;
	},

	/**
  * Overrides ej.Diagram.prototype._recordPinPointChanged
  */
	customizeRecordPinPointChanged: function () {
		if (Terrasoft.Diagram.custom._recordPinPointChanged) {
			return;
		}
		var baseRecordPinPointChanged = ej.Diagram.prototype._recordPinPointChanged;
		ej.Diagram.prototype._recordPinPointChanged = function (args) {
			if (!args.object.node) {
				return;
			}
			baseRecordPinPointChanged.apply(this, arguments);
		};
	},

	/**
  * Overrides ej.Diagram.prototype._recordRotationChanged
  */
	customizeRecordRotationChanged: function () {
		if (Terrasoft.Diagram.custom._recordRotationChanged) {
			return;
		}
		var baseRecordRotationChanged = ej.Diagram.prototype._recordRotationChanged;
		ej.Diagram.prototype._recordRotationChanged = function (args) {
			if (!args.object.node) {
				return;
			}
			baseRecordRotationChanged.apply(this, arguments);
		};
	},

	/**
  * Overrides ej.Diagram.prototype._recordSizeChanged
  */
	customizeRecordSizeChanged: function () {
		if (Terrasoft.Diagram.custom._recordSizeChanged) {
			return;
		}
		var baseRecordSizeChanged = ej.Diagram.prototype._recordSizeChanged;
		ej.Diagram.prototype._recordSizeChanged = function (args) {
			if (!args.object.node) {
				return;
			}
			baseRecordSizeChanged.apply(this, arguments);
		};
	}

});