define([
    'scripts/knockout-3.3.0',
    'scripts/ACTComponent.js',
    'ace/autocompletion/SymbolTooltipCompleter',
    'ace/autocompletion/PrototypeTooltip',
    'ace/ace',
    'ace/ext-language_tools',
], function(ko, ACTComponent, SymbolTooltipCompleter, PrototypeTooltip) {

    function EditorComponentViewModel(params, element) {
        //********************************************************************************
        //---------------------------------INIT-------------------------------------------
        //********************************************************************************
        var _this = this;
        //Contains the completion on the current symbol.
        _this.snippets = [];
        _this.aceId = "ace-editor" + params.Id;
        _this.editor = null;
        _this.element = $(element);
        _this.$prototypeTooltip = $(element).find('.prototype-tooltip');
        //Specifies whether the first variable has been completed so that autocompletion can be shown.
        //(Showing completion on the first variable would be very annoying when declaring it.)
        _this.isFirstVariable = true;
        _this.showScopeVariableCompletion = false;
        _this.changedBeforeLostFocus = false;
        _this.lastExpression = "";
        _this.markers = []; // markers are used to highlight the lines.
        _this.shouldShowHistorySearch = ko.observable(false);
        _this.inputSearchValue = ko.observable("");
        _this.hasHistorySearchFocus = ko.observable(false);
        _this.breakpoints = [];


        $(element).find('#ace-editor').attr("id", _this.aceId);

        //---Init knockout variables---//
        //Object containing the information of the tooltip for the help.
        _this.helpTooltip = new SymbolTooltipCompleter();
        //Object containing the information of the tooltip for the prototypes.
        //There are two distinct ko object for prototype and help tooltip since both can appear on the screen with different values.
        _this.prototypeTooltipCompleter = new SymbolTooltipCompleter();
        _this.prototypeTooltip = new PrototypeTooltip(_this.prototypeTooltipCompleter, _this.$prototypeTooltip, _this.editor);
        //---callbacks---//
        _this.OnUpdateIntellisenseInfo = params.Model.OnUpdateIntellisenseInfo;
        _this.ExecuteCommand = params.Model.ExecuteCommand;
        _this.NavigateThroughHistory = params.Model.NavigateThroughHistory;
        _this.OnHistorySearch = params.Model.OnHistorySearch;
        _this.OnResetHistorySearch = params.Model.OnResetHistorySearch;
        _this.OnTextSelected = params.Model.OnTextSelected;
        _this.OnLostFocus = params.Model.OnLostFocus;
        _this.OnGutterClicked = params.Model.OnGutterClicked;
        _this.OpenReferenceGuide = params.Model.OpenReferenceGuide;
        _this.base = new ACTComponent($(element), params);
        _this.params = params;
        _this.heightType = _this.params.baseModel.definition().HeightType;
        _this.snippets = params.Model.Snippets;
        _this.intellisense = params.Model.IntellisenseInfo;

        //---Back from C#---//
        _this.base.onMessage = function(name, obj) {
            // ! list need .$values
            if (_this.editor === null) return;
            switch (name) {
                case "SetContent":
                    _this.setContent(obj);
                    break;
                case "AppendContent":
                    _this.appendContent(obj);
                    break;
                case "UpdateIntellisenseInfo":
                    // Do not update proposals if it has not changed. This would lead to problem
                    // closing the completer when several proposition contains the actual validated name.
                    if (_this.lastExpression == obj.Expression && obj.Expression !== "") {
                        break;
                    }
                    _this.intellisense = obj;
                    _this.lastExpression = _this.intellisense.Expression;
                    // Update prototype tooltip.
                    if (_this.intellisense.ParameterInfo && _this.intellisense.ParameterInfo.length > 0) {
                        _this.prototypeTooltip.display(_this.intellisense.ParameterInfo);
                    } else {
                        _this.prototypeTooltip.close();
                    }
                    if (obj.Expression !== "" || _this.intellisense.PreSelectedProposalName) {
                        // Do not show popup on first variable's change. Otherwise, the completion would keep
                        // reappearing when 'Enter' is pressed.
                        // The popup should ony appear after '=' sign for preselected name.
                        _this.editor.execCommand("startAutocomplete");
                    }
                    break;
                case "SetAnnotations":
                    _this.setAnnotations(obj.$values);
                    break;
                case "ShowLineNumbers":
                    _this.showGutter(obj);
                    break;
                case "UpdateSnippets":
                    _this.snippets = obj.$values;
                    break;
                case "SetBreakpoints":
                    _this.params.Model.Breakpoints = obj.$values;
                    _this.setBreakpoints();
                    break;
                // case "EnableBreakpoint":
                //     _this.enableBreakpoint(obj.Line);
                //     break;
                // case "DisableBreakpoint":
                //     _this.disableBreakpoint(obj.Line);
                //     break;
                case "HighlightLines":
                    if (_this.editor === null) return;
                    var lines = obj.$values;
                    // clean previous markers first.
                    _this.markers.forEach(function(marker) {
                        _this.editor.session.removeMarker(marker);
                    });
                    _this.markers = [];
                    for (var i = 0; i < lines.length; i++) {
                        var row = lines[i] - 1;
                        var marker = _this.editor.session.addMarker(new _this.Range(row, 0, row, 1), "line-highlight", "fullLine");
                        _this.markers.push(marker);
                    }
                    break;
                case "SetReadOnly":
                    if (_this.editor === null) return;
                    _this.editor.setReadOnly(obj);
                    break;
                case "FontSize":
                    _this.editor.setOption("fontSize", obj + "pt");
                    break;
                case "SetHistorySearchMatch":
                    if (obj === "") {
                        $(".search-container input#search").addClass("searchError");
                    } else {
                        $(".search-container input#search").removeClass("searchError");
                        _this.setContent(obj);
                    }
                    break;
                case "OpenHistorySearch":
                    _this.openHistorySearch();
                    break;
                case "EditorMode":
                    _this.setEditorMode(obj);
                    _this.params.Model.EditorMode = obj;
                    break;
                case "ReadOnly":
                    _this.setReadOnly(obj);
                    break;
                case "Undo":
                    _this.undo();
                    break;
                case "Redo":
                    _this.redo();
                    break;
            }
        };

        _this.base.onRefresh = function(obj) {};

        _this.base.addCallbackX(_this, "onLostFocus", "OnLostFocus");
        _this.base.addCallback1(_this, "onUpdateIntellisenseInfo", "OnUpdateIntellisenseInfo");
        _this.base.addCallback1(_this, "executeCommand", "ExecuteCommand");
        _this.base.addCallback1(_this, "navigateThroughHistory", "NavigateThroughHistory");
        _this.base.addCallback1(_this, "onGutterClicked", "OnGutterClicked");
        _this.base.addCallbackX(_this, "onHistorySearch", "OnHistorySearch");
        _this.base.addCallback1(_this, "onResetHistorySearch", "OnResetHistorySearch");
        _this.base.addCallback1(_this, "openReferenceGuide", "OpenReferenceGuide");
        _this.base.addCallback1(_this, "onTextSelected", "OnTextSelected");

        function escapeHTML(str) {
            return str.replace(/&/g, "&#38;").replace(/"/g, "&#34;").replace(/'/g, "&#39;").replace(/</g, "&#60;");
        }

        //********************************************************************************
        //--------------------------------ACE INIT----------------------------------------
        //*****************************************************=**************************
        require.config({
            paths: {
                "ace": "../html/ace"
            }
        });
        // load ace and extensions
        require(["ace/ace"], function(ace) {
            _this.editor = ace.edit(_this.aceId);
            setOptions(_this.editor);
            _this.editorHeight = 0;
            var langTools = require("ace/ext/language_tools");

            var snippetManager = require("ace/snippets").snippetManager;

            var content = _this.params.Model.Content;
            if (content) {
                _this.editor.setValue(content, 0);
                _this.editor.getSession().selection.clearSelection();
            }

            //Define autocompletion
            var autoCompleter = {
                type: "autocompletion",
                getCompletions: function(editor, session, cursorPixelPosition, prefix, callback) {
                    //In case multiple ace coexists, do not use other's completer.
                    if (editor.container.id !== _this.aceId) {
                        return;
                    }
                    if (!_this.intellisense || !_this.intellisense.MemberList) {
                        callback(null, []);
                        return;
                    }
                    callback(null, _this.intellisense.MemberList.map(function(symbolDescriptor) {
                        var metadata = "";
                        var score = 1000;
                        switch (symbolDescriptor.Category) {
                            case "Property":
                                metadata = "P";
                                break;
                            case "Method":
                                metadata = "M";
                                break;
                            case "Type":
                                switch (symbolDescriptor.TypeCategory) {
                                    case "Enum":
                                        metadata = "E";
                                        break;
                                    default:
                                        metadata = "T";
                                        break;
                                }
                                break;
                            case "Namespace":
                                metadata = "N";
                                break;
                            case "Field":
                                metadata = "F";
                                break;
                            case "Python":
                                metadata = "-";
                                break;
                            case "Variable":
                                metadata = "V";
                                break;
                            default:
                                metadata = "-";
                                break;
                        }
                        return {
                            caption: symbolDescriptor.Name,
                            value: symbolDescriptor.Name,
                            score: score,
                            meta: metadata,
                            doc: symbolDescriptor,
                            type: "autocompletion"
                        };
                    }));
                },
                getDocTooltip: function(item) {
                    if (item.type == "autocompletion" && item.doc) {
                        var overloadsNumber = _this.countOverloadsInCurrentCompletion(item.doc.Name);
                        _this.helpTooltip.updateInfo(item.doc, overloadsNumber);
                        item.docHTML = [$(element).find('.help-tooltip').html(), ].join("");
                    }
                }
            };


            var snippetCompleter = {
                type: "snippets",
                getCompletions: function(editor, session, pos, prefix, callback) {
                    if (editor.container.id !== _this.aceId) {
                        return;
                    }
                    // do not display snippets inside a statement.
                    if (!_this.isFirstVariable()) {
                        return;
                    }
                    var snippetMap = snippetManager.snippetMap;
                    var completions = [];
                    // add python snippets from the ace/Snippets folder.

                    var mode = _this.params.Model.EditorMode.toLowerCase();
                    var snippets = snippetMap[mode] || [];
                    for (var i = snippets.length; i--;) {
                        var s = snippets[i];
                        var caption = s.name || s.tabTrigger;
                        if (!caption)
                            continue;
                        completions.push({
                            caption: caption,
                            snippet: s.content,
                            meta: "S",
                            type: "snippet"
                        });
                    }
                    // add snippets.
                    if (_this.snippets) {
                        _this.snippets.forEach(function(snippet) {
                            completions.push({
                                caption: snippet.Label,
                                score: 100,
                                meta: "S",
                                type: "snippet",
                                snippet: snippet.Value
                            });
                        });
                    }

                    callback(null, completions);
                },
                getDocTooltip: function(item) {
                    if (item.type == "snippet" && !item.docHTML) {
                        item.docHTML = [
                            "<b>", escapeHTML(item.caption), "</b>", "<hr></hr>",
                            escapeHTML(item.snippet)
                        ].join("");
                    }
                }
            };

            function setEditorMode(mode) {
                var modeStr = null;
                if (mode != null)
                    modeStr = "ace/mode/" + mode.toLowerCase();
                _this.editor.getSession().setMode(modeStr);
            }
            _this.setEditorMode = setEditorMode;

            function setReadOnly(val) {
                _this.editor.setReadOnly(val);
            }
            _this.setReadOnly = setReadOnly

            _this.undo = function() {
                _this.editor.undo();
            }

            _this.redo = function() {
                _this.editor.redo();
            }

            /**
             * Sets various options of the editor.
             * @param {object} editor
             */
            function setOptions(editor) {
                _this.editor.setTheme("ace/theme/monokai");
                _this.Range = require('ace/range').Range;
                setEditorMode(_this.params.Model.EditorMode);
                setReadOnly(_this.params.Model.ReadOnly);
                var monospaceFont = "monospace";
                if (navigator.appVersion.indexOf("Win") != -1) {
                    monospaceFont = "Consolas";
                }
                _this.editor.setOptions({
                    enableBasicAutocompletion: true,
                    enableLiveAutocompletion: false,
                    enableSnippets: true,
                    fontFamily: monospaceFont,
                    dragEnabled: false,
                    fontSize: _this.params.Model.FontSize + "pt",
                });
                _this.editor.setBehavioursEnabled(true); //auto-pairing of special characters like quotation marks, parenthesis or brackets.
                _this.editor.setShowInvisibles(true); //show invisible characters.
                _this.editor.setWrapBehavioursEnabled(true);
                _this.editor.setShowPrintMargin(false);
                _this.editor.getSession().setUseWrapMode(true);
                _this.editor.resize(true);
                _this.editor.$blockScrolling = Infinity;
                _this.editor.renderer.setShowGutter(_this.params.Model.ShowLineNumbers); //show or hide lines number
            }




            /**
             * Updates the height of the editor depending on the number of lines. (Only if AuotResizing and FitToContent are enabled.)
             * Initial height = 2 lines height.
             */
            _this.editorHeightUpdate = function() {
                if (_this.heightType !== "FitToContent" && _this.params.Model.UseAutoResizing) return;
                var newHeight = _this.editor.getSession().getScreenLength() * _this.editor.renderer.lineHeight; // + _this.editor.renderer.scrollBar.getWidth();
                if (_this.editor.getSession().getScreenLength() === 1) {
                    newHeight *= 2;
                }
                if (_this.editorHeight == newHeight) return;
                _this.editorHeight = newHeight;
                var windowHeight = $(window).height();
                var $aceContainer = _this.element.find('#ace-container');
                if (newHeight >= windowHeight - 75) { // _this.editor bigger than the history.
                    $aceContainer.height(windowHeight - 75);
                } else {
                    $aceContainer.height(newHeight.toString() + "px");
                }
                _this.params.gridPanel.resizeChildren();
                _this.editor.resize();
            };

            //-----INIT EDITOR VALUE-----//
            //Specifies wether the tooltip must be shown or not.
            _this.editor.hideAutocompletion = false;
            //Init proposals to the scope variables.
            // _this.autocompleters = _this.scopeVariables;
            _this.snippetCompleter = snippetCompleter;
            _this.editor.completers = [autoCompleter, snippetCompleter];

            //set initial size to the command line.
            _this.editorHeightUpdate();


            //-----EDITOR FUNCTION----//
            /**
             * Appends specified text to the editor.
             * @param {string} content
             */
            _this.appendContent = function(content) {
                if (content === undefined) return;
                snippetManager.insertSnippet(_this.editor, content);
                // _this.editor.insert(content);
                _this.editor.focus();
            };

            /**
             * Sets the specified to the editor (replace current value).
             * @param {string} content
             */
            _this.setContent = function(content) {
                if (content === undefined) return;
                _this.editor.setValue(content, 0);
                _this.editor.getSession().selection.clearSelection();
                _this.changedBeforeLostFocus = false;
                _this.editor.focus();
            };

            _this.showGutter = function(bool) {
                _this.editor.renderer.setShowGutter(bool);
            };

            _this.setAnnotations = function(annotations) {
                for (var i = 0; i < annotations.length; i++) {
                    annotations[i].row = annotations[i].Row;
                    annotations[i].text = annotations[i].Text;
                    annotations[i].type = annotations[i].Type.toLowerCase();
                }
                var editSession = _this.editor.getSession();
                editSession.setAnnotations(annotations);
            };
            //********************************************************************************
            //--------------------------------CUSTOM COMMANDS---------------------------------
            //*****************************************************=**************************
            _this.editor.commands.addCommand({ //assign enter keyCommand.
                name: "Enter",
                exec: function() {
                    _this.editor.hideAutocompletion = false;
                    _this.showScopeVariableCompletion = false;
                    _this.prototypeTooltip.currentPrototypeName = "";

                    if (_this.params.Model.UseConsoleKeyBinding) {
                        _this.enterCallBack();
                        return true;
                    } else {
                        return false;
                    }
                },
                bindKey: {
                    mac: "enter",
                    win: "enter"
                }
            });

            /**
             * Executes command(s) and populate history with it.
             */
            _this.enterCallBack = function() {
                _this.editor.hideAutocompletion = false;
                _this.shouldShowHistorySearch(false);
                var currentValue = _this.editor.session.doc.getAllLines();
                _this.executeCommand(currentValue);
                _this.editor.setValue('', 0);
                _this.editorHeightUpdate();
            };


            _this.editor.commands.addCommand({ //show autocompletion.
                name: "ShowAutocompletion",
                exec: function() {
                    if (!_this.editor.completer)
                        return;
                    _this.editor.hideAutocompletion = false;
                    _this.showScopeVariableCompletion = true;
                    _this.editor.completer.autoInsert = false;
                    _this.editor.completer.autoSelect = true;
                    _this.editor.completer.showPopup(_this.editor);
                    _this.editor.completer.cancelContextMenu();
                    _this.resizeAutocompletion();
                },
                bindKey: {
                    mac: "Ctrl-Space",
                    win: "Ctrl-Space"
                }
            });


            _this.editor.commands.addCommand({ //assign ctrl-arrow keyCommand to display the previous line command.
                name: "Previous",
                bindKey: {
                    mac: "Ctrl-Up",
                    win: "Ctrl-Up"
                },
                exec: function() {
                    if (_this.params.Model.UseConsoleKeyBinding) {
                        _this.navigateThroughHistory("previous");
                        return true;
                    } else {
                        return false;
                    }
                }
            });

            _this.editor.commands.addCommand({
                name: "Next",
                exec: function() {
                    if (_this.params.Model.UseConsoleKeyBinding) {
                        _this.navigateThroughHistory("next");
                        return true;
                    } else {
                        return false;
                    }
                },
                bindKey: {
                    mac: "Ctrl-Down",
                    win: "Ctrl-Down"
                }
            });

            //Fix issue that insert 'special space character' when the Escape key is pressed.
            _this.editor.commands.addCommand({
                name: "ProhibitEsc",
                exec: function() {
                    _this.prototypeTooltip.hide();
                    return true;
                },
                bindKey: {
                    mac: "Esc",
                    win: "Esc"
                }
            });

            //Remove the ctrl-f binding which is not working properly.
            _this.editor.commands.removeCommand('find');

            _this.editor.commands.addCommand({
                name: "HistorySearch",
                exec: function() {
                    if (_this.params.Model.UseConsoleKeyBinding) {
                        _this.openHistorySearch();
                        return true;
                    } else {
                        return false;
                    }
                },
                bindKey: {
                    mac: "Ctrl-r",
                    win: "Ctrl-r"
                }
            });

            //********************************************************************************
            //-------------------------------TOOLTIPS-----------------------------------------
            //*****************************************************=**************************

            _this.countOverloadsInCurrentCompletion = function(name) {
                var overloads = -1;
                for (var i = 0; i < _this.intellisense.MemberList.length; i++) {
                    if (_this.intellisense.MemberList[i].Name === name) {
                        overloads++;
                    }
                }
                return overloads;
            };


            //Fix issue when metadata are hidden by the scrollbar.
            _this.resizeAutocompletion = function() {
                setTimeout(function() {
                    var $autocompleteContent = $(".ace_autocomplete .ace_content");
                    var $autocompleteScroller = $(".ace_autocomplete .ace_scroller");
                    if ($autocompleteScroller.css("right") !== "0px") {
                        var offset = parseInt($autocompleteScroller.css("right"));
                        var newWidth = parseInt($autocompleteContent.css("width")) - offset;
                        $autocompleteContent.css("width", newWidth + "px");
                    }
                }, 150);
            };

            _this.setAutocompletionPosition = function() {
                if (_this.$prototypeTooltip.css('visibility') === "hidden") return;
                var completer = _this.editor.completer;
                if (_this.$prototypeTooltip && completer) {
                    var popup = completer.popup;
                    if (popup) {
                        var previousPosition = 0;
                        if (popup.isTopdown) {
                            previousPosition = parseInt($(popup.container).css('top'));
                            $(popup.container).css('top', previousPosition + _this.$prototypeTooltip.height() + 'px');
                        } else {
                            previousPosition = parseInt($(popup.container).css('bottom'));
                            $(popup.container).css('bottom', previousPosition + _this.$prototypeTooltip.height() + 'px');
                        }
                    }
                }
            };

            /**
             * Try to select the matching enum type in the tooltip row.
             * Returns true if the given symbol has been selected successfuly. False otherwise.
             * @param {string} symbolName: the symbol to be selected.
             */
            _this.trySelectProposal = function(symbolName) {
                var row = 0;
                var completer = _this.editor.completer;
                if (completer) {
                    var popup = completer.popup;
                    if (popup) {
                        for (var i = 0; i < popup.data.length; i++) {
                            if (popup.data[i].caption === symbolName) {
                                popup.setRow(i);
                                return true;
                            }
                        }
                    }
                }
                return false;
            };

            // _this.showTokenTooltip = function(text, x, y){
            //     //example with container creation via JS
            //     var div = document.getElementById('tooltip_0');
            //     if(div === null){
            //         div = document.createElement('div');		
            //         div.setAttribute('id', 'tooltip_0'); 
            //         div.setAttribute('class', 'token-tooltip'); // and make sure myclass has some styles in css
            //         document.body.appendChild(div);
            //     }

            //     div.style.left = position.pageX + 'px';
            //     div.style.top = position.pageY + 'px';
            //     if(text){
            //         div.style.display = "block";
            //         div.innerText = text;
            //     }else{
            //         div.style.display = "none";
            //         div.innerText = "";
            //     }
            // };

            _this.prototypeTooltip = new PrototypeTooltip(_this.prototypeTooltipCompleter, _this.$prototypeTooltip, _this.editor);

            //********************************************************************************
            //--------------------------------EVENTS------------------------------------------
            //*****************************************************=**************************

            /**
             * Updates tooltips (prototypes and completion proposals) based on the valid command before the cursor.
             */
            _this.updateAutocompletion = function() {
                var endPosition = _this.editor.getSelectionRange().start;
                var startPosition = {
                    row: endPosition.row,
                    column: 0
                };
                var textRange = {
                    start: startPosition,
                    end: endPosition
                };
                var originalCommand = _this.editor.session.getTextRange(textRange);
                _this.onUpdateIntellisenseInfo(originalCommand);
                _this.resizeAutocompletion();

            };

            _this.isFirstVariable = function() {
                if (!_this.intellisense)
                    return false;
                return _this.intellisense.Expression === "";
            };

            _this.editor.commands.on("afterExec", function(e) {
                var commandName = e.command.name;
                if (commandName === "startAutocomplete" || commandName === "ShowAutocompletion") {
                    // Scope variables.
                    if (_this.isFirstVariable()) {
                        // Autoselect enum value after "= "
                        if (_this.intellisense.PreSelectedProposalName) {
                            if (!_this.trySelectProposal(_this.intellisense.PreSelectedProposalName)) {
                                // Do not show completion if the previous auto selection failed.
                                _this.editor.completer.detach();
                            }
                        } else if (!_this.showScopeVariableCompletion || _this.editor.hideAutocompletion) {
                            _this.editor.completer.detach();
                        }
                    } else {
                        _this.showScopeVariableCompletion = false;
                    }
                    _this.setAutocompletionPosition();
                    return;
                }
            });

            _this.editor.selection.on('changeCursor', function(e) {
                _this.updateAutocompletion();
            });

            _this.editor.session.doc.on('change', function(e) {
                _this.editorHeightUpdate();
                _this.changedBeforeLostFocus = true;
            });

            // _this.editor.on("changeSelection", function (e){
            //     var selectionRange = _this.editor.selection.getRange();
            //     var selectedText = _this.editor.session.doc.getTextRange(selectionRange);
            //     if(selectedText.length > 0){
            //         _this.onTextSelected(selectedText, x, y);
            //         var pixelPosition = _this.editor.renderer.textToScreenCoordinates(position);
            //         pixelPosition.pageY += _this.editor.renderer.lineHeight;
            //         updateTooltip(pixelPosition, text);
            //     }else{
            //         updateTooltip(_this.editor.renderer.textToScreenCoordinates(position));
            //     }
            // }
            // );

            _this.editor.on('blur', function(e) {
                var currentValue = _this.editor.getValue();
                _this.onLostFocus(currentValue, _this.changedBeforeLostFocus);
                _this.changedBeforeLostFocus = false;
            });

            _this.editor.on('focus', function(e) {
                if (_this.shouldShowHistorySearch()) {
                    _this.editor.blur();
                    _this.hasHistorySearchFocus(true);
                }
            });

            $(element).on('resize', function() {
                _this.editor.resize();
            });

            // Breakpoints
            _this.editor.on("guttermousedown", function(e) {
                var target = e.domEvent.target;

                if (target.className.indexOf("ace_gutter-cell") == -1) {
                    return;
                }
                if (!_this.editor.isFocused()) {
                    return;
                }
                if (e.clientX > 25 + target.getBoundingClientRect().left) {
                    return;
                }
                var row = e.getDocumentPosition().row;
                _this.onGutterClicked(row + 1); // row start at 0 while on our side we start at 1.
                e.stop();
            });

            _this.editor.session.on("changeBreakpoint", function(e) {
                // captures set and clear breakpoint events
            });

            _this.setBreakpoints = function() {
                _this.editor.session.clearBreakpoints();
                var breakpoints = _this.params.Model.Breakpoints;
                if (breakpoints) {
                    breakpoints.forEach(function(breakpoint) {
                        var row = breakpoint.Line;
                        _this.editor.session.setBreakpoint(row - 1);
                    });
                }
            };

            // Enabling disabling breakpoint does not seem to work as the modification seems to be overriden
            // later by ace when introducing the class ace_breakpoint.
            // The issue is that we cannot modify directly ace_breakpoint since at this point, it
            // is not available.
            // _this.disableBreakpoint = function(row) {
            //     var breakpointElement = $(".ace_gutter-cell:nth-child(" + row + ")");
            //     breakpointElement.removeClass("breakpoint-enable");
            //     breakpointElement.addClass("breakpoint-disable");
            // };
            // _this.enableBreakpoint = function(row) {
            //     var breakpointElement = $(".ace_gutter-cell:nth-child(" + row + ")");
            //     breakpointElement.removeClass("breakpoint-disable");
            //     breakpointElement.addClass("breakpoint-enable");
            // };

            _this.setBreakpoints();

            // The following two lines is a hack to instantiate the autocompletion popup on the ace side.
            // However, we do not want to show it in the beginning, since it would be annoying when assigning variables.
            if (_this.intellisense) {
                _this.editor.execCommand("startAutocomplete");
                if (_this.editor.completer) // seems to happen when we don't have the python editor
                    _this.editor.completer.detach();
            }

            params.ready(true);
        });


        /* ---------------------------------------
         * History Search 
         * --------------------------------------*/
        _this.openHistorySearch = function() {
            _this.inputSearchValue(_this.editor.getValue());
            _this.shouldShowHistorySearch(true);
            _this.onHistorySearch(_this.inputSearchValue(), "Up");
            _this.hasHistorySearchFocus(true);
        };

        _this.upSearchClicked = function() {
            _this.onHistorySearch(_this.inputSearchValue(), "Up");
            _this.hasHistorySearchFocus(true);
        };

        _this.downSearchClicked = function() {
            _this.onHistorySearch(_this.inputSearchValue(), "Down");
            _this.hasHistorySearchFocus(true);
        };

        _this.historySearchChanged = function(data, event) {
            var searchValue = event.target.value;
            _this.onHistorySearch(searchValue, "Up");
            return true; // propagate event
        };

        _this.historySearchKeydown = function(data, event) {
            switch (event.keyCode) {
                case 13: // Enter
                    _this.closeHistorySearch();
                    return false;
                case 27: // Esc
                    _this.closeHistorySearch();
                    return false;
                    break;
                case 82: // the "r" key.
                    if (event.ctrlKey) {
                        if (event.shiftKey) {
                            _this.onHistorySearch(_this.inputSearchValue(), "Down");
                            _this.hasHistorySearchFocus(true);
                        } else {
                            _this.onHistorySearch(_this.inputSearchValue(), "Up");
                            _this.hasHistorySearchFocus(true);
                        }
                        return false;
                    }
            }
            return true; // propagate event to write in input
        };

        _this.closeHistorySearch = function() {
            _this.shouldShowHistorySearch(false);
            _this.inputSearchValue("");
            _this.onResetHistorySearch();
            _this.editor.focus();
        };

        params.baseModel.resizeFunctions[params.baseModel.resizeFunctions.length] = function(height, width) {
            if (_this.editor) {
                _this.editor.resize();
            }
        };

        _this.visibilityChange = function() {
            // lineHeight == 0 when the editor isn't visible
            // lineHeight don't instantly change and their are no event to know when it happens
            if (_this.editor.renderer.lineHeight === 0) {
                setTimeout(function() {
                    _this.visibilityChange();
                }, 100);
            } else {
                _this.editorHeightUpdate();
            }
        };
        params.baseModel.showFunctions.push(_this.visibilityChange);

        _this.base.initialize();
    }

    return {
        createViewModel: function(params, componentInfo) {
            return new EditorComponentViewModel(params, componentInfo.element);
        }
    };
});
