(function ($) {
    "use strict";
    /// <summary>takes a string and repeats it "n" times.</summary>
    /// <param name="num" type="Number">times to repeat the string</param>
    /// <returns>rep = '*'.repeat(5);    // rep = '*****'</returns>
    String.prototype.repeat = function (num) {
        return new Array(num + 1).join(this);
    };

    /// <summary>Removes duplicates from concatenated strings</summary>
    /// <returns>Array</returns>
    Array.prototype.unique = function () {
        var a, // array
            i, // incremental counter
            j; // next incremental counter

        a = this.concat();
        for (i = 0; i < a.length; ++i) {
            for (j = i + 1; j < a.length; ++j) {
                if (a[i] === a[j]){
                    a.splice(j, 1);
                }
            }
        }

        return a;
    };

    /// <summary>Default settings for profanityFilter plugin</summary>
    var defaults = {
        replaceWith: '*',
        customSwears: null,
        externalSwears: null,
        filter: true,
        profaneText: function () {}
    };


    /// <summary>jQuery plugin used to filter profanity on the attached element</summary>
    /// <param name="settings">user overridden settings</param>
    /// <returns>text from an element but blots out the swear words</returns>
    $.fn.profanityFilter = function (settings, callback) {

        var options = $.extend({}, defaults, settings),
            localStorageIsEnabled;

        localStorageIsEnabled = function() {
              var uid = new Date(),
                  result;

              try {
                localStorage.setItem("uid", uid);
                result = localStorage.getItem("uid") == uid;
                localStorage.removeItem("uid");
                return result && localStorage;
              } catch(e) {}
        }();

        function allTextNodes(parent) {
            function getChildNodes(parent) {
                var x,
                    out = [];

                for (x = 0; x < parent.childNodes.length; x += 1) {
                    out[x] = parent.childNodes[x];
                }

                return out;
            }

            var cursor,
                closed = [],
                open = getChildNodes(parent);

            while (open.length) {
                cursor = open.shift();
                if (cursor.nodeType === 1) {
                    open.unshift.apply(open, getChildNodes(cursor));
                }
                if (cursor.nodeType === 3) {
                    closed.push(cursor);
                }
            }

            return closed;
        }

        function readJsonFromController(file) {
            var request = new XMLHttpRequest();
            request.open('GET', file, false);
            request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
            request.send(null);
            try {
                return JSON.parse(request.responseText);
            } catch (e) {
                return '';
            }
        }

        var lastRandomNumber = null;
        function generateRandomNumber(max) {
          var randomNumber = Math.floor((Math.random()*(max)));
          if (lastRandomNumber == null) {
            lastRandomNumber = randomNumber;
          } else {
            if (randomNumber == lastRandomNumber && max !=0) {
              randomNumber +=1;
            }
          }

          if (randomNumber > max) {
            //set it back to zero
            randomNumber = 0;
          }

          lastRandomNumber = randomNumber;

          return randomNumber;
        }


        return this.each(function () {

            var badWords,
                i,
                nodes = allTextNodes(this),
                re,
                rep,
                x,
                inputs = $(this).find(':input'),
                profane = false,
                data = [],
                localSwearsKey = 'localSwears' + options.externalSwears;

            if (options.externalSwears !== null) {
                if (localStorageIsEnabled) {
                    if (localStorage.getItem(localSwearsKey) === null) {
                        // stringify the array so that it can be stored in local storage
                        localStorage.setItem(localSwearsKey, JSON.stringify(readJsonFromController(options.externalSwears)));
                    }
                    badWords = JSON.parse(localStorage.getItem(localSwearsKey));
                } else {
                    badWords = readJsonFromController(options.externalSwears);
                }
                if (options.customSwears !== null) {
                    badWords = badWords.concat(options.customSwears).unique();
                }
            } else {
                if (options.customSwears !== null) {
                    badWords = options.customSwears;
                }
            }

            // GET OUT, there are no Swears set either custom, external OR local.
            if (badWords === null) {
                return;
            }

            // We've got an array of swears, let's proceed with removing them from the element.
            for (i = 0; i < badWords.length; i += 1) {
                re = new RegExp('\\b' + badWords[i] + '\\b', 'gi');

                var rand = generateRandomNumber(options.replaceWith.length -1);

                rep = options.replaceWith[rand];
                if (typeof options.replaceWith == 'string') {
                  rep = options.replaceWith[rand].repeat(badWords[i].length);
                }

                // Text nodes
                for (x = 0; x < nodes.length; x += 1) {
                    if (re.test(nodes[x].nodeValue)) {
                        profane = true;
                        data.push(badWords[i]);
                        if (options.filter) {
                            nodes[x].nodeValue = nodes[x].nodeValue.replace(re, rep);
                        }
                    }
                }

                // Text input values
                for (var x = 0; x < inputs.length; x++) {
                    if (re.test(inputs[x].value)) {
                        profane = true;
                        data.push(badWords[i]);
                        if (options.filter) {
                            $(inputs[x]).val(inputs[x].value.replace(re, rep));
                        }
                    }
                }
            }

            if (profane) {
                options.profaneText(data.unique());
            };
        });
    };
})(jQuery);