1 define(['require', 'Collections'], function(require, Collection) {
  2 	/**
  3 	 * @description An Auto Suggest class for suggestion for user edit in the Expression editor, It can suggest the next available variables and show available ranges in the workbook,
  4 	 * and context based ranges or variables. 
  5 	 * @name EditorAutoSuggest
  6 	 * @class EditorAutoSuggest
  7 	 * @example require('EditorAutoSuggest')
  8 	 * @exports editorAutoSuggest
  9 	 * @version 1.0
 10 	 * @module EditorAutoSuggest
 11 	 **/
 12 	var editorAutoSuggest = {
 13 		/**
 14 		 * @name getCompletions
 15 		 * @memberOf EditorAutoSuggest
 16 		 * @method 
 17 		 * @param editor The Editor Object, where it can access the entire editor context.
 18 		 * @param session 
 19 		 * @param pos @Integer	The position where the editing is happening
 20 		 * @param prefix @String The prefix string for the editing context.
 21 		 * @param callback @Function, The function need to be called back once the suggestion is completed
 22 		 * @description The method will parse the existing expression and evaluates teh expression parsed, so that it can assume what could be the next token for this.
 23 		 * Accordingly it will suggest a good range of items.
 24 		 * @function
 25 		 */
 26 		getCompletions : function(editor, session, pos, prefix, callback) {
 27 			//if (prefix.length === 0) { callback(null, []); return; }
 28 			var formula = this.getSuggestionFormula(editor.getValue(), pos);
 29 			var validFormula = true;
 30 			var result = [];
 31 			if (formula.trim().length != 0) {
 32 				var parsingController = parent.require('ParsingController');
 33 				try {
 34 					parsingController.parsePartialFormula(formula);
 35 				} catch(e) {
 36 					validFormula = false;
 37 					bodyFact = false;
 38 					msg = e.message;
 39 					console.log(msg);
 40 
 41 					//Checking whether it is an Excel Formula provided. Transforming to a Nexcel expression and parsing again.
 42 					if (msg.indexOf("Expecting 'OR', '(', '>', '<', '<=', '>=', '=', '!=', '+', '-', '*', '/', '^', got 'EOF'") > 0) {
 43 						var lastTocken = parent.helper._CXT.getTokenStack().pop();
 44 						if (lastTocken.type == "RANGE_NAME") {
 45 							parent.helper._CXT.getTokenStack().push(lastTocken);
 46 							if (this.isTockenStackContains(parent.helper._CXT.getTokenStack(), "IF"))
 47 								bodyFact = true;
 48 							//this.addRangeSuggestions(prefix, result, bodyFact);
 49 						} else {
 50 							try {
 51 								parsingController.parsePartialFormula("in(F,T):-dir(F,T)." + formula);
 52 							} catch(e) {
 53 								msg = e.message;
 54 							}
 55 						}
 56 					}
 57 					if (msg.indexOf('Expecting ')) {
 58 						expected = msg.substring(e.message.indexOf('Expecting ') + 10);
 59 					} else
 60 						expected = msg.split(':')[1];
 61 
 62 					this.addSuggestions(prefix, expected, result, parent.helper._CXT, bodyFact);
 63 
 64 				}
 65 			}
 66 			if (validFormula) {
 67 				this.addRangeSuggestions(prefix, result);
 68 			}
 69 			/* $.getJSON(
 70 			 "http://rhymebrain.com/talk?function=getRhymes&word=" + prefix,
 71 			 function(wordList) {
 72 			 // wordList like [{"word":"flow","freq":24,"score":300,"flags":"bc","syllables":"1"}]
 73 			 callback(null, wordList.map(function(ea) {
 74 			 return {name: 'Asker'+ea.word, value: ea.word, score: ea.score, meta: "rhyme"};
 75 			 }));
 76 			 });*/
 77 			callback(null, result.map(function(ea) {
 78 				return {
 79 					name : ea.word,
 80 					value : ea.word,
 81 					score : ea.score,
 82 					meta : ea.meta
 83 				};
 84 			}));
 85 		},
 86 		/**
 87 		 *
 88 		 */
 89 		addSuggestions : function(prefix, errorMsg, suggestionList, _CXT, bodyFact) {
 90 			var tockenStack = _CXT.getTokenStack();
 91 			errorMsg = errorMsg.replace(/\s+/g, '');
 92 			expected = errorMsg.replace(/["']/g, "");
 93 			expected = expected.split(',');
 94 			var expTokenSet = parent.require('Collections').Set();
 95 			expTokenSet.addAll(expected);
 96 			var top1 = tockenStack.pop();
 97 			var top2 = tockenStack.pop();
 98 			var suggestFromExpected = true;
 99 			//direct
100 			if (top1.type == 'RANGE_NAME') {
101 				this.addRangeSuggestions(prefix, suggestionList, bodyFact);
102 				suggestionList.push({
103 					"word" : '(',
104 					"freq" : 1,
105 					"score" : 200,
106 					"syllables" : "0",
107 					"meta" : "Tocken"
108 				});
109 				suggestFromExpected = false;
110 			} else if (top1.type == "BRACE" && top1.token == "(" || top1.type == "AND" && top2.type == "Variable" || top1.type == "AND" && top2.type == "STRING_CONST") {
111 				/*var localVars = _CXT.getVariables();
112 				 var varSet = parent.require('Collections').Set();
113 				 for(var i=0; i< localVars.length; i++){
114 				 if(!varSet.contains(localVars[i])){
115 				 varSet.add(localVars[i]);
116 				 suggestionList.push({"word":localVars[i], "freq":1, "score":200, "syllables":"0","meta":"Local variable"});
117 				 }
118 				 }*/
119 				this.addVariables(_CXT, suggestionList);
120 				suggestFromExpected = false;
121 			} else if (top1.token == "NOT" || top2.type == "AND" ) {
122 				bodyFact = true;
123 				this.addRangeSuggestions(prefix, suggestionList, bodyFact);
124 				suggestFromExpected = false;
125 			}else if (top1.type == "AND" || (top2.type == "BRACE" && top2.token == ")")) {
126 				bodyFact = true;
127 				suggestionList.push({
128 					"word" : 'NOT \n\t',
129 					"freq" : 1,
130 					"score" : 200,
131 					"syllables" : "0",
132 					"meta" : "Token"
133 				});
134 				this.addRangeSuggestions(prefix, suggestionList, bodyFact);
135 				suggestFromExpected = false;
136 			}
137 			if (expTokenSet.contains('OR') && this.isTockenStackContains(_CXT.getTokenStack(), "IF")) {
138 				suggestionList.push({
139 					"word" : ",\n\t",
140 					"freq" : 1,
141 					"score" : 200,
142 					"syllables" : "0",
143 					"meta" : "Token"
144 				});
145 				suggestionList.push({
146 					"word" : '.\n',
147 					"freq" : 1,
148 					"score" : 200,
149 					"syllables" : "0",
150 					"meta" : "Token"
151 				});
152 				return;
153 			}
154 			if (expTokenSet.contains('IF') && !this.isTockenStackContains(_CXT.getTokenStack(), "IF")) {
155 				suggestionList.push({
156 					"word" : ' :- \n\t',
157 					"freq" : 1,
158 					"score" : 200,
159 					"syllables" : "0",
160 					"meta" : "Token"
161 				});
162 				return;
163 			}
164 			if (prefix.length > 0 && top1.type == "Variable") {
165 				this.addVariables(_CXT, suggestionList);
166 			}
167 			/*	if(expTokenSet.contains('AND')){
168 			 suggestionList.push({"word":', ', "freq":1, "score":200, "syllables":"0","meta":"Tocken"});
169 			 return;
170 			 }*/
171 			for (var i = 0; suggestFromExpected && i < expected.length - 1; i++) {
172 				var token = expected[i];
173 				console.log('Adding the brackets and comma/..........');
174 				if (token == "AND")
175 					suggestionList.push({
176 						"word" : ",\n",
177 						"freq" : 1,
178 						"score" : 200,
179 						"syllables" : "0",
180 						"meta" : "Token"
181 					});
182 				if (token == ")")
183 					suggestionList.push({
184 						"word" : ")",
185 						"freq" : 1,
186 						"score" : 201,
187 						"syllables" : "0",
188 						"meta" : "Token"
189 					});
190 				if (token == "(")
191 					suggestionList.push({
192 						"word" : "(",
193 						"freq" : 1,
194 						"score" : 202,
195 						"syllables" : "0",
196 						"meta" : "Token"
197 					});
198 
199 			}
200 		},
201 		/**
202 		 *
203 		 */
204 		addVariables : function(_CXT, suggestionList) {
205 			console.log('Add Variable.......');
206 			var localVars = _CXT.getVariables();
207 			var varSet = parent.require('Collections').Set();
208 			for (var i = 0; i < localVars.length; i++) {
209 				if (!varSet.contains(localVars[i])) {
210 					varSet.add(localVars[i]);
211 					suggestionList.push({
212 						"word" : localVars[i],
213 						"freq" : 1,
214 						"score" : 200,
215 						"syllables" : "0",
216 						"meta" : "Variable"
217 					});
218 				}
219 			}
220 		},
221 		/**
222 		 *
223 		 */
224 		getSuggestionFormula : function(formula, pos) {
225 			var rows = formula.split('\n');
226 			var suggestionFormula = "";
227 			for (var i = 0; i < rows.length; i++) {
228 				if (i == pos.row) {
229 					suggestionFormula = suggestionFormula + '\n' + rows[i].substring(0, pos.column);
230 					break;
231 				} else
232 					suggestionFormula = suggestionFormula + '\n' + rows[i];
233 			}
234 			return suggestionFormula;
235 		},
236 		/**
237 		 *
238 		 */
239 		addRangeSuggestions : function(prefix, suggestionList, bodyFact) {
240 			var nexcelGroupList = parent.require('NexcelGroupList');
241 			var nameList = nexcelGroupList.getNameList();
242 			var NexcelUI = parent.require('NexcelUI');
243 			for (var i = 0; i < nameList.size(); i++) {
244 				var name = nameList.get(i);
245 				if (name.startsWith(prefix)) {
246 					var groupObj = nexcelGroupList.getGroupObjByName(name);
247 					var fields = [];
248 					if (groupObj.colNameAvailable != "0") {
249 						fields = NexcelUI.getGroupHeaderDataArray(name);
250 					} else {
251 						for ( j = 0; j < parseInt(groupObj.colSize); j++)
252 							fields.push('Col' + (j + 1));
253 					}
254 					
255 					//result.push({"word":name, "freq":1, "score":202, "syllables":"0"});
256 					//result.push({"word":str, "freq":1, "score":201, "syllables":"0"});
257 					if (bodyFact){
258 						var locFields =[]; 
259 						for(var j=0; j<fields.length; j++)
260 							locFields.push('\u2423');//56d7
261 						var str = name + "(" + locFields.join(',') + ")";
262 						suggestionList.push({
263 							"word" : str,
264 							"freq" : 1,
265 							"score" : 200,
266 							"syllables" : "0",
267 							"meta" : "Ranges"
268 						});
269 					}else{
270 						var str = name + "(" + fields.join(',') + ")";
271 						suggestionList.push({
272 							"word" : str + ":- ",
273 							"freq" : 1,
274 							"score" : 200,
275 							"syllables" : "0",
276 							"meta" : "Ranges"
277 						});
278 					}
279 				}
280 			}
281 		},
282 		/**
283 		 *
284 		 */
285 		isTockenStackContains : function(stack, tockenType) {
286 			var elem = stack.getElements();
287 			for (var i = 0; i < elem.length; i++) {
288 				if (elem[i].type == tockenType)
289 					return elem[i];
290 			}
291 			return false;
292 		},
293 		/**
294 		 * @name onEditorValueChange
295 		 * @memberOf EditorAutoSuggest
296 		 * @method 
297 		 * @param editor The Editor Object, where it can access the entire editor context.
298 		 * @description The method is a callback function , once any data in tghe editor got changed, teh function will be called and it will show the error messages on the left hand panel.
299 		 * @function
300 		 * @return void
301 		 */
302 		onEditorValueChange:function(editor){
303 			var formula = editor.getValue();
304 			editor.getSession().setAnnotations([]);
305 			if (formula.trim().length != 0) {
306 				var parsingController = parent.require('ParsingController');
307 				try {
308 					parsingController.parsePartialFormula(formula);
309 				} catch(e) {
310 					validFormula = false;
311 					bodyFact = false;
312 					msg = e.message;
313 					console.log(msg);
314 					if(msg.startsWith('Parse error on line')){
315 						var line = msg.split(':')[0];
316 						lineNumber = line.substring(20);
317 						var errorMsg = msg.substring(msg.indexOf('Expecting')); 
318 						editor.getSession().setAnnotations([{
319 						    row: parseInt(lineNumber)-1,
320 						    column: 10,
321 						    text: errorMsg,
322 						    type: "error" // also warning and information
323 						}]);
324 					}else if(msg.startsWith('Lexical error on line ')){
325 						var line = msg.split(':')[0];
326 						lineNumber = line.substring(22);
327 						var errorMsg = msg.substring(msg.indexOf('Unrecognized ')); 
328 						if(errorMsg.indexOf('\u56d7')>0){
329 							editor.getSession().setAnnotations([{
330 							    row: parseInt(lineNumber)-1,
331 							    column: 10,
332 							    text: "Please fill the variable name in \u56d7",
333 							    type: "error" // also warning and information
334 							}]);
335 						}else
336 							editor.getSession().setAnnotations([{
337 							    row: parseInt(lineNumber)-1,
338 							    column: 10,
339 							    text: errorMsg,
340 							    type: "error" // also warning and information
341 							}]);
342 					}
343 				}
344 				if(programs.length>0){
345 					var groupObj = parent.require('EditorController').currentInstance.groupObj;
346 					var totalGroupPrograms = 0;
347 					var range;
348 					for(var i=0; i< programs.length; i++){
349 						if(programs[i].type() ==ProgramType.RUNTIME_TABLE){
350 							//TODO Implement Error in Runtime Table
351 						}else{
352 							var prog = programs[i].getProgram();
353 							var factName = prog.getFact().getName();
354 							if(factName != groupObj.name) {
355 								range = editor.find(factName);
356 								/*editor.getSession().setAnnotations([{
357 								    row: range.start.row,
358 								    column: range.start.column,
359 								    text: "Expressions fact name ("+factName+") should be the same name as the group name("+groupObj.name+")",
360 								    type: "error" // also warning and information
361 								}]);*/
362 							}else{
363 								totalGroupPrograms++;
364 							}
365 							//Error on only fact in group(A,B). in Editor formula
366 							if(!prog.getBody()){
367 								var fact = prog.getFact();
368 								var factName = fact.getName();
369 								var range = editor.find(prog.toString());
370 								editor.getSession().setAnnotations([{
371 								    row: range.start.row,
372 								    column: range.start.column,
373 								    text: programs[i]+" doesnt have a body part, please correct the expression of the form "+fact.toString()+":-"+fact.toString()+".",
374 								    type: "error" // also warning and information
375 								}]);
376 								//throw programs[i]+" doesnt have a body part, please correct the expression of the form <br/>"+factName+"Copy("+fact.getVariables().join(',')+"):-<br/>     "+fact.toString()+".";
377 							}
378 						}
379 					}
380 					if(totalGroupPrograms == 0){
381 						editor.getSession().setAnnotations([{
382 						    row: range.start.row,
383 						    column: range.start.column,
384 						    text: "Expressions must have atleat one expression with group name-"+groupObj.name,
385 						    type: "error" // also warning and information
386 						}]);
387 					}
388 				}
389 			}
390 		},
391 		/**
392 		 * @name setUpEditorCommand
393 		 * @memberOf EditorAutoSuggest
394 		 * @method 
395 		 * @param editor The Editor Object, where it can access the entire editor context.
396 		 * @description The method is will set up the basic editor command, so that the user can edit or update anything with shortcurt keys.
397 		 * @return void
398 		 */
399 		setUpEditorCommand:function(editor){
400 			editor.commands.addCommand({
401 			    name: "format",
402 			    bindKey: {win: "Ctrl-Shift-F", mac: "Command-Option-Shift-F"},
403 			    exec: function(editor) {
404 			    	parent.require('EditorController').formatEditorFormula();
405 			    }
406 			});
407 		}
408 	};
409 	return editorAutoSuggest;
410 });
411