//dojo.provide("lib.CheckBoxTree");
//dojo.provide("lib.CheckBoxStoreModel");

// THIS WIDGET IS BASED ON DOJO/DIJIT 1.4.0 AND WILL NOT WORK WITH PREVIOUS VERSIONS
//
//	Release date: 02/05/2010
//

//dojo.require("dijit.Tree");
//dojo.require("dijit.form.CheckBox");

require(["dojo/_base/declare", "dijit/tree/TreeStoreModel"], function (declare) {

	return declare( "lib.CheckBoxStoreModel", dijit.tree.TreeStoreModel,
		{
			// checkboxAll: Boolean
			//		If true, every node in the tree will receive a checkbox regardless if the 'checkbox' attribute
			//		is specified in the dojo.data.
			checkboxAll: true,

			// checkboxState: Boolean
			// 		The default state applied to every checkbox unless otherwise specified in the dojo.data.
			//		(see also: checkboxIdent)
			checkboxState: false,

			// checkboxRoot: Boolean
			//		If true, the root node will receive a checkbox eventhough it's not a true entry in the store.
			//		This attribute is independent of the showRoot attribute of the tree itself. If the tree
			//		attribute 'showRoot' is set to false to checkbox for the root will not show either.
			checkboxRoot: false,

			// checkboxStrict: Boolean
			//		If true, a strict parent-child checkbox relation is maintained. For example, if all children
			//		are checked the parent will automatically be checked or if any of the children are unchecked
			//		the parent will be unchecked.
			checkboxStrict: true,

			// checkboxIdent: String
			//		The attribute name (attribute of the dojo.data.item) that specifies that items checkbox initial
			//		state. Example:	{ name:'Egypt', type:'country', checkbox: true }
			//		If a dojo.data.item has no 'checkbox' attribute specified it will depend on the attribute
			//		'checkboxAll' if one will be created automatically and if so what the initial state will be as
			//		specified by 'checkboxState'.
			checkboxIdent: "checkbox",

			updateCheckbox: function(/*dojo.data.Item*/ storeItem, /*Boolean*/ newState ) {
				// summary:
				//		Update the checkbox state (true/false) for the item and the associated parent and
				//		child checkboxes if any.
				// description:
				//		Update a single checkbox state (true/false) for the item and the associated parent
				//		and child checkboxes if any. This function is called from the tree if a user checked
				//		or unchecked a checkbox on the tree. The parent and child tree nodes are updated to
				//		maintain consistency if 'checkboxStrict' is set to true.
				//	storeItem:
				//		The item in the dojo.data.store whos checkbox state needs updating.
				//	newState:
				//		The new state of the checkbox: true or false
				//	example:
				//	| model.updateCheckboxState(item, true);
				//

				this._setCheckboxState( storeItem, newState );
				//if( this.checkboxStrict ) { I don't need all this 1-1 stuff, only parent -> child (fox)
				this._updateChildCheckbox( storeItem, newState );
				//this._updateParentCheckbox( storeItem, newState );
				//}
			},
			setAllChecked: function(checked) {
				var items = this.store._arrayOfAllItems;
				this.setCheckboxState(items, checked);
			},
			setCheckboxState: function(items, checked) {
				for (var i = 0; i < items.length; i++) {
					this._setCheckboxState(items[i], checked);
				}
			},
			getCheckedItems: function() {
				var items = this.store._arrayOfAllItems;
				var result = [];

				for (var i = 0; i < items.length; i++) {
					if (this.store.getValue(items[i], 'checkbox'))
						result.push(items[i]);
				}

				return result;
			},

			getCheckboxState: function(/*dojo.data.Item*/ storeItem) {
				// summary:
				//		Get the current checkbox state from the dojo.data.store.
				// description:
				//		Get the current checkbox state from the dojo.data store. A checkbox can have three
				//		different states: true, false or undefined. Undefined in this context means no
				//		checkbox identifier (checkboxIdent) was found in the dojo.data store. Depending on
				//		the checkbox attributes as specified above the following will take place:
				//		a) 	If the current checkbox state is undefined and the checkbox attribute 'checkboxAll' or
				//			'checkboxRoot' is true one will be created and the default state 'checkboxState' will
				//			be applied.
				//		b)	If the current state is undefined and 'checkboxAll' is false the state undefined remains
				//			unchanged and is returned. This will prevent any tree node from creating a checkbox.
				//
				//	storeItem:
				//		The item in the dojo.data.store whos checkbox state is returned.
				//	example:
				//	| var currState = model.getCheckboxState(item);
				//
				var currState = undefined;

				// Special handling required for the 'fake' root entry (the root is NOT a dojo.data.item).
				// this stuff is only relevant for Forest store -fox
				/*		if ( storeItem == this.root ) {
				 if( typeof(storeItem.checkbox) == "undefined" ) {
				 this.root.checkbox = undefined;		// create a new checbox reference as undefined.
				 if( this.checkboxRoot ) {
				 currState = this.root.checkbox = this.checkboxState;
				 }
				 } else {
				 currState = this.root.checkbox;
				 }
				 } else {	// a valid dojo.store.item
				 currState = this.store.getValue(storeItem, this.checkboxIdent);
				 if( currState == undefined && this.checkboxAll) {
				 this._setCheckboxState( storeItem, this.checkboxState );
				 currState = this.checkboxState;
				 }
				 } */

				currState = this.store.getValue(storeItem, this.checkboxIdent);
				if( currState == undefined && this.checkboxAll) {
					this._setCheckboxState( storeItem, this.checkboxState );
					currState = this.checkboxState;
				}

				return currState;  // the current state of the checkbox (true/false or undefined)
			},

			_setCheckboxState: function(/*dojo.data.Item*/ storeItem, /*Boolean*/ newState ) {
				// summary:
				//		Set/update the checkbox state on the dojo.data store.
				// description:
				//		Set/update the checkbox state on the dojo.data.store. Retreive the current
				//		state of the checkbox and validate if an update is required, this will keep
				//		update events to a minimum. On completion a 'onCheckboxChange' event is
				//		triggered.
				//		If the current state is undefined (ie: no checkbox attribute specified for
				//		this dojo.data.item) the 'checkboxAll' attribute is checked	to see if one
				//		needs to be created. In case of the root the 'checkboxRoot' attribute is checked.
				//		NOTE: the store.setValue function will create the 'checkbox' attribute for the
				//		item if none exists.
				//	storeItem:
				//		The item in the dojo.data.store whos checkbox state is updated.
				//	newState:
				//		The new state of the checkbox: true or false
				//	example:
				//	| model.setCheckboxState(item, true);
				//
				var stateChanged = true;

				if( storeItem != this.root ) {
					var currState = this.store.getValue(storeItem, this.checkboxIdent);
					if( currState != newState && ( currState !== undefined || this.checkboxAll ) ) {
						this.store.setValue(storeItem, this.checkboxIdent, newState);
					} else {
						stateChanged = false;	// No changes to the checkbox
					}
				} else {  // Tree root instance
					if( this.root.checkbox != newState && ( this.root.checkbox !== undefined || this.checkboxRoot ) ) {
						this.root.checkbox = newState;
					} else {
						stateChanged = false;
					}
				}
				if( stateChanged ) {	// In case of any changes trigger the update event.
					this.onCheckboxChange(storeItem);
				}
				return stateChanged;
			},

			_updateChildCheckbox: function(/*dojo.data.Item*/ parentItem, /*Boolean*/ newState ) {
				//	summary:
				//		Set all child checkboxes to true/false depending on the parent checkbox state.
				//	description:
				//		If a parent checkbox changes state, all child and grandchild checkboxes will be
				//		updated to reflect the change. For example, if the parent state is set to true,
				//		all child and grandchild checkboxes will receive that same 'true' state.
				//		If a child checkbox changes state and has multiple parent, all of its parents
				//		need to be re-evaluated.
				//	parentItem:
				//		The parent dojo.data.item whos child/grandchild checkboxes require updating.
				//	newState:
				//		The new state of the checkbox: true or false
				//

				if( this.mayHaveChildren( parentItem )) {
					this.getChildren( parentItem, dojo.hitch( this,
						function( children ) {
							dojo.forEach( children, function(child) {
								if( this._setCheckboxState(child, newState) ) {
									var parents = this._getParentsItem(child);
									if( parents.length > 1 ) {
										this._updateParentCheckbox( child, newState );
									}
								}
								if( this.mayHaveChildren( child )) {
									this._updateChildCheckbox( child, newState );
								}
							}, this );
						}),
						function(err) {
							console.error(this, ": updating child checkboxes: ", err);
						}
					);
				}
			},

			_updateParentCheckbox: function(/*dojo.data.Item*/ storeItem, /*Boolean*/ newState ) {
				//	summary:
				//		Update the parent checkbox state depending on the state of all child checkboxes.
				//	description:
				//		Update the parent checkbox state depending on the state of all child checkboxes.
				//		The parent checkbox automatically changes state if ALL child checkboxes are true
				//		or false. If, as a result, the parent checkbox changes state, we will check if
				//		its parent needs to be updated as well all the way upto the root.
				//	storeItem:
				//		The dojo.data.item whos parent checkboxes require updating.
				//	newState:
				//		The new state of the checkbox: true or false
				//
				var parents = this._getParentsItem(storeItem);
				dojo.forEach( parents, function( parentItem ) {
					if( newState ) { // new state = true (checked)
						this.getChildren( parentItem, dojo.hitch( this,
							function(siblings) {
								var allChecked  = true;
								dojo.some( siblings, function(sibling) {
									siblState = this.getCheckboxState(sibling);
									if( siblState !== undefined && allChecked )
										allChecked = siblState;
									return !(allChecked);
								}, this );
								if( allChecked ) {
									this._setCheckboxState( parentItem, true );
									this._updateParentCheckbox( parentItem, true );
								}
							}),
							function(err) {
								console.error(this, ": updating parent checkboxes: ", err);
							}
						);
					} else { 	// new state = false (unchecked)
						if( this._setCheckboxState( parentItem, false ) ) {
							this._updateParentCheckbox( parentItem, false );
						}
					}
				}, this );
			},

			_getParentsItem: function(/*dojo.data.Item*/ storeItem ) {
				// summary:
				//		Get the parent(s) of a dojo.data item.
				// description:
				//		Get the parent(s) of a dojo.data item. The '_reverseRefMap' entry of the item is
				//		used to identify the parent(s). A child will have a parent reference if the parent
				//		specified the '_reference' attribute.
				//		For example: children:[{_reference:'Mexico'}, {_reference:'Canada'}, ...
				//	storeItem:
				//		The dojo.data.item whos parent(s) will be returned.
				//
				var parents = [];

				if( storeItem != this.root ) {
					var references = storeItem[this.store._reverseRefMap];
					for(itemId in references ) {
						parents.push(this.store._itemsByIdentity[itemId]);
					}
					if (!parents.length) {
						parents.push(this.root);
					}
				}
				return parents; // parent(s) of a dojo.data.item (Array of dojo.data.items)
			},

			validateData: function(/*dojo.data.Item*/ storeItem, /*thisObject*/ scope ) {
				// summary:
				//		Validate/normalize the parent(s) checkbox data in the dojo.data store.
				// description:
				//		Validate/normalize the parent-child checkbox relationship if the attribute
				//		'checkboxStrict' is set to true. This function is called as part of the post
				//		creation of the Tree instance. All parent checkboxes are set to the appropriate
				//		state according to the actual state(s) of their children.
				//		This will potentionally overwrite whatever was specified for the parent in the
				//		dojo.data store. This will garantee the tree is in a consistent state after startup.
				//	storeItem:
				//		The element to start traversing the dojo.data.store, typically model.root
				//	scope:
				//		The scope to use when this method executes.
				//	example:
				//	| this.model.validateData(this.model.root, this.model);
				//
				if( !scope.checkboxStrict ) {
					return;
				}
				scope.getChildren( storeItem, dojo.hitch( scope,
					function(children) {
						var allChecked  = true;
						var childState;
						dojo.forEach( children, function( child ) {
							if( this.mayHaveChildren( child )) {
								this.validateData( child, this );
							}
							childState = this.getCheckboxState( child );
							if( childState !== undefined && allChecked )
								allChecked = childState;
						}, this);

						if ( this._setCheckboxState( storeItem, allChecked) ) {
							this._updateParentCheckbox( storeItem, allChecked);
						}
					}),
					function(err) {
						console.error(this, ": validating checkbox data: ", err);
					}
				);
			},

			onCheckboxChange: function(/*dojo.data.Item*/ storeItem ) {
				// summary:
				//		Callback whenever a checkbox state has changed state, so that
				//		the Tree can update the checkbox.  This callback is generally
				//		triggered by the '_setCheckboxState' function.
				// tags:
				//		callback
			}

		});

});

require(["dojo/_base/declare", "dijit/Tree"], function (declare) {

	return declare("lib._CheckBoxTreeNode", dijit._TreeNode,
		{
			// _checkbox: [protected] dojo.doc.element
			//		Local reference to the dojo.doc.element of type 'checkbox'
			_checkbox: null,

			_createCheckbox: function () {
				// summary:
				//		Create a checkbox on the CheckBoxTreeNode
				// description:
				//		Create a checkbox on the CheckBoxTreeNode. The checkbox is ONLY created if a
				//		valid reference was found in the dojo.data store or the attribute 'checkboxAll'
				//		is set to true. If the current state is 'undefined' no reference was found and
				//		'checkboxAll' is set to false.
				//		Note: the attribute 'checkboxAll' is validated by the getCheckboxState function
				//		therefore no need to do that here. (see getCheckboxState for details).
				//
				var currState = this.tree.model.getCheckboxState(this.item);
				if (currState !== undefined) {
					this._checkbox = new dijit.form.CheckBox();
					//this._checkbox = dojo.doc.createElement('input');
					this._checkbox.type = 'checkbox';
					this._checkbox.attr('checked', currState);
					dojo.place(this._checkbox.domNode, this.expandoNode, 'after');
				}
			},

			postCreate: function () {
				// summary:
				//		Handle the creation of the checkbox after the CheckBoxTreeNode has been instanciated.
				// description:
				//		Handle the creation of the checkbox after the CheckBoxTreeNode has been instanciated.
				this._createCheckbox();
				this.inherited(arguments);
			}

		});
});

require(["dojo/_base/declare", "dijit/Tree"], function (declare) {

	return declare( "lib.CheckBoxTree", dijit.Tree,
		{

			onNodeChecked: function(/*dojo.data.Item*/ storeItem, /*treeNode*/ treeNode) {
				// summary:
				//		Callback when a checkbox tree node is checked
				// tags:
				//		callback
			},

			onNodeUnchecked: function(/*dojo.data.Item*/ storeItem, /* treeNode */ treeNode) {
				// summary:
				//		Callback when a checkbox tree node is unchecked
				// tags:
				//		callback
			},

			_onClick: function(/*TreeNode*/ nodeWidget, /*Event*/ e) {
				// summary:
				//		Translates click events into commands for the controller to process
				// description:
				//		the _onClick function is called whenever a 'click' is detected. This
				//		instance of _onClick only handles the click events associated with
				//		the checkbox whos DOM name is INPUT.
				//
				var domElement = e.target;

				// Only handle checkbox clicks here
				if(domElement.type != 'checkbox') {
					return this.inherited( arguments );
				}

				this._publish("execute", { item: nodeWidget.item, node: nodeWidget} );
				// Go tell the model to update the checkbox state

				this.model.updateCheckbox( nodeWidget.item, nodeWidget._checkbox.checked );
				// Generate some additional events
				//this.onClick( nodeWidget.item, nodeWidget, e );
				if(nodeWidget._checkbox.checked) {
					this.onNodeChecked( nodeWidget.item, nodeWidget);
				} else {
					this.onNodeUnchecked( nodeWidget.item, nodeWidget);
				}
				this.focusNode(nodeWidget);
			},

			_onCheckboxChange: function(/*dojo.data.Item*/ storeItem ) {
				// summary:
				//		Processes notification of a change to a checkbox state (triggered by the model).
				// description:
				//		Whenever the model changes the state of a checkbox in the dojo.data.store it will
				//		trigger the 'onCheckboxChange' event allowing the Tree to make the same changes
				//		on the tree Node. There are several conditions why a tree node or checkbox does not
				//		exists:
				//		a) 	The node has not been created yet (the user has not expanded the tree node yet).
				//		b) 	The checkbox may be null if condition (a) exists or no 'checkbox' attribute was
				//			specified for the associated dojo.data.item and the attribute 'checkboxAll' is
				//			set to false.
				// tags:
				//		callback
				var model 	 = this.model,
					identity = model.getIdentity(storeItem),
					nodes 	 = this._itemNodesMap[identity];

				// As of dijit.Tree 1.4 multiple references (parents) are supported, therefore we may have
				// to update multiple nodes which are all associated with the same dojo.data.item.
				if( nodes ) {
					dojo.forEach( nodes, function(node) {
						if( node._checkbox != null ) {
							node._checkbox.attr('checked', this.model.getCheckboxState( storeItem ));
						}
					}, this );
				}
			},

			postCreate: function() {
				// summary:
				//		Handle any specifics related to the tree and model after the instanciation of the Tree.
				// description:
				//		Validate if we have a 'write' store first. Subscribe to the 'onCheckboxChange' event
				//		(triggered by the model) and kickoff the initial checkbox data validation.
				//
				var store = this.model.store;
				if(!store.getFeatures()['dojo.data.api.Write']){
					throw new Error("lib.CheckboxTree: store must support dojo.data.Write");
				}
				this.connect(this.model, "onCheckboxChange", "_onCheckboxChange");
				this.model.validateData( this.model.root, this.model );
				this.inherited(arguments);
			},

			_createTreeNode: function( args ) {
				// summary:
				//		Create a new CheckboxTreeNode instance.
				// description:
				//		Create a new CheckboxTreeNode instance.
				return new lib._CheckBoxTreeNode(args);
			}

		});

});

