// twitter-like mention input
// For ment.io, refer to https://github.com/jeff-collins/ment.io
commonModule
    .directive('contenteditable', ['$rootScope', '$translate', 'mentioUtil', function ($rootScope, $translate, mentioUtil) {
        return {
            restrict: 'A', // only activate on element attribute
            require: ['?ngModel', '^?mentionInput'], // get a hold of NgModelController and parent directive
            link: function (scope, element, attrs, ctrl) {
                if (!ctrl) return;

                var ngModel = ctrl[0];
                var mentionInput = ctrl[1];

                // do nothing if no ng-model or not in mention-input directive
                if (!ngModel || !mentionInput) {
                    return;
                }

                var nameStr = $translate.instant('Name');
                var descStr = $translate.instant('Description');
                var keyCodeExp = /@[A-Za-z0-9\.]+?\s/g; // like @VAT.ZPXSE.17
                var formulaExp = /[A-Za-z_]+[A-Za-z0-9_]*\([^\(\)]*\)/g; // like BB("VAT001", 1, 1, 0)
                var tagExp = /<[^>]+?>/g; // like <a>, <span style=''>, <br/>
                //var otherTagExp = /(?!<pre(>|<\/>|\s.*>))<[^>]+?>/g; // like <a>, <span style=''>, <br/>

                scope.isValid = true;

                var read = function () {
                    if (mentioUtil.clearEmptyTextNode) {
                        mentioUtil.clearEmptyTextNode(element[0]);
                    }

                    // If show-name is true, replace code with name; otherwise do nothing.
                    var txt = element.text();
                    if (scope.showName) {
                        var pres = element.find('pre').toArray();
                        pres.forEach(function (e) {
                            if (angular.element(e).attr('data-type') === '0') {
                                var code = angular.element(e).attr('data-code');
                                if (code) {
                                    txt = txt.replace(angular.element(e).text(), code + ' ');
                                }
                            }
                        });
                    }

                    if (txt) { // replace &nbsp; and \r, \n if exist
                        txt = txt.replace(/\xA0/g, ' ').replace(/\n/g, '').replace(/\r/g, '');
                        txt = _.unescape(txt);
                    }
                    ngModel.$setViewValue(txt);
                };

                var customValidator = function (ngModelValue) {
                    if (_.isFunction(scope.customValidator)) {
                        scope.isValid = scope.customValidator({
                            'value': ngModelValue,
                            'ngModel': ngModel
                        });
                    }

                    return ngModelValue;
                };

                ngModel.$parsers.unshift(customValidator);
                ngModel.$formatters.unshift(customValidator);

                // Specify how UI should be updated
                ngModel.$render = function () {
                    // trans between $viewValue and element html
                    // $viewValue sample: @CH.BQ.QCYE +@VAT.ZPXSE.17
                    // element html sample:<pre>@CH.BQ.QCYE </pre>+<pre>@VAT.ZPXSE.17 </pre>
                    var viewValue = ngModel.$viewValue;
                    viewValue = _.escape(viewValue);
                    var matches = viewValue ? viewValue.match(keyCodeExp) : [];
                    if (matches) {
                        matches = _.uniq(matches);
                        matches.forEach(function (m) {
                            var patt = new RegExp(m, "g");
                            var item;
                            if (!_.isEmpty(scope.keyValueList)) {
                                item = _.findWhere(scope.keyValueList, { code: $.trim(m) });
                            }

                            if (!_.isEmpty(item) && !_.isEmpty(item.code)) {
                                if (scope.showName) {
                                    viewValue = viewValue.replace(patt, '<pre data-type="0" title="' + item.description
                                        + '" data-code="' + item.code + '" contenteditable="false">' + '@' + item.name + ' </pre>');
                                }
                                else {
                                    viewValue = viewValue.replace(patt, '<pre data-type="0" title="' + nameStr + ':' + item.name + '&#10;'
                                        + descStr + ':' + item.description + '" contenteditable="false">' + item.code + ' </pre>');
                                }
                            }
                            else {
                                viewValue = viewValue.replace(patt, '<pre data-type="0" contenteditable="false">' + $.trim(m) + ' </pre>');
                            }
                        });
                    }

                    matches = viewValue ? viewValue.match(formulaExp) : [];
                    if (matches) {
                        matches.forEach(function (m) {
                            var firstBracketIdx = m.indexOf('(');
                            var formulaName = m.substring(0, firstBracketIdx);
                            var paramStr = m.substring(firstBracketIdx + 1);
                            var item;
                            if (!_.isEmpty(scope.formulaList)) {
                                item = _.findWhere(scope.formulaList, { code: formulaName });
                            }

                            if (!_.isEmpty(item) && !_.isEmpty(item.code)) {
                                viewValue = viewValue.replace(m, '<pre data-type="1" data-description="' + item.description + '" data-name="' + item.name
                                    + '" data-code="' + item.code + '" contenteditable="inherit"><span contenteditable="false">'
                                    + item.code + '(</span>' + paramStr + '</pre>');
                            }
                        });
                    }
                    element.html(viewValue || '');
                };

                // Listen for change events to enable binding
                element.on('blur keyup change', function () {
                    if (!$rootScope.$$phase) {
                        scope.$apply(read);
                    }
                    else {
                        read();
                    }
                });
            }
        };
    }])
    .directive('mentionInput', ['$rootScope', '$log', '$translate', 'mentioUtil', 'enums',
    function ($rootScope, $log, $translate, mentioUtil, enums) {
        'use strict';
        $log.debug('mentionInput.ctor()...');

        return {
            restrict: 'E',
            templateUrl: '/app/common/controls/mention-input/mention-input.html' + '?_=' + Math.random(),
            replace: true,
            scope: {
                inputClass: '@',
                inputId: '@',
                ngModelVal: '=ngModel',
                inputReadonly: '=',
                includeBtn: '=',
                showName: '=',
                keyValueList: '=*mentionList',
                formulaList: '=*',
                displayMode: '=?', // input or textarea
                mentionApi: '=?',
                firedName: '=?',
                firedTagType: '=?',
                firedFormulaParams: '=?',
                btnClick: '&',
                btn2Click: '&',
                customValidator: '&',
                doubleClick: '&',
                inputFocus: '&',
                inputBlur: '&'
            },
            controller: 'mentionInputController',
            link: function (scope, element, attr) {
                // Register api functions in internalApi
                // If selectorApi passed is not undefined, we will use it as internalApi
                scope.internalApi = scope.mentionApi || {};
                scope.rangeInfo = {};
                
                var content = element.find('.mention-input-content');
                var nameStr = $translate.instant('Name');
                var descStr = $translate.instant('Description');

                // Set input classes
                var classAttrs = scope.inputClass;
                if (!_.isEmpty(classAttrs)) {
                    content.addClass(classAttrs);
                }

                scope.$watch('inputReadonly', function (newValue, oldValue) {
                    if (!newValue) {
                        content.attr('contenteditable', true);
                    }
                    else if (!oldValue) {
                        content.attr('contenteditable', false);
                    }
                });

                scope.$watch('rangeInfo', function (newValue, oldValue) {
                    if (newValue) {
                        getFiredFormulaInfo();
                    }
                });

                // Build tag for selected key value item
                var getKeyValueText = function (item) {
                    var rtn;
                    if (!_.isEmpty(item) && !_.isEmpty(item.code)) {
                        if (scope.showName) {
                            rtn = '<pre data-type="0" title="' + item.description + '" data-code="' + item.code
                                + '" contenteditable="false">' + '@' + item.name + ' </pre>';
                        }
                        else {
                            rtn = '<pre data-type="0" title="' + nameStr + ':' + item.name + '&#10;' + descStr + ':' + item.description
                                + '" contenteditable="false">' + item.code + ' </pre>';
                        }
                    }
                    else {
                        rtn = '<pre contenteditable="false">' + (item.code || ('@' + item.label)) + ' </pre>';
                    }

                    return rtn;
                };

                // Build tag for selected formula item
                var getFormulaText = function (item) {
                    var rtn;
                    if (!_.isEmpty(item) && !_.isEmpty(item.code)) {
                        rtn = '<pre data-type="1" data-description="' + item.description + '" data-name="' + item.name
                            + '" data-code="' + item.code + '" contenteditable="inherit"><span contenteditable="false">'
                            + item.code + '(</span>)</pre>';
                    }
                    else {
                        rtn = item.code || item.label;
                    }

                    return rtn;
                };

                // multiple types of elements can be selected:
                // <pre contenteditable='false'>@QCYE</pre>
                // <pre contenteditable='inherit'><span contenteditable='false'>BB</span>(1,2,3,4)</pre>
                var getStartEndInfo = function (range) {
                    var rtn = null;

                    if (!range) {
                        var selection = window.getSelection();
                        try {
                            range = selection.getRangeAt(0);
                        }
                        catch (ex) {

                        }

                        if (!range) {
                            return rtn;
                        }
                    }

                    // Only workable with range objects.
                    if (Object.prototype.toString.call(range).indexOf('Range') >= 0) {
                        rtn = {
                            startEle: range.startContainer,
                            endEle: range.endContainer,
                            startOffset: range.startOffset, // The selection offset in the startEle, -1 represents the whole element is selected.
                            endOffset: range.endOffset // The selection offset in the endEle, -1 represents the whole element is selected.
                        }

                        var curNode = rtn.startEle;
                        for (var i = 0; i < 3; i++) {
                            if (!curNode || curNode === content[0]) {
                                break;
                            }

                            curNode = curNode.parentNode;
                        }

                        if (curNode !== content[0]) {
                            rtn.startEle = null;
                            rtn.startOffset = -1;
                        }

                        curNode = rtn.endEle;
                        for (var i = 0; i < 3; i++) {
                            if (!curNode || curNode === content[0]) {
                                break;
                            }

                            curNode = curNode.parentNode;
                        }

                        if (curNode !== content[0]) {
                            rtn.endEle = null;
                            rtn.endOffset = -1;
                        }

                        // End element in the range is the contenteditable div itself
                        // That means the real end element is the last element on or before the endOffset.
                        if (rtn.endEle === content[0]) {
                            rtn.endEle = rtn.endOffset <= 0
                                ? null : rtn.endEle.childNodes[rtn.endOffset - 1];
                            rtn.endOffset = -1;
                        }

                        // Ignore empty #text
                        while (rtn.endEle && rtn.endEle.nodeType === enums.domNodeType.text
                            && rtn.endEle.nodeValue === '') {
                            rtn.endEle = rtn.endEle.previousSibling;
                            rtn.endOffset = -1;
                        }

                        // #text not empty , get its parent. If parent is not contenteditable div itself,
                        // set endEle to the parent element.
                        if (rtn.endEle && rtn.endEle.nodeType === enums.domNodeType.text) {
                            var parentEle = rtn.endEle.parentNode;
                            if (!parentEle.isContentEditable) {
                                // For IE, the startContainer/endContainer are always #text
                                // even if actually it is an element
                                rtn.endEle = parentEle;
                            }
                            else if (rtn.endOffset === 0 && parentEle === content[0]) {
                                // endOffset === 0 means the #text is next to the real endEle
                                rtn.endEle = rtn.endEle.previousSibling;
                                if (rtn.endEle) {
                                    if (rtn.endEle.nodeType === enums.domNodeType.text) {
                                        rtn.endOffset = rtn.endEle.length;
                                    }
                                    else {
                                        rtn.endOffset = -1;
                                    }
                                }
                            }
                            else if (rtn.endOffset < 0) {
                                // endOffset < 0 means the range.endOffset is actually the offset of #text in its parent
                                // set endOffset to the end of #text
                                rtn.endOffset = rtn.endEle.length;
                            }
                        }

                        // Start element in the range is the contenteditable div itself
                        // That means the real start element is the first element on or after the startOffset.
                        if (rtn.startEle === content[0]) {
                            rtn.startEle = rtn.startOffset >= rtn.startEle.childNodes.length
                                ? null : rtn.startEle.childNodes[rtn.startOffset];
                            rtn.startOffset = -1;
                        }

                        // Ignore empty #text
                        while (rtn.startEle && rtn.startEle.nodeType === enums.domNodeType.text
                            && rtn.startEle.nodeValue === '') {
                            rtn.startEle = rtn.startEle.nextSibling;
                            rtn.startOffset = -1;
                        }

                        // #text not empty , get its parent. If parent is not contenteditable div itself,
                        // set startEle to the parent element.
                        if (rtn.startEle && rtn.startEle.nodeType === enums.domNodeType.text) {
                            var parentEle = rtn.startEle.parentNode;
                            if (!parentEle.isContentEditable) {
                                // For IE, the startContainer/endContainer are always #text
                                // even if actually it is an element
                                rtn.startEle = parentEle;
                            }
                            else if (rtn.startOffset === rtn.startEle.length && parentEle === content[0]) {
                                // startOffset === startEle.length means the #text is previous to the real startEle
                                rtn.startEle = rtn.startEle.nextSibling;
                                if (rtn.startEle) {
                                    if (rtn.startEle.nodeType === enums.domNodeType.text) {
                                        rtn.startOffset = 0;
                                    }
                                    else {
                                        rtn.startOffset = -1;
                                    }
                                }
                            }
                            else if (rtn.startOffset < 0) {
                                // startOffset < 0 means the range.startOffset is actually the offset of #text in its parent
                                // set startOffset to the start of #text
                                rtn.startOffset = 0;
                            }
                        }
                    }

                    return rtn;
                };

                var getFiredFormulaInfo = function () {
                    if (!attr.firedName || !attr.firedFormulaParams) {
                        return;
                    }

                    var selection = window.getSelection();
                    var range;
                    try {
                        range = selection.getRangeAt(0);
                    }
                    catch (ex) {

                    }

                    if (!range) {
                        getFiredFormulaByElement(null);
                        return;
                    }

                    $log.debug(range);

                    var startEndInfo = getStartEndInfo(range);

                    $log.debug(startEndInfo);

                    if (!startEndInfo) {
                        getFiredFormulaByElement(null);
                        return;
                    }

                    if (startEndInfo.startEle && startEndInfo.startEle.parentNode !== content[0]) {
                        startEndInfo.startEle = startEndInfo.startEle.parentNode;
                    }

                    if (startEndInfo.endEle && startEndInfo.endEle.parentNode !== content[0]) {
                        startEndInfo.endEle = startEndInfo.endEle.parentNode;
                    }

                    if (range.startContainer !== range.endContainer
                        && startEndInfo.startEle !== startEndInfo.endEle) {
                        getFiredFormulaByElement(null);
                    }
                    else if (range.startContainer === range.endContainer) { // caret between two elements
                        if (startEndInfo.endEle && startEndInfo.endEle.nodeType !== enums.domNodeType.text) {
                            getFiredFormulaByElement(startEndInfo.endEle);
                        }
                        else if (startEndInfo.startEle && startEndInfo.startEle.nodeType !== enums.domNodeType.text) {
                            getFiredFormulaByElement(startEndInfo.startEle);
                        }
                        else {
                            getFiredFormulaByElement(null);
                        }
                    }
                    else if (startEndInfo.endEle && startEndInfo.endEle.nodeType !== enums.domNodeType.text) { // Same element, not #text
                        getFiredFormulaByElement(startEndInfo.endEle);
                    }
                    else {
                        getFiredFormulaByElement(null);
                    }
                };

                var getFiredFormulaByElement = function (ele) {
                    if (!ele) {
                        scope.firedName = '';
                        scope.firedFormulaParams = '';
                        scope.firedTag = null;
                    }
                    else {
                        var wrappedEle = angular.element(ele);
                        if (wrappedEle.attr('data-type') === '1') {
                            var firedFormulaParams = wrappedEle.text();
                            var firstBracketPos = firedFormulaParams.indexOf('(');
                            if (firstBracketPos > 0) {
                                scope.firedName = wrappedEle.attr('data-code');
                                firedFormulaParams = firedFormulaParams.substring(firstBracketPos + 1, firedFormulaParams.length);
                                scope.firedFormulaParams = firedFormulaParams;
                                scope.firedTag = ele;
                                scope.firedTagType = wrappedEle.attr('data-type');
                            }
                            else {
                                scope.firedName = '';
                                scope.firedFormulaParams = '';
                                scope.firedTag = null;
                            }
                        }
                        else {
                            var firedName = wrappedEle.attr('data-code');
                            if (firedName && firedName.length > 1 && firedName.charAt(0) === '@') {
                                firedName = firedName.substring(1);
                                scope.firedName = firedName;
                                scope.firedFormulaParams = '';
                                scope.firedTag = ele;
                                scope.firedTagType = wrappedEle.attr('data-type');
                            }
                        }
                    }
                };

                var deleteRange = function (startEndInfo) {
                    if (startEndInfo && startEndInfo.startEle && startEndInfo.endEle) {
                        // If part of the range is out of the editor, block this operation.
                        if (startEndInfo.startEle.parentNode !== content[0]
                            && (!startEndInfo.startEle.parentNode || startEndInfo.startEle.parentNode.parentNode !== content[0])
                            || startEndInfo.endEle.parentNode !== content[0]
                            && (!startEndInfo.endEle.parentNode || startEndInfo.endEle.parentNode.parentNode !== content[0])) {
                            return;
                        }

                        var startEle = startEndInfo.startEle;
                        var startOffset = startEndInfo.startOffset;
                        var endEle = startEndInfo.endEle;
                        var endOffset = startEndInfo.endOffset;

                        // Delete the elements between start element and end element
                        // If start element and end element are not in the same level or not in same parent, treat both children of content[0].
                        if (startEle.parentNode !== content[0] && endEle.parentNode !== content[0] && startEle.parentNode !== endEle.parentNode) {
                            startEle = startEle.parentNode;
                            endEle = endEle.parentNode;
                        }
                        else if (startEle.parentNode !== content[0] && endEle.parentNode === content[0]) {
                            startEle = startEle.parentNode;
                        }
                        else if (endEle.parentNode !== content[0] && startEle.parentNode === content[0]) {
                            endEle = endEle.parentNode;
                        }

                        var curEle = startEle;
                        var toDeleteNodes = [];
                        if (curEle !== endEle) {
                            while (curEle.nextSibling !== endEle && curEle.nextSibling) {
                                curEle = curEle.nextSibling;
                                toDeleteNodes.push(curEle);
                            }

                            toDeleteNodes.forEach(function (n) {
                                if (n.parentNode === content[0]) {
                                    n.parentNode.removeChild(n);
                                }
                            });
                        }

                        // If selection is in a #text node, delete the selected content
                        if (startEle.nodeType === enums.domNodeType.text
                            && startEle === endEle) {
                            startEle.deleteData(startOffset, endOffset - startOffset);
                        }
                        else {
                            // If selection start or end is not #text, just delete the element directly; otherwise delete the selected content
                            if (startEle.nodeType !== enums.domNodeType.text) {
                                if (startEle.parentNode === content[0]) {
                                    content[0].removeChild(startEle);
                                }
                                else if (startEle.parentNode && startEle.parentNode.parentNode === content[0]) {
                                    content[0].removeChild(startEle.parentNode);
                                }
                            }
                            else {
                                startEle.deleteData(startOffset, startEle.length - startOffset);
                            }

                            if (endEle.nodeType !== enums.domNodeType.text) {
                                if (endEle.parentNode === content[0]) {
                                    content[0].removeChild(endEle);
                                }
                                else if (endEle.parentNode && endEle.parentNode.parentNode === content[0]) {
                                    content[0].removeChild(endEle.parentNode);
                                }
                            }
                            else {
                                endEle.deleteData(0, endOffset);
                            }
                        }
                    }
                };

                // <div contenteditable>""|"text1"|""|<pre>"" "preText" "" "" </pre>|""|" "|"" </div>
                var handleBackspace = function (e) {
                    e.preventDefault();
                    var selection = window.getSelection();
                    var range;
                    try {
                        range = selection.getRangeAt(0);
                    }
                    catch (ex) {

                    }

                    var startEndInfo = getStartEndInfo(range);
                    if (startEndInfo) {
                        // selection type is caret
                        if (range.startContainer === range.endContainer
                            && range.startOffset === range.endOffset
                            && startEndInfo.endEle) {

                            if (startEndInfo.endEle.nodeType === enums.domNodeType.text) {
                                if (startEndInfo.endOffset > 0) { // Delete character before caret
                                    startEndInfo.endEle.deleteData(startEndInfo.endOffset - 1, 1);
                                }
                            }
                            else {
                                if (startEndInfo.endEle.parentNode === content[0]) {
                                    content[0].removeChild(startEndInfo.endEle);
                                }
                                else if (startEndInfo.endEle.parentNode
                                    && startEndInfo.endEle.parentNode.parentNode === content[0]) {
                                    content[0].removeChild(startEndInfo.endEle.parentNode);
                                }
                            }
                        }
                        else { // selection type is range
                            deleteRange(startEndInfo);
                        }
                    }

                    content.trigger('keyup');
                };

                var handleDelete = function (e) {
                    e.preventDefault();
                    var selection = window.getSelection();
                    var range;
                    try {
                        range = selection.getRangeAt(0);
                    }
                    catch (ex) {

                    }

                    var startEndInfo = getStartEndInfo(range);
                    if (startEndInfo) {
                        // selection type is caret
                        if (range.startContainer === range.endContainer
                            && range.startOffset === range.endOffset
                            && startEndInfo.startEle) {

                            if (startEndInfo.startEle.nodeType === enums.domNodeType.text) {
                                if (startEndInfo.startOffset < startEndInfo.startEle.length) { // Delete character after caret
                                    startEndInfo.startEle.deleteData(startEndInfo.startOffset, 1);
                                }
                            }
                            else {
                                if (startEndInfo.startEle.parentNode === content[0]) {
                                    content[0].removeChild(startEndInfo.startEle);
                                }
                                else if (startEndInfo.startEle.parentNode
                                    && startEndInfo.startEle.parentNode.parentNode === content[0]) {
                                    content[0].removeChild(startEndInfo.startEle.parentNode);
                                }
                            }
                        }
                        else { // selection type is range
                            deleteRange(startEndInfo);
                        }
                    }

                    content.trigger('keyup');
                };

                var isValid = function () {
                    return scope.isValid;
                };

                if (scope.internalApi) {
                    scope.internalApi.isValid = isValid;
                    scope.internalApi.triggerValidator = function (val, field) {
                        if (_.isFunction(scope.customValidator)) {
                            scope.isValid = scope.customValidator({
                                'value': val,
                                'ngModel': field
                            });
                        }
                    };
                }

                content.bind('keydown', function (e) {
                    if (scope.inputReadonly) {
                        return;
                    }

                    // Clear empty text node in contenteditable to make sure mentio work.
                    // And after empty text been cleared, Enter does not work issue is fixed. Why???
                    mentioUtil.clearEmptyTextNode(content[0]);

                    // Since backspace in IE11 will delete characters in contenteditable="false" elements,
                    // we have to add custom backspace handler.
                    switch (e.keyCode) {
                        case enums.keyCode.enter: // prevent the default behaviour of return key pressed
                            e.preventDefault();
                            break;
                        case enums.keyCode.backspace:
                            handleBackspace(e);
                            break;
                        case enums.keyCode.delete:
                            handleDelete(e);
                            break;
                        default:
                            break;
                    }
                });

                // Fix issue - <pre contenteditable="false"></pre> can be modified in IE11
                content.bind('keypress', function (e) {
                    if (scope.inputReadonly || e.ctrlKey || e.metaKey || e.altKey) {
                        return;
                    }

                    var selection = window.getSelection();
                    var range;
                    try {
                        range = selection.getRangeAt(0);
                    }
                    catch (ex) {

                    }

                    var startEndInfo = getStartEndInfo(range);
                    if (startEndInfo && startEndInfo.endEle) {
                        // selection type is caret
                        if (range.startContainer === range.endContainer
                            && range.startOffset === range.endOffset) {
                            // caret is not in #text, but after or in a <pre> node in contenteditable
                            if (startEndInfo.endEle.nodeType !== enums.domNodeType.text
                                && startEndInfo.endEle.parentNode === content[0]) {
                                // caret after <span> node in <pre> node, only appear in IE.
                                if (startEndInfo.endOffset === 1
                                    && startEndInfo.childNodes
                                    && startEndInfo.childNodes[0] !== enums.domNodeType.text) {
                                    return;
                                }

                                // Find the offset of the node in contenteditable
                                var offset = -1;
                                for (offset = 0; offset < content[0].childNodes.length; offset++) {
                                    var n = content[0].childNodes[offset];
                                    if (n === startEndInfo.endEle) {
                                        break;
                                    }
                                }

                                if (offset < 0) {
                                    // Cannot find the node offset, it may not be in the contenteditable
                                    return;
                                }

                                // caret is at the beginning of editable <pre> node, only appear in IE.
                                if (startEndInfo.endOffset === 0 && offset === 0 && startEndInfo.endEle.childNodes.length > 0
                                    && startEndInfo.endEle.childNodes[0] !== enums.domNodeType.text) {
                                    // Add the key to the left of the node.
                                    //e.preventDefault();
                                    var txtNode = document.createTextNode('\xA0');
                                    //var newRange = document.createRange();
                                    var maxLength = content[0].childNodes ? content[0].childNodes.length : 0;
                                    range.setStart(content[0], 0);
                                    range.setEnd(content[0], 0);
                                    range.insertNode(txtNode);
                                    range.collapse(false);
                                    range = range.cloneRange();
                                    range.setStart(txtNode, 0);
                                    range.setEnd(txtNode, txtNode.length);
                                    //range.collapse(true);
                                    selection.removeAllRanges();
                                    selection.addRange(range);
                                }
                                else {
                                    // Add the key to the right of the node.
                                    //e.preventDefault();
                                    var txtNode = document.createTextNode('\xA0');
                                    //var newRange = document.createRange();
                                    var maxLength = content[0].childNodes ? content[0].childNodes.length : 0;
                                    range.setStart(content[0], maxLength > offset ? (offset + 1) : maxLength);
                                    range.setEnd(content[0], maxLength > offset ? (offset + 1) : maxLength);
                                    range.insertNode(txtNode);
                                    range.collapse(false);
                                    range = range.cloneRange();
                                    range.setStart(txtNode, 0);
                                    range.setEnd(txtNode, txtNode.length);
                                    //range.collapse(true);
                                    selection.removeAllRanges();
                                    selection.addRange(range);
                                }
                            }
                            // caret is in #text, but in the end of the text node in editable <pre> node in contenteditable
                            else if (startEndInfo.endEle.nodeType === enums.domNodeType.text
                                && startEndInfo.endEle.parentNode
                                && startEndInfo.endEle.parentNode.parentNode === content[0]
                                && startEndInfo.endEle.length === range.endOffset) {
                                // Find the offset of the node in contenteditable
                                var offset = -1;
                                for (offset = 0; offset < content[0].childNodes.length; offset++) {
                                    var n = content[0].childNodes[offset];
                                    if (n === startEndInfo.endEle.parentNode) {
                                        break;
                                    }
                                }

                                if (offset < 0) {
                                    // Cannot find the node offset, it may not be in the contenteditable
                                    return;
                                }

                                // Add the key to the right of the node.
                                //e.preventDefault();
                                var txtNode = document.createTextNode('\xA0');
                                //var newRange = range.cloneRange();
                                var maxLength = content[0].childNodes ? content[0].childNodes.length : 0;
                                range.setStart(content[0], maxLength > offset ? (offset + 1) : maxLength);
                                range.setEnd(content[0], maxLength > offset ? (offset + 1) : maxLength);
                                range.insertNode(txtNode);
                                range.collapse(false);
                                range = range.cloneRange();
                                range.setStart(txtNode, txtNode.length);
                                range.collapse(true);
                                selection.removeAllRanges();
                                selection.addRange(range);
                            }
                            // caret is at the beginning of <span> node in editable <pre>, only appear in IE.
                            else if (startEndInfo.endEle.nodeType !== enums.domNodeType.text
                                && startEndInfo.endEle.parentNode
                                && startEndInfo.endEle.parentNode.parentNode === content[0]
                                && 0 === range.endOffset) {
                                // Find the offset of the node in contenteditable
                                var offset = -1;
                                for (offset = 0; offset < content[0].childNodes.length; offset++) {
                                    var n = content[0].childNodes[offset];
                                    if (n === startEndInfo.endEle.parentNode) {
                                        break;
                                    }
                                }

                                if (offset < 0) {
                                    // Cannot find the node offset, it may not be in the contenteditable
                                    return;
                                }

                                // Add the key to the left of <pre> node.
                                //e.preventDefault();
                                var txtNode = document.createTextNode('\xA0');
                                //var newRange = document.createRange();
                                range.setStart(content[0], offset);
                                range.setEnd(content[0], offset);
                                range.insertNode(txtNode);
                                range.collapse(false);
                                range = range.cloneRange();
                                range.setStart(txtNode, offset);
                                range.setEnd(txtNode, offset + txtNode.length);
                                //range.collapse(true);
                                selection.removeAllRanges();
                                selection.addRange(range);
                            }
                        }
                        else if (startEndInfo.startEle.nodeType !== enums.domNodeType.text
                            || startEndInfo.endEle.nodeType !== enums.domNodeType.text) {
                            // selection type is range and startEle or EndEle is not #text,
                            // then prevent the operation.
                            e.preventDefault();
                        }
                    }
                });

                var onDoubleClick = function () {
                    if (_.isFunction(scope.doubleClick)) {
                        if (_.isEmpty(scope.firedTag)) {
                            $log.debug('dblclick event: get fired formula');
                            getFiredFormulaInfo();
                        }

                        scope.doubleClick({
                            '$event': {
                                tagInfo: scope.firedTag,
                                name: scope.firedName,
                                formulaParams: scope.firedFormulaParams,
                                tagType: scope.firedTagType
                            }
                        });
                    }
                };

                // Block pasting elements
                content.bind('paste', function (e) {
                    e.preventDefault();
                    if (scope.inputReadonly) {
                        return;
                    }

                    var text = null;
                    if (window.clipboardData && clipboardData.setData) {
                        // IE
                        text = window.clipboardData.getData('text');
                    } else {
                        text = (e.originalEvent || e).clipboardData.getData('text/plain');
                    }

                    text = text.replace(/\r/g, '').replace(/\n/g, '');

                    // For IE, only support 11+
                    var selection = window.getSelection();
                    var range;
                    try {
                        range = selection.getRangeAt(0);
                    }
                    catch (ex) {

                    }

                    var startEndInfo = getStartEndInfo(range);
                    if (startEndInfo) {
                        // Block paste operation while selection start or end is not #text node.
                        if (startEndInfo.startEle && startEndInfo.endEle
                            && (startEndInfo.startEle.nodeType !== enums.domNodeType.text
                            || startEndInfo.endEle.nodeType !== enums.domNodeType.text)) {
                            return;
                        }
                        // selection type is not caret, then delete the selected range first.
                        if (range.startContainer !== range.endContainer
                            || range.startOffset !== range.endOffset) {
                            deleteRange(startEndInfo);
                        }

                        var txtNode = document.createTextNode(text);
                        range.insertNode(txtNode);
                        // Preserve the selection
                        range = range.cloneRange();
                        range.setStart(txtNode, txtNode.length);
                        range.collapse(true);
                        selection.removeAllRanges();
                        selection.addRange(range);
                    }

                    content.trigger('keyup');
                });

                content.bind('focus', function (e) {
                    if (_.isFunction(scope.inputFocus)) {
                        scope.inputFocus({
                            '$event': e
                        });
                    }
                });

                content.bind('blur', function (e) {
                    if (_.isFunction(scope.inputBlur)) {
                        scope.inputBlur({
                            '$event': e
                        });
                    }
                });

                scope.getKeyValueText = getKeyValueText;
                scope.getFormulaText = getFormulaText;
                scope.getFiredFormulaInfo = getFiredFormulaInfo;
                scope.onDoubleClick = onDoubleClick;
            }
        };
    }
]);