(function() {
    if (typeof jQuery === "undefined") {
        return;
    }
    var $ = jQuery;

    var Wikipower = function() {
        var textArea;
        var SUGGESTION_LIST_MAX_ROW_COUNT = 10;

        function bind(textAreaId) {
            textArea = $("#" + textAreaId);
            if (textArea) {
                textArea.keyup(keyUpHandler).keydown(keyDownHandler);
            }

            textArea.click(function() {
                ResultManager.hide();
                TextAreaActionHandler.change("keyup", null);
                TextAreaActionHandler.change("keydown", null);
            });
        }

        function unbind(textAreaId) {
            $("#" + textAreaId).unbind("keyup", keyUpHandler);
            $("#" + textAreaId).unbind("keydown", keyDownHandler);
        }

        var ContentUtil = function() {
            function getContentToPosition(obj, position) {
                var content = obj.value;
                return content.substring(0, position);
            }

            function getContentAfterPosition(obj, position) {
                var content = obj.value;
                return content.substring(position);
            }

            function getStrStart(obj, position) {
                var content = obj.value;
                return content.lastIndexOf("\n", position - 1) + 1;
            }

            function getStrEnd(obj, position) {
                var content = obj.value;
                var strEnd = content.indexOf("\n", position);
                strEnd = strEnd < 0 ? content.length : strEnd;
                return strEnd;
            }

            function getCurrentString(obj, position) {
                var content = obj.value;
                var start = getStrStart(obj, position);
                var end = getStrEnd(obj, position);
                return content.substring(start, end);
            }

            function getCurrStrToPosition(obj, position) {
                var content = obj.value;
                var strStart = getStrStart(obj, position);
                return content.substring(strStart, position);
            }

            function getRange(obj) {
                var start, end;
                if (document.selection) {
                    var range = document.selection.createRange();
                    var stored_range = range.duplicate();
                    stored_range.moveToElementText(obj);
                    stored_range.setEndPoint('EndToEnd', range);
                    start = stored_range.text.replace(/\r\n/g, "\n").length - range.text.length;
                    end = start + range.text.length;
                } else if (obj.selectionStart) {
                    start = obj.selectionStart;
                    end = obj.selectionEnd;
                }
                return {
                    start: start,
                    end: end
                }
            }

            function getRangeText(obj) {
                var range = document.selection.createRange();
                return range.text;
            }

            function escapeMatchText(text) {
                var resultText = text;
                function replaceChar(char, escapeTwice) {
                    resultText = resultText.replace(new RegExp("\\" + char, "g"), escapeTwice ? "\\\\" : "\\" + char);
                }
                for (var i = 0, chars = "*.?()^$"; i < chars.length; i++) {
                    replaceChar(chars.charAt(i), true);
                }
                for (var i = 0, chars = "{}[]"; i < chars.length; i++) {
                    replaceChar(chars.charAt(i), false);
                }
                return resultText;
            }

            function insertAtCaret(obj, text, cutcnt) {
                if (document.selection) {
                    obj.focus();
                    var orig = obj.value.replace(/\r\n/g, "\n");
                    var range = document.selection.createRange();

                    if (range.parentElement() != obj) {
                        return false;
                    }

                    range.moveEnd("character", cutcnt);
                    range.text = text;

                    var actual = tmp = obj.value.replace(/\r\n/g, "\n");

                    for (var diff = 0; diff < orig.length; diff++) {
                        if (orig.charAt(diff) != actual.charAt(diff)) break;
                    }

                    for (var index = 0, start = 0;
                         tmp.match(escapeMatchText(text))
                                 && (tmp = tmp.replace(text, ""))
                                 && index <= diff;
                         index = start + text.length
                            ) {
                        start = actual.indexOf(text, index);
                    }
                } else if (obj.selectionStart) {
                    var start = obj.selectionStart;
                    var end = obj.selectionEnd + cutcnt;

                    obj.value = obj.value.substr(0, start)
                            + text
                            + obj.value.substr(end, obj.value.length);
                }

                if (start != null) {
                    setCaretTo(obj, start + text.length);
                    return start;
                } else {
                    obj.value += text;
                }
            }

            function setCaretTo(obj, pos) {
                if (obj.createTextRange) {
                    var range = obj.createTextRange();
                    range.move("character", pos);
                    range.select();
                    return range;
                } else if (obj.selectionStart) {
                    obj.focus();
                    obj.setSelectionRange(pos, pos);
                }
            }

            function surroundWithText(obj, text) {
                var rightText = text;
                function replaceMirror(str, left, right) {
                    if (str.indexOf(right) <= str.indexOf(left)) {
                        rightText = str.replace(new RegExp("\\" + left, "g"), right);
                    }
                }
                for (var i = 0, pairs = "{}()[]"; i < pairs.length; i += 2) {
                    replaceMirror(rightText, pairs.charAt(i), pairs.charAt(i + 1));
                }
                if (document.selection) {
                    var rangeText = getRangeText(obj);
                    var start = insertAtCaret(obj, text + rangeText + rightText, 0);
                    var end = start + rangeText.length;
                } else if (obj.selectionStart) {
                    var start = obj.selectionStart;
                    var end = obj.selectionEnd;

                    obj.value = obj.value.substr(0, start)
                            + text
                            + obj.value.substring(start, end)
                            + rightText
                            + obj.value.substring(end);
                }

                if (start != null) {
                    setSelectionTo(obj, start + text.length, end + text.length);
                } else {
                    obj.value += text;
                }
            }

            function setSelectionTo(obj, start, end) {
                if (obj.createTextRange) {
                    var range = setCaretTo(obj, start);
                    range.moveEnd("character", end - start);
                    range.select();
                } else if (obj.selectionStart) {
                    obj.focus();
                    obj.setSelectionRange(start, end);
                }
            }

            return {
                getContentToPosition: getContentToPosition,
                getContentAfterPosition: getContentAfterPosition,
                getStrStart: getStrStart,
                getStrEnd: getStrEnd,
                getCurrentString: getCurrentString,
                getCurrStrToPosition: getCurrStrToPosition,
                getRange: getRange,
                insertAtCaret: insertAtCaret,
                setCaretTo: setCaretTo,
                surroundWithText: surroundWithText,
                setSelectionTo: setSelectionTo
            }
        }();

        var TextAreaActionHandler = function() {
            var activeHandlers = {
                keydown: null,
                keyup: null
            }, defaultHandlers = {
                keydown: null,
                keyup: null
            }, registeredHandlers = {
                keydown: null,
                keyup: null
            };

            function stopEvent(e) {
                e.preventDefault();
                e.stopPropagation();
            }

            function handle(event, e) {
                var func = activeHandlers[event] || defaultHandlers[event];
                if ($.isFunction(func)) {
                    func(e);
                }
            }

            function registerHandle(event, name, func) {
                if (name == null) {
                    defaultHandlers[event] = func;
                } else {
                    if (!registeredHandlers[event]) {
                        registeredHandlers[event] = {};
                    }
                    registeredHandlers[event][name] = func;
                }
            }

            function changeHandler(event, name) {
                activeHandlers[event] = registeredHandlers[event][name] || defaultHandlers[event];
            }

            return {
                handle: handle,
                register: registerHandle,
                stopEvent: stopEvent,
                change: changeHandler
            };
        }();

        var altKey = false;
        TextAreaActionHandler.register("keydown", null, function(e) {
            if (e.which == 32 && e.ctrlKey && e.shiftKey) {
                TextAreaActionHandler.stopEvent(e);
                TextAreaActionHandler.change("keydown", "mirror");
            } else if (e.which == 32 && e.ctrlKey) {
                TextAreaActionHandler.stopEvent(e);
                TextAreaActionHandler.change("keydown", "lookup");
            }
            if (e.which == 18) {
                altKey = true;
            }
        });

        function wikipowerHandler(e) {
            var target = e.target;
            var range = ContentUtil.getRange(target);
            if (range.start == range.end) {
                var position = range.start;
                var strStart = ContentUtil.getStrStart(target, position);
                ResultManager.display(target, position);
                requestLookUp({
                    str: ContentUtil.getCurrentString(target, position),
                    position: position > strStart ? position - strStart : 0,
                    max: SUGGESTION_LIST_MAX_ROW_COUNT,
                    space: ($("#confluence-space-key").attr("content") || ""),
                    pageId: ($("#pageId").attr("value") || ""),
                    contextPath: ($("#confluence-context-path").attr("content") || "")
                }, function(result) {
                    if (!result[0].ends || result[0].ends.length == 0) {
                        ResultManager.displayNothing(target, position);
                    } else {
                        ResultManager.fill(result[0], function(end, command) {
                            var replacerData = end.markup || end.str;
                            if (command == "complete") {
                                ContentUtil.insertAtCaret(target, replacerData, 0);
                            } else if (command == "replace") {
                                ContentUtil.insertAtCaret(target, replacerData, end.cutcnt);
                            }
                        });
                    }
                });
                TextAreaActionHandler.change("keyup", "lookup");
            } else {
                var oldValue = target.value;
                var surroundPosition = ResultManager.displaySurround(target, range.end);
                TextAreaActionHandler.change("keyup", "mirror");
                var mirrorInpt = $("<input type='text' value='' style='position:absolute;left:-1000000px;top:" + surroundPosition.top + ";'/>").appendTo("body");

                function bindGlobalClick() {
                    $(document).unbind("click", bindGlobalClick);
                    textArea.focus();
                    mirrorInpt.remove();
                    ResultManager.hide();
                }

                $(document).click(bindGlobalClick);
                mirrorInpt.focus().keydown(function(e) {
                    if (e.which == 9) {
                        TextAreaActionHandler.stopEvent(e);
                    } else if (e.which == 13) {
                        TextAreaActionHandler.stopEvent(e);
                        textArea.focus();
                        var val = mirrorInpt.val();
                        val = $.trim(val);
                        if (val.length > 0) {
                            target.value = oldValue;
                            ContentUtil.setSelectionTo(target, range.start, range.end);
                            ContentUtil.surroundWithText(target, val, range.start);
                        }
                        mirrorInpt.remove();
                        ResultManager.hide();
                        TextAreaActionHandler.change("keyup", null);
                        TextAreaActionHandler.change("keydown", null);
                    } else if (e.which == 27) {
                        TextAreaActionHandler.stopEvent(e);
                        mirrorInpt.remove();
                        target.value = oldValue;
                        ContentUtil.setSelectionTo(target, range.start, range.end);
                        ResultManager.hide();
                        textArea.focus();
                    }
                }).keyup(function() {
                    var input = $(this);
                    var val = input.val();
                    ResultManager.setSurroundText(val);
                    target.value = oldValue;
                    ContentUtil.setSelectionTo(target, range.start, range.end);
                    ContentUtil.surroundWithText(target, val, range.start);
                    input.focus().val(val);
                });
            }
        }

        TextAreaActionHandler.register("keyup", null, function(e) {
            if (e.which == 32 && e.ctrlKey) {
                ResultManager.hide();
                wikipowerHandler(e);
            }
            if (e.which == 18) {
                altKey = false;
            }
        });

        TextAreaActionHandler.register("keydown", "lookup", function(e) {
            if (e.which == 32 && e.ctrlKey) {
                TextAreaActionHandler.stopEvent(e);
            } else if (/*e.which == 37 || e.which == 39 ||*/ e.which == 32) {
                ResultManager.hide();
                TextAreaActionHandler.change("keydown", null);
            } else if (e.which == 27) {
                TextAreaActionHandler.stopEvent(e);
                ResultManager.hide();
                TextAreaActionHandler.change("keydown", null);
            } else if (e.which == 38) {
                ResultManager.selectPrev();
                TextAreaActionHandler.stopEvent(e);
            } else if (e.which == 40) {
                ResultManager.selectNext();
                TextAreaActionHandler.stopEvent(e);
            } else if (e.which == 13) {
                TextAreaActionHandler.stopEvent(e);
                TextAreaActionHandler.change("keydown", null);
            } else if (e.which == 9) {
                TextAreaActionHandler.stopEvent(e);
                TextAreaActionHandler.change("keydown", null);
            }
        });

        TextAreaActionHandler.register("keyup", "lookup", function(e) {
            if (e.which == 32 && e.ctrlKey) {
                //do nothing
            } else if (e.which == 27 || e.which == 32) {
                TextAreaActionHandler.change("keyup", null);
            } else if (e.which == 13) {
                ResultManager.completeSelected();
                TextAreaActionHandler.change("keyup", null);
            } else if (e.which >= 65 && e.which <= 90) {
                ResultManager.hide();
                wikipowerHandler(e);
            } else if (e.which == 37 || e.which == 39 ) {
                ResultManager.hide();
                wikipowerHandler(e);
            } else if (e.which == 8) {
                ResultManager.hide();
                wikipowerHandler(e);
            } else if (e.which == 9) {
                ResultManager.replaceSelected();
                TextAreaActionHandler.change("keyup", null);
            }
        });

        function keyDownHandler(e) {
            TextAreaActionHandler.handle("keydown", e);
        }

        function keyUpHandler(e) {
            TextAreaActionHandler.handle("keyup", e);
        }

        var ResultManager = function() {
            var resultPopup, resultsContainer, loadingContainer, noSuggestions, toBeContinue, itemDescriptionContainer, surroundWith;
            var bgColor = "#eee8aa", hlColor = "#eeaa5e";
            $(function() {
                resultPopup = $("<div style='display:none; border: 1px solid #000; position: absolute; background: " + bgColor + ";text-align:left;padding:2px;'>" +
                                    "<div class='wikipower-loading'>Loading...</div>" +
                                    "<div class='wikipower-nothing' style='display:none;background:#ff9999;'>No suggestions</div>" +
                                    "<div class='wikipower-results' style='display:none;'></div>" +
                                    "<div class='wikipower-tobecontinue' style='display:none;'>...</div>" +
                                    "<div class='wikipower-item-description' style='display:none;border-top:2px #e9967a solid;margin-top:2px;width:300px;font-style:italic;'></div>" +
                                    "<div class='wikipower-surround' style='display:none;'><div style='background:#99ccff;'>Surround with</div><div class='wikipower-surround-result' style='height:inherit;'>&nbsp;</div></div>" +
                                    "</div>").appendTo("body");
                resultsContainer = resultPopup.find(".wikipower-results");
                loadingContainer = resultPopup.find(".wikipower-loading");
                noSuggestions = resultPopup.find(".wikipower-nothing");
                toBeContinue = resultPopup.find(".wikipower-tobecontinue");
                itemDescriptionContainer = resultPopup.find(".wikipower-item-description");
                surroundWith = resultPopup.find(".wikipower-surround");
            });

            function onSelectResult(end, command, callback) {
                hideResult();
                callback(end, command);
            }

            function selectRow($row) {
                resultsContainer.find("div").removeClass("wikipower-hlrow").css("background", bgColor);
                $row.addClass("wikipower-hlrow").css("background", hlColor);
                itemDescriptionContainer.html($row.children('.descr').html());
            }

            function selectNext() {
                var nextRow = resultsContainer.find(".wikipower-hlrow").next();
                if (nextRow.size() > 0) {
                    selectRow(nextRow);
                } else {
                    selectRow(resultsContainer.find("div:first"));
                }
            }

            function selectPrev() {
                var prevRow = resultsContainer.find(".wikipower-hlrow").prev();
                if (prevRow.size() > 0) {
                    selectRow(prevRow);
                } else {
                    selectRow(resultsContainer.find("div:last"));
                }
            }

            function completeSelected() {
                resultsContainer.find(".wikipower-hlrow").trigger("wikipower_complete");
            }

            function replaceSelected() {
                resultsContainer.find(".wikipower-hlrow").trigger("wikipower_replace");
            }

            function appendResult(start, end, callback) {
                var $row = $(["<div style='cursor:pointer;border-bottom:", hlColor, " 1px solid;'>",
                                  "<span style='font-family:monospace;'>", start, "</span>",
                                  "<span style='font-weight:bold;font-family:monospace;'>", end.str, "</span>",
                                  "<span style='display:none;' class='descr'>", end.descr || "", "</span>",
                              "</div>"].join("")).appendTo(resultsContainer);
                $row.mouseover(function() {
                    selectRow($(this));
                }).bind("wikipower_complete", function() {
                    onSelectResult(end, "complete", callback);
                }).bind("wikipower_replace", function() {
                    onSelectResult(end, "replace", callback);
                });
            }

            function displayLoading(obj, position) {
                loadingContainer.show();
                resultsContainer.hide();
                toBeContinue.hide();
                itemDescriptionContainer.hide();
                surroundWith.hide();
                setPosition(obj, position);
                resultPopup.show();
            }

            function displayNothing(obj, position) {
                loadingContainer.hide();
                resultsContainer.hide();
                toBeContinue.hide();
                itemDescriptionContainer.hide();
                surroundWith.hide();
                noSuggestions.show();
                setPosition(obj, position, true);
                resultPopup.show();
            }

            function displaySurround(obj, position) {
                loadingContainer.hide();
                resultsContainer.hide();
                toBeContinue.hide();
                itemDescriptionContainer.hide();
                noSuggestions.hide();
                surroundWith.show();
                resultPopup.show();
                return setPosition(obj, position);
            }

            function setSurroundText(text) {
                if ($.trim(text).length == 0) {
                    surroundWith.find(".wikipower-surround-result").html("&nbsp;");
                } else {
                    surroundWith.find(".wikipower-surround-result").text(text);
                }

            }

            function displayResults() {
                loadingContainer.hide();
                resultsContainer.show();
                itemDescriptionContainer.show();
            }

            function setPosition(obj, position, isSup) {
                var testBlock = $("<span style='position:absolute;left: -1000000px;top:-1000000px;white-space:pre;'></span>").appendTo("body").attr("class", textArea.attr("class"));
                var contentToPosition = ContentUtil.getContentToPosition(obj, position);
                testBlock.text(contentToPosition);
                var top = testBlock.height() - textArea.scrollTop();
                if ($.browser.msie && parseInt($.browser.version) == 8) {
                    var brs = contentToPosition.match(/\n/g);
                    if (brs && brs.length > 0) {
                        var lines = brs.length;
                        top = parseInt(top * (lines + 1) / (lines * 2 + 1));
                    }
                }
                testBlock.text(ContentUtil.getCurrStrToPosition(obj, position));
                var left = testBlock.width();
                var lineHeight = testBlock.height();
                if (ContentUtil.getStrStart(obj, position) == ContentUtil.getStrEnd(obj, position)) {
                    testBlock.text("&nbsp;");
                    top = top + testBlock.height();
                }
                testBlock.remove();
                var areaPosition = textArea.position();
                var leftPosition = areaPosition.left + left + "px";
                var topPosition = areaPosition.top + top + (isSup ? -2.5 * lineHeight : 0) + "px";
                resultPopup.css({
                    "left": leftPosition,
                    "top": topPosition,
                    "z-index": (parseInt(textArea.css("z-index")) || 0) + 1
                });
                return {
                    left: leftPosition,
                    top: topPosition
                }
            }

            function fillResult(data, callback) {
                var startString = data.start, endStrings = data.ends;


                // Shift back
                var testBlock = $("<span style='position:absolute;left: -1000000px;top:-1000000px;white-space:pre;'></span>").appendTo("body").attr("class", textArea.attr("class"));
                testBlock.text(startString);
                var leftShiftBack = testBlock.width();
                testBlock.remove();

                resultPopup.css({
                    "left": (resultPopup.position().left - leftShiftBack - 2)
                });

                $.each(endStrings, function() {
                    appendResult(startString, this, callback);
                });
                if (data.more) {
                    toBeContinue.show();
                }
                selectRow(resultsContainer.find("div:first"));
                displayResults();
            }

            function hideResult() {
                resultsContainer.empty();
                resultPopup.unbind().hide();
                noSuggestions.hide();
                toBeContinue.hide();
                surroundWith.hide();
                surroundWith.find(".wikipower-surround-result").text("");
            }

            return {
                fill: fillResult,
                hide: hideResult,
                display: displayLoading,
                selectNext: selectNext,
                selectPrev: selectPrev,
                completeSelected: completeSelected,
                replaceSelected: replaceSelected,
                displayNothing: displayNothing,
                displaySurround: displaySurround,
                setSurroundText: setSurroundText
            }
        }();

        function requestLookUp(data, callback) {
            $.ajax({
                url: ($("#confluence-context-path").attr("content") || "") + "/plugins/servlet/wikipower/autocomplete",
                data: data,
                dataType: "json",
                type: "POST",
                error: function(XMLHttpRequest, textStatus, errorThrown) {
                    if (console && console.log) {
                        console.log(textStatus, errorThrown);
                    }
                    ResultManager.hide();
                },
                success: function(data, textStatus) {
                    if (textStatus == "success") {
                        callback(data);
                    } else {
                        ResultManager.hide();
                    }
                }
            });
        }

        return {
            bind: bind,
            unbind: unbind
        }
    }();

    $(function() {
        Wikipower.bind("markupTextarea");
    });
})();
