/*
 * This file is part of the TYPO3 CMS project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

/**
 * Module: TYPO3/CMS/T3editor/CodeCompletion/TsCodeCompletion
 * Contains the TsCodeCompletion class
 */
define([
	'jquery',
	'TYPO3/CMS/T3editor/Plugins/CodeCompletion/TsRef',
	'TYPO3/CMS/T3editor/Plugins/CodeCompletion/TsParser',
	'TYPO3/CMS/T3editor/Plugins/CodeCompletion/CompletionResult'
], function ($, TsRef, TsParser, CompletionResult) {
	/**
	 *
	 * @type {{tsRef: *, outerDiv: null, options: {ccWords: number}, index: number, currWord: number, cc_up: null, cc_down: null, mousePos: {x: number, y: number}, proposals: null, compResult: null, cc: number, linefeedsPrepared: boolean, currentCursorPosition: null, extTsObjTree: {}, latestCursorNode: null, codemirror: null, parser: null, plugins: string[], $codeCompleteBox: (*|jQuery)}}
	 * @exports TYPO3/CMS/T3editor/CodeCompletion/TsCodeCompletion
	 */
	var TsCodeCompletion = {
		tsRef: TsRef,
		outerDiv: null,
		options: {
			ccWords : 10
		},
		index: 0,
		currWord: 0,
		cc_up: null,
		cc_down: null,
		mousePos: {
			x:0,
			y:0
		},
		proposals: null,
		compResult: null,
		cc: 0,
		linefeedsPrepared: false,
		currentCursorPosition: null,
		extTsObjTree: {},
		latestCursorNode: null,
		codemirror: null,
		parser: null,
		plugins: [
			'TYPO3/CMS/T3editor/Plugins/CodeCompletion/DescriptionPlugin'
		],
		$codeCompleteBox: $('<div />', {class: 't3e_codeCompleteWrap'}).append(
			$('<div />', {class: 't3e_codeCompleteBox'})
		)
	};

	/**
	 * All external templates along the rootline have to be loaded,
	 * this function retrieves the JSON code by committing a AJAX request
	 */
	TsCodeCompletion.loadExtTemplatesAsync = function() {
		var id = TsCodeCompletion.getGetVar('id');
		if (id === '') {
			return;
		}
		$.ajax({
			url: TYPO3.settings.ajaxUrls['t3editor_codecompletion_loadtemplates'],
			data: {
				pageId: id
			},
			success: function(response) {
				TsCodeCompletion.extTsObjTree.c = response;
				TsCodeCompletion.resolveExtReferencesRec(TsCodeCompletion.extTsObjTree.c);
			}
		});
	};

	/**
	 * Load registered plugins
	 */
	TsCodeCompletion.loadPluginArray = function() {
		$.ajax({
			url: TYPO3.settings.ajaxUrls['t3editor_get_plugins'],
			success: function(response) {
				TsCodeCompletion.plugins = $.merge(TsCodeCompletion.plugins, response);
				// register an internal plugin
				TsCodeCompletion.loadPlugins();
			}
		});
	};

	/**
	 * Load and initialize the plugins fetched by TsCodeCompletion.loadPluginArray()
	 */
	TsCodeCompletion.loadPlugins = function() {
		for (var i = 0; i < TsCodeCompletion.plugins.length; i++) {
			require([TsCodeCompletion.plugins[i]], function(plugin) {
				if (typeof plugin.init === 'function') {
					plugin.init({
						tsRef: TsCodeCompletion.tsRef,
						codeCompleteBox: TsCodeCompletion.$codeCompleteBox,
						codemirror: TsCodeCompletion.codemirror
					});
				} else {
					console.warn('Cannot initialize plugin ' + TsCodeCompletion.plugins[i] + ', missing "init" method');
				}
			});
		}
	};

	/**
	 * Get the value of a given GET parameter
	 *
	 * @param {String} name
	 * @return {String}
	 */
	TsCodeCompletion.getGetVar = function(name) {
		var get_string = document.location.search,
			return_value = '',
			value;

		do { //This loop is made to catch all instances of any get variable.
			var name_index = get_string.indexOf(name + '=');
			if (name_index !== -1) {
				get_string = get_string.substr(name_index + name.length + 1, get_string.length - name_index);
				var end_of_value = get_string.indexOf('&');
				if (end_of_value !== -1) {
					value = get_string.substr(0, end_of_value);
				} else {
					value = get_string;
				}

				if (return_value === '' || value === '') {
					return_value += value;
				} else {
					return_value += ', ' + value;
				}
			}
		} while (name_index !== -1);

		// Restores all the blank spaces.
		var space = return_value.indexOf('+');
		while (space !== -1) {
			return_value = return_value.substr(0, space) + ' ' + return_value.substr(space + 1, return_value.length);
			space = return_value.indexOf('+');
		}

		return return_value;
	};

	/**
	 * Since the references are not resolved server side we have to do it client-side
	 * Benefit: less loading time due to less data which has to be transmitted
	 *
	 * @param {Array} childNodes
	 */
	TsCodeCompletion.resolveExtReferencesRec = function(childNodes) {
		for (var key in childNodes) {
			var childNode;
			// if the childnode has a value and there is a part of a reference operator ('<')
			// and it does not look like a html tag ('>')
			if (childNodes[key].v && childNodes[key].v[0] === '<' && childNodes[key].v.indexOf('>') === -1 ) {
				var path = $.trim(childNodes[key].v.replace(/</, ''));
				// if there are still whitespaces it's no path
				if (path.indexOf(' ') === -1) {
					childNode = TsCodeCompletion.getExtChildNode(path);
					// if the node was found - reference it
					if (childNode !== null) {
						childNodes[key] = childNode;
					}
				}
			}
			// if there was no reference-resolving then we go deeper into the tree
			if (!childNode && childNodes[key].c) {
				TsCodeCompletion.resolveExtReferencesRec(childNodes[key].c);
			}
		}
	};

	/**
	 * Get the child node of given path
	 *
	 * @param {String} path
	 * @returns {Object}
	 */
	TsCodeCompletion.getExtChildNode = function(path) {
		var extTree = TsCodeCompletion.extTsObjTree,
			path = path.split('.'),
			pathSeg;

		for (var i = 0; i < path.length; i++) {
			pathSeg = path[i];
			if (typeof extTree.c === 'undefined' || typeof extTree.c[pathSeg] === 'undefined') {
				return null;
			}
			extTree = extTree.c[pathSeg];
		}
		return extTree;
	};

	/**
	 * Replaces editor functions insertNewlineAtCursor and indentAtCursor
	 * with modified ones that only execute when codecompletion box is not shown
	 * @todo check if this works correctly after updating the codemirror base
	 */
	TsCodeCompletion.prepareLinefeeds = function() {
		TsCodeCompletion.codemirror.win.select.insertNewlineAtCursor_original = TsCodeCompletion.codemirror.win.select.insertNewlineAtCursor;
		TsCodeCompletion.codemirror.win.select.insertNewlineAtCursor = function(window) {
			if (TsCodeCompletion.cc === 0) {
				TsCodeCompletion.codemirror.win.select.insertNewlineAtCursor_original(window);
			}
		};
		TsCodeCompletion.codemirror.editor.indentAtCursor_original = TsCodeCompletion.codemirror.editor.indentAtCursor;
		TsCodeCompletion.codemirror.editor.indentAtCursor = function() {
			if (TsCodeCompletion.cc === 0) {
				TsCodeCompletion.codemirror.editor.indentAtCursor_original();
			}
		};
		TsCodeCompletion.linefeedsPrepared = true;
	};

	/**
	 *
	 * @param cursorNode
	 * @returns {*}
	 */
	TsCodeCompletion.getFilter = function(cursorNode) {
		if (cursorNode.currentText) {
			var filter = cursorNode.currentText.replace('.', '');
			return filter.replace(/\s/g, '');
		}
		return '';
	};

	/**
	 * @returns {*}
	 */
	TsCodeCompletion.getCursorNode = function() {
		var cursorNode = TsCodeCompletion.codemirror.win.select.selectionTopNode(TsCodeCompletion.codemirror.win.document.body, false);
		// cursorNode is null if the cursor is positioned at the beginning of the first line
		if (cursorNode === null) {
			cursorNode = TsCodeCompletion.codemirror.editor.container.firstChild;
		} else if (cursorNode.tagName === 'BR') {
			// if cursor is at the end of the line -> jump to beginning of the next line
			cursorNode = cursorNode.nextSibling;
		}
		return cursorNode;
	};

	/**
	 *
	 * @param {Object} cursor
	 * @returns {String}
	 */
	TsCodeCompletion.getCurrentLine = function(cursor) {
		var line = '',
			currentNode = cursor.start.node.parentNode;

		while (currentNode.tagName !== 'BR') {
			if (currentNode.hasChildNodes()
				&& currentNode.firstChild.nodeType === 3
				&& currentNode.currentText.length > 0)
			{
				line = currentNode.currentText + line;
			}
			if (typeof currentNode.previousSibling === 'undefined') {
				break;
			} else {
				currentNode = currentNode.previousSibling;
			}
		}
		return line;
	};

	/**
	 * Eventhandler function executed after keystroke release
	 * triggers CC on pressed dot and typing on
	 *
	 * @param {Event} e fired prototype event object
	 * @type void
	 */
	TsCodeCompletion.keyUp = function(e) {
		// 190 = .
		if (e.which === 190) {
			TsCodeCompletion.initCodeCompletion();
			TsCodeCompletion.refreshCodeCompletion();
		} else if (TsCodeCompletion.cc === 1) {
			// 38 = KEYUP, 40 = KEYDOWN
			if (e.which !== 40 && e.which !== 38) {
				if (e.which === 13) {
					// return
					e.preventDefault();
					if (TsCodeCompletion.currWord !== -1) {
						TsCodeCompletion.insertCurrWordAtCursor();
					}
					TsCodeCompletion.endAutoCompletion();
				} else {
					TsCodeCompletion.refreshCodeCompletion();
				}
			}
		}
	};

	TsCodeCompletion.scroll = function(e) {
		TsCodeCompletion.refreshCodeCompletionPosition();
	};

	/**
	 * Highlights entry in codecomplete box by id
	 *
	 * @param {Number} id
	 */
	TsCodeCompletion.mouseOver = function(id) {
		TsCodeCompletion.highlightCurrWord(id);
		for (var i = 0; i < TsCodeCompletion.plugins.length; i++) {
			require([TsCodeCompletion.plugins[i]], function(plugin) {
				if (typeof plugin.afterMouseOver === 'function') {
					plugin.afterMouseOver(TsCodeCompletion.proposals[TsCodeCompletion.currWord], TsCodeCompletion.compResult);
				}
			});
		}
	};

	/**
	 * Event handler function executed after clicking in the editor.
	 */
	TsCodeCompletion.click = function() {
		if (TsCodeCompletion.latestCursorNode !== TsCodeCompletion.getCursorNode()) {
			TsCodeCompletion.endAutoCompletion();
		}
	};

	/**
	 * Eventhandler function executed after keystroke release
	 * triggers CC on pressed dot and typing on
	 *
	 * @param {Event} e fired prototype event object
	 */
	TsCodeCompletion.keyDown = function(e) {
		if (!TsCodeCompletion.linefeedsPrepared) {
			TsCodeCompletion.prepareLinefeeds();
		}
		if (TsCodeCompletion.cc === 1) {
			if (e.which === 38) {
				// Arrow up: move up cursor in codecomplete box
				e.preventDefault();
				TsCodeCompletion.codeCompleteBoxMoveUpCursor();
				for (var i = 0; i < TsCodeCompletion.plugins.length; i++) {
					require([TsCodeCompletion.plugins[i]], function(plugin) {
						if (typeof plugin.afterKeyUp === 'function') {
							plugin.afterKeyUp(TsCodeCompletion.proposals[TsCodeCompletion.currWord], TsCodeCompletion.compResult);
						}
					});
				}
			} else if (e.which === 40) {
				// Arrow down: move down cursor in codecomplete box
				e.preventDefault();
				TsCodeCompletion.codeCompleteBoxMoveDownCursor();
				for (var i = 0; i < TsCodeCompletion.plugins.length; i++) {
					require([TsCodeCompletion.plugins[i]], function(plugin) {
						if (typeof plugin.afterKeyDown === 'function') {
							plugin.afterKeyDown(TsCodeCompletion.proposals[TsCodeCompletion.currWord], TsCodeCompletion.compResult);
						}
					});
				}
			} else if (e.which === 27 || e.which === 37 || e.which === 39) {
				// Esc, Arrow Left, Arrow Right: if codecomplete box is showing, hide it
				TsCodeCompletion.endAutoCompletion();
			} else if (e.which === 32 && (!e.ctrlKey || !e.metaKey)) {
				// space
				TsCodeCompletion.endAutoCompletion();
			} else if (e.which === 32 && (e.ctrlKey || e.metaKey)) {
				// CTRL + space
				TsCodeCompletion.refreshCodeCompletion();
			} else if (e.which === 8) {
				// backspace
				var cursorNode = TsCodeCompletion.codemirror.win.select.selectionTopNode(TsCodeCompletion.codemirror.win.document.body, false);
				if (cursorNode.innerHTML === '.') {
					// force full refresh at keyUp
					TsCodeCompletion.compResult = null;
				}
			}
		} else { // if autocompletion is deactivated and ctrl+space is pressed
			if (e.which === 32 && (e.ctrlKey || e.metaKey)) {
				e.preventDefault();
				TsCodeCompletion.initCodeCompletion();
				TsCodeCompletion.refreshCodeCompletion();
			}
		}
	};

	/**
	 * Initializes the code completion
	 */
	TsCodeCompletion.initCodeCompletion = function() {
		if (TsCodeCompletion.outerDiv.has(TsCodeCompletion.$codeCompleteBox).length === 0) {
			TsCodeCompletion.outerDiv.append(TsCodeCompletion.$codeCompleteBox);
		}
	};

	/**
	 * Refreshes the code completion list based on the cursor's position
	 */
	TsCodeCompletion.refreshCodeCompletion = function() {
		// init vars for up/down moving in word list
		TsCodeCompletion.cc_up = 0;
		TsCodeCompletion.cc_down = TsCodeCompletion.options.ccWords-1;

		// clear the last completion wordposition
		TsCodeCompletion.currWord = -1;
		TsCodeCompletion.codemirror.editor.highlightAtCursor();

		// retrieves the node right to the cursor
		TsCodeCompletion.currentCursorPosition = TsCodeCompletion.codemirror.win.select.markSelection(TsCodeCompletion.codemirror);
		TsCodeCompletion.latestCursorNode = TsCodeCompletion.getCursorNode();

		// the cursornode has to be stored cause inserted breaks have to be deleted after pressing enter if the codecompletion is active
		var filter = TsCodeCompletion.getFilter(TsCodeCompletion.latestCursorNode);

		if (TsCodeCompletion.compResult === null || TsCodeCompletion.latestCursorNode.innerHTML === '.') {
			// TODO: implement cases: operatorCompletion reference/copy path completion (formerly found in getCompletionResults())
			var currentTsTreeNode = TsCodeCompletion.parser.buildTsObjTree(TsCodeCompletion.codemirror.editor.container.firstChild, TsCodeCompletion.latestCursorNode);
			TsCodeCompletion.compResult = CompletionResult.init({
				tsRef: TsRef,
				tsTreeNode: currentTsTreeNode
			});
		}

		TsCodeCompletion.proposals = TsCodeCompletion.compResult.getFilteredProposals(filter);

		// if proposals are found - show box
		if (TsCodeCompletion.proposals.length > 0) {
			// TODO: Drop instance dependency
			var index = 0;
			// make UL list of completation proposals
			var $ul = $('<ul />');
			for (var i = 0; i < TsCodeCompletion.proposals.length; i++) {
				var $li = $('<li />', {id: 'cc_word_' + i}).data('item', i).append(
						$('<span />', {class: 'word_' + TsCodeCompletion.proposals[i].cssClass}).text(TsCodeCompletion.proposals[i].word)
					).on('click', function() {
						TsCodeCompletion.insertCurrWordAtCursor($(this).data('item'));
						TsCodeCompletion.endAutoCompletion();
					}).on('mouseover', function() {
						TsCodeCompletion.mouseOver($(this).data('item'));
					});

				$ul.append($li);
			}

			// put HTML and show box
			var $codeCompleteBox = TsCodeCompletion.$codeCompleteBox.find('.t3e_codeCompleteBox');
			$codeCompleteBox.html($ul);
			$codeCompleteBox.scrollTop(0);

			// init styles
			$codeCompleteBox.css({
				overflow: 'scroll',
				height: ((TsCodeCompletion.options.ccWords + 1) * $('#cc_word_0').height()) + 'px'
			});

			var wrapOffset = TsCodeCompletion.codemirror.options.originalTextarea.parent().find('.t3e_iframe_wrap').offset(),
				$cursorNode = $(TsCodeCompletion.latestCursorNode),
				nodeOffset = $cursorNode.offset();

			var leftpos = Math.round(wrapOffset.left + nodeOffset.left + TsCodeCompletion.latestCursorNode.offsetWidth - $(TsCodeCompletion.codemirror.frame.contentDocument.body).scrollLeft()) + 'px',
				toppos = Math.round($cursorNode.position().top + TsCodeCompletion.latestCursorNode.offsetHeight - $cursorNode.scrollTop() - $(TsCodeCompletion.codemirror.frame.contentDocument.body).scrollTop()) + 'px';

			TsCodeCompletion.$codeCompleteBox.css({
				left: leftpos,
				top: toppos
			});

			// set flag to 1 - needed for continue typing word.
			TsCodeCompletion.cc = 1;

			// highlight first word in list
			TsCodeCompletion.highlightCurrWord(0);
			for (var i = 0; i < TsCodeCompletion.plugins.length; i++) {
				require([TsCodeCompletion.plugins[i]], function(plugin) {
					if (typeof plugin.afterCCRefresh === 'function') {
						plugin.afterCCRefresh(TsCodeCompletion.proposals[TsCodeCompletion.currWord], TsCodeCompletion.compResult);
					}
				});
			}
		} else {
			TsCodeCompletion.endAutoCompletion();
		}
	};

	/**
	 * refresh the position of the completion list (after scroll)
	 */
	TsCodeCompletion.refreshCodeCompletionPosition = function() {
		if (TsCodeCompletion.proposals && TsCodeCompletion.proposals.length > 0) {
			var wrapOffset = TsCodeCompletion.codemirror.options.originalTextarea.parent().find('.t3e_iframe_wrap').offset(),
				$cursorNode = $(TsCodeCompletion.latestCursorNode),
				nodeOffset = $cursorNode.offset();

			var leftpos = Math.round(wrapOffset.left + nodeOffset.left + TsCodeCompletion.latestCursorNode.offsetWidth - $(TsCodeCompletion.codemirror.frame.contentDocument.body).scrollLeft()) + 'px',
				toppos = Math.round($cursorNode.position().top + TsCodeCompletion.latestCursorNode.offsetHeight - $cursorNode.scrollTop() - $(TsCodeCompletion.codemirror.frame.contentDocument.body).scrollTop()) + 'px';

			TsCodeCompletion.$codeCompleteBox.css({
				left: leftpos,
				top: toppos
			});
		}
	};

	/**
	 * Stop code completion and call hooks
	 */
	TsCodeCompletion.endAutoCompletion = function() {
		TsCodeCompletion.cc = 0;
		TsCodeCompletion.$codeCompleteBox.remove();
		// force full refresh
		TsCodeCompletion.compResult = null;
		for (var i = 0; i < TsCodeCompletion.plugins.length; i++) {
			require([TsCodeCompletion.plugins[i]], function(plugin) {
				if (typeof plugin.endCodeCompletion === 'function') {
					plugin.endCodeCompletion();
				}
			});
		}
	};

	/**
	 * Move cursor in autocomplete box up
	 */
	TsCodeCompletion.codeCompleteBoxMoveUpCursor = function() {
		var id;
		// if previous position was first or position not initialized - then move cursor to last word, else decrease position
		if (TsCodeCompletion.currWord === 0 || TsCodeCompletion.currWord === -1) {
			id = TsCodeCompletion.proposals.length - 1;
		} else {
			id = TsCodeCompletion.currWord - 1;
		}
		// hightlight new cursor position
		TsCodeCompletion.highlightCurrWord(id);
		// update id of first and last showing proposals and scroll box
		if (TsCodeCompletion.currWord < TsCodeCompletion.cc_up || TsCodeCompletion.currWord === (TsCodeCompletion.proposals.length - 1)) {
			TsCodeCompletion.cc_up = TsCodeCompletion.currWord;
			TsCodeCompletion.cc_down = TsCodeCompletion.currWord + (TsCodeCompletion.options.ccWords - 1);
			if (TsCodeCompletion.cc_up === (TsCodeCompletion.proposals.length - 1)) {
				TsCodeCompletion.cc_down = TsCodeCompletion.proposals.length - 1;
				TsCodeCompletion.cc_up = TsCodeCompletion.cc_down - (TsCodeCompletion.options.ccWords - 1);
			}
			TsCodeCompletion.$codeCompleteBox.find('.t3e_codeCompleteBox').scrollTop(TsCodeCompletion.cc_up * 18);
		}
	};

	/**
	 * Move cursor in codecomplete box down
	 */
	TsCodeCompletion.codeCompleteBoxMoveDownCursor = function() {
		var id;
		// if previous position was last word in list - then move cursor to first word if not than	position ++
		if (TsCodeCompletion.currWord === TsCodeCompletion.proposals.length - 1) {
			id = 0;
		} else {
			id = TsCodeCompletion.currWord + 1;
		}
		// highlight new cursor position
		TsCodeCompletion.highlightCurrWord(id);

		// update id of first and last showing proposals and scroll box
		if (TsCodeCompletion.currWord > TsCodeCompletion.cc_down || TsCodeCompletion.currWord === 0) {
			TsCodeCompletion.cc_down = TsCodeCompletion.currWord;
			TsCodeCompletion.cc_up = TsCodeCompletion.currWord - (TsCodeCompletion.options.ccWords - 1);
			if (TsCodeCompletion.cc_down == 0) {
				TsCodeCompletion.cc_up = 0;
				TsCodeCompletion.cc_down = TsCodeCompletion.options.ccWords - 1;
			}
			TsCodeCompletion.$codeCompleteBox.find('.t3e_codeCompleteBox').scrollTop(TsCodeCompletion.cc_up * 18);
		}
	};

	/**
	 * Highlight the active word in the code completion list
	 *
	 * @param {Number} id
	 */
	TsCodeCompletion.highlightCurrWord = function(id) {
		if (TsCodeCompletion.currWord !== -1) {
			$('#cc_word_' + TsCodeCompletion.currWord).removeClass('active');
		}
		$('#cc_word_' + id).addClass('active');
		TsCodeCompletion.currWord = id;
	};

	/**
	 * Insert selected word into text from codecompletebox
	 */
	TsCodeCompletion.insertCurrWordAtCursor = function() {
		var word = TsCodeCompletion.proposals[TsCodeCompletion.currWord].word;
		// tokenize current line
		TsCodeCompletion.codemirror.editor.highlightAtCursor();
		var select = TsCodeCompletion.codemirror.win.select;
		var cursorNode = TsCodeCompletion.getCursorNode();

		if (cursorNode.currentText
			&& cursorNode.currentText !== '.'
			&& $.trim(cursorNode.currentText) !== '' ) {
			// if there is some typed text already, left to the "." -> simply replace node content with the word
			cursorNode.innerHTML = word;
			cursorNode.currentText = word;
			select.setCursorPos(TsCodeCompletion.codemirror.editor.container, {node: cursorNode, offset: 0});
		} else { // if there is no text there, insert the word at the cursor position
			TsCodeCompletion.codemirror.replaceSelection(word);
		}
	};

	/**
	 * Save the mouse position
	 *
	 * @param {Event} e
	 */
	TsCodeCompletion.saveMousePos = function(e) {
		TsCodeCompletion.mousePos.x = e.clientX;
		TsCodeCompletion.mousePos.y = e.clientY;
	};

	$(document).on('t3editor:init', function(e, codemirror, $outerDiv) {
		TsCodeCompletion.codemirror = codemirror;
		TsCodeCompletion.outerDiv = $outerDiv;

		TsCodeCompletion.parser = TsParser.init(TsCodeCompletion.tsRef, TsCodeCompletion.extTsObjTree);
		TsCodeCompletion.tsRef.loadTsrefAsync();

		$(codemirror.win)
			.on('click', TsCodeCompletion.click)
			.on('scroll', TsCodeCompletion.scroll)
			.on('mousemove', TsCodeCompletion.saveMousePos)
			.on('keydown', function(e) {
				TsCodeCompletion.codemirror = codemirror;
				TsCodeCompletion.outerDiv = $outerDiv;
				TsCodeCompletion.keyDown(e);
			})
			.on('keyup', function(e) {
				TsCodeCompletion.codemirror = codemirror;
				TsCodeCompletion.outerDiv = $outerDiv;
				TsCodeCompletion.keyUp(e);
			});

		TsCodeCompletion.loadExtTemplatesAsync();
		TsCodeCompletion.loadPluginArray();
	});

	return TsCodeCompletion;
});
