1 (function (GCN) {
  2 
  3 	'use strict';
  4 
  5 	/**
  6 	 * Maps constructcategories that were fetched via the Rest API
  7 	 * into a sorted nested array of constructs.
  8 	 *
  9 	 * @param {object<string, object>} constructs
 10 	 * @return {object<string, object>}
 11 	 */
 12 	function mapConstructCategories(constructs) {
 13 		var constructKeyword;
 14 		var categoryMap = { categories: {}, categorySortorder: [] };
 15 		var constructCategoryArray = [];
 16 
 17 		for (constructKeyword in constructs) {
 18 			if (constructs.hasOwnProperty(constructKeyword)) {
 19 				var construct = constructs[constructKeyword];
 20 
 21 				var constructCategoryName = construct.category;
 22 				var categorySortorder = construct.categorySortorder;
 23 
 24 				// Use a custom name for constructs that have not been assigned to a category
 25 				if (!constructCategoryName) {
 26 					constructCategoryName = "GCN_UNCATEGORIZED";
 27 					categorySortorder = -1;
 28 				}
 29 
 30 				// Initialize the inner array of constructs
 31 				if (!categoryMap.categories[constructCategoryName]) {
 32 					var newCategory = {};
 33 					newCategory.constructs = {};
 34 					newCategory.sortorder = categorySortorder;
 35 					newCategory.name = constructCategoryName;
 36 					categoryMap.categories[constructCategoryName] = newCategory;
 37 					constructCategoryArray.push(newCategory);
 38 				}
 39 
 40 				// Add the construct to the category
 41 				categoryMap.categories[constructCategoryName].constructs[constructKeyword] = construct;
 42 			}
 43 		}
 44 
 45 		// Sort the categories by the sortorder
 46 		constructCategoryArray.sort(function (a, b) {
 47 			return a.sortorder - b.sortorder;
 48 		});
 49 
 50 		// Add the sorted category names to the sortorder field
 51 		var k;
 52 		for (k in constructCategoryArray) {
 53 			if (constructCategoryArray.hasOwnProperty(k)) {
 54 				var category = constructCategoryArray[k];
 55 				if (typeof category.sortorder !== 'undefined' && category.sortorder !== -1) {
 56 					categoryMap.categorySortorder.push(category.name);
 57 				}
 58 			}
 59 		}
 60 
 61 		return categoryMap;
 62 	}
 63 
 64 	/**
 65 	 * Maps constructs, that were fetched via the Rest API, using their keyword
 66 	 * as the keys.
 67 	 *
 68 	 * @param {object<string, object>} constructs Consturcts mapped against
 69 	 *                                            their id.
 70 	 * @return {object<string, object>} Constructs mapped against their keys.
 71 	 */
 72 	function mapConstructs(constructs) {
 73 		if (!constructs) {
 74 			return {};
 75 		}
 76 		var map = {};
 77 		var constructId;
 78 		for (constructId in constructs) {
 79 			if (constructs.hasOwnProperty(constructId)) {
 80 				map[constructs[constructId].keyword] = constructs[constructId];
 81 			}
 82 		}
 83 		return map;
 84 	}
 85 
 86 	/**
 87 	 * Node object.
 88 	 *
 89 	 * @name NodeAPI
 90 	 * @class
 91 	 * @augments Chainback
 92 	 */
 93 	var NodeAPI = GCN.defineChainback({
 94 		/** @lends NodeAPI */
 95 
 96 		__chainbacktype__: 'NodeAPI',
 97 		_extends: GCN.ContentObjectAPI,
 98 		_type: 'folder',
 99 
100 		_data: {
101 			folderId: null
102 		},
103 
104 		/**
105 		 * @private
106 		 * @type {object<string, number} Constructs for this node are cached
107 		 *                               here so that we only need to fetch
108 		 *                               this once.
109 		 */
110 		_constructs: null,
111 
112 		/**
113 		 * @private
114 		 * @type {object<string, object} Constructs categories for this node.
115 		 *                               Cached here so that we only need to
116 		 *                               fetch this once.
117 		 */
118 		_constructCategories: null,
119 
120 		/**
121 		 * Retrieves a list of constructs and constructs categories that are
122 		 * assigned to this node and passes it as the only argument into the
123 		 * the `success()' callback.
124 		 *
125 		 * @param {function(Array.<object>)=} success Callback to receive an
126 		 *                                            array of constructs.
127 		 * @param {function(GCNError):boolean=} error Custom error handler.
128 		 * @return Returns the constructs / categories
129 		 * @throws INVALID_ARGUMENTS
130 		 */
131 		constructs: function (success, error) {
132 			if (!success) {
133 				GCN.error('INVALID_ARGUMENTS', 'the `constructs()\' method ' +
134 					'requires at least a success callback to be given');
135 			}
136 			if (this._constructs) {
137 				success(this._constructs);
138 			} else {
139 				var fork = this._fork();
140 				var fetchConstructs = function () {
141 					fork._authAjax({
142 						url: GCN.settings.BACKEND_PATH + '/rest/construct/list.json',
143 						type: 'GET',
144 						error: function (xhr, status, msg) {
145 							GCN.handleHttpError(xhr, msg, error);
146 						},
147 						success: function (response) {
148 							if (GCN.getResponseCode(response) === 'OK') {
149 								fork._constructs = mapConstructs(response.constructs);
150 								success(fork._constructs);
151 							} else {
152 								GCN.handleResponseError(response, error);
153 								fork._merge();
154 							}
155 						},
156 						complete: function () {
157 							fork._merge();
158 						}
159 					});
160 					return false;
161 				};
162 
163 				// Fetch the constructs regardless of whether we are able to
164 				// read the current node content object since we are currently
165 				// only able to read all nodes system wide.
166 				fork._read(fetchConstructs, fetchConstructs);
167 			}
168 		},
169 
170 		/**
171 		 * Removes this node object.
172 		 *
173 		 * @param {function=} success Callback.
174 		 * @param {function(GCNError):boolean=} error Custom error handler.
175 		 * @param {function} success callback
176 		 */
177 		remove: function (success, error) {
178 
179 		},
180 
181 		/**
182 		 * @FIXME: Is it really possible to save changes to a node?  If not,
183 		 *         then we should not surface this method.
184 		 */
185 		save: function () {},
186 
187 		/**
188 		 * Retreives the top-level folders of this node's root folder.
189 		 *
190 		 * @param {function(FolderAPI)=} success
191 		 * @param {function(GCNError):boolean=} error Custom error handler.
192 		 */
193 		'!folders': function (success, error) {
194 			return this.folder(null, error).folders(success, error);
195 		},
196 
197 		/**
198 		 * Helper method that will load the constructs of this node.
199 		 *
200 		 * @private
201 		 * @this {NodeAPI}
202 		 * @param {function(Array.<object>)} success callback
203 		 * @param {function(GCNError):boolean=} error callback
204 		 */
205 		constructCategories: function (success, error) {
206 			if (this._constructCategories) {
207 				success(this._constructCategories);
208 			} else {
209 				var that = this;
210 				this._continueWith(function (child) {
211 					that._data.id = child._data.nodeId;
212 
213 					that.constructs(function (constructs) {
214 						that._constructCategories = mapConstructCategories(constructs);
215 						success(that._constructCategories);
216 					}, error);
217 				}, error);
218 			}
219 		}
220 
221 	});
222 
223 	GCN.node = GCN.exposeAPI(NodeAPI);
224 	GCN.NodeAPI = NodeAPI;
225 
226 }(GCN));
227