/**
 * @license Copyright (c) CKSource - Frederico Knabben. All rights reserved.
 * For licensing, see LICENSE.html or http://ckeditor.com/license
 */

CKEDITOR.plugins.add("wordcount",
    {
        lang: "ar,bg,ca,cs,da,de,el,en,es,eu,fa,fi,fr,he,hr,hu,it,ka,ko,ja,nl,no,pl,pt,pt-br,ru,sk,sv,tr,uk,zh-cn,zh,ro", // %REMOVE_LINE_CORE%
        version: "1.17.10",
        requires: "htmlwriter,notification,undo",
        bbcodePluginLoaded: false,
        onLoad: function() {
            CKEDITOR.document.appendStyleSheet(this.path + "css/wordcount.css");
        },
        init: function(editor) {
            var defaultFormat = "",
                lastWordCount = -1,
                lastCharCount = -1,
                lastParagraphs = -1,
                limitReachedNotified = false,
                limitRestoredNotified = false,
                timeoutId = 0,
                notification = null;


            var dispatchEvent = function(type, currentLength, maxLength) {
                if (typeof document.dispatchEvent == "undefined") {
                    return;
                }

                type = "ckeditor.wordcount." + type;

                var cEvent;
                var eventInitDict = {
                    bubbles: false,
                    cancelable: true,
                    detail: {
                        currentLength: currentLength,
                        maxLength: maxLength
                    }
                };

                try {
                    cEvent = new CustomEvent(type, eventInitDict);
                } catch (o_O) {
                    cEvent = document.createEvent("CustomEvent");
                    cEvent.initCustomEvent(
                        type,
                        eventInitDict.bubbles,
                        eventInitDict.cancelable,
                        eventInitDict.detail
                    );
                }

                document.dispatchEvent(cEvent);
            };

            // Default Config
            var defaultConfig = {
                showRemaining: false,
                showParagraphs: true,
                showWordCount: true,
                showCharCount: false,
                countBytesAsChars: false,
                countSpacesAsChars: false,
                countHTML: false,
                countLineBreaks: false,
                hardLimit: true,
                warnOnLimitOnly: false,
                wordDelims: '',

                //MAXLENGTH Properties
                maxWordCount: -1,
                maxCharCount: -1,
                maxParagraphs: -1,

                // Filter
                filter: null,

                // How long to show the 'paste' warning
                pasteWarningDuration: 0,

                //DisAllowed functions
                wordCountGreaterThanMaxLengthEvent: function(currentLength, maxLength) {
                    dispatchEvent("wordCountGreaterThanMaxLengthEvent", currentLength, maxLength);
                },
                charCountGreaterThanMaxLengthEvent: function(currentLength, maxLength) {
                    dispatchEvent("charCountGreaterThanMaxLengthEvent", currentLength, maxLength);
                },

                //Allowed Functions
                wordCountLessThanMaxLengthEvent: function(currentLength, maxLength) {
                    dispatchEvent("wordCountLessThanMaxLengthEvent", currentLength, maxLength);
                },
                charCountLessThanMaxLengthEvent: function(currentLength, maxLength) {
                    dispatchEvent("charCountLessThanMaxLengthEvent", currentLength, maxLength);
                }
            };

            // Get Config & Lang
            var config = CKEDITOR.tools.extend(defaultConfig, editor.config.wordcount || {}, true);

            if (config.showParagraphs) {
              if (config.maxParagraphs > -1) {
                  if (config.showRemaining) {
                      defaultFormat += "%paragraphsCount% " + editor.lang.wordcount.ParagraphsRemaining;
                  } else {
                      defaultFormat += editor.lang.wordcount.Paragraphs + " %paragraphsCount%";

                      defaultFormat += "/" + config.maxParagraphs;
                  }
              } else {
                  defaultFormat += editor.lang.wordcount.Paragraphs + " %paragraphsCount%";
              }
            }

            if (config.showParagraphs && (config.showWordCount || config.showCharCount)) {
                defaultFormat += ", ";
            }

            if (config.showWordCount) {
                if (config.maxWordCount > -1) {
                    if (config.showRemaining) {
                        defaultFormat += "%wordCount% " + editor.lang.wordcount.WordCountRemaining;
                    } else {
                        defaultFormat += editor.lang.wordcount.WordCount + " %wordCount%";

                        defaultFormat += "/" + config.maxWordCount;
                    }
                } else {
                    defaultFormat += editor.lang.wordcount.WordCount + " %wordCount%";
                }
            }

            if (config.showCharCount && config.showWordCount) {
                defaultFormat += ", ";
            }

            if (config.showCharCount) {
                if (config.maxCharCount > -1) {
                    if (config.showRemaining) {
                        defaultFormat += "%charCount% " +
                            editor.lang.wordcount[config.countHTML
                                ? "CharCountWithHTMLRemaining"
                                : "CharCountRemaining"];
                    } else {
                        defaultFormat += editor.lang.wordcount[config.countHTML
                                ? "CharCountWithHTML"
                                : "CharCount"] +
                            " %charCount%";

                        defaultFormat += "/" + config.maxCharCount;
                    }
                } else {
                    defaultFormat += editor.lang.wordcount[config.countHTML ? "CharCountWithHTML" : "CharCount"] +
                        " %charCount%";
                }
            }

            var format = defaultFormat;

            bbcodePluginLoaded = typeof editor.plugins.bbcode != "undefined";

            function counterId(editorInstance) {
                return "cke_wordcount_" + editorInstance.name;
            }

            function counterElement(editorInstance) {
                return document.getElementById(counterId(editorInstance));
            }

            function strip(html) {
                if (bbcodePluginLoaded) {
                    // stripping out BBCode tags [...][/...]
                    return html.replace(/\[.*?\]/gi, "");
                }

                var tmp = document.createElement("div");

                // Add filter before strip
                html = filter(html);

                tmp.innerHTML = html;

                if (tmp.textContent == "" && typeof tmp.innerText == "undefined") {
                    return "";
                }

                return tmp.textContent || tmp.innerText;
            }

            /**
             * Implement filter to add or remove before counting
             * @param html
             * @returns string
             */
            function filter(html) {
                if (config.filter instanceof CKEDITOR.htmlParser.filter) {
                    var fragment = CKEDITOR.htmlParser.fragment.fromHtml(html),
                        writer = new CKEDITOR.htmlParser.basicWriter();
                    config.filter.applyTo(fragment);
                    fragment.writeHtml(writer);
                    return writer.getHtml();
                }
                return html;
            }

            function countCharacters(text) {
                if (config.countHTML) {
                    return config.countBytesAsChars ? countBytes(filter(text)) : filter(text).length;
                }

                var normalizedText;

                // strip body tags
                if (editor.config.fullPage) {
                    var i = text.search(new RegExp("<body>", "i"));
                    if (i != -1) {
                        var j = text.search(new RegExp("</body>", "i"));
                        text = text.substring(i + 6, j);
                    }

                }

                normalizedText = text;

                if (!config.countSpacesAsChars) {
                    normalizedText = text.replace(/\s/g, "").replace(/&nbsp;/g, "");
                }

                if (config.countLineBreaks) {
                    normalizedText = normalizedText.replace(/(\r\n|\n|\r)/gm, " ");
                } else {
                    normalizedText = normalizedText.replace(/(\r\n|\n|\r)/gm, "").replace(/&nbsp;/gi, " ");
                }

                normalizedText = strip(normalizedText).replace(/^([\t\r\n]*)$/, "");

                return config.countBytesAsChars ? countBytes(normalizedText) : normalizedText.length;
            }

            function countBytes(text) {
                var count = 0, stringLength = text.length, i;
                text = String(text || "");
                for (i = 0; i < stringLength; i++) {
                    var partCount = encodeURI(text[i]).split("%").length;
                    count += partCount == 1 ? 1 : partCount - 1;
                }
                return count;
            }

            function countParagraphs(text) {
                return (text.replace(/&nbsp;/g, " ").replace(/(<([^>]+)>)/ig, "").replace(/^\s*$[\n\r]{1,}/gm, "++")
                    .split("++").length);
            }

            function countWords(text) {
                var normalizedText = text.replace(/(<([^>]+)>)/ig, " ").replace(/(\r\n|\n|\r)/gm, "").replace(/^\s+|\s+$/g, "")
                    .replace("&nbsp;", " ");

                normalizedText = strip(normalizedText);

                var re = config.wordDelims ? new RegExp('[\\s'+config.wordDelims+']+') : /\s+/;
                var words = normalizedText.split(re);
                
                re = config.wordDelims ? new RegExp('^([\\s\\t\\r\\n'+config.wordDelims+']*)$') : /^([\s\t\r\n]*)$/;
                for (var wordIndex = words.length - 1; wordIndex >= 0; wordIndex--) {
                    if (!words[wordIndex] || words[wordIndex].match(re)) {
                        words.splice(wordIndex, 1);
                    }
                }

                return (words.length);
            }

            function limitReached(editorInstance, notify) {
                limitReachedNotified = true;
                limitRestoredNotified = false;

                if (!config.warnOnLimitOnly) {
                    if (config.hardLimit) {
                        if (editor.mode === "source" && editor.plugins.codemirror) {
                            window["codemirror_" + editor.id].undo();
                        } else {
                            editorInstance.execCommand("undo");
                            editorInstance.execCommand("undo");
                        }
                           
                    }
                }

                if (!notify) {
                    counterElement(editorInstance).className = "cke_path_item cke_wordcountLimitReached";
                    editorInstance.fire("limitReached", { firedBy: "wordCount.limitReached" }, editor);
                }
            }

            function limitRestored(editorInstance) {
                limitRestoredNotified = true;
                limitReachedNotified = false;

                if (!config.warnOnLimitOnly) {
                    editorInstance.fire("saveSnapshot");
                }

                counterElement(editorInstance).className = "cke_path_item";
            }

            function updateCounter(editorInstance) {
                if (!counterElement(editorInstance)) {
                    return;
                }

                var paragraphs = 0,
                    wordCount = 0,
                    charCount = 0,
                    text;

                // BeforeGetData and getData events are fired when calling
                // getData(). We can prevent this by passing true as an
                // argument to getData(). This allows us to fire the events
                // manually with additional event data: firedBy. This additional
                // data helps differentiate calls to getData() made by
                // wordCount plugin from calls made by other plugins/code.
                editorInstance.fire("beforeGetData", { firedBy: "wordCount.updateCounter" }, editor);
                text = editorInstance.getData(true);
                editorInstance.fire("getData", { dataValue: text, firedBy: "wordCount.updateCounter" }, editor);

                if (text) {
                    if (config.showCharCount) {
                        charCount = countCharacters(text);
                    }

                    if (config.showParagraphs) {
                        paragraphs = countParagraphs(text);
                    }

                    if (config.showWordCount) {
                        wordCount = countWords(text);
                    }
                }

                var html = format;
                if (config.showRemaining) {
                    if (config.maxCharCount >= 0) {
                        html = html.replace("%charCount%", config.maxCharCount - charCount);
                    } else {
                        html = html.replace("%charCount%", charCount);
                    }

                    if (config.maxWordCount >= 0) {
                        html = html.replace("%wordCount%", config.maxWordCount - wordCount);
                    } else {
                        html = html.replace("%wordCount%", wordCount);
                    }

                    if (config.maxParagraphs >= 0) {
                        html = html.replace("%paragraphsCount%", config.maxParagraphs - paragraphs);
                    } else {
                        html = html.replace("%paragraphsCount%", paragraphs);
                    }
                } else {
                    html = html.replace("%wordCount%", wordCount).replace("%charCount%", charCount).replace("%paragraphsCount%", paragraphs);
                }

                (editorInstance.config.wordcount || (editorInstance.config.wordcount = {})).wordCount = wordCount;
                (editorInstance.config.wordcount || (editorInstance.config.wordcount = {})).charCount = charCount;

                if (CKEDITOR.env.gecko) {
                    counterElement(editorInstance).innerHTML = html;
                } else {
                    counterElement(editorInstance).innerText = html;
                }

                if (charCount == lastCharCount && wordCount == lastWordCount && paragraphs == lastParagraphs) {
                    if (charCount == config.maxCharCount || wordCount == config.maxWordCount || paragraphs > config.maxParagraphs) {
                        editorInstance.fire("saveSnapshot");
                    }
                    return true;
                }

                //If the limit is already over, allow the deletion of characters/words. Otherwise,
                //the user would have to delete at one go the number of offending characters
                var deltaWord = wordCount - lastWordCount;
                var deltaChar = charCount - lastCharCount;
                var deltaParagraphs = paragraphs - lastParagraphs;

                lastWordCount = wordCount;
                lastCharCount = charCount;
                lastParagraphs = paragraphs;

                if (lastWordCount == -1) {
                    lastWordCount = wordCount;
                }
                if (lastCharCount == -1) {
                    lastCharCount = charCount;
                }
                if (lastParagraphs == -1) {
                    lastParagraphs = paragraphs;
                }

                // Check for word limit and/or char limit
                if ((config.maxWordCount > -1 && wordCount > config.maxWordCount && deltaWord > 0) ||
                    (config.maxCharCount > -1 && charCount > config.maxCharCount && deltaChar > 0) ||
                    (config.maxParagraphs > -1 && paragraphs > config.maxParagraphs && deltaParagraphs > 0)) {

                    limitReached(editorInstance, limitReachedNotified);
                } else if ((config.maxWordCount == -1 || wordCount <= config.maxWordCount) &&
                    (config.maxCharCount == -1 || charCount <= config.maxCharCount) &&
                    (config.maxParagraphs == -1 || paragraphs <= config.maxParagraphs)) {

                    limitRestored(editorInstance);
                } else {
                    editorInstance.fire("saveSnapshot");
                }

                // update instance
                editorInstance.wordCount =
                {
                    paragraphs: paragraphs,
                    wordCount: wordCount,
                    charCount: charCount
                };


                // Fire Custom Events
                if (config.charCountGreaterThanMaxLengthEvent && config.charCountLessThanMaxLengthEvent) {
                    if (charCount > config.maxCharCount && config.maxCharCount > -1) {
                        config.charCountGreaterThanMaxLengthEvent(charCount, config.maxCharCount);
                    } else {
                        config.charCountLessThanMaxLengthEvent(charCount, config.maxCharCount);
                    }
                }

                if (config.wordCountGreaterThanMaxLengthEvent && config.wordCountLessThanMaxLengthEvent) {
                    if (wordCount > config.maxWordCount && config.maxWordCount > -1) {
                        config.wordCountGreaterThanMaxLengthEvent(wordCount, config.maxWordCount);

                    } else {
                        config.wordCountLessThanMaxLengthEvent(wordCount, config.maxWordCount);
                    }
                }

                return true;
            }

            function isCloseToLimits() {
                if (config.maxWordCount > -1 && config.maxWordCount - lastWordCount < 5) {
                    return true;
                }

                if (config.maxCharCount > -1 && config.maxCharCount - lastCharCount < 20) {
                    return true;
                }

                if (config.maxParagraphs > -1 && config.maxParagraphs - lastParagraphs < 1) {
                    return true;
                }

                return false;
            }

            editor.on("key",
                function (event) {
                    var ms = isCloseToLimits() ? 5 : 250;

                    if (editor.mode === "source") {
                        clearTimeout(timeoutId);
                        timeoutId = setTimeout(
                            updateCounter.bind(this, event.editor),
                            ms
                        );
                    }

                    if (event.data.keyCode == 13) {
                        clearTimeout(timeoutId);
                        timeoutId = setTimeout(
                            updateCounter.bind(this, event.editor),
                            ms
                        );
                    }
                },
                editor);

            editor.on("change",
                function(event) {
                    var ms = isCloseToLimits() ? 5 : 250;
                    clearTimeout(timeoutId);
                    timeoutId = setTimeout(
                        updateCounter.bind(this, event.editor),
                        ms
                    );
                },
                editor);

            editor.on("uiSpace",
                function (event) {
                    var wordcountClass = "cke_wordcount";

                    if (editor.lang.dir == "rtl") {
                        wordcountClass = wordcountClass + " cke_wordcount_rtl";
                    }

                    if (editor.elementMode === CKEDITOR.ELEMENT_MODE_INLINE) {
                        if (event.data.space == "top") {
                            event.data.html += "<div class=\"" + wordcountClass +"\" style=\"\"" +
                                " title=\"" +
                                editor.lang.wordcount.title +
                                "\"" +
                                "><span id=\"" +
                                counterId(event.editor) +
                                "\" class=\"cke_path_item\">&nbsp;</span></div>";
                        }
                    } else {
                        if (event.data.space == "bottom") {
                            event.data.html += "<div class=\""+wordcountClass+"\" style=\"\"" +
                                " title=\"" +
                                editor.lang.wordcount.title +
                                "\"" +
                                "><span id=\"" +
                                counterId(event.editor) +
                                "\" class=\"cke_path_item\">&nbsp;</span></div>";
                        }
                    }

                },
                editor,
                null,
                100);

            editor.on("dataReady",
                function(event) {
                    updateCounter(event.editor);
                },
                editor,
                null,
                100);

            editor.on("paste",
                function(event) {
                    if (!config.warnOnLimitOnly && (config.maxWordCount > 0 || config.maxCharCount > 0 || config.maxParagraphs > 0)) {

                        // Check if pasted content is above the limits
                        var wordCount = -1,
                            charCount = -1,
                            paragraphs = -1;

                        var mySelection = event.editor.getSelection(),
                            selectedText = mySelection.getNative() 
                              ? mySelection.getNative().toString().trim()
                              : '';


                        // BeforeGetData and getData events are fired when calling
                        // getData(). We can prevent this by passing true as an
                        // argument to getData(). This allows us to fire the events
                        // manually with additional event data: firedBy. This additional
                        // data helps differentiate calls to getData() made by
                        // wordCount plugin from calls made by other plugins/code.
                        event.editor.fire("beforeGetData", { firedBy: "wordCount.onPaste" }, event.editor);
                        var text = event.editor.getData(true);
                        event.editor.fire("getData", { dataValue: text, firedBy: "wordCount.onPaste" }, event.editor);

                        if (selectedText.length > 0) {
                            var plaintext = event.editor.document.getBody().getText();

                            if (plaintext.length === selectedText.length) {
                                text = "";
                            }
                        }


                        text += event.data.dataValue;

                        if (config.showCharCount) {
                            charCount = countCharacters(text);
                        }

                        if (config.showWordCount) {
                            wordCount = countWords(text);
                        }

                        if (config.showParagraphs) {
                            paragraphs = countParagraphs(text);
                        }


                        // Instantiate the notification when needed and only have one instance
                        if (notification === null) {
                            notification = new CKEDITOR.plugins.notification(event.editor,
                                {
                                    message: event.editor.lang.wordcount.pasteWarning,
                                    type: "warning",
                                    duration: config.pasteWarningDuration
                                });
                        }

                        if (config.maxCharCount > 0 && charCount > config.maxCharCount && config.hardLimit) {
                            if (!notification.isVisible()) {
                                notification.show();
                            }
                            event.cancel();
                        }

                        if (config.maxWordCount > 0 && wordCount > config.maxWordCount && config.hardLimit) {
                            if (!notification.isVisible()) {
                                notification.show();
                            }
                            event.cancel();
                        }

                        if (config.maxParagraphs > 0 && paragraphs > config.maxParagraphs && config.hardLimit) {
                            if (!notification.isVisible()) {
                                notification.show();
                            }
                            event.cancel();
                        }
                    }
                },
                editor,
                null,
                100);

            editor.on("afterPaste",
                function(event) {
                    updateCounter(event.editor);
                },
                editor,
                null,
                100);

            editor.on("afterPasteFromWord",
                function (event) {
                    updateCounter(event.editor);
                },
                editor,
                null,
                100);
        }
    });
