<?xml version="1.0" encoding="utf-8" ?> 
<!-- XML Component File -->
<application-components>
	 <application-component id = "hierarchy" participant-id ="canvas">
			<![CDATA[
         component_init:function(){

						/// Setup properties
						///
						this.getStatus().node_counter = 0;

						/// Setup object pointer references
						///
						/// nodes[] contains objects that may or may not have a shape
						///
						this.getPointers().nodes = [];

						/// node_tree[] is a jagged array of depth[int],node_indices[]
						///
						this.getPointers().node_tree = [];
									
						this.getPointers().hierarchy_decorator = org.cote.js.appcomp.ApplicationComponent.newInstance();
						this.getPointers().hierarchy_decorator.loadComponent("hierarchy-decorator","Gizmos/component.hierarchy_decorator.xml");
						this.getPointers().hierarchy_decorator.SetHierarchy(this);
         },
				 SetCanvasComponent : function(o){
						if(typeof o.getObjectType == "undefined" || o.getObjectType() != "application_component"){
							alert("Expecting 'canvas' application component");
							return;
						}
						this.getPointers().canvas = o;	 
						o.AddShapeDecorator(this.getPointers().hierarchy_decorator);
				 },
					NodeHierarchyToJSON : function(){
						if(!this.getPointers().node_tree[1]) return "{\"Hierarchy\":[]}";
						var aN = this.getPointers().node_tree[1];
						var a = [];
						for(var i = 0;i < aN.length; i++){
							var oN = this.getPointers().nodes[aN[i]];
							if(!oN) continue;
							a.push(this.NodeToJSON(oN));
						}
						return "{\"Hierarchy\":[" + a.join(",") + "]}";
					},
					NodeToJSON : function(oNode){
						var a = [];
						a.push("{\"name\":\"" + oNode.text + "\",\"id\":\"" + oNode.id + "\",\"nodes\":[");
						var c = [];
						for(var i = 0; i < oNode.children.length;i++){
							c.push(this.NodeToJSON(this.getPointers().nodes[oNode.children[i]]));
							
						}
						a.push(c.join(","));
						a.push("]}");
						return a.join("");
					},
					ClearHierarchy : function(){
						this.getStatus().node_counter = 0;
						this.getPointers().nodes = [];
						this.getPointers().node_tree = [];
						this.getPointers().canvas.Clear();
					},

					Render : function(){
							this.getPointers().canvas.ClearTempCanvas();
							this.getPointers().canvas.ClearCanvas();
							//org.cote.js.xml.removeChildren(document.getElementById("_spot"));
							//OrganizeTree();
							//PlotOrganizationsSubStrate(1);
							this.BalanceTree();
							this.PlotOrganizations(1);
							this.getPointers().canvas.Rasterize();
					},
					PlotOrganizations : function(iDepth){
							
							var iLeafWidth = 0;
							var vNodes = this.GetNodesAtDepthByParent(iDepth);
							var aNodes = this.GetNodesAtDepth(iDepth);
							
							var iPrevWidth = 0;
							var iStartLeft = 0;

							// iterate through parent node aggregate
							//
							for(var p = 0; p < vNodes.length; p++){
								if(!vNodes[p]) continue;

								var iLeafWidth = 0;
								var bMod = 0;

								// iterate through leaf nodes
								//
								for(var i = 0; i < vNodes[p].length; i++){
									var oNode = vNodes[p][i];
									var oParent = 0;
									var iGridColumn = 0;
									if(oNode.parent >= 0) oParent = this.getPointers().nodes[oNode.parent];

									// pixel width of the maximum leaf nodeset
									//
									iLeafWidth = oNode.left_breadth + oNode.right_breadth + 1;
									var iGridMod = 0;

									if(oParent){
											
										var iModCol = oParent.children.length;
										if(!iPrevWidth) iPrevWidth = oParent.grid_column - oParent.left_breadth;
										if(oParent.children.length == 1){
											iGridColumn = oParent.grid_column;
										}
										else{
											iGridMod = (
												oParent.children.length % 2 == 0
												&&
												(oNode.position + oNode.right_breadth) >= Math.ceil(oParent.children.length/2)
												? 1 : 0
												);

											/// If the position is pushed to the right of the parent w / even children
											/// Then skip the extra alignment bump.
											/// The alignment bump is only to distribute even children to either side of the parent
											///
											if(!bMod && (iPrevWidth + oNode.left_breadth) > oParent.grid_column) bMod = 1;
											iGridColumn = (!bMod ? iGridMod : 0) + iPrevWidth + oNode.left_breadth;
											bMod = (!bMod && iGridMod ? 1 : 0);
										}
										iPrevWidth = iGridColumn + oNode.right_breadth + 1;
									}
									else{
										iGridColumn = iPrevWidth + (iGridMod ? iGridMod : oNode.left_breadth);
										iPrevWidth += iLeafWidth + (oNode.children.length % 2 == 0 ? 1 : 0);
									}
									oNode.grid_column = iGridColumn;

									iStartLeft = iGridColumn * this.getPointers().canvas.getStatus().DefaultShapeGridUnit;

									var iX = iStartLeft;
									var iY = (this.getPointers().canvas.getStatus().DefaultShapeRadius * 2 * (iDepth-1)) + (this.getPointers().canvas.getStatus().DefaultShapeVerticalSpacing * (iDepth - 1));

									oNode.shape = 
										//Rect(iX - SvgHierarchy.ShapeRadius, iY - SvgHierarchy.ShapeRadius, SvgHierarchy.ShapeRadius * 2, SvgHierarchy.ShapeRadius *2, "#0000FF","#CFCFCF")
										this.getPointers().canvas.Rect(iX, iY, this.getPointers().canvas.getStatus().DefaultShapeRadius * 2, this.getPointers().canvas.getStatus().DefaultShapeRadius *2, "#0000FF","#CFCFCF")
										//Circle(iX, iY, SvgHierarchy.ShapeRadius, "#0000FF", "#CFCFCF")
									;
									 
									oNode.shape.reference_id = oNode.index;

									if(oNode.parent >=0) this.getPointers().canvas.ConnectShapes(this.getPointers().nodes[oNode.parent].shape, oNode.shape);
									var oText = this.getPointers().canvas.Text(oNode.text,iX,iY,"#00FF00");
									oText.reference_id = oNode.index;
									
								}
							}		

							if(this.getPointers().node_tree.length > iDepth) this.PlotOrganizations(iDepth + 1);
							return;

					},
					BalanceTree : function(){
							var aN = this.getPointers().node_tree;
							for(var i = aN.length - 1; i >=0; i--){
								if(typeof aN[i] != "object"){
									/// Tree not extended at this depth
									continue;
								};
								for(var c = aN[i].length - 1; c>=0; c--){
									if(typeof aN[i][c] != "number"){
										alert('Splice error at ' + i + ":" + c);
										continue;
									}
									if(this.getPointers().nodes[aN[i][c]].parent >= 0) this.UpdateLeaf(this.getPointers().nodes[aN[i][c]],this.getPointers().nodes[this.getPointers().nodes[aN[i][c]].parent]);
								}
							}
					},
					GetNodesAtDepthByParent : function(iDepth){
							var a = this.GetNodesAtDepth(iDepth);
							var oa = [];
							for(var i = 0; i < a.length; i++){
								var o = a[i];
								var iP = o.parent;
								// root node is -1
								// so bump to 0 for the purposes of sorting
								//
								if(iP < 0) iP = 0;
								if(!oa[iP]) oa[iP] = [];
								oa[iP].push(o);
							}
							return oa;
					},
					GetNodesAtDepth : function(iDepth){
							var a = (this.getPointers().node_tree[iDepth] ? this.getPointers().node_tree[iDepth] : []);
							var oa = [];
							for(var i = 0; i < a.length; i++){
								oa.push(this.getPointers().nodes[a[i]]);
							}
							return oa;
					},
					ReparentOrganizationNode : function(oNode,oNewParent){
							// node cannot be parented below itself.
							//
							if(oNode.index == oNewParent.index || oNode.parent == oNewParent.index){
								org.cote.js.message.MessageService.sendMessage("Nothing to do");
								return 0;		
							}
							if(this.IsChild(oNewParent,oNode)){
								org.cote.js.message.MessageService.sendMessage("Cannot reparent to a child node");
								return 0;
							}
							if(oNode.parent >= 0){
								org.cote.js.message.MessageService.sendMessage("Removing node " + oNode.text + " from " + this.getPointers().nodes[oNode.parent].text);
							}
							else{
								org.cote.js.message.MessageService.sendMessage("Removing node " + oNode.text + " from top organization");
							}
							
							this.RemoveNodeFromTree(oNode,1);
							if(oNode.parent >=0) this.RemoveNodeFromParent(oNode);
							
							org.cote.js.message.MessageService.sendMessage("Attaching node " + oNode.text + " (" + oNode.index + ") to " + oNewParent.text + " (" + oNewParent.index + ")");
							oNode.parent = oNewParent.index;
							oNode.depth = oNewParent.depth + 1;

							this.AddNodeToTree(oNode);
							
							this.BalanceTree();

							this.Render();
							
					},
					AddNodeToTree : function(oNode, bNoReParent){
							var _p = this.getPointers();
							/// Expand the tree depth to accommodate the specified level
							///
							for(var i = 1; i <= oNode.depth; i++){
								if(_p.node_tree[i]) continue;
								_p.node_tree[i] = [];
							}
							
							/// Insert the node reference (using in-memory index) into the tree at the specified depth
							//
							var l = _p.node_tree[oNode.depth].length;
							_p.node_tree[oNode.depth][l] = oNode.index;
							
							if(!bNoReParent) this.AddNodeAtParent(oNode);

							/// If the node has any children, rebuild the children tree
							/// This occurs when moving nodes around via Remove then Add
							/// Make sure the depth correctly adjusted
							///
							for(var i = 0; i < oNode.children.length; i++){
								_p.nodes[oNode.children[i]].depth = oNode.depth + 1;
								this.AddNodeToTree(_p.nodes[oNode.children[i]],1);
							}
							return oNode;
					},
					RemoveNodeFromTree : function(oNode, bKeepParent){
							if(typeof this.getPointers().node_tree[oNode.depth] != "object") return 0;
							
							for(var i = 0; i < oNode.children.length;i++) this.RemoveNodeFromTree(this.getPointers().nodes[oNode.children[i]], bKeepParent);
							for(var i = 0; i < this.getPointers().node_tree[oNode.depth].length; i++){
								if(this.getPointers().node_tree[oNode.depth][i] == oNode.index){
									this.getPointers().node_tree[oNode.depth].splice(i,1);
									//org.cote.js.message.MessageService.sendMessage("Splice node " + oNode.text + " at " + oNode.index);
									break;
								}
							}
							/// Debug - make sure node is out of tree
							///
							/*
							for(var n = 0; n < this.getPointers().node_tree.length; n++){
								if(typeof this.getPointers().node_tree[n] != "object") continue;
								for(var i = 0; i < this.getPointers().node_tree[n].length; i++){
									if(this.getPointers().node_tree[n][i] == oNode.index) alert('failed to splice');
								}
							}
							*/
							if(!bKeepParent && oNode.parent >= 0) this.RemoveNodeFromParent(oNode);
							oNode.depth = -1;
					},
					IsChild : function(oNode, oPossibleParent){
							var bCheck = 0;
							for(var i = 0; i < oPossibleParent.children.length; i++){
								if(oNode.index == oPossibleParent.children[i] || this.IsChild(oNode,this.getPointers().nodes[oPossibleParent.children[i]])){
									bCheck = 1;
									break;
								}
							}
							return bCheck;
					},

					/// Update the Parent node children array with a reference to the specified node
					///
					AddNodeAtParent : function(oNode){
						if(oNode.depth <= 1 || oNode.parent < 0) return;
						var oP = this.getPointers().nodes[oNode.parent];
						var iL = oP.children.length;
						oP.children[iL] = oNode.index;
						oNode.position = iL;
						this.UpdateLeaf(oNode, oP);
					},
					RemoveNodeFromParent : function(oNode){
							if(oNode.parent < 0) return;
							var oP = this.getPointers().nodes[oNode.parent];
							oP.children.splice(oNode.position,1);
							//org.cote.js.message.MessageService.sendMessage("Splice node " + oNode.text + " from parent " + oP.text + " at " + oNode.position);
							/// Reset the remaining node positions
							///
							for(var i = oNode.position; i >= 0 && i < oP.children.length; i++){
								var oCN = this.getPointers().nodes[oP.children[i]];
								//org.cote.js.message.MessageService.sendMessage("Reposition adjacent child " + oCN.text + " from " + oCN.position + " to " + (oCN.position - 1));
								oCN.position--;
								//UpdateLeaf(oCN,oP);
							}
									
							/// Orphan the node
							///
							oNode.parent = -1;
							oNode.position = -1;
							
						},
					UpdateLeaf : function(oNode, oParent,iNewWidth){
						var iChildLen = oParent.children.length;
						if(!iNewWidth){
							iNewWidth = 1;
						}
						if(oNode.children.length == 0){
							oNode.center_breadth = 0;
							oNode.left_breadth = 0;
							oNode.right_breadth = 0;
						}
						oNode.leaf_width = Math.max(iNewWidth,Math.max(oNode.children.length,1));
						oParent.leaf_width = Math.max(oParent.children.length,1);
						
						// If the parent has children
						//    Then balance the array length with any odd value centered
						//
						if(iChildLen){
							var iSplit = Math.max(iNewWidth,iChildLen)/2;
							oParent.left_breadth = Math.floor(iSplit);
							oParent.center_breadth = (iChildLen %2);
							oParent.right_breadth = Math.ceil((iChildLen - oParent.center_breadth)/2);
							// If the node is on the right breadth, then pad the parent's required right breadth
							//
							var iNodeBreadth = oNode.left_breadth + oNode.right_breadth;

							if(oNode.position >= Math.ceil(iChildLen/2)){
								oParent.right_breadth += iNodeBreadth;
							}
							else oParent.right_breadth += oNode.right_breadth;

							if(Node.position <= Math.floor(iChildLen/2)){
								oParent.left_breadth += iNodeBreadth;
							}
							else oParent.left_breadth += oNode.left_breadth;
						}

						if(oParent.depth < 1 || oParent.parent < 0) return;
						
						var oP = this.getPointers().nodes[oParent.parent];
						var iP = 0;
						for(var p= 0; p < oP.children.length; p++){
							iP += this.getPointers().nodes[oP.children[p]].leaf_width;
						}
						
						this.UpdateLeaf(oParent,this.getPointers().nodes[oParent.parent],iP);
					},
					NewOrganizationNode : function(sId,sText,oParent){
						return this.Merge(this.NewNode(sId,sText,"org",oParent),
							{
							}
						);
					},
					Merge : function(s,t){
							for(var i in s){
								if(typeof t[i] == "undefined") t[i]=s[i];
							}
							return t;
					},

					NewNode : function(sId, sText, sType,oParent){
							var iIndex = this.getPointers().nodes.length;
							return this.getPointers().nodes[iIndex] = this.AddNodeToTree({
								text:(!sText ? sId : sText),
								id:sId,
								type:sType,
								left_breadth : 0,
								center_breadth : 0,
								right_breadth : 0,
								leaf_width : 0,
								children:[],
								parent:(oParent ? oParent.index : -1),
								// Index in the master node collection
								index : iIndex,
								// Position in the parent's child collection,
								position : 0,
								// Grid column position; placeholder for the renderer
								//
								grid_column : 0,
								shape : 0,
								depth:(oParent ? oParent.depth + 1 : 1)
							});
					}
			]]>
	</application-component>
</application-components>
